mirror of
https://github.com/architectury/architectury-plugin.git
synced 2026-03-28 04:07:01 -05:00
Refactor to the transformer system, bump version to 2.0 for an unstable test
This commit is contained in:
@@ -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}")
|
||||
|
||||
|
||||
@@ -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> {
|
||||
)
|
||||
)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<File> = 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<String, String> = 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, "<init>", "()V", null, arrayOf()).also {
|
||||
it.visitVarInsn(Opcodes.ALOAD, 0)
|
||||
it.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()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<String>()
|
||||
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<JsonObject>(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<String, String>?): 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<String, String> {
|
||||
val mcpMappings = readMCPMappings(getRootExtension().minecraft)
|
||||
val mutableMap = mutableMapOf<String, String>()
|
||||
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<String, String> {
|
||||
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<String, String>().also { readMappings(it, file.inputStream()) }
|
||||
}
|
||||
|
||||
private fun readMappings(mutableMap: MutableMap<String, String>, 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 == "<init>") {
|
||||
for (insnNode in it.instructions) {
|
||||
if (insnNode.opcode == Opcodes.INVOKESPECIAL) {
|
||||
insnNode as MethodInsnNode
|
||||
if (insnNode.name == "<init>" && 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
|
||||
}
|
||||
}
|
||||
@@ -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<String>()
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<File> = 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<File> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Transformer>()
|
||||
|
||||
@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<String>()
|
||||
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
|
||||
@@ -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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String>()
|
||||
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<JsonObject>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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, "<init>", "()V", null, arrayOf()).also {
|
||||
it.visitVarInsn(Opcodes.ALOAD, 0)
|
||||
it.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
|
||||
it.visitInsn(Opcodes.RETURN)
|
||||
it.visitMaxs(1, 1)
|
||||
it.visitEnd()
|
||||
}
|
||||
classWriter.visitEnd()
|
||||
classWriter.toByteArray()
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<File> = 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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 == "<init>") {
|
||||
for (insnNode in it.instructions) {
|
||||
if (insnNode.opcode == Opcodes.INVOKESPECIAL) {
|
||||
insnNode as MethodInsnNode
|
||||
if (insnNode.name == "<init>" && 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<File> = 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
|
||||
) {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<File> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user