diff --git a/build.gradle b/build.gradle index ff5a5fb..177d036 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { } group "me.shedaniel" -version = "1.3." + (System.getenv("GITHUB_RUN_NUMBER") == null ? (((short) new Random().nextInt()).abs() + 1000).toString() : System.getenv("GITHUB_RUN_NUMBER")) +version = "2.0." + (System.getenv("GITHUB_RUN_NUMBER") == null ? (((short) new Random().nextInt()).abs() + 1000).toString() : System.getenv("GITHUB_RUN_NUMBER")) logger.lifecycle(":building architectury plugin v${version}") diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt index f9a6ee8..0f2228c 100644 --- a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt +++ b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt @@ -1,5 +1,6 @@ package me.shedaniel.architect.plugin +import me.shedaniel.architect.plugin.transformers.* import net.fabricmc.loom.util.LoggerFilter import org.gradle.api.Plugin import org.gradle.api.Project @@ -17,29 +18,46 @@ class ArchitectPlugin : Plugin { ) ) - project.extensions.create("architect", ArchitectPluginExtension::class.java, project) - project.extensions.add("architectury", project.extensions.getByName("architect")) + project.extensions.create("architectury", ArchitectPluginExtension::class.java, project) - project.tasks.register("transformForge", RemapMCPTask::class.java) { - it.fakeMod = false - it.remapMcp = false + project.tasks.register("transformProductionFabric", TransformingTask::class.java) { it.group = "Architectury" + it(RemapMixinVariables) + it(TransformExpectPlatform) + it(TransformInjectables) + it(AddRefmapName) } - project.tasks.register("transformForgeFakeMod", RemapMCPTask::class.java) { - it.fakeMod = true - it.remapMcp = false + project.tasks.register("transformDevelopmentFabric", TransformingTask::class.java) { it.group = "Architectury" + it(GenerateFakeFabricModJson) + it(TransformExpectPlatform) + it(TransformInjectables) } - project.tasks.register("transformArchitectJar", TransformTask::class.java) { + project.tasks.register("transformProductionForge", TransformingTask::class.java) { it.group = "Architectury" - it.runtime = false + it(RemapMixinVariables) + it(TransformExpectPlatform) + it(TransformInjectables) + it(AddRefmapName) + + it(TransformForgeBytecode) + it(RemoveFabricModJson) + it(TransformForgeEnvironment) + it(FixForgeMixin) } - project.tasks.register("transformArchitectJarRuntime", TransformTask::class.java) { + project.tasks.register("transformDevelopmentForge", TransformingTask::class.java) { it.group = "Architectury" - it.runtime = true + it(TransformExpectPlatform) + it(TransformInjectables) + + it(TransformForgeBytecode) + it(RemoveFabricModJson) + it(TransformForgeEnvironment) + it(GenerateFakeForgeMod) + it(FixForgeMixin) } project.repositories.apply { diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt index 780c26e..a1b5eb2 100644 --- a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt +++ b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt @@ -30,15 +30,13 @@ open class ArchitectPluginExtension(val project: Project) { add("compileOnly", "me.shedaniel:architectury-injectables:1.0.4") } } - + if (forgeEnabled) { - project.configurations.create("mcp") - project.configurations.create("mcpGenerateMod") - project.configurations.create("transformForge") - project.configurations.create("transformForgeFakeMod") + project.configurations.create("transformProductionForge") + project.configurations.create("transformDevelopmentForge") } - project.configurations.create("transformed") - project.configurations.create("transformedRuntime") + project.configurations.create("transformProductionFabric") + project.configurations.create("transformDevelopmentFabric") val buildTask = project.tasks.getByName("build") val jarTask = project.tasks.getByName("jar") { @@ -46,101 +44,73 @@ open class ArchitectPluginExtension(val project: Project) { it.archiveClassifier.set("dev") } as AbstractArchiveTask - val transformArchitectJarTask = project.tasks.getByName("transformArchitectJar") { - it as TransformTask + val transformProductionFabricTask = project.tasks.getByName("transformProductionFabric") { + it as TransformingTask - it.archiveClassifier.set("transformed") + it.archiveClassifier.set("transformProductionFabric") it.input.set(jarTask.archiveFile.get()) - project.artifacts.add("archives", it) + project.artifacts.add("transformProductionFabric", it) it.dependsOn(jarTask) buildTask.dependsOn(it) it.outputs.upToDateWhen { false } - } as TransformTask - val transformArchitectRuntimeJarTask = project.tasks.getByName("transformArchitectJarRuntime") { - it as TransformTask + } as TransformingTask + val transformDevelopmentFabricTask = project.tasks.getByName("transformDevelopmentFabric") { + it as TransformingTask - it.archiveClassifier.set("transformedRuntime") + it.archiveClassifier.set("transformDevelopmentFabric") it.input.set(jarTask.archiveFile.get()) - project.artifacts.add("archives", it) + project.artifacts.add("transformDevelopmentFabric", it) it.dependsOn(jarTask) buildTask.dependsOn(it) it.outputs.upToDateWhen { false } - } as TransformTask + } as TransformingTask val remapJarTask = project.tasks.getByName("remapJar") { it as RemapJarTask it.archiveClassifier.set("") - it.input.set(transformArchitectJarTask.archiveFile.get()) - it.dependsOn(transformArchitectJarTask) - it.mustRunAfter(transformArchitectJarTask) + it.input.set(transformProductionFabricTask.archiveFile.get()) + it.dependsOn(transformProductionFabricTask) + it.mustRunAfter(transformProductionFabricTask) } as RemapJarTask if (forgeEnabled) { - val transformForgeTask = project.tasks.getByName("transformForge") { - it as RemapMCPTask + val transformProductionForgeTask = project.tasks.getByName("transformProductionForge") { + it as TransformingTask - it.input.set(transformArchitectJarTask.archiveFile.get()) - it.archiveClassifier.set("transformedForge") - it.dependsOn(transformArchitectJarTask) + it.input.set(jarTask.archiveFile.get()) + it.archiveClassifier.set("transformProductionForge") + + project.artifacts.add("transformProductionForge", it) + it.dependsOn(jarTask) buildTask.dependsOn(it) it.outputs.upToDateWhen { false } - } as RemapMCPTask + } as TransformingTask - val transformForgeFakeModTask = project.tasks.getByName("transformForgeFakeMod") { - it as RemapMCPTask + val transformDevelopmentForgeTask = project.tasks.getByName("transformDevelopmentForge") { + it as TransformingTask - it.input.set(transformArchitectRuntimeJarTask.archiveFile.get()) - it.archiveClassifier.set("transformedForgeFakeMod") - it.dependsOn(transformArchitectRuntimeJarTask) + it.input.set(jarTask.archiveFile.get()) + it.archiveClassifier.set("transformDevelopmentForge") + + project.artifacts.add("transformDevelopmentForge", it) { artifact -> + artifact.builtBy(it) + } + it.dependsOn(jarTask) buildTask.dependsOn(it) it.outputs.upToDateWhen { false } - } as RemapMCPTask + } as TransformingTask - transformForgeTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() - transformForgeFakeModTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() - - project.artifacts { - it.add( - "transformForge", mapOf( - "file" to transformForgeTask.archiveFile.get().asFile, - "type" to "jar", - "builtBy" to transformForgeTask - ) - ) - it.add( - "transformForgeFakeMod", mapOf( - "file" to transformForgeFakeModTask.archiveFile.get().asFile, - "type" to "jar", - "builtBy" to transformForgeFakeModTask - ) - ) - } + transformProductionForgeTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() + transformDevelopmentForgeTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() project.extensions.getByType(LoomGradleExtension::class.java).generateSrgTiny = true } - transformArchitectJarTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() - transformArchitectRuntimeJarTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() - - project.artifacts { - it.add( - "transformed", mapOf( - "file" to transformArchitectJarTask.archiveFile.get().asFile, - "type" to "jar", - "builtBy" to transformArchitectJarTask - ) - ) - it.add( - "transformedRuntime", mapOf( - "file" to transformArchitectRuntimeJarTask.archiveFile.get().asFile, - "type" to "jar", - "builtBy" to transformArchitectRuntimeJarTask - ) - ) - } + transformProductionFabricTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() + transformDevelopmentFabricTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar() } } diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/RemapMCPTask.kt b/src/main/kotlin/me/shedaniel/architect/plugin/RemapMCPTask.kt deleted file mode 100644 index 69f6afa..0000000 --- a/src/main/kotlin/me/shedaniel/architect/plugin/RemapMCPTask.kt +++ /dev/null @@ -1,540 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -package me.shedaniel.architect.plugin - -import com.google.gson.GsonBuilder -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import me.shedaniel.architect.plugin.utils.GradleSupport -import me.shedaniel.architect.plugin.utils.validateJarFs -import net.fabricmc.loom.LoomGradleExtension -import net.fabricmc.loom.util.LoggerFilter -import net.fabricmc.loom.util.TinyRemapperMappingsHelper -import net.fabricmc.mapping.tree.TinyTree -import net.fabricmc.tinyremapper.* -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.TaskAction -import org.gradle.jvm.tasks.Jar -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.AnnotationNode -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.MethodInsnNode -import org.zeroturnaround.zip.ZipUtil -import java.io.* -import java.net.URL -import java.nio.file.Files -import java.nio.file.Path -import java.util.* -import java.util.jar.Manifest -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream -import java.util.zip.ZipOutputStream -import kotlin.collections.LinkedHashSet - -open class RemapMCPTask : Jar() { - private val fromM: String = "named" - var remapMcp = true - var fakeMod = false - val input: RegularFileProperty = GradleSupport.getFileProperty(project) - private val environmentClass = "net/fabricmc/api/Environment" - - @TaskAction - fun doTask() { - LoggerFilter.replaceSystemOut() - val input: Path = this.input.asFile.get().toPath() - val intermediate: Path = input.parent.resolve(input.toFile().nameWithoutExtension + "-intermediate.jar") - val output: Path = this.archiveFile.get().asFile.toPath() - - intermediate.toFile().delete() - output.toFile().delete() - - if (!Files.exists(input)) { - throw FileNotFoundException(input.toString()) - } - - run { - val zipOutputStream = ZipOutputStream(intermediate.toFile().outputStream()) - zipOutputStream.use { - ZipInputStream(Files.newInputStream(input)).use { - while (true) { - val entry = it.nextEntry ?: break - zipOutputStream.putNextEntry(ZipEntry(entry.name)) - var allBytes = it.readBytes() - if (entry.name.toString().endsWith(".class")) { - val reader = ClassReader(allBytes) - if ((reader.access and Opcodes.ACC_MODULE) == 0) { - val node = ClassNode(Opcodes.ASM8) - reader.accept(node, ClassReader.EXPAND_FRAMES) - val writer = ClassWriter(0) - transform(node).accept(writer) - allBytes = writer.toByteArray() - } - } - zipOutputStream.write(allBytes) - zipOutputStream.closeEntry() - } - } - } - - if (ZipUtil.containsEntry(intermediate.toFile(), "fabric.mod.json")) { - ZipUtil.removeEntry(intermediate.toFile(), "fabric.mod.json") - } - } - - - val classpathFiles: Set = LinkedHashSet( - project.configurations.getByName("compileClasspath").files - ) - val classpath = classpathFiles.asSequence().map { obj: File -> obj.toPath() } - .filter { p: Path -> input != p && Files.exists(p) }.toList().toTypedArray() - - val remapperBuilder: TinyRemapper.Builder = TinyRemapper.newRemapper() - if (remapMcp) { - val mappings = getMappings() - val mojmapToMcpClass: Map = createMojmapToMcpClass(mappings) - remapperBuilder.withMappings( - remapToMcp( - TinyRemapperMappingsHelper.create(mappings, fromM, fromM, false), - mojmapToMcpClass - ) - ) - remapperBuilder.skipLocalVariableMapping(true) - } else { - remapperBuilder.withMappings(remapToMcp(null, null)) - remapperBuilder.skipLocalVariableMapping(true) - } - - mapMixin(remapperBuilder) - - project.logger.lifecycle( - ":${ - listOfNotNull( - "remapping".takeIf { remapMcp }, - "transforming" - ).joinToString(" and ") - } " + input.fileName + " => " + output.fileName + if (fakeMod) " (with fake mod)" else "" - ) - - val architectFolder = project.rootProject.buildDir.resolve("tmp/architect") - architectFolder.deleteRecursively() - architectFolder.mkdirs() - val fakeModId = "generated_" + UUID.randomUUID().toString().filterNot { it == '-' }.take(7) - if (fakeMod) { - val modsToml = architectFolder.resolve("META-INF/mods.toml") - modsToml.parentFile.mkdirs() - modsToml.writeText( - """ -modLoader = "javafml" -loaderVersion = "[33,)" -license = "Generated" -[[mods]] -modId = "$fakeModId" - """.trimIndent() - ) - val mcmeta = architectFolder.resolve("pack.mcmeta") - mcmeta.parentFile.mkdirs() - mcmeta.writeText( - """ -{"pack":{"description":"Generated","pack_format":4}} - """.trimIndent() - ) - } - - val remapper = remapperBuilder.build() - - try { - project.validateJarFs(output) - OutputConsumerPath.Builder(output).build().use { outputConsumer -> - outputConsumer.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, null) - outputConsumer.addNonClassFiles(architectFolder.toPath(), NonClassCopyMode.UNCHANGED, null) - remapper.readClassPath(*classpath) - remapper.readInputs(intermediate) - remapper.apply(outputConsumer) - - if (fakeMod) { - val className = "generated/$fakeModId" - val classWriter = ClassWriter(0) - classWriter.visit(52, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null) - val modAnnotation = classWriter.visitAnnotation("Lnet/minecraftforge/fml/common/Mod;", false) - modAnnotation.visit("value", fakeModId) - modAnnotation.visitEnd() - classWriter.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, arrayOf()).also { - it.visitVarInsn(Opcodes.ALOAD, 0) - it.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false) - it.visitInsn(Opcodes.RETURN) - it.visitMaxs(1, 1) - it.visitEnd() - } - classWriter.visitEnd() - outputConsumer.accept(className, classWriter.toByteArray()) - } - } - } catch (e: Exception) { - remapper.finish() - throw RuntimeException("Failed to remap $input to $output", e) - } - - architectFolder.deleteRecursively() - remapper.finish() - - intermediate.toFile().delete() - - fixMixins(output.toFile()) - - if (!Files.exists(output)) { - throw RuntimeException("Failed to remap $input to $output - file missing!") - } - } - - private fun mapMixin(remapperBuilder: TinyRemapper.Builder) { - val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) - val srg = project.extensions.getByType(LoomGradleExtension::class.java).mappingsProvider.mappingsWithSrg - for (mixinMapFile in loomExtension.allMixinMappings) { - if (mixinMapFile.exists()) { - remapperBuilder.withMappings { sink -> - TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), "named", "intermediary").load(object : - IMappingProvider.MappingAcceptor { - override fun acceptClass(srcName: String, dstName: String) { - sink.acceptClass(dstName, srg.classes - .firstOrNull { it.getName("intermediary") == dstName } - ?.getName("srg") ?: dstName - ) - } - - override fun acceptMethod(method: IMappingProvider.Member, dstName: String) { - sink.acceptMethod( - IMappingProvider.Member(method.owner, dstName, method.desc), - srg.classes - .flatMap { it.methods } - .firstOrNull { it.getName("intermediary") == dstName } - ?.getName("srg") ?: dstName) - } - - override fun acceptField(field: IMappingProvider.Member, dstName: String) { - sink.acceptField( - IMappingProvider.Member(field.owner, dstName, field.desc), - srg.classes - .flatMap { it.fields } - .firstOrNull { it.getName("intermediary") == dstName } - ?.getName("srg") ?: dstName) - } - - override fun acceptMethodArg(method: IMappingProvider.Member, lvIndex: Int, dstName: String) {} - - override fun acceptMethodVar( - method: IMappingProvider.Member, - lvIndex: Int, - startOpIdx: Int, - asmIndex: Int, - dstName: String - ) { - } - }) - } - } - } - } - - private fun fixMixins(output: File) { - val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) - val gson = GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create() - val mixinConfigs = mutableListOf() - val refmap = loomExtension.getRefmapName() - ZipUtil.iterate(output) { stream, entry -> - if (!entry.isDirectory && entry.name.endsWith(".json") && - !entry.name.contains("/") && !entry.name.contains("\\") - ) { - try { - InputStreamReader(stream).use { reader -> - val json: JsonObject? = gson.fromJson(reader, JsonObject::class.java) - if (json != null) { - val hasMixins = json.has("mixins") && json["mixins"].isJsonArray - val hasClient = json.has("client") && json["client"].isJsonArray - val hasServer = json.has("server") && json["server"].isJsonArray - if (json.has("package") && (hasMixins || hasClient || hasServer)) { - mixinConfigs.add(entry.name) - } - } - } - } catch (ignored: Exception) { - } - } - } - if (mixinConfigs.isNotEmpty()) { - if (ZipUtil.containsEntry(output, "META-INF/MANIFEST.MF")) { - ZipUtil.transformEntry(output, "META-INF/MANIFEST.MF") { input, zipEntry, out -> - val manifest = Manifest(input) - manifest.mainAttributes.putValue("MixinConfigs", mixinConfigs.joinToString(",")) - out.putNextEntry(ZipEntry(zipEntry.name)) - manifest.write(out) - out.closeEntry() - } - } - } - if (ZipUtil.containsEntry(output, refmap)) { - ZipUtil.transformEntry(output, refmap) { input, zipEntry, out -> - val refmapElement: JsonObject = JsonParser().parse(InputStreamReader(input)).asJsonObject.deepCopy() - if (refmapElement.has("mappings")) { - refmapElement["mappings"].asJsonObject.entrySet().forEach { (_, value) -> - remapRefmap(value.asJsonObject) - } - } - if (refmapElement.has("data")) { - val data = refmapElement["data"].asJsonObject - if (data.has("named:intermediary")) { - data.add("searge", data["named:intermediary"].deepCopy().also { - it.asJsonObject.entrySet().forEach { (_, value) -> - remapRefmap(value.asJsonObject) - } - }) - data.remove("named:intermediary") - } - } - out.putNextEntry(ZipEntry(zipEntry.name)) - out.write(gson.toJson(refmapElement).toByteArray()) - out.closeEntry() - } - } else { - project.logger.warn("Failed to locate refmap: $refmap") - } - } - - private fun remapRefmap(obj: JsonObject) { - val srg = project.extensions.getByType(LoomGradleExtension::class.java).mappingsProvider.mappingsWithSrg - val methodPattern = "L(.*);(.*)(\\(.*)".toRegex() - val methodPatternWithoutClass = "(.*)(\\(.*)".toRegex() - val fieldPattern = "(.*):(.*)".toRegex() - - obj.keySet().forEach { key -> - val originalRef = obj[key].asString - - val methodMatch = methodPattern.matchEntire(originalRef) - val fieldMatch = fieldPattern.matchEntire(originalRef) - val methodMatchWithoutClass = methodPatternWithoutClass.matchEntire(originalRef) - - when { - methodMatch != null -> { - val matchedClass = - srg.classes.firstOrNull { it.getName("intermediary") == methodMatch.groups[1]!!.value } - val replacementName: String = srg.classes.asSequence() - .flatMap { it.methods.asSequence() } - .filter { it.getName("intermediary") == methodMatch.groups[2]!!.value } - .firstOrNull { it.getDescriptor("intermediary") == methodMatch.groups[3]!!.value } - ?.getName("srg") ?: methodMatch.groups[2]!!.value - obj.addProperty( - key, originalRef - .replaceFirst( - methodMatch.groups[1]!!.value, - matchedClass?.getName("srg") ?: methodMatch.groups[1]!!.value - ) - .replaceFirst(methodMatch.groups[2]!!.value, replacementName) - .replaceFirst(methodMatch.groups[3]!!.value, methodMatch.groups[3]!!.value.remapDescriptor { - srg.classes.firstOrNull { def -> def.getName("intermediary") == it }?.getName("srg") - ?: it - }) - ) - } - fieldMatch != null -> { - val replacementName: String = srg.classes.asSequence() - .flatMap { it.fields.asSequence() } - .filter { it.getName("intermediary") == fieldMatch.groups[1]!!.value } - .firstOrNull { it.getDescriptor("intermediary") == fieldMatch.groups[2]!!.value } - ?.getName("srg") ?: fieldMatch.groups[1]!!.value - obj.addProperty( - key, originalRef - .replaceFirst(fieldMatch.groups[1]!!.value, replacementName) - .replaceFirst(fieldMatch.groups[2]!!.value, fieldMatch.groups[2]!!.value.remapDescriptor { - srg.classes.firstOrNull { def -> def.getName("intermediary") == it }?.getName("srg") - ?: it - }) - ) - } - methodMatchWithoutClass != null -> { - val replacementName: String = srg.classes.asSequence() - .flatMap { it.methods.asSequence() } - .filter { it.getName("intermediary") == methodMatchWithoutClass.groups[1]!!.value } - .firstOrNull { it.getDescriptor("intermediary") == methodMatchWithoutClass.groups[2]!!.value } - ?.getName("srg") ?: methodMatchWithoutClass.groups[1]!!.value - obj.addProperty( - key, originalRef - .replaceFirst(methodMatchWithoutClass.groups[1]!!.value, replacementName) - .replaceFirst( - methodMatchWithoutClass.groups[2]!!.value, - methodMatchWithoutClass.groups[2]!!.value.remapDescriptor { - srg.classes.firstOrNull { def -> def.getName("intermediary") == it }?.getName("srg") - ?: it - }) - ) - } - else -> logger.warn("Failed to remap refmap value: $originalRef") - } - } - } - - private fun String.remapDescriptor(classMappings: (String) -> String): String { - return try { - val reader = StringReader(this) - val result = StringBuilder() - var insideClassName = false - val className = StringBuilder() - while (true) { - val c: Int = reader.read() - if (c == -1) { - break - } - if (c == ';'.toInt()) { - insideClassName = false - result.append(classMappings(className.toString())) - } - if (insideClassName) { - className.append(c.toChar()) - } else { - result.append(c.toChar()) - } - if (!insideClassName && c == 'L'.toInt()) { - insideClassName = true - className.setLength(0) - } - } - result.toString() - } catch (e: IOException) { - throw AssertionError(e) - } - } - - private fun remapToMcp(parent: IMappingProvider?, mojmapToMcpClass: Map?): IMappingProvider = - IMappingProvider { out -> - out.acceptClass("net/fabricmc/api/Environment", "net/minecraftforge/api/distmarker/OnlyIn") - out.acceptClass("net/fabricmc/api/EnvType", "net/minecraftforge/api/distmarker/Dist") - out.acceptField( - IMappingProvider.Member("net/fabricmc/api/EnvType", "SERVER", "Lnet/fabricmc/api/EnvType;"), - "DEDICATED_SERVER" - ) - - parent?.load(object : IMappingProvider.MappingAcceptor { - override fun acceptClass(srcName: String?, dstName: String?) { - out.acceptClass(srcName, mojmapToMcpClass!![srcName] ?: srcName) - } - - override fun acceptMethod(method: IMappingProvider.Member?, dstName: String?) { - } - - override fun acceptMethodArg(method: IMappingProvider.Member?, lvIndex: Int, dstName: String?) { - } - - override fun acceptMethodVar( - method: IMappingProvider.Member?, - lvIndex: Int, - startOpIdx: Int, - asmIndex: Int, - dstName: String? - ) { - } - - override fun acceptField(field: IMappingProvider.Member?, dstName: String?) { - } - }) - } - - private fun getMappings(): TinyTree { - val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) - return loomExtension.mappingsProvider.mappings - } - - private fun getRootExtension(): ArchitectPluginExtension = - project.rootProject.extensions.getByType(ArchitectPluginExtension::class.java) - - private fun createMojmapToMcpClass(mappings: TinyTree): Map { - val mcpMappings = readMCPMappings(getRootExtension().minecraft) - val mutableMap = mutableMapOf() - mappings.classes.forEach { clazz -> - val official = clazz.getName("official") - val named = clazz.getName("named") - val mcp = mcpMappings[official] - if (mcp != null) { - mutableMap[named] = mcp - } - } - return mutableMap - } - - private fun readMCPMappings(version: String): Map { - val file = project.rootProject.file(".gradle/mappings/mcp-$version.tsrg") - if (file.exists().not()) { - file.parentFile.mkdirs() - file.writeText(URL("https://raw.githubusercontent.com/MinecraftForge/MCPConfig/master/versions/release/$version/joined.tsrg").readText()) - } - return mutableMapOf().also { readMappings(it, file.inputStream()) } - } - - private fun readMappings(mutableMap: MutableMap, inputStream: InputStream) { - inputStream.bufferedReader().forEachLine { - if (!it.startsWith("\t")) { - val split = it.split(" ") - val obf = split[0] - val className = split[1] - mutableMap[obf] = className - } - } - } - - val forgeEvent = "Lme/shedaniel/architectury/ForgeEvent;" - val forgeEventCancellable = "Lme/shedaniel/architectury/ForgeEventCancellable;" - - val cancellable = "Lnet/minecraftforge/eventbus/api/Cancelable;" - - private fun transform(node: ClassNode): ClassNode { - if (node.access and Opcodes.ACC_INTERFACE == 0) { - if (node.visibleAnnotations?.any { it.desc == forgeEvent || it.desc == forgeEventCancellable } == true) { - node.superName = "net/minecraftforge/eventbus/api/Event" - node.methods.forEach { - if (it.name == "") { - for (insnNode in it.instructions) { - if (insnNode.opcode == Opcodes.INVOKESPECIAL) { - insnNode as MethodInsnNode - if (insnNode.name == "" && insnNode.owner == "java/lang/Object") { - insnNode.owner = "net/minecraftforge/eventbus/api/Event" - break - } - } - } - } - } - node.signature?.let { - node.signature = it.substringBeforeLast('L') + "Lnet/minecraftforge/eventbus/api/Event;" - } - // if @ForgeEventCancellable, add the cancellable annotation from forge - node.visibleAnnotations.apply { - if (any { it.desc == forgeEventCancellable }) { - add(AnnotationNode(cancellable)) - } - } - } - } - node.visibleAnnotations = (node.visibleAnnotations ?: mutableListOf()).apply { - val invisibleEnvironments = - node.invisibleAnnotations?.filter { it.desc == "L${environmentClass};" } ?: emptyList() - node.invisibleAnnotations?.removeAll(invisibleEnvironments) - addAll(invisibleEnvironments) - } - node.fields.forEach { field -> - field.visibleAnnotations = (field.visibleAnnotations ?: mutableListOf()).apply { - val invisibleEnvironments = - field.invisibleAnnotations?.filter { it.desc == "L${environmentClass};" } ?: emptyList() - field.invisibleAnnotations?.removeAll(invisibleEnvironments) - addAll(invisibleEnvironments) - } - } - node.methods.forEach { method -> - method.visibleAnnotations = (method.visibleAnnotations ?: mutableListOf()).apply { - val invisibleEnvironments = - method.invisibleAnnotations?.filter { it.desc == "L${environmentClass};" } ?: emptyList() - method.invisibleAnnotations?.removeAll(invisibleEnvironments) - addAll(invisibleEnvironments) - } - } - return node - } -} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt b/src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt deleted file mode 100644 index 6f49cc8..0000000 --- a/src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt +++ /dev/null @@ -1,218 +0,0 @@ -package me.shedaniel.architect.plugin - -import me.shedaniel.architect.plugin.utils.ClassTransformer -import me.shedaniel.architect.plugin.utils.Transform -import org.gradle.api.Project -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.Handle -import org.objectweb.asm.Opcodes -import org.objectweb.asm.commons.ClassRemapper -import org.objectweb.asm.commons.Remapper -import org.objectweb.asm.tree.* -import org.zeroturnaround.zip.ZipUtil -import java.io.File -import java.io.InputStream -import java.lang.invoke.CallSite -import java.lang.invoke.MethodHandles -import java.lang.invoke.MethodType -import java.util.* -import java.util.zip.ZipEntry -import kotlin.properties.Delegates - -const val expectPlatform = "Lme/shedaniel/architectury/ExpectPlatform;" -const val expectPlatformNew = "Lme/shedaniel/architectury/annotations/ExpectPlatform;" - -fun Project.projectUniqueIdentifier(): String { - val cache = File(project.file(".gradle"), "architectury-cache") - cache.mkdirs() - val uniqueIdFile = File(cache, "projectID") - var id by Delegates.notNull() - if (uniqueIdFile.exists()) { - id = uniqueIdFile.readText() - } else { - id = UUID.randomUUID().toString().filterNot { it == '-' } - uniqueIdFile.writeText(id) - } - var name = project.name - if (project.rootProject != project) name = project.rootProject.name + "_" + name - return "architectury_inject_${name}_$id".filter { Character.isJavaIdentifierPart(it) } -} - -fun transformExpectPlatform(project: Project): ClassTransformer { - val projectUniqueIdentifier by lazy { project.projectUniqueIdentifier() } - var injectedClass = !project.extensions.getByType(ArchitectPluginExtension::class.java).injectInjectables - return { clazz, classAdder -> - if (!injectedClass) { - injectedClass = true - Transform::class.java.getResourceAsStream("/annotations-inject/injection.jar").use { stream -> - ZipUtil.iterate(stream) { input: InputStream, entry: ZipEntry -> - if (entry.name.endsWith(".class")) { - val newName = "$projectUniqueIdentifier/${ - entry.name.substringBeforeLast(".class").substringAfterLast('/') - }" - classAdder(newName, input.readBytes().let { - val node = ClassNode(Opcodes.ASM8) - ClassReader(it).accept(node, ClassReader.EXPAND_FRAMES) - val writer = ClassWriter(ClassWriter.COMPUTE_MAXS) - val remapper = ClassRemapper(writer, object : Remapper() { - override fun map(internalName: String?): String { - if (internalName?.startsWith("me/shedaniel/architect/plugin/callsite") == true) { - return internalName.replace( - "me/shedaniel/architect/plugin/callsite", - projectUniqueIdentifier - ) - } - return super.map(internalName) - } - }) - node.apply { - name = newName - }.accept(remapper) - - writer.toByteArray() - }) - } - } - } - } - - clazz.methods.mapNotNull { method -> - when { - method?.visibleAnnotations?.any { it.desc == expectPlatform } == true -> method to "me/shedaniel/architectury/PlatformMethods" - method?.invisibleAnnotations?.any { it.desc == expectPlatformNew } == true -> { - method to "$projectUniqueIdentifier/PlatformMethods" - } - else -> null - } - }.forEach { (method, platformMethodsClass) -> - if (method.access and Opcodes.ACC_STATIC == 0) { - System.err.println("@ExpectPlatform can only apply to static methods!") - } else { - method.instructions.clear() - val endOfDesc = method.desc.lastIndexOf(')') - val returnValue = method.desc.substring(endOfDesc + 1) - val args = method.desc.substring(1, endOfDesc) - var cursor = 0 - var inClass = false - var index = 0 - while (cursor < args.length) { - val char = args[cursor] - if (inClass) { - if (char == ';') { - method.instructions.addLoad(char, index++) - inClass = false - } - } else when (char) { - '[' -> Unit - 'L' -> inClass = true - else -> method.instructions.addLoad(char, when (char) { - 'J', 'D' -> index.also { index += 2 } - else -> index++ - }) - } - cursor++ - } - - val methodType = MethodType.methodType( - CallSite::class.java, - MethodHandles.Lookup::class.java, - String::class.java, - MethodType::class.java - ) - - val handle = Handle( - Opcodes.H_INVOKESTATIC, - platformMethodsClass, - "platform", - methodType.toMethodDescriptorString(), - false - ) - - method.instructions.add( - InvokeDynamicInsnNode( - method.name, - method.desc, - handle - ) - ) - - method.instructions.addReturn(returnValue.first { it != '[' }) - method.maxStack = -1 - } - } - - clazz - } -} - -private fun InsnList.addLoad(type: Char, index: Int) { - when (type) { - ';' -> add( - VarInsnNode( - Opcodes.ALOAD, - index - ) - ) - 'I', 'S', 'B', 'C', 'Z' -> add( - VarInsnNode( - Opcodes.ILOAD, - index - ) - ) - 'F' -> add( - VarInsnNode( - Opcodes.FLOAD, - index - ) - ) - 'J' -> add( - VarInsnNode( - Opcodes.LLOAD, - index - ) - ) - 'D' -> add( - VarInsnNode( - Opcodes.DLOAD, - index - ) - ) - else -> throw IllegalStateException("Invalid Type: $type") - } -} - -private fun InsnList.addReturn(type: Char) { - when (type) { - 'L' -> add( - InsnNode( - Opcodes.ARETURN - ) - ) - 'I', 'S', 'B', 'C', 'Z' -> add( - InsnNode( - Opcodes.IRETURN - ) - ) - 'F' -> add( - InsnNode( - Opcodes.FRETURN - ) - ) - 'J' -> add( - InsnNode( - Opcodes.LRETURN - ) - ) - 'D' -> add( - InsnNode( - Opcodes.DRETURN - ) - ) - 'V' -> add( - InsnNode( - Opcodes.RETURN - ) - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/TransformTask.kt b/src/main/kotlin/me/shedaniel/architect/plugin/TransformTask.kt deleted file mode 100644 index 20c8f05..0000000 --- a/src/main/kotlin/me/shedaniel/architect/plugin/TransformTask.kt +++ /dev/null @@ -1,175 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -package me.shedaniel.architect.plugin - -import me.shedaniel.architect.plugin.utils.GradleSupport -import me.shedaniel.architect.plugin.utils.Transform -import me.shedaniel.architect.plugin.utils.validateJarFs -import net.fabricmc.loom.LoomGradleExtension -import net.fabricmc.loom.util.LoggerFilter -import net.fabricmc.tinyremapper.* -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.TaskAction -import org.gradle.jvm.tasks.Jar -import org.zeroturnaround.zip.ByteSource -import org.zeroturnaround.zip.ZipUtil -import java.io.File -import java.nio.file.Files -import java.nio.file.Path -import java.util.* -import kotlin.collections.LinkedHashSet - -open class TransformTask : Jar() { - val input: RegularFileProperty = GradleSupport.getFileProperty(project) - var runtime = false - - @TaskAction - fun doTask() { - val input: Path = this.input.asFile.get().toPath() - val intermediate: Path = input.parent.resolve(input.toFile().nameWithoutExtension + "-intermediate.jar") - val intermediate2: Path = input.parent.resolve(input.toFile().nameWithoutExtension + "-intermediate2.jar") - val output: Path = this.archiveFile.get().asFile.toPath() - - if (!runtime) { - val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) - var remapperBuilder = TinyRemapper.newRemapper() - for (mixinMapFile in loomExtension.allMixinMappings) { - if (mixinMapFile.exists()) { - remapperBuilder = remapperBuilder.withMappings( - TinyUtils.createTinyMappingProvider( - mixinMapFile.toPath(), - "named", - "intermediary" - ) - ) - } - } - - val remapper = remapperBuilder.build() - - val classpathFiles: Set = LinkedHashSet( - project.configurations.getByName("compileClasspath").files - ) - val classpath = classpathFiles.asSequence().map { obj: File -> obj.toPath() }.filter { p: Path -> - input != p && Files.exists(p) - }.toList().toTypedArray() - - LoggerFilter.replaceSystemOut() - try { - project.validateJarFs(intermediate) - OutputConsumerPath.Builder(intermediate).build().use { outputConsumer -> - outputConsumer.addNonClassFiles(input) - remapper.readClassPath(*classpath) - remapper.readInputs(input) - remapper.apply(outputConsumer) - } - } catch (e: Exception) { - remapper.finish() - throw RuntimeException("Failed to remap $input to $intermediate", e) - } - - remapper.finish() - } else { - Files.copy(input, intermediate) - val fakeModId = "generated_" + UUID.randomUUID().toString().filterNot { it == '-' }.take(7) - ZipUtil.addOrReplaceEntries( - intermediate.toFile(), arrayOf( - ByteSource( - "fabric.mod.json", """{ - "schemaVersion": 1, - "id": "$fakeModId", - "name": "Generated Mod (Please Ignore)", - "version": "1.0.0", - "custom": { - "fabric-loom:generated": true - } -}""".toByteArray() - ) - ) - ) - } - - Files.deleteIfExists(intermediate2) - project.logger.lifecycle(":transforming " + input.fileName + " => " + intermediate.fileName) - Transform.transform(intermediate, intermediate2, transformExpectPlatform(project)) - - Files.deleteIfExists(intermediate) - Files.deleteIfExists(output) - - if (project.extensions.getByType(ArchitectPluginExtension::class.java).injectInjectables) { - transformArchitecturyInjectables(intermediate2, output) - } else { - Files.copy(intermediate2, output) - } - - Files.deleteIfExists(intermediate2) - - if (!runtime) { - val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) - var refmapHelperClass: Class<*>? = null - runCatching { - refmapHelperClass = Class.forName("net.fabricmc.loom.util.MixinRefmapHelper") - }.onFailure { - runCatching { - refmapHelperClass = Class.forName("net.fabricmc.loom.build.MixinRefmapHelper") - }.onFailure { - throw ClassNotFoundException("Failed to find MixinRefmapHelper!") - } - } - - val method = refmapHelperClass!!.getDeclaredMethod( - "addRefmapName", - String::class.java, - String::class.java, - Path::class.java - ) - if ( - method.invoke( - null, - loomExtension.getRefmapName(), - loomExtension.mixinJsonVersion, - output - ) as Boolean - ) { - project.logger.debug("Transformed mixin reference maps in output JAR!") - } - } - } - - private fun transformArchitecturyInjectables(intermediate2: Path, output: Path) { - val remapper = TinyRemapper.newRemapper() - .withMappings { sink -> - sink.acceptClass( - "me/shedaniel/architectury/targets/ArchitecturyTarget", - project.projectUniqueIdentifier() + "/PlatformMethods" - ) - sink.acceptMethod( - IMappingProvider.Member( - "me/shedaniel/architectury/targets/ArchitecturyTarget", - "getCurrentTarget", - "()Ljava/lang/String;" - ), "getModLoader" - ) - } - .build() - - val classpathFiles: Set = LinkedHashSet( - project.configurations.getByName("compileClasspath").files - ) - val classpath = classpathFiles.asSequence().map { obj: File -> obj.toPath() } - .filter { p: Path -> this.input.asFile.get().toPath() != p && Files.exists(p) }.toList().toTypedArray() - - try { - project.validateJarFs(output) - OutputConsumerPath.Builder(output).build().use { outputConsumer -> - outputConsumer.addNonClassFiles(intermediate2, NonClassCopyMode.UNCHANGED, null) - remapper.readClassPath(*classpath) - remapper.readInputs(intermediate2) - remapper.apply(outputConsumer) - } - } catch (e: Exception) { - remapper.finish() - throw RuntimeException("Failed to remap $intermediate2 to $output", e) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/TransformingTask.kt b/src/main/kotlin/me/shedaniel/architect/plugin/TransformingTask.kt new file mode 100644 index 0000000..0167cce --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/TransformingTask.kt @@ -0,0 +1,84 @@ +package me.shedaniel.architect.plugin + +import me.shedaniel.architect.plugin.utils.GradleSupport +import org.gradle.api.Project +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.TaskAction +import org.gradle.jvm.tasks.Jar +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.util.* +import kotlin.properties.Delegates +import kotlin.time.Duration +import kotlin.time.ExperimentalTime +import kotlin.time.nanoseconds + +open class TransformingTask : Jar() { + val input: RegularFileProperty = GradleSupport.getFileProperty(project) + val transformers = mutableListOf() + + @ExperimentalTime + @TaskAction + fun doTask() { + val input: Path = this.input.asFile.get().toPath() + val taskOutputs = transformers.mapIndexed { index, _ -> + project.file("build") + .resolve("architectury-plugin/" + input.toFile().nameWithoutExtension + "-intermediate-${index}.jar") + .toPath() + } + val output: Path = this.archiveFile.get().asFile.toPath() + Files.deleteIfExists(output) + transformers.forEachIndexed { index, transformer -> + val i = if (index == 0) input else taskOutputs[index - 1] + val o = if (index == taskOutputs.lastIndex) output else taskOutputs[index] + Files.deleteIfExists(o) + Files.createDirectories(o.parent) + runCatching { + measureTime { + transformer(project, i, o) + if (index != 0) { + Files.deleteIfExists(i) + } + }.let { duration -> + project.logger.lifecycle(":finished transforming step ${index + 1}/${transformers.size} [${transformer::class.simpleName}] in $duration") + } + }.onFailure { + throw RuntimeException( + "Failed transformer step ${index + 1}/${transformers.size} [${transformer::class.simpleName}]", + it + ) + } + } + } + + operator fun invoke(transformer: Transformer) { + transformers.add(transformer) + } +} + +@ExperimentalTime +private inline fun measureTime(block: () -> Unit): Duration { + val current = System.nanoTime() + block() + val finished = System.nanoTime() + return (finished - current).nanoseconds +} + +fun Project.projectUniqueIdentifier(): String { + val cache = File(project.file(".gradle"), "architectury-cache") + cache.mkdirs() + val uniqueIdFile = File(cache, "projectID") + var id by Delegates.notNull() + if (uniqueIdFile.exists()) { + id = uniqueIdFile.readText() + } else { + id = UUID.randomUUID().toString().filterNot { it == '-' } + uniqueIdFile.writeText(id) + } + var name = project.name + if (project.rootProject != project) name = project.rootProject.name + "_" + name + return "architectury_inject_${name}_$id".filter { Character.isJavaIdentifierPart(it) } +} + +typealias Transformer = (project: Project, input: Path, output: Path) -> Unit \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/AddRefmapName.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/AddRefmapName.kt new file mode 100644 index 0000000..5902abf --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/AddRefmapName.kt @@ -0,0 +1,41 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.Transformer +import net.fabricmc.loom.LoomGradleExtension +import org.gradle.api.Project +import java.nio.file.Files +import java.nio.file.Path + +object AddRefmapName : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + Files.copy(input, output) + val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) + var refmapHelperClass: Class<*>? = null + runCatching { + refmapHelperClass = Class.forName("net.fabricmc.loom.util.MixinRefmapHelper") + }.onFailure { + runCatching { + refmapHelperClass = Class.forName("net.fabricmc.loom.build.MixinRefmapHelper") + }.onFailure { + throw ClassNotFoundException("Failed to find MixinRefmapHelper!") + } + } + + val method = refmapHelperClass!!.getDeclaredMethod( + "addRefmapName", + String::class.java, + String::class.java, + Path::class.java + ) + if ( + method.invoke( + null, + loomExtension.getRefmapName(), + loomExtension.mixinJsonVersion, + output + ) as Boolean + ) { + project.logger.debug("Transformed mixin reference maps in output JAR!") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/FixForgeMixin.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/FixForgeMixin.kt new file mode 100644 index 0000000..33b98d1 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/FixForgeMixin.kt @@ -0,0 +1,191 @@ +package me.shedaniel.architect.plugin.transformers + +import com.google.gson.GsonBuilder +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import me.shedaniel.architect.plugin.Transformer +import net.fabricmc.loom.LoomGradleExtension +import org.gradle.api.Project +import org.zeroturnaround.zip.ZipUtil +import java.io.File +import java.io.IOException +import java.io.InputStreamReader +import java.io.StringReader +import java.nio.file.Files +import java.nio.file.Path +import java.util.jar.Manifest +import java.util.zip.ZipEntry + +object FixForgeMixin : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + Files.copy(input, output) + fixMixins(project, output.toFile()) + } + + private fun fixMixins(project: Project, output: File) { + val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) + val gson = GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create() + val mixinConfigs = mutableListOf() + val refmap = loomExtension.getRefmapName() + ZipUtil.iterate(output) { stream, entry -> + if (!entry.isDirectory && entry.name.endsWith(".json") && + !entry.name.contains("/") && !entry.name.contains("\\") + ) { + try { + InputStreamReader(stream).use { reader -> + val json: JsonObject? = gson.fromJson(reader, JsonObject::class.java) + if (json != null) { + val hasMixins = json.has("mixins") && json["mixins"].isJsonArray + val hasClient = json.has("client") && json["client"].isJsonArray + val hasServer = json.has("server") && json["server"].isJsonArray + if (json.has("package") && (hasMixins || hasClient || hasServer)) { + mixinConfigs.add(entry.name) + } + } + } + } catch (ignored: Exception) { + } + } + } + if (mixinConfigs.isNotEmpty()) { + if (ZipUtil.containsEntry(output, "META-INF/MANIFEST.MF")) { + ZipUtil.transformEntry(output, "META-INF/MANIFEST.MF") { input, zipEntry, out -> + val manifest = Manifest(input) + manifest.mainAttributes.putValue("MixinConfigs", mixinConfigs.joinToString(",")) + out.putNextEntry(ZipEntry(zipEntry.name)) + manifest.write(out) + out.closeEntry() + } + } + } + if (ZipUtil.containsEntry(output, refmap)) { + ZipUtil.transformEntry(output, refmap) { input, zipEntry, out -> + val refmapElement: JsonObject = JsonParser().parse(InputStreamReader(input)).asJsonObject.deepCopy() + if (refmapElement.has("mappings")) { + refmapElement["mappings"].asJsonObject.entrySet().forEach { (_, value) -> + remapRefmap(project, value.asJsonObject) + } + } + if (refmapElement.has("data")) { + val data = refmapElement["data"].asJsonObject + if (data.has("named:intermediary")) { + data.add("searge", data["named:intermediary"].deepCopy().also { + it.asJsonObject.entrySet().forEach { (_, value) -> + remapRefmap(project, value.asJsonObject) + } + }) + data.remove("named:intermediary") + } + } + out.putNextEntry(ZipEntry(zipEntry.name)) + out.write(gson.toJson(refmapElement).toByteArray()) + out.closeEntry() + } + } else { + project.logger.info("Failed to locate refmap: $refmap") + } + } + + private fun remapRefmap(project: Project, obj: JsonObject) { + val srg = project.extensions.getByType(LoomGradleExtension::class.java).mappingsProvider.mappingsWithSrg + val methodPattern = "L(.*);(.*)(\\(.*)".toRegex() + val methodPatternWithoutClass = "(.*)(\\(.*)".toRegex() + val fieldPattern = "(.*):(.*)".toRegex() + + obj.keySet().forEach { key -> + val originalRef = obj[key].asString + + val methodMatch = methodPattern.matchEntire(originalRef) + val fieldMatch = fieldPattern.matchEntire(originalRef) + val methodMatchWithoutClass = methodPatternWithoutClass.matchEntire(originalRef) + + when { + methodMatch != null -> { + val matchedClass = + srg.classes.firstOrNull { it.getName("intermediary") == methodMatch.groups[1]!!.value } + val replacementName: String = srg.classes.asSequence() + .flatMap { it.methods.asSequence() } + .filter { it.getName("intermediary") == methodMatch.groups[2]!!.value } + .firstOrNull { it.getDescriptor("intermediary") == methodMatch.groups[3]!!.value } + ?.getName("srg") ?: methodMatch.groups[2]!!.value + obj.addProperty( + key, originalRef + .replaceFirst( + methodMatch.groups[1]!!.value, + matchedClass?.getName("srg") ?: methodMatch.groups[1]!!.value + ) + .replaceFirst(methodMatch.groups[2]!!.value, replacementName) + .replaceFirst(methodMatch.groups[3]!!.value, methodMatch.groups[3]!!.value.remapDescriptor { + srg.classes.firstOrNull { def -> def.getName("intermediary") == it }?.getName("srg") + ?: it + }) + ) + } + fieldMatch != null -> { + val replacementName: String = srg.classes.asSequence() + .flatMap { it.fields.asSequence() } + .filter { it.getName("intermediary") == fieldMatch.groups[1]!!.value } + .firstOrNull { it.getDescriptor("intermediary") == fieldMatch.groups[2]!!.value } + ?.getName("srg") ?: fieldMatch.groups[1]!!.value + obj.addProperty( + key, originalRef + .replaceFirst(fieldMatch.groups[1]!!.value, replacementName) + .replaceFirst(fieldMatch.groups[2]!!.value, fieldMatch.groups[2]!!.value.remapDescriptor { + srg.classes.firstOrNull { def -> def.getName("intermediary") == it }?.getName("srg") + ?: it + }) + ) + } + methodMatchWithoutClass != null -> { + val replacementName: String = srg.classes.asSequence() + .flatMap { it.methods.asSequence() } + .filter { it.getName("intermediary") == methodMatchWithoutClass.groups[1]!!.value } + .firstOrNull { it.getDescriptor("intermediary") == methodMatchWithoutClass.groups[2]!!.value } + ?.getName("srg") ?: methodMatchWithoutClass.groups[1]!!.value + obj.addProperty( + key, originalRef + .replaceFirst(methodMatchWithoutClass.groups[1]!!.value, replacementName) + .replaceFirst( + methodMatchWithoutClass.groups[2]!!.value, + methodMatchWithoutClass.groups[2]!!.value.remapDescriptor { + srg.classes.firstOrNull { def -> def.getName("intermediary") == it }?.getName("srg") + ?: it + }) + ) + } + else -> project.logger.warn("Failed to remap refmap value: $originalRef") + } + } + } + + private fun String.remapDescriptor(classMappings: (String) -> String): String { + return try { + val reader = StringReader(this) + val result = StringBuilder() + var insideClassName = false + val className = StringBuilder() + while (true) { + val c: Int = reader.read() + if (c == -1) { + break + } + if (c == ';'.toInt()) { + insideClassName = false + result.append(classMappings(className.toString())) + } + if (insideClassName) { + className.append(c.toChar()) + } else { + result.append(c.toChar()) + } + if (!insideClassName && c == 'L'.toInt()) { + insideClassName = true + className.setLength(0) + } + } + result.toString() + } catch (e: IOException) { + throw AssertionError(e) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/GenerateFakeFabricModJson.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/GenerateFakeFabricModJson.kt new file mode 100644 index 0000000..0d467c9 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/GenerateFakeFabricModJson.kt @@ -0,0 +1,31 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.Transformer +import org.gradle.api.Project +import org.zeroturnaround.zip.ByteSource +import org.zeroturnaround.zip.ZipUtil +import java.nio.file.Files +import java.nio.file.Path +import java.util.* + +object GenerateFakeFabricModJson : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + Files.copy(input, output) + val fakeModId = "generated_" + UUID.randomUUID().toString().filterNot { it == '-' }.take(7) + ZipUtil.addOrReplaceEntries( + output.toFile(), arrayOf( + ByteSource( + "fabric.mod.json", """{ + "schemaVersion": 1, + "id": "$fakeModId", + "name": "Generated Mod (Please Ignore)", + "version": "1.0.0", + "custom": { + "fabric-loom:generated": true + } +}""".toByteArray() + ) + ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/GenerateFakeForgeMod.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/GenerateFakeForgeMod.kt new file mode 100644 index 0000000..382d091 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/GenerateFakeForgeMod.kt @@ -0,0 +1,52 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.Transformer +import org.gradle.api.Project +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.zeroturnaround.zip.ByteSource +import org.zeroturnaround.zip.ZipUtil +import java.nio.file.Files +import java.nio.file.Path +import java.util.* + +object GenerateFakeForgeMod : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + val fakeModId = "generated_" + UUID.randomUUID().toString().filterNot { it == '-' }.take(7) + Files.copy(input, output) + ZipUtil.addEntries( + output.toFile(), arrayOf( + ByteSource( + "META-INF/mods.toml", + """modLoader = "javafml" + loaderVersion = "[33,)" + license = "Generated" + [[mods]] + modId = "$fakeModId"""".toByteArray() + ), + ByteSource( + "pack.mcmeta", + """{"pack":{"description":"Generated","pack_format":4}}""".toByteArray() + ), + ByteSource( + "generated/$fakeModId.class", + ClassWriter(0).let { classWriter -> + classWriter.visit(52, Opcodes.ACC_PUBLIC, "generated/$fakeModId", null, "java/lang/Object", null) + val modAnnotation = classWriter.visitAnnotation("Lnet/minecraftforge/fml/common/Mod;", false) + modAnnotation.visit("value", fakeModId) + modAnnotation.visitEnd() + classWriter.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, arrayOf()).also { + it.visitVarInsn(Opcodes.ALOAD, 0) + it.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false) + it.visitInsn(Opcodes.RETURN) + it.visitMaxs(1, 1) + it.visitEnd() + } + classWriter.visitEnd() + classWriter.toByteArray() + } + ) + ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/RemapMixinVariables.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/RemapMixinVariables.kt new file mode 100644 index 0000000..7502a75 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/RemapMixinVariables.kt @@ -0,0 +1,56 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.Transformer +import me.shedaniel.architect.plugin.utils.validateJarFs +import net.fabricmc.loom.LoomGradleExtension +import net.fabricmc.loom.util.LoggerFilter +import net.fabricmc.tinyremapper.OutputConsumerPath +import net.fabricmc.tinyremapper.TinyRemapper +import net.fabricmc.tinyremapper.TinyUtils +import org.gradle.api.Project +import java.io.File +import java.nio.file.Files +import java.nio.file.Path + +object RemapMixinVariables : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) + var remapperBuilder = TinyRemapper.newRemapper() + for (mixinMapFile in loomExtension.allMixinMappings) { + if (mixinMapFile.exists()) { + remapperBuilder = remapperBuilder.withMappings( + TinyUtils.createTinyMappingProvider( + mixinMapFile.toPath(), + "named", + "intermediary" + ) + ) + } + } + + val remapper = remapperBuilder.build() + + val classpathFiles: Set = LinkedHashSet( + project.configurations.getByName("compileClasspath").files + ) + val classpath = classpathFiles.asSequence().map { obj: File -> obj.toPath() }.filter { p: Path -> + input != p && Files.exists(p) + }.toList().toTypedArray() + + LoggerFilter.replaceSystemOut() + try { + project.validateJarFs(output) + OutputConsumerPath.Builder(output).build().use { outputConsumer -> + outputConsumer.addNonClassFiles(input) + remapper.readClassPath(*classpath) + remapper.readInputs(input) + remapper.apply(outputConsumer) + } + } catch (e: Exception) { + remapper.finish() + throw RuntimeException("Failed to remap $input to $output", e) + } + + remapper.finish() + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/RemoveFabricModJson.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/RemoveFabricModJson.kt new file mode 100644 index 0000000..27013d2 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/RemoveFabricModJson.kt @@ -0,0 +1,16 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.Transformer +import org.gradle.api.Project +import org.zeroturnaround.zip.ZipUtil +import java.nio.file.Files +import java.nio.file.Path + +object RemoveFabricModJson : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + Files.copy(input, output) + if (ZipUtil.containsEntry(output.toFile(), "fabric.mod.json")) { + ZipUtil.removeEntry(output.toFile(), "fabric.mod.json") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformExpectPlatform.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformExpectPlatform.kt new file mode 100644 index 0000000..f2769f8 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformExpectPlatform.kt @@ -0,0 +1,206 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.ArchitectPluginExtension +import me.shedaniel.architect.plugin.Transformer +import me.shedaniel.architect.plugin.projectUniqueIdentifier +import me.shedaniel.architect.plugin.utils.ClassTransformer +import me.shedaniel.architect.plugin.utils.Transform +import org.gradle.api.Project +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Handle +import org.objectweb.asm.Opcodes +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.commons.Remapper +import org.objectweb.asm.tree.* +import org.zeroturnaround.zip.ZipUtil +import java.io.InputStream +import java.lang.invoke.CallSite +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.nio.file.Path +import java.util.zip.ZipEntry + +object TransformExpectPlatform : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + Transform.transform(input, output, transformExpectPlatform(project)) + } + + fun transformExpectPlatform(project: Project): ClassTransformer { + val projectUniqueIdentifier by lazy { project.projectUniqueIdentifier() } + var injectedClass = !project.extensions.getByType(ArchitectPluginExtension::class.java).injectInjectables + return { clazz, classAdder -> + if (!injectedClass) { + injectedClass = true + Transform::class.java.getResourceAsStream("/annotations-inject/injection.jar").use { stream -> + ZipUtil.iterate(stream) { input: InputStream, entry: ZipEntry -> + if (entry.name.endsWith(".class")) { + val newName = "$projectUniqueIdentifier/${ + entry.name.substringBeforeLast(".class").substringAfterLast('/') + }" + classAdder(newName, input.readBytes().let { + val node = ClassNode(Opcodes.ASM8) + ClassReader(it).accept(node, ClassReader.EXPAND_FRAMES) + val writer = ClassWriter(ClassWriter.COMPUTE_MAXS) + val remapper = ClassRemapper(writer, object : Remapper() { + override fun map(internalName: String?): String { + if (internalName?.startsWith("me/shedaniel/architect/plugin/callsite") == true) { + return internalName.replace( + "me/shedaniel/architect/plugin/callsite", + projectUniqueIdentifier + ) + } + return super.map(internalName) + } + }) + node.apply { + name = newName + }.accept(remapper) + + writer.toByteArray() + }) + } + } + } + } + + clazz.methods.mapNotNull { method -> + when { + method?.visibleAnnotations?.any { it.desc == TransformInjectables.expectPlatform } == true -> method to "me/shedaniel/architectury/PlatformMethods" + method?.invisibleAnnotations?.any { it.desc == TransformInjectables.expectPlatformNew } == true -> { + method to "$projectUniqueIdentifier/PlatformMethods" + } + else -> null + } + }.forEach { (method, platformMethodsClass) -> + if (method.access and Opcodes.ACC_STATIC == 0) { + System.err.println("@ExpectPlatform can only apply to static methods!") + } else { + method.instructions.clear() + val endOfDesc = method.desc.lastIndexOf(')') + val returnValue = method.desc.substring(endOfDesc + 1) + val args = method.desc.substring(1, endOfDesc) + var cursor = 0 + var inClass = false + var index = 0 + while (cursor < args.length) { + val char = args[cursor] + if (inClass) { + if (char == ';') { + method.instructions.addLoad(char, index++) + inClass = false + } + } else when (char) { + '[' -> Unit + 'L' -> inClass = true + else -> method.instructions.addLoad(char, when (char) { + 'J', 'D' -> index.also { index += 2 } + else -> index++ + }) + } + cursor++ + } + + val methodType = MethodType.methodType( + CallSite::class.java, + MethodHandles.Lookup::class.java, + String::class.java, + MethodType::class.java + ) + + val handle = Handle( + Opcodes.H_INVOKESTATIC, + platformMethodsClass, + "platform", + methodType.toMethodDescriptorString(), + false + ) + + method.instructions.add( + InvokeDynamicInsnNode( + method.name, + method.desc, + handle + ) + ) + + method.instructions.addReturn(returnValue.first { it != '[' }) + method.maxStack = -1 + } + } + + clazz + } + } + + private fun InsnList.addLoad(type: Char, index: Int) { + when (type) { + ';' -> add( + VarInsnNode( + Opcodes.ALOAD, + index + ) + ) + 'I', 'S', 'B', 'C', 'Z' -> add( + VarInsnNode( + Opcodes.ILOAD, + index + ) + ) + 'F' -> add( + VarInsnNode( + Opcodes.FLOAD, + index + ) + ) + 'J' -> add( + VarInsnNode( + Opcodes.LLOAD, + index + ) + ) + 'D' -> add( + VarInsnNode( + Opcodes.DLOAD, + index + ) + ) + else -> throw IllegalStateException("Invalid Type: $type") + } + } + + private fun InsnList.addReturn(type: Char) { + when (type) { + 'L' -> add( + InsnNode( + Opcodes.ARETURN + ) + ) + 'I', 'S', 'B', 'C', 'Z' -> add( + InsnNode( + Opcodes.IRETURN + ) + ) + 'F' -> add( + InsnNode( + Opcodes.FRETURN + ) + ) + 'J' -> add( + InsnNode( + Opcodes.LRETURN + ) + ) + 'D' -> add( + InsnNode( + Opcodes.DRETURN + ) + ) + 'V' -> add( + InsnNode( + Opcodes.RETURN + ) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformForgeBytecode.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformForgeBytecode.kt new file mode 100644 index 0000000..53f7fe7 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformForgeBytecode.kt @@ -0,0 +1,72 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.Transformer +import me.shedaniel.architect.plugin.utils.Transform +import org.gradle.api.Project +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.AnnotationNode +import org.objectweb.asm.tree.MethodInsnNode +import java.nio.file.Path + +object TransformForgeBytecode : Transformer { + val forgeEvent = "Lme/shedaniel/architectury/ForgeEvent;" + val forgeEventCancellable = "Lme/shedaniel/architectury/ForgeEventCancellable;" + val cancellable = "Lnet/minecraftforge/eventbus/api/Cancelable;" + + private val environmentClass = "net/fabricmc/api/Environment" + + override fun invoke(project: Project, input: Path, output: Path) { + Transform.transform(input, output) { node, classAdder -> + if (node.access and Opcodes.ACC_INTERFACE == 0) { + if (node.visibleAnnotations?.any { it.desc == forgeEvent || it.desc == forgeEventCancellable } == true) { + node.superName = "net/minecraftforge/eventbus/api/Event" + node.methods.forEach { + if (it.name == "") { + for (insnNode in it.instructions) { + if (insnNode.opcode == Opcodes.INVOKESPECIAL) { + insnNode as MethodInsnNode + if (insnNode.name == "" && insnNode.owner == "java/lang/Object") { + insnNode.owner = "net/minecraftforge/eventbus/api/Event" + break + } + } + } + } + } + node.signature?.let { + node.signature = it.substringBeforeLast('L') + "Lnet/minecraftforge/eventbus/api/Event;" + } + // if @ForgeEventCancellable, add the cancellable annotation from forge + node.visibleAnnotations.apply { + if (any { it.desc == forgeEventCancellable }) { + add(AnnotationNode(cancellable)) + } + } + } + } + node.visibleAnnotations = (node.visibleAnnotations ?: mutableListOf()).apply { + val invisibleEnvironments = + node.invisibleAnnotations?.filter { it.desc == "L${environmentClass};" } ?: emptyList() + node.invisibleAnnotations?.removeAll(invisibleEnvironments) + addAll(invisibleEnvironments) + } + node.fields.forEach { field -> + field.visibleAnnotations = (field.visibleAnnotations ?: mutableListOf()).apply { + val invisibleEnvironments = + field.invisibleAnnotations?.filter { it.desc == "L${environmentClass};" } ?: emptyList() + field.invisibleAnnotations?.removeAll(invisibleEnvironments) + addAll(invisibleEnvironments) + } + } + node.methods.forEach { method -> + method.visibleAnnotations = (method.visibleAnnotations ?: mutableListOf()).apply { + val invisibleEnvironments = + method.invisibleAnnotations?.filter { it.desc == "L${environmentClass};" } ?: emptyList() + method.invisibleAnnotations?.removeAll(invisibleEnvironments) + addAll(invisibleEnvironments) + } + } + node + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformForgeEnvironment.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformForgeEnvironment.kt new file mode 100644 index 0000000..aeb8454 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformForgeEnvironment.kt @@ -0,0 +1,100 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.Transformer +import me.shedaniel.architect.plugin.utils.validateJarFs +import net.fabricmc.loom.LoomGradleExtension +import net.fabricmc.tinyremapper.* +import org.gradle.api.Project +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import java.io.File +import java.nio.file.Files +import java.nio.file.Path + +object TransformForgeEnvironment : Transformer { + override fun invoke(project: Project, input: Path, output: Path) { + val remapperBuilder: TinyRemapper.Builder = TinyRemapper.newRemapper() + .withMappings(remapEnvironment()) + .skipLocalVariableMapping(true) + + mapMixin(project, remapperBuilder) + + val classpathFiles: Set = LinkedHashSet( + project.configurations.getByName("compileClasspath").files + ) + val classpath = classpathFiles.asSequence().map { obj: File -> obj.toPath() } + .filter { p: Path -> input != p && Files.exists(p) }.toList().toTypedArray() + val remapper = remapperBuilder.build() + + try { + project.validateJarFs(output) + OutputConsumerPath.Builder(output).build().use { outputConsumer -> + outputConsumer.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, null) + remapper.readClassPath(*classpath) + remapper.readInputs(input) + remapper.apply(outputConsumer) + } + } catch (e: Exception) { + remapper.finish() + throw RuntimeException("Failed to remap $input to $output", e) + } + } + + private fun remapEnvironment(): IMappingProvider = IMappingProvider { out -> + out.acceptClass("net/fabricmc/api/Environment", "net/minecraftforge/api/distmarker/OnlyIn") + out.acceptClass("net/fabricmc/api/EnvType", "net/minecraftforge/api/distmarker/Dist") + out.acceptField( + IMappingProvider.Member("net/fabricmc/api/EnvType", "SERVER", "Lnet/fabricmc/api/EnvType;"), + "DEDICATED_SERVER" + ) + } + + private fun mapMixin(project: Project, remapperBuilder: TinyRemapper.Builder) { + val loomExtension = project.extensions.getByType(LoomGradleExtension::class.java) + val srg = project.extensions.getByType(LoomGradleExtension::class.java).mappingsProvider.mappingsWithSrg + for (mixinMapFile in loomExtension.allMixinMappings) { + if (mixinMapFile.exists()) { + remapperBuilder.withMappings { sink -> + TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), "named", "intermediary").load(object : + IMappingProvider.MappingAcceptor { + override fun acceptClass(srcName: String, dstName: String) { + sink.acceptClass(dstName, srg.classes + .firstOrNull { it.getName("intermediary") == dstName } + ?.getName("srg") ?: dstName + ) + } + + override fun acceptMethod(method: IMappingProvider.Member, dstName: String) { + sink.acceptMethod( + IMappingProvider.Member(method.owner, dstName, method.desc), + srg.classes + .flatMap { it.methods } + .firstOrNull { it.getName("intermediary") == dstName } + ?.getName("srg") ?: dstName) + } + + override fun acceptField(field: IMappingProvider.Member, dstName: String) { + sink.acceptField( + IMappingProvider.Member(field.owner, dstName, field.desc), + srg.classes + .flatMap { it.fields } + .firstOrNull { it.getName("intermediary") == dstName } + ?.getName("srg") ?: dstName) + } + + override fun acceptMethodArg(method: IMappingProvider.Member, lvIndex: Int, dstName: String) {} + + override fun acceptMethodVar( + method: IMappingProvider.Member, + lvIndex: Int, + startOpIdx: Int, + asmIndex: Int, + dstName: String + ) { + } + }) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformInjectables.kt b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformInjectables.kt new file mode 100644 index 0000000..96894db --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/transformers/TransformInjectables.kt @@ -0,0 +1,64 @@ +package me.shedaniel.architect.plugin.transformers + +import me.shedaniel.architect.plugin.ArchitectPluginExtension +import me.shedaniel.architect.plugin.Transformer +import me.shedaniel.architect.plugin.projectUniqueIdentifier +import me.shedaniel.architect.plugin.utils.validateJarFs +import net.fabricmc.tinyremapper.IMappingProvider +import net.fabricmc.tinyremapper.NonClassCopyMode +import net.fabricmc.tinyremapper.OutputConsumerPath +import net.fabricmc.tinyremapper.TinyRemapper +import org.gradle.api.Project +import java.io.File +import java.nio.file.Files +import java.nio.file.Path + +object TransformInjectables : Transformer { + const val expectPlatform = "Lme/shedaniel/architectury/ExpectPlatform;" + const val expectPlatformNew = "Lme/shedaniel/architectury/annotations/ExpectPlatform;" + + override fun invoke(project: Project, input: Path, output: Path) { + if (project.extensions.getByType(ArchitectPluginExtension::class.java).injectInjectables) { + transformArchitecturyInjectables(project, input, output) + } else { + Files.copy(input, output) + } + } + + private fun transformArchitecturyInjectables(project: Project, input: Path, output: Path) { + val remapper = TinyRemapper.newRemapper() + .withMappings { sink -> + sink.acceptClass( + "me/shedaniel/architectury/targets/ArchitecturyTarget", + project.projectUniqueIdentifier() + "/PlatformMethods" + ) + sink.acceptMethod( + IMappingProvider.Member( + "me/shedaniel/architectury/targets/ArchitecturyTarget", + "getCurrentTarget", + "()Ljava/lang/String;" + ), "getModLoader" + ) + } + .build() + + val classpathFiles: Set = LinkedHashSet( + project.configurations.getByName("compileClasspath").files + ) + val classpath = classpathFiles.asSequence().map { obj: File -> obj.toPath() } + .filter { p: Path -> Files.exists(p) }.toList().toTypedArray() + + try { + project.validateJarFs(output) + OutputConsumerPath.Builder(output).build().use { outputConsumer -> + outputConsumer.addNonClassFiles(input, NonClassCopyMode.UNCHANGED, null) + remapper.readClassPath(*classpath) + remapper.readInputs(input) + remapper.apply(outputConsumer) + } + } catch (e: Exception) { + remapper.finish() + throw RuntimeException("Failed to remap $input to $output", e) + } + } +} \ No newline at end of file