Transform @ExpectPlatform

This commit is contained in:
shedaniel
2020-11-12 21:44:29 +08:00
parent 84e5125cd9
commit 49f129ae8c
9 changed files with 349 additions and 28 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -7,11 +7,13 @@ import org.gradle.api.plugins.JavaPluginExtension
class ArchitectPlugin : Plugin<Project> {
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> {
}
}
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"
}
}
}

View File

@@ -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
)
)
}
}
}

View File

@@ -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, "<init>", "()V", null, arrayOf()).also {
it.visitVarInsn(Opcodes.ALOAD, 0)
it.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)

View File

@@ -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
)
)
}
}

View File

@@ -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())
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}
}
}
}