allow specification of a different intermediary namespace,

allow alternative version manifests
This commit is contained in:
moehreag 2024-08-20 11:06:17 +02:00
parent 755ef292ab
commit 170bbe7d0f
10 changed files with 62 additions and 301 deletions

View file

@ -19,4 +19,5 @@ object Constants {
const val CLEAR_GLOBAL_CACHE_TASK = "clearGlobalCache"
const val ASM_VERSION = Opcodes.ASM9
const val ALTERNATIVE_RUNTIME_JAR_NAME = "runtime.jar"
const val MOJANG_MANIFEST_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
}

View file

@ -203,12 +203,17 @@ class PhytotelmaPlugin : Plugin<Project> {
project.tasks.getByName(JavaPlugin.JAR_TASK_NAME).actions.addLast { task ->
val storage = ProjectStorage.get(project)
if (storage.targetNamespace != Constants.MOJMAP_NAMESPACE) {
val mappings = MappingBundle.merge(
storage.mappings!!.reverse(), MojmapProvider.get(
val moj = if (storage.intermediaryNs == Constants.MOJMAP_NAMESPACE) {
MojmapProvider.get(
storage.minecraftVersion!!,
globalCacheDir.resolve("net/minecraft/client/${storage.minecraftVersion}/client-${storage.minecraftVersion}.txt")
).orElseThrow().reverse().renameDstNamespace(Constants.MOJMAP_NAMESPACE)
).forNamespaces(storage.targetNamespace, Constants.MOJMAP_NAMESPACE)
} else null
val mappings = (moj?.let {
MappingBundle.merge(
storage.mappings!!.reverse(), it
)
} ?: storage.mappings!!).forNamespaces(storage.targetNamespace, storage.intermediaryNs)
val includeConfiguration = project.configurations.findByName(Constants.INCLUDE_CONFIGURATION)
task.outputs.files.forEach { file ->
val temp = Files.createTempFile("", file.name)

View file

@ -6,16 +6,14 @@ import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import dev.frogmc.thyroxine.api.data.DocumentationData
import dev.frogmc.thyroxine.api.data.DocumentationData.Field
import dev.frogmc.thyroxine.api.data.DocumentationData.Method
import dev.frogmc.thyroxine.api.data.MappingBundle
import dev.frogmc.thyroxine.api.data.MappingData
import dev.frogmc.thyroxine.api.data.Member
import org.gradle.api.Project
import java.nio.file.Path
import java.util.*
import kotlin.io.path.*
import kotlin.io.path.absolutePathString
import kotlin.io.path.exists
import kotlin.io.path.reader
import kotlin.io.path.writeText
object ProjectStorage {
@ -24,7 +22,6 @@ object ProjectStorage {
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(ProjectData::class.java, ProjectDataTypeAdapter())
//.registerTypeAdapter(MappingBundle::class.java, MappingBundleTypeAdapter())
.create()
fun get(project: Project): ProjectData {
@ -36,7 +33,7 @@ object ProjectStorage {
data[id] = projectData
} else {
println("Creating project data store for $id, size: ${data.size}, ${this.hashCode()}")
data[id] = ProjectData(null, null, null, null, null, null)
data[id] = ProjectData()
}
}
return data[id]!!
@ -51,13 +48,6 @@ object ProjectStorage {
}
private fun write(path: Path, projectData: ProjectData) {
/**
* If you're wondering "why not use the stream-based API, it should offer better performance *especially*
* for huge documents like this!"
* The answer is because it doesn't work. Somewhere in the buffer handling the document just
* gets cut off and left in an invalid state. Yes, 30+ MiB Strings aren't ideal either.
* But I'd honestly prefer that nightmare over invalid json files.
*/
path.writeText(gson.toJson(projectData))
}
@ -77,8 +67,12 @@ class ProjectData(
var remappedGameJarPath: Path?,
var mappings: MappingBundle?,
var mappingsName: String?,
var targetNamespace: String?
)
var intermediaryNs: String?,
var targetNamespace: String?,
var manifestUrl: String?
) {
internal constructor() : this(null, null, null, null, null, null, null, null)
}
class ProjectDataTypeAdapter : TypeAdapter<ProjectData>() {
override fun write(out: JsonWriter, value: ProjectData) {
@ -87,8 +81,6 @@ class ProjectDataTypeAdapter : TypeAdapter<ProjectData>() {
out.name("local_cache_dir").value(value.localCacheDir?.absolutePathString())
out.name("minecraft_version").value(value.minecraftVersion)
out.name("remapped_game_jar_path").value(value.remappedGameJarPath?.absolutePathString())
/*out.name("mappings")
value.mappings?.let { MappingBundleTypeAdapter().write(out, it) }?:out.nullValue()*/
out.name("mappings_name").value(value.mappingsName)
out.name("target_namespace").value(value.targetNamespace)
out.endObject()
@ -96,7 +88,7 @@ class ProjectDataTypeAdapter : TypeAdapter<ProjectData>() {
override fun read(r: JsonReader): ProjectData {
r.beginObject()
val data = ProjectData(null, null, null, null, null, null)
val data = ProjectData()
while (r.peek() != JsonToken.END_OBJECT) {
val name = r.nextName()
if (r.peek() == JsonToken.STRING) {
@ -122,8 +114,6 @@ class ProjectDataTypeAdapter : TypeAdapter<ProjectData>() {
data.targetNamespace = value
}
}
/*} else if (name == "mappings") {
data.mappings = MappingBundleTypeAdapter().read(r)*/
} else {
r.skipValue()
}
@ -133,258 +123,3 @@ class ProjectDataTypeAdapter : TypeAdapter<ProjectData>() {
}
}
/*class MappingBundleTypeAdapter : TypeAdapter<MappingBundle>() {
override fun write(out: JsonWriter, value: MappingBundle) {
out.beginObject()
out.name("data").beginArray()
value.data.forEach {
out.beginObject()
out.name("srcNs").value(it.srcNamespace)
out.name("dstNs").value(it.dstNamespace)
out.name("classes").beginObject()
it.classes.forEach { (k, v) ->
out.name(k).value(v)
}
out.endObject()
out.name("methods").beginArray()
it.methods.forEach { (k, v) ->
out.beginObject()
out.name("owner").value(k.owner)
out.name("desc").value(k.descriptor)
out.name("name").value(k.name)
out.name("value").value(v)
out.endObject()
}
out.endArray()
out.name("fields").beginArray()
it.fields.forEach { (k, v) ->
out.beginObject()
out.name("owner").value(k.owner)
out.name("desc").value(k.descriptor)
out.name("name").value(k.name)
out.name("value").value(v)
out.endObject()
}
out.endArray()
out.name("parameters").beginArray()
it.parameters.forEach { (k, v) ->
out.beginObject()
out.name("owner").value(k.owner)
out.name("desc").value(k.descriptor)
out.name("name").value(k.name)
out.name("value").beginObject()
v.forEach { (i, s) ->
out.name("$i").value(s)
}
out.endObject()
out.endObject()
}
out.endArray()
out.endObject()
}
out.endArray()
out.name("documentation").beginArray()
value.documentation.forEach {
out.beginObject()
out.name("ns").value(it.namespace)
out.name("classes").beginArray()
it.classes.forEach { c ->
out.beginObject()
out.name("name").value(c.name)
out.name("javadoc").beginArray()
c.javadoc.forEach { d ->
out.value(d)
}
out.endArray()
out.name("methods").beginArray()
c.methods.forEach { m ->
out.beginObject()
out.name("name").value(m.name)
out.name("desc").value(m.descriptor)
out.name("javadoc").beginArray()
m.javadoc.forEach { d ->
out.value(d)
}
out.endArray()
out.endObject()
}
out.endArray()
out.name("fields").beginArray()
c.fields.forEach { f ->
out.beginObject()
out.name("name").value(f.name)
out.name("desc").value(f.descriptor)
out.name("javadoc").beginArray()
f.javadoc.forEach { d ->
out.value(d)
}
out.endArray()
out.endObject()
}
out.endArray()
out.endObject()
}
out.endArray()
out.endObject()
}
out.endArray()
out.endObject()
}
override fun read(r: JsonReader): MappingBundle {
val out = MappingBundle()
r.beginObject()
r.nextName()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
out.data.add(readData(r))
r.endObject()
}
r.endArray()
r.nextName()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val namespace = r.nextString()
val data = DocumentationData(namespace)
out.documentation.add(data)
r.nextName()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val name = r.nextString()
r.nextName()
val javadoc = mutableListOf<String>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
javadoc.add(r.nextString())
}
r.endArray()
r.nextName()
val methods = mutableListOf<Method>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val methodName = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val methodJavadoc = mutableListOf<String>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
methodJavadoc.add(r.nextString())
}
r.endArray()
methods.add(Method(methodName, desc, methodJavadoc))
r.endObject()
}
r.endArray()
r.nextName()
val fields = mutableListOf<Field>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val fieldName = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val fieldJavadoc = mutableListOf<String>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
fieldJavadoc.add(r.nextString())
}
r.endArray()
fields.add(Field(fieldName, desc, fieldJavadoc))
r.endObject()
}
r.endArray()
data.classes.add(DocumentationData.Class(name, javadoc, fields, methods))
r.endObject()
}
r.endArray()
r.endObject()
}
r.endArray()
r.endObject()
return out
}
private fun readData(r: JsonReader): MappingData {
r.nextName()
val srcNs = r.nextString()
r.nextName()
val dstNs = r.nextString()
r.nextName()
r.beginObject()
val classes = mutableMapOf<String, String>()
while (r.peek() != JsonToken.END_OBJECT) {
classes[r.nextName()] = r.nextString()
}
r.endObject()
r.nextName()
r.beginArray()
val methods = mutableMapOf<Member, String>()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val owner = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val name = r.nextString()
r.nextName()
val value = r.nextString()
methods[Member(owner, name, desc)] = value
r.endObject()
}
r.endArray()
r.nextName()
r.beginArray()
val fields = mutableMapOf<Member, String>()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val owner = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val name = r.nextString()
r.nextName()
val value = r.nextString()
fields[Member(owner, name, desc)] = value
r.endObject()
}
r.endArray()
r.nextName()
r.beginArray()
val parameters = mutableMapOf<Member, MutableMap<Int, String>>()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val owner = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val name = r.nextString()
r.nextName()
r.beginObject()
val values = mutableMapOf<Int, String>()
while (r.peek() != JsonToken.END_OBJECT) {
values[r.nextName().toInt()] = r.nextString()
}
r.endObject()
parameters[Member(owner, name, desc)] = values
r.endObject()
}
r.endArray()
return MappingData(srcNs, dstNs, classes, methods, fields, parameters)
}
}*/

View file

@ -18,8 +18,8 @@ object VersionChecker {
@Suppress("DEPRECATION")
fun downloadClient(project: Project, version: String): Path {
fetchVersionData(version)
val clientData = fetchClientDownload(version)
fetchVersionData(project, version)
val clientData = fetchClientDownload(project, version)
// download client data
val downloadDirectory = PhytotelmaPlugin.globalCacheDir.resolve("net/minecraft/client/$version/")
val downloadFile = downloadDirectory.resolve("client-$version.jar")
@ -47,39 +47,39 @@ object VersionChecker {
dir.resolve("client-$version.pom").writeText(content)
}
fun getDependencies(version: String, action: (String) -> Unit) {
fetchVersionData(version).libraries.map { it.name }.forEach {
fun getDependencies(project: Project, version: String, action: (String) -> Unit) {
fetchVersionData(project, version).libraries.map { it.name }.forEach {
action.invoke(it)
}
}
fun validateVersion(version: String, ignoreCache: Boolean = false, offlineMode: Boolean): Boolean {
if (validVersions.isEmpty() || ignoreCache) getValidVersions(ignoreCache)
fun validateVersion(project: Project, version: String, ignoreCache: Boolean = false, offlineMode: Boolean): Boolean {
if (validVersions.isEmpty() || ignoreCache) getValidVersions(project, ignoreCache)
if (!validVersions.any { it.id == version }) {
if (!offlineMode) {
return validateVersion(version, ignoreCache = true, offlineMode = false)
return validateVersion(project, version, ignoreCache = true, offlineMode = false)
}
return false
}
return true
}
fun getVersionUrl(version: String): String {
if (validVersions.isEmpty()) getValidVersions()
fun getVersionUrl(project: Project, version: String): String {
if (validVersions.isEmpty()) getValidVersions(project)
return validVersions.first { it.id == version }.url
}
fun fetchVersionData(version: String): VersionData {
fun fetchVersionData(project: Project, version: String): VersionData {
if (versionData == null || versionData!!.id != version) {
val url = getVersionUrl(version)
val url = getVersionUrl(project, version)
val response = getUrl(url)
versionData = Gson().fromJson(response, VersionData::class.java)
}
return versionData!!
}
fun fetchClientDownload(version: String): VersionClientData {
return fetchVersionData(version).downloads.client
fun fetchClientDownload(project: Project, version: String): VersionClientData {
return fetchVersionData(project, version).downloads.client
}
private fun rawDownload(url: String): ByteArray {
@ -90,12 +90,12 @@ object VersionChecker {
return CachingHttpClient.getString(URI.create(url), ignoreCache)
}
private fun getValidVersions(ignoreCache: Boolean = false) {
private fun getValidVersions(project: Project, ignoreCache: Boolean = false) {
if (validVersions.isNotEmpty() && !ignoreCache) return
// get json from https://piston-meta.mojang.com/mc/game/version_manifest_v2.json
// make http request
val url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
val url = ProjectStorage.get(project).manifestUrl!!
val response = getUrl(url, ignoreCache)
// response is in format of {versions: [{id: "1.17.1", type: "release"}, ...]}
// parse json

View file

@ -49,7 +49,7 @@ abstract class PhytotelmaBuildTask : DefaultTask() {
.plus(
"""
Built-By: Phytotelma ${this.javaClass.`package`.implementationVersion}
Target-Namespace: Mojmap
Target-Namespace: ${ProjectStorage.get(project).intermediaryNs}
Built-For: Minecraft ${ProjectStorage.get(project).minecraftVersion}
Build-Date: ${LocalDateTime.now()}
""".trimIndent()

View file

@ -28,6 +28,8 @@ abstract class MinecraftConfiguration @Inject constructor(
val version: Property<String> = objects.property(String::class.java).unset()
var mappings: Provider<MappingBundle> = mojmapParchment()
val intermediaryNamespace: Property<String> = objects.property(String::class.java).convention(Constants.MOJMAP_NAMESPACE)
val manifestUrl: Property<String> = objects.property(String::class.java).convention(Constants.MOJANG_MANIFEST_URL)
internal lateinit var mappingsName: String
internal lateinit var targetNamespace: String
@ -123,6 +125,23 @@ abstract class MinecraftConfiguration @Inject constructor(
}
}
fun feather(action: Action<VersionConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(VersionConfiguration::class.java)
action.execute(conf)
if (!conf.version.isPresent) {
error("No version provided for yarn!")
}
mappingsName = "yarn(${conf.version.get()})"
targetNamespace = "yarn"
// Use qm via intermediary because hashed publications are broken
return@provider twoStepMappings(
"net.ornithemc:calamus-intermediary:${version.get()}:v2",
"net.fabricmc:yarn:${conf.version.get()}:v2"
).flatten(true).renameDstNamespace(targetNamespace)
}
}
fun twoStepMappings(action: Action<TwoStepMappingsConfiguration>): Provider<MappingBundle> {
return project.provider {
val conf = objects.newInstance(TwoStepMappingsConfiguration::class.java)

View file

@ -35,7 +35,7 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
}
val version = mcConf.version.get()
if (VersionChecker.validateVersion(version, offlineMode = project.gradle.startParameter.isOffline)) {
if (VersionChecker.validateVersion(project, version, offlineMode = project.gradle.startParameter.isOffline)) {
val projectData = ProjectStorage.get(project)
projectData.minecraftVersion = version
@ -50,6 +50,7 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
remappedJar.createParentDirectories()
projectData.remappedGameJarPath = remappedJar
var applyAW = AccessWidener.needsUpdate(project)
projectData.intermediaryNs = mcConf.intermediaryNamespace.get()
if (remappedJar.notExists() || applyAW || mcConf.mappingsName != projectData.mappingsName) {
projectData.mappingsName = mcConf.mappingsName
println("Remapping the game...")
@ -67,7 +68,7 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
Constants.MINECRAFT_CONFIGURATION,
"net.minecrell:terminalconsoleappender:1.2.0"
)
VersionChecker.getDependencies(version) {
VersionChecker.getDependencies(project, version) {
project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, it)
}
project.dependencies.add(Constants.MINECRAFT_CONFIGURATION, "net.minecraft:client:$version:remapped")

View file

@ -21,7 +21,7 @@ object AssetDownloader {
fun download(project: Project, manualInvocation: Boolean = false) {
val version = ProjectStorage.get(project).minecraftVersion!!
val path = PhytotelmaPlugin.globalCacheDir.resolve("assets")
val assetIndex = VersionChecker.fetchVersionData(version).assetIndex
val assetIndex = VersionChecker.fetchVersionData(project, version).assetIndex
val dest = path.resolve("indexes").resolve(assetIndex.id + ".json")
var overwrite = manualInvocation
if (dest.notExists()) {

View file

@ -25,7 +25,7 @@ object RunConfigGenerator {
if (assetIndexPath.notExists()) {
assetIndexPath.createDirectories()
}
val indexId = VersionChecker.fetchVersionData(ProjectStorage.get(project).minecraftVersion!!).assetIndex.id
val indexId = VersionChecker.fetchVersionData(project, ProjectStorage.get(project).minecraftVersion!!).assetIndex.id
val log4jPath = project.rootDir.resolve(".gradle/phytotelma/log4j.xml").toPath().absolute()
if (log4jPath.notExists()) {
log4jPath.createParentDirectories()

View file

@ -29,7 +29,7 @@ abstract class RunGameTask @Inject constructor(env: Env) : JavaExec() {
if (assetIndexPath.notExists()) {
assetIndexPath.createDirectories()
}
val indexId = VersionChecker.fetchVersionData(ProjectStorage.get(project).minecraftVersion!!).assetIndex.id
val indexId = VersionChecker.fetchVersionData(project, ProjectStorage.get(project).minecraftVersion!!).assetIndex.id
val log4jPath = project.rootDir.resolve(".gradle/phytotelma/log4j.xml").toPath().absolute()
if (log4jPath.notExists()) {
log4jPath.createParentDirectories()