diff --git a/README.md b/README.md index 7bb4aaf..a35fd0b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Architect is a gradle plugin to allow easier multi-modloader set-ups using a common module. ### Examples -- [Hardcore Questing Mode](https://github.com/shedaniel/HQM/tree/002b5df265fd26b8df6a3b6b28cbc5bfe76573a6) +- [Hardcore Questing Mode](https://github.com/lorddusk/HQM) - [Light Overlay](https://github.com/shedaniel/LightOverlay) ### Important Information @@ -11,6 +11,9 @@ Architect is a gradle plugin to allow easier multi-modloader set-ups using a com - No mixins in the common module. - You **MUST** run `gradlew build` or `gradlew :common:build` to update the MCP remapped version of `common`, or else the version of common module that the forge module is using will not update. +### Implementing Platform Specific APIs +![](https://media.discordapp.net/attachments/586186202781188108/776428814309785620/unknown.png?width=1191&height=439) + ### How does it work Fabric Side: diff --git a/build.gradle b/build.gradle index 1e5644b..74d4207 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,9 @@ plugins { } group "me.shedaniel" -version = "1.0." + (System.getenv("GITHUB_RUN_NUMBER") == null ? "9999" : System.getenv("GITHUB_RUN_NUMBER")) +version = "1.1." + (System.getenv("GITHUB_RUN_NUMBER") == null ? (((short) new Random().nextInt()).abs() + 1000).toString() : System.getenv("GITHUB_RUN_NUMBER")) + +logger.lifecycle(":building architect plugin v${version}") sourceCompatibility = targetCompatibility = 1.8 diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt index c39aec4..41c2f00 100644 --- a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt +++ b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPlugin.kt @@ -7,11 +7,13 @@ import org.gradle.api.plugins.JavaPluginExtension class ArchitectPlugin : Plugin { override fun apply(project: Project) { - project.apply(mapOf( + project.apply( + mapOf( "plugin" to "java", "plugin" to "eclipse", "plugin" to "idea" - )) + ) + ) project.extensions.create("architect", ArchitectPluginExtension::class.java, project) project.afterEvaluate { @@ -21,9 +23,17 @@ class ArchitectPlugin : Plugin { } } - project.tasks.register("remapMcp", RemapMCPTask::class.java) + project.tasks.register("remapMcp", RemapMCPTask::class.java) { + it.group = "Architect" + } + project.tasks.register("remapMcpFakeMod", RemapMCPTask::class.java) { it.fakeMod = true + it.group = "Architect" + } + + project.tasks.register("transformArchitectJar", TransformTask::class.java) { + it.group = "Architect" } } } \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt index 912ac8c..dd419f9 100644 --- a/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt +++ b/src/main/kotlin/me/shedaniel/architect/plugin/ArchitectPluginExtension.kt @@ -2,7 +2,9 @@ package me.shedaniel.architect.plugin +import net.fabricmc.loom.task.RemapJarTask import org.gradle.api.Project +import org.gradle.api.tasks.bundling.AbstractArchiveTask open class ArchitectPluginExtension(val project: Project) { var minecraft = "" @@ -10,38 +12,77 @@ open class ArchitectPluginExtension(val project: Project) { fun common() { project.configurations.create("mcp") project.configurations.create("mcpGenerateMod") + project.configurations.create("transformed") - project.tasks.getByName("remapMcp") { + val buildTask = project.tasks.getByName("build") + val jarTask = project.tasks.getByName("jar") { + it as AbstractArchiveTask + it.archiveClassifier.set("dev") + } as AbstractArchiveTask + + val transformArchitectJarTask = project.tasks.getByName("transformArchitectJar") { + it as TransformTask + + it.archiveClassifier.set("transformed") + it.input.set(jarTask.archiveFile.get()) + + project.artifacts.add("archives", it) + it.dependsOn(jarTask) + buildTask.dependsOn(it) + it.outputs.upToDateWhen { false } + } as TransformTask + + val remapJarTask = project.tasks.getByName("remapJar") { + it as RemapJarTask + + it.archiveClassifier.set("") + it.input.set(transformArchitectJarTask.archiveFile.get()) + it.dependsOn(transformArchitectJarTask) + it.mustRunAfter(transformArchitectJarTask) + } as RemapJarTask + + val remapMCPTask = project.tasks.getByName("remapMcp") { it as RemapMCPTask - it.input.set(project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-dev.jar")) + it.input.set(transformArchitectJarTask.archiveFile.get()) it.archiveClassifier.set("mcp") - it.dependsOn(project.tasks.getByName("jar")) - project.tasks.getByName("build").dependsOn(it) + it.dependsOn(transformArchitectJarTask) + buildTask.dependsOn(it) it.outputs.upToDateWhen { false } - } + } as RemapMCPTask - project.tasks.getByName("remapMcpFakeMod") { + val remapMCPFakeModTask = project.tasks.getByName("remapMcpFakeMod") { it as RemapMCPTask - it.input.set(project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-dev.jar")) + it.input.set(transformArchitectJarTask.archiveFile.get()) it.archiveClassifier.set("mcpGenerateMod") - it.dependsOn(project.tasks.getByName("jar")) - project.tasks.getByName("build").dependsOn(it) + it.dependsOn(transformArchitectJarTask) + buildTask.dependsOn(it) it.outputs.upToDateWhen { false } - } + } as RemapMCPTask project.artifacts { - it.add("mcp", mapOf( - "file" to project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-mcp.jar"), + it.add( + "mcp", mapOf( + "file" to remapMCPTask.archiveFile.get().asFile, "type" to "jar", - "builtBy" to project.tasks.getByName("remapMcp") - )) - it.add("mcpGenerateMod", mapOf( - "file" to project.file("${project.buildDir}/libs/${project.properties["archivesBaseName"]}-${project.version}-mcpGenerateMod.jar"), + "builtBy" to remapMCPTask + ) + ) + it.add( + "mcpGenerateMod", mapOf( + "file" to remapMCPFakeModTask.archiveFile.get().asFile, "type" to "jar", - "builtBy" to project.tasks.getByName("remapMcpFakeMod") - )) + "builtBy" to remapMCPFakeModTask + ) + ) + it.add( + "transformed", mapOf( + "file" to transformArchitectJarTask.archiveFile.get().asFile, + "type" to "jar", + "builtBy" to transformArchitectJarTask + ) + ) } } } \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/RemapMCPTask.kt b/src/main/kotlin/me/shedaniel/architect/plugin/RemapMCPTask.kt index 2d35ebc..8d702e5 100644 --- a/src/main/kotlin/me/shedaniel/architect/plugin/RemapMCPTask.kt +++ b/src/main/kotlin/me/shedaniel/architect/plugin/RemapMCPTask.kt @@ -2,8 +2,8 @@ package me.shedaniel.architect.plugin +import me.shedaniel.architect.plugin.utils.GradleSupport import net.fabricmc.loom.LoomGradleExtension -import net.fabricmc.loom.util.GradleSupport import net.fabricmc.loom.util.TinyRemapperMappingsHelper import net.fabricmc.mapping.tree.TinyTree import net.fabricmc.tinyremapper.IMappingProvider @@ -32,7 +32,7 @@ open class RemapMCPTask : Jar() { private val fromM: String = "named" private val toM: String = "official" var fakeMod = false - val input: RegularFileProperty = GradleSupport.getfileProperty(project) + val input: RegularFileProperty = GradleSupport.getFileProperty(project) private val environmentClass = "net/fabricmc/api/Environment" @TaskAction @@ -133,9 +133,9 @@ modId = "$fakeModId" val className = "generated/$fakeModId" val classWriter = ClassWriter(0) classWriter.visit(52, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null) - val mixinAnnotation = classWriter.visitAnnotation("Lnet/minecraftforge/fml/common/Mod;", false) - mixinAnnotation.visit("value", fakeModId) - mixinAnnotation.visitEnd() + 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) diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt b/src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt new file mode 100644 index 0000000..82695a1 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/TransformExpect.kt @@ -0,0 +1,142 @@ +package me.shedaniel.architect.plugin + +import org.objectweb.asm.Handle +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.* +import java.lang.invoke.CallSite +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType + +const val expectPlatform = "Lme/shedaniel/architectury/ExpectPlatform;" + +fun transformExpectPlatform(): (ClassNode, (String, ByteArray) -> Unit) -> ClassNode = { clazz, classAdder -> + if (clazz.access and Opcodes.ACC_INTERFACE == 0) { + clazz.methods.filter { method -> method?.visibleAnnotations?.any { it.desc == expectPlatform } == true } + .forEach { method -> + if (method.access and Opcodes.ACC_STATIC == 0) { + System.err.println("@ExpectPlatform can only apply to static methods!") + } else { + println("Found ${clazz.name}#${method.name}") + 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, 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, + "me/shedaniel/architectury/PlatformMethods", + "platform", + methodType.toMethodDescriptorString(), + false + ) + + method.instructions.add( + InvokeDynamicInsnNode( + method.name, + method.desc, + handle + ) + ) + + method.instructions.addReturn(returnValue.first { it != '[' }) + } + } + } + + 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 new file mode 100644 index 0000000..9a6f0b2 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/TransformTask.kt @@ -0,0 +1,23 @@ +@file:Suppress("UnstableApiUsage") + +package me.shedaniel.architect.plugin + +import me.shedaniel.architect.plugin.utils.GradleSupport +import me.shedaniel.architect.plugin.utils.Transform +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.TaskAction +import org.gradle.jvm.tasks.Jar +import java.nio.file.Path + +open class TransformTask : Jar() { + val input: RegularFileProperty = GradleSupport.getFileProperty(project) + + @TaskAction + fun doTask() { + val input: Path = this.input.asFile.get().toPath() + val output: Path = this.archiveFile.get().asFile.toPath() + + project.logger.lifecycle(":transforming " + input.fileName + " => " + output.fileName) + Transform.transform(input, output, transformExpectPlatform()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/utils/GradleSupport.kt b/src/main/kotlin/me/shedaniel/architect/plugin/utils/GradleSupport.kt new file mode 100644 index 0000000..c2d8482 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/utils/GradleSupport.kt @@ -0,0 +1,32 @@ +package me.shedaniel.architect.plugin.utils + +import org.gradle.api.Project +import org.gradle.api.file.RegularFileProperty + +object GradleSupport { + fun getFileProperty(project: Project): RegularFileProperty { + return try { + getFilePropertyModern(project) + } catch (var3: Exception) { + try { + getFilePropertyLegacy(project) + } catch (var2: Exception) { + throw RuntimeException("Failed to find file property", var2) + } + } + } + + private fun getFilePropertyModern(project: Project): RegularFileProperty { + return getFilePropertyLegacyFromObject(project.objects) + } + + private fun getFilePropertyLegacy(project: Project): RegularFileProperty { + return getFilePropertyLegacyFromObject(project.layout) + } + + private fun getFilePropertyLegacyFromObject(`object`: Any): RegularFileProperty { + val method = `object`.javaClass.getDeclaredMethod("fileProperty") + method.isAccessible = true + return method.invoke(`object`) as RegularFileProperty + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/architect/plugin/utils/Transform.kt b/src/main/kotlin/me/shedaniel/architect/plugin/utils/Transform.kt new file mode 100644 index 0000000..e7bbd93 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/architect/plugin/utils/Transform.kt @@ -0,0 +1,68 @@ +package me.shedaniel.architect.plugin.utils + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import java.io.File +import java.io.FileNotFoundException +import java.nio.file.Files +import java.nio.file.Path +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +object Transform { + inline fun transform(input: Path, output: Path, transform: (ClassNode, (String, ByteArray) -> Unit) -> ClassNode) { + output.toFile().delete() + + if (!Files.exists(input)) { + throw FileNotFoundException(input.toString()) + } + + if (input.toFile().absolutePath.endsWith(".class")) { + var allBytes = Files.newInputStream(input).readBytes() + 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) { name, bytes -> + File(output.toFile().parentFile, "$name.class").also { + it.delete() + it.writeBytes(bytes) + } + }.accept(writer) + allBytes = writer.toByteArray() + } + output.toFile().writeBytes(allBytes) + } else { + val zipOutputStream = ZipOutputStream(output.toFile().outputStream()) + zipOutputStream.use { + ZipInputStream(Files.newInputStream(input)).use { + while (true) { + val entry = it.nextEntry ?: break + 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) { name, bytes -> + zipOutputStream.putNextEntry(ZipEntry(name)) + zipOutputStream.write(bytes) + zipOutputStream.closeEntry() + }.accept(writer) + allBytes = writer.toByteArray() + } + } + zipOutputStream.putNextEntry(ZipEntry(entry.name)) + zipOutputStream.write(allBytes) + zipOutputStream.closeEntry() + } + } + } + } + } +} \ No newline at end of file