diff --git a/build.gradle.kts b/build.gradle.kts index 2fa9b3a3..5e7f0302 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -97,6 +97,10 @@ repositories { maven("https://maven.terraformersmc.com/") { name = "TerraformersMC" } + + maven("https://maven.su5ed.dev/releases") { + name = "Su5ed" + } } dependencies { @@ -150,6 +154,9 @@ dependencies { // Remapping SRG to Intermediary implementation(include("net.minecraftforge:srgutils:0.4.13")!!) + // Use Sinytra Connector's fork of ForgeAutoRenamingTool + implementation(include("dev.su5ed.sinytra:ForgeAutoRenamingTool:${property("forgerenamer_version")}")!!) + // Runtime mods for testing modRuntimeOnly ("com.terraformersmc:modmenu:4.1.0") { exclude("net.fabricmc", "fabric-loader") diff --git a/gradle.properties b/gradle.properties index beb1f0c9..68c540f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -64,5 +64,8 @@ eventbus_version=6.0.4 # https://files.minecraftforge.net/net/minecraftforge/forgespi/index.html forgespi_version=6.0.2 +# https://maven.su5ed.dev/#/releases/dev/su5ed/sinytra/ForgeAutoRenamingTool +forgerenamer_version=1.0.8 + # https://files.minecraftforge.net/cpw/mods/securejarhandler/index.html securejarhandler_version=2.1.4 diff --git a/src/main/kotlin/net/minecraftforge/fml/util/ObfuscationReflectionHelper.kt b/src/main/kotlin/net/minecraftforge/fml/util/ObfuscationReflectionHelper.kt index 2748ffed..f10f5a20 100644 --- a/src/main/kotlin/net/minecraftforge/fml/util/ObfuscationReflectionHelper.kt +++ b/src/main/kotlin/net/minecraftforge/fml/util/ObfuscationReflectionHelper.kt @@ -1,30 +1,115 @@ package net.minecraftforge.fml.util import cpw.mods.modlauncher.api.INameMappingService -import net.fabricmc.loader.impl.FabricLoaderImpl +import net.fabricmc.loader.api.FabricLoader +import org.slf4j.LoggerFactory import xyz.bluspring.kilt.loader.remap.KiltRemapper +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Method +import java.util.* object ObfuscationReflectionHelper { - private val srgIntermediaryTree = KiltRemapper.srgIntermediaryTree + private val LOGGER = LoggerFactory.getLogger(ObfuscationReflectionHelper::class.java) + + private val fabricRemapper = FabricLoader.getInstance().mappingResolver + private val srgIntermediaryTree = KiltRemapper.srgIntermediaryMapping private val srgMappedFields = - srgIntermediaryTree.classes.flatMap { it.fields }.associateBy { it.getName("searge") } + srgIntermediaryTree.classes.flatMap { it.fields.map { f -> f.original to fabricRemapper.mapFieldName("intermediary", it.mapped, f.mapped, f.mappedDescriptor) } }.associateBy { it.first } private val srgMappedMethods = - srgIntermediaryTree.classes.flatMap { it.methods }.associateBy { it.getName("searge") } - private val mappingEnvironment = FabricLoaderImpl.INSTANCE + srgIntermediaryTree.classes.flatMap { it.methods.map { f -> f.original to fabricRemapper.mapMethodName("intermediary", it.mapped, f.mapped, f.mappedDescriptor) } }.associateBy { it.first } @JvmStatic fun remapName(domain: INameMappingService.Domain, name: String): String { - // TODO: Make this remap from MojMap to Intermediary. - // We might have to package MojMap alongside Kilt, however that would very much - // violate the redistribution part of MojMaps. - return name + return when (domain) { + INameMappingService.Domain.CLASS -> { + fabricRemapper.mapClassName("intermediary", srgIntermediaryTree.remapClass(name)) + } + + INameMappingService.Domain.FIELD -> { + srgMappedFields[name]?.second ?: name + } + + INameMappingService.Domain.METHOD -> { + srgMappedMethods[name]?.second ?: name + } + } + } + + @JvmStatic + fun getPrivateValue(classToAccess: Class, instance: E, fieldName: String): T { + return try { + findField(classToAccess, fieldName).get(instance) as T + } catch (e: UnableToFindFieldException) { + LOGGER.error("Unable to locate field $fieldName (${remapName(INameMappingService.Domain.FIELD, fieldName)}) on type ${classToAccess.name}", e) + throw e + } catch (e: IllegalAccessException) { + LOGGER.error("Unable to access field $fieldName (${remapName(INameMappingService.Domain.FIELD, fieldName)}) on type ${classToAccess.name}", e) + throw UnableToAccessFieldException(e) + } + } + + @JvmStatic + fun setPrivateValue(classToAccess: Class, instance: T, value: E, fieldName: String) { + try { + findField(classToAccess, fieldName).set(instance, value) + } catch (e: UnableToFindFieldException) { + LOGGER.error("Unable to locate any field $fieldName on type ${classToAccess.name}", e) + throw e + } catch (e: IllegalAccessException) { + LOGGER.error("Unable to access any field $fieldName on type ${classToAccess.name}", e) + throw UnableToAccessFieldException(e) + } + } + + @JvmStatic + fun findMethod(clazz: Class<*>, methodName: String, vararg parameterTypes: Class<*>): Method { + return try { + val m = clazz.getDeclaredMethod(remapName(INameMappingService.Domain.METHOD, methodName), *parameterTypes) + m.isAccessible = true + m + } catch (e: Exception) { + throw UnableToFindMethodException(e) + } + } + + @JvmStatic + fun findConstructor(clazz: Class, vararg parameterTypes: Class<*>): Constructor { + return try { + val constructor = clazz.getDeclaredConstructor(*parameterTypes) + constructor.isAccessible = true + constructor + } catch (e: NoSuchMethodException) { + val desc = StringBuilder() + desc.append(clazz.simpleName) + + val joiner = StringJoiner(", ", "(", ")") + for (type in parameterTypes) { + joiner.add(type.simpleName) + } + + desc.append(joiner) + + throw UnknownConstructorException("Could not find constructor '$desc' in $clazz") + } + } + + @JvmStatic + fun findField(clazz: Class, fieldName: String): Field { + return try { + val f = clazz.getDeclaredField(remapName(INameMappingService.Domain.FIELD, fieldName)) + f.isAccessible = true + f + } catch (e: Exception) { + throw UnableToFindFieldException(e) + } } - class UnableToAccessFieldException private constructor(e: Exception) : RuntimeException(e) + open class UnableToAccessFieldException internal constructor(e: Exception) : RuntimeException(e) - class UnableToFindFieldException private constructor(e: Exception) : RuntimeException(e) + open class UnableToFindFieldException internal constructor(e: Exception) : RuntimeException(e) - class UnableToFindMethodException(failed: Throwable?) : RuntimeException(failed) + open class UnableToFindMethodException(failed: Throwable?) : RuntimeException(failed) - class UnknownConstructorException(message: String?) : RuntimeException(message) + open class UnknownConstructorException(message: String?) : RuntimeException(message) } \ No newline at end of file diff --git a/src/main/kotlin/xyz/bluspring/kilt/loader/asm/AccessTransformerLoader.kt b/src/main/kotlin/xyz/bluspring/kilt/loader/asm/AccessTransformerLoader.kt index 07f978dd..2089e9c8 100644 --- a/src/main/kotlin/xyz/bluspring/kilt/loader/asm/AccessTransformerLoader.kt +++ b/src/main/kotlin/xyz/bluspring/kilt/loader/asm/AccessTransformerLoader.kt @@ -17,7 +17,6 @@ object AccessTransformerLoader { private val whitespace = Pattern.compile("[ \t]+") private val classTransformInfo = mutableMapOf() - private val mc = KiltRemapper.mcRemapper private val mappingResolver = FabricLoader.getInstance().mappingResolver private fun println(info: String) { @@ -54,8 +53,8 @@ object AccessTransformerLoader { else Final.DEFAULT // class name - val srgClassName = split[1] - val intermediaryClassName = KiltRemapper.remapClass(srgClassName.replace(".", "/")) + val srgClassName = split[1].replace(".", "/") + val intermediaryClassName = KiltRemapper.remapClass(srgClassName) // field / method if (split.size > 2 && !split[2].startsWith("#")) { @@ -80,7 +79,7 @@ object AccessTransformerLoader { val intermediaryDescriptor = KiltRemapper.remapDescriptor(descriptor, toIntermediary = true) val mappedDescriptor = KiltRemapper.remapDescriptor(descriptor) - val methodName = mappingResolver.mapMethodName("intermediary", intermediaryClassName.replace("/", "."), mc.mapFieldName(srgClassName, name, descriptor), intermediaryDescriptor) + val methodName = mappingResolver.mapMethodName("intermediary", intermediaryClassName.replace("/", "."), KiltRemapper.srgIntermediaryMapping.getClass(srgClassName)?.remapField(name) ?: name, intermediaryDescriptor) val transformInfo = classTransformInfo[intermediaryClassName] ?: ClassTransformInfo(AccessType.DEFAULT, Final.DEFAULT) val pair = Pair(methodName, mappedDescriptor) @@ -105,8 +104,8 @@ object AccessTransformerLoader { } else { // field val name = split[2] - val fieldInfo = KiltRemapper.srgIntermediaryTree.classes.firstOrNull { it.getName("searge") == srgClassName }?.fields?.firstOrNull { it.getName("searge") == name } ?: continue - val fieldName = mappingResolver.mapMethodName("intermediary", intermediaryClassName.replace("/", "."), mc.mapFieldName(srgClassName, name, fieldInfo.getDescriptor("searge")), fieldInfo.getDescriptor("intermediary")) + val fieldInfo = KiltRemapper.srgIntermediaryMapping.getClass(srgClassName)?.fields?.firstOrNull { it.original == name } ?: continue + val fieldName = mappingResolver.mapMethodName("intermediary", intermediaryClassName.replace("/", "."), KiltRemapper.srgIntermediaryMapping.getClass(srgClassName)?.remapField(name) ?: name, fieldInfo.mappedDescriptor) val transformInfo = classTransformInfo[intermediaryClassName] ?: ClassTransformInfo(AccessType.DEFAULT, Final.DEFAULT) diff --git a/src/main/kotlin/xyz/bluspring/kilt/loader/remap/DevMappingRenamer.kt b/src/main/kotlin/xyz/bluspring/kilt/loader/remap/DevMappingRenamer.kt new file mode 100644 index 00000000..db384725 --- /dev/null +++ b/src/main/kotlin/xyz/bluspring/kilt/loader/remap/DevMappingRenamer.kt @@ -0,0 +1,21 @@ +package xyz.bluspring.kilt.loader.remap + +import net.fabricmc.loader.api.FabricLoader +import net.minecraftforge.srgutils.IMappingFile +import net.minecraftforge.srgutils.IRenamer + +class DevMappingRenamer : IRenamer { + private val resolver = FabricLoader.getInstance().mappingResolver + + override fun rename(value: IMappingFile.IField): String { + return resolver.mapFieldName("intermediary", value.parent.mapped.replace("/", "."), value.mapped, value.mappedDescriptor) + } + + override fun rename(value: IMappingFile.IMethod): String { + return resolver.mapMethodName("intermediary", value.parent.mapped.replace("/", "."), value.mapped, value.mappedDescriptor) + } + + override fun rename(value: IMappingFile.IClass): String { + return resolver.mapClassName("intermediary", value.mapped.replace("/", ".")).replace(".", "/") + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/bluspring/kilt/loader/remap/KiltAsmRemapper.kt b/src/main/kotlin/xyz/bluspring/kilt/loader/remap/KiltAsmRemapper.kt deleted file mode 100644 index b288403c..00000000 --- a/src/main/kotlin/xyz/bluspring/kilt/loader/remap/KiltAsmRemapper.kt +++ /dev/null @@ -1,237 +0,0 @@ -package xyz.bluspring.kilt.loader.remap - -import net.fabricmc.loader.api.FabricLoader -import org.objectweb.asm.ClassReader -import org.objectweb.asm.Opcodes -import org.objectweb.asm.commons.Remapper -import org.objectweb.asm.signature.SignatureReader -import org.objectweb.asm.signature.SignatureWriter -import org.objectweb.asm.tree.ClassNode -import java.util.jar.JarFile - -// TODO: please make faster and make it work in dev -class KiltAsmRemapper(val dependencies: List) : Remapper() { - private val mc = KiltRemapper.mcRemapper - private val mappingResolver = FabricLoader.getInstance().mappingResolver - private val from = "intermediary" - - override fun mapFieldName(owner: String, name: String, descriptor: String): String { - val mcClass = mc.map(owner) - val mcName = mc.mapFieldName(owner, name, descriptor) - - if (mcName != name) - return mappingResolver.mapFieldName(from, mcClass.replace("/", "."), mcName, KiltRemapper.remapDescriptor(descriptor, toIntermediary = true)) - - val nodes = recursiveLoadClasses(owner) - val nodeMatch = nodes.firstOrNull { it.fields.any { f -> f.name == name && f.desc == descriptor } } - - if (nodeMatch != null) { - return super.mapFieldName(owner, name, descriptor) - } else { - for (node in nodes) { - val mcClasses = mutableListOf() - - if (node.superName != null && remappedPackages.any { node.superName.startsWith(it) }) { - mcClasses.add(node.superName) - } - - if (node.interfaces != null) { - for (iface in node.interfaces) { - if (remappedPackages.any { iface.startsWith(it) }) { - mcClasses.add(iface) - } - } - } - - for (mcClass2 in mcClasses) { - val mapped = mappingResolver.mapFieldName("intermediary", - KiltRemapper.remapClass(mcClass2, true).replace("/", "."), - mc.mapFieldName(mcClass2, name, descriptor), - KiltRemapper.remapDescriptor(descriptor, toIntermediary = true) - ) - - if (mapped != name) - return mapped - } - } - } - - return super.mapFieldName(owner, name, descriptor) - } - - override fun mapMethodName(owner: String, name: String, descriptor: String): String { - // i hate that this is a possibility - if (name.startsWith("f_") && name.endsWith("_")) - return mapFieldName(owner, name, descriptor) - - val mcClass = mc.map(owner) - val mcName = mc.mapMethodName(owner, name, descriptor) - - if (mcName != name) - return mappingResolver.mapMethodName(from, - mcClass.replace("/", "."), - mcName, - KiltRemapper.remapDescriptor(descriptor, toIntermediary = true) - ) - - val nodes = recursiveLoadClasses(owner) - - nodes.forEach { node -> - val nodeName = mc.mapMethodName(KiltRemapper.unmapClass(node.name).replace(".", "/"), name, descriptor) - - if (nodeName != name) - return mappingResolver.mapMethodName(from, node.name.replace("/", "."), nodeName, KiltRemapper.remapDescriptor(descriptor, toIntermediary = true)) - } - - val nodeMatch = nodes.firstOrNull { - it.methods.any { f -> f.name == name && f.desc == descriptor } - } - - if (nodeMatch != null) { - return super.mapMethodName(owner, name, descriptor) - } else { - for (node in nodes) { - val mcClasses = mutableListOf() - - if (node.superName != null && remappedPackages.any { node.superName.startsWith(it) }) { - mcClasses.add(node.superName) - } - - if (node.interfaces != null) { - for (iface in node.interfaces) { - if (remappedPackages.any { iface.startsWith(it) }) { - mcClasses.add(iface) - } - } - } - - for (mcClass2 in mcClasses) { - val mapped = mappingResolver.mapMethodName(from, - KiltRemapper.remapClass(mcClass2, true).replace("/", "."), - mc.mapMethodName(mcClass2.replace("/", "."), name, descriptor), - KiltRemapper.remapDescriptor(descriptor, toIntermediary = true) - ) - - if (mapped != name) - return mapped - } - } - } - - return super.mapMethodName(owner, name, descriptor) - } - - private fun recursiveLoadClasses(name: String): List { - // let's just speed that up. - if (notMappedCache.contains(name)) - return emptyList() - - val classList = mutableListOf() - val node: ClassNode - - // Cache any classes that are loaded, primarily to save on load times. - if (classLoadCache.contains(name)) { - node = classLoadCache[name]!! - } else { - val bestMatchingDependency = dependencies.firstOrNull { jar -> - jar.getJarEntry("$name.class") != null - } - - if (bestMatchingDependency != null) { - val classEntry = bestMatchingDependency.getJarEntry("$name.class") - val data = ClassReader(bestMatchingDependency.getInputStream(classEntry)) - node = ClassNode(Opcodes.ASM9) - - data.accept(node, 0) - - classLoadCache[name] = node - } else { - val mcClassNode = KiltRemapper.getGameClassNode(KiltRemapper.remapClass(name, ignoreWorkaround = true)) - - if (mcClassNode == null) { - notMappedCache.add(name) - return emptyList() - } - node = mcClassNode - - classLoadCache[name] = node - } - } - - if (node.superName != null) - classList.addAll(recursiveLoadClasses(node.superName.replace(".", "/"))) - - if (node.interfaces != null) - node.interfaces.forEach { - classList.addAll(recursiveLoadClasses(it.replace(".", "/"))) - } - - classList.add(node) - - return classList - } - - override fun mapRecordComponentName(owner: String, name: String, descriptor: String): String { - return mapFieldName(owner, name, descriptor) - } - - override fun map(name: String): String { - return KiltRemapper.remapClass(name, false) - } - - override fun mapSignature(signature: String?, typeSignature: Boolean): String? { - if (signature == null) - return null - - return remapSignature(signature) - } - - private fun remapSignature(signature: String): String { - val parser = SignatureReader(signature) - val writer = object : SignatureWriter() { - override fun visitClassType(name: String?) { - super.visitClassType(if (name != null) KiltRemapper.remapClass(name) else null) - } - - override fun visitInnerClassType(name: String?) { - super.visitInnerClassType(if (name != null) KiltRemapper.remapClass(name) else null) - } - - override fun visitTypeVariable(name: String?) { - super.visitTypeVariable(if (name != null) KiltRemapper.remapClass(name) else null) - } - } - - parser.accept(writer) - - return writer.toString() - } - - override fun mapInnerClassName(name: String, ownerName: String?, innerName: String?): String { - return KiltRemapper.remapClass(name) - } - - override fun mapInvokeDynamicMethodName(name: String, descriptor: String): String { - // this is what you call terrible performance. - // TODO: please find a way to make this faster. - KiltRemapper.srgIntermediaryTree.classes.forEach { c -> - c.methods.forEach { m -> - if (m.getName("searge") == name && m.getDescriptor("searge") == descriptor) { - return mappingResolver.mapMethodName("intermediary", c.getName("intermediary").replace("/", "."), m.getName("intermediary"), m.getDescriptor("intermediary")) - } - } - } - - return name - } - - companion object { - private val remappedPackages = listOf( - "net/minecraft/", - "com/mojang/", - "net/minecraftforge/" - ) - private val classLoadCache = mutableMapOf() - private val notMappedCache = mutableListOf() - } -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/bluspring/kilt/loader/remap/KiltRemapper.kt b/src/main/kotlin/xyz/bluspring/kilt/loader/remap/KiltRemapper.kt index 5d457ac7..01f53b1c 100644 --- a/src/main/kotlin/xyz/bluspring/kilt/loader/remap/KiltRemapper.kt +++ b/src/main/kotlin/xyz/bluspring/kilt/loader/remap/KiltRemapper.kt @@ -11,20 +11,22 @@ import net.fabricmc.loader.impl.game.GameProviderHelper import net.fabricmc.loader.impl.launch.FabricLauncherBase import net.fabricmc.loader.impl.util.SystemProperties import net.fabricmc.mapping.tree.TinyMappingFactory -import net.fabricmc.mapping.tree.TinyTree -import net.fabricmc.mapping.util.AsmRemapperFactory +import net.minecraftforge.fart.api.ClassProvider +import net.minecraftforge.fart.internal.EnhancedClassRemapper +import net.minecraftforge.fart.internal.EnhancedRemapper +import net.minecraftforge.fart.internal.RenamingTransformer +import net.minecraftforge.srgutils.IMappingFile import org.apache.commons.codec.digest.DigestUtils import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes -import org.objectweb.asm.commons.ClassRemapper import org.objectweb.asm.tree.ClassNode import org.slf4j.LoggerFactory import xyz.bluspring.kilt.Kilt +import xyz.bluspring.kilt.loader.KiltLoader import xyz.bluspring.kilt.loader.mod.ForgeMod import xyz.bluspring.kilt.loader.remap.fixers.EventClassVisibilityFixer import xyz.bluspring.kilt.loader.remap.fixers.EventEmptyInitializerFixer -import xyz.bluspring.kilt.util.KiltHelper import java.io.ByteArrayOutputStream import java.io.File import java.nio.charset.StandardCharsets @@ -33,22 +35,35 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.* import java.util.concurrent.ConcurrentLinkedQueue +import java.util.function.Consumer import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream import java.util.jar.Manifest +import java.util.stream.Collectors import kotlin.io.path.absolutePathString +import kotlin.io.path.toPath object KiltRemapper { // Keeps track of the remapper changes, so every time I update the remapper, // it remaps all the mods following the remapper changes. // this can update by like 12 versions in 1 update, so don't worry too much about it. - const val REMAPPER_VERSION = 103 + const val REMAPPER_VERSION = 104 + + val logConsumer = Consumer { + logger.debug(it) + } private val logger = LoggerFactory.getLogger("Kilt Remapper") + + private val launcher = FabricLauncherBase.getLauncher() + internal val useNamed = launcher.targetNamespace != "intermediary" + // This is created automatically using https://github.com/BluSpring/srg2intermediary // srg -> intermediary - val srgIntermediaryTree: TinyTree = TinyMappingFactory.load(this::class.java.getResourceAsStream("/srg_intermediary.tiny")!!.bufferedReader()) + val srgIntermediaryMapping = IMappingFile.load(this::class.java.getResourceAsStream("/srg_intermediary.tiny")!!) + .rename(DevMappingRenamer()) + val intermediarySrgMapping = srgIntermediaryMapping.reverse() private val kiltWorkaroundTree = TinyMappingFactory.load(this::class.java.getResourceAsStream("/kilt_workaround_mappings.tiny")!!.bufferedReader()) // Mainly for debugging, so already-remapped Forge mods will be remapped again. @@ -57,13 +72,6 @@ object KiltRemapper { // Mainly for debugging, used to test unobfuscated mods and ensure that Kilt is running as intended. private val disableRemaps = System.getProperty("kilt.noRemap")?.lowercase() == "true" - // Generates local mappings file, you can use this if you're having trouble with the local ones. - private val generateLocalMappingCache = System.getProperty("kilt.genLocalMapping")?.lowercase() == "true" - - private val launcher = FabricLauncherBase.getLauncher() - internal val useNamed = launcher.targetNamespace != "intermediary" - - val mcRemapper = AsmRemapperFactory(srgIntermediaryTree).getRemapper("searge", "intermediary") private val mappingResolver = FabricLoader.getInstance().mappingResolver private val namespace: String = if (useNamed) launcher.targetNamespace else "intermediary" @@ -186,9 +194,19 @@ object KiltRemapper { val jarOutput = JarOutputStream(output) // Use the regular mod file - val remapper = KiltAsmRemapper(dependencies.map { JarFile(it.modFile) }.toMutableList().apply { - this.add(JarFile(mod.modFile)) - }) + val classProvider = ClassProvider.builder().apply { + this.addLibrary(srgGamePath) + + dependencies.forEach { + this.addLibrary(it.modFile?.toPath()) + } + + this.addLibrary(mod.modFile?.toPath()) + }.build() + + val remapper = EnhancedRemapper(classProvider, srgIntermediaryMapping, logConsumer) + + val entriesToMap = mutableListOf>() for (entry in jar.entries()) { if (!entry.name.endsWith(".class")) { @@ -256,12 +274,8 @@ object KiltRemapper { val intermediaryDesc = remapDescriptor(srgDesc) val intermediaryField = mappingResolver.mapFieldName("intermediary", - intermediaryClass.replace("/", "."), - srgIntermediaryTree.classes.firstOrNull { - it.getName("searge") == srgClass - }?.fields?.firstOrNull { - it.getName("searge") == srgField - }?.getName("intermediary") ?: srgField, + intermediaryClass.replace("/", ".").removePrefix("L").removeSuffix(";"), + remapper.mapFieldName(srgClass.removePrefix("L").removeSuffix(";"), srgField, srgDesc) ?: srgField, intermediaryDesc ) @@ -274,12 +288,8 @@ object KiltRemapper { val intermediaryDesc = remapDescriptor(srgDesc) val intermediaryMethod = mappingResolver.mapMethodName("intermediary", - intermediaryClass.replace("/", "."), - srgIntermediaryTree.classes.firstOrNull { - it.getName("searge") == srgClass - }?.methods?.firstOrNull { - it.getName("searge") == srgMethod - }?.getName("intermediary") ?: srgMethod, + intermediaryClass.replace("/", ".").removePrefix("L").removeSuffix(";"), + remapper.mapMethodName(srgClass.removePrefix("L").removeSuffix(";"), srgMethod, srgDesc) ?: srgMethod, intermediaryDesc ) @@ -318,22 +328,27 @@ object KiltRemapper { EventEmptyInitializerFixer.fixClass(classNode) ObjectHolderDefinalizer.processClass(classNode) + entriesToMap.add(JarEntry(entry.name) to classNode) + } + + for ((entry, classNode) in entriesToMap) { try { val classWriter = ClassWriter(0) - val visitor = ClassRemapper(classWriter, remapper) + val visitor = EnhancedClassRemapper(classWriter, remapper, RenamingTransformer(remapper, false)) classNode.accept(visitor) - jarOutput.putNextEntry(JarEntry(entry.name)) + jarOutput.putNextEntry(entry) jarOutput.write(classWriter.toByteArray()) jarOutput.closeEntry() } catch (e: Exception) { - logger.error("Failed to remap class ${classNode.name}!") + logger.error("Failed to remap class ${entry.name}!") e.printStackTrace() exceptions.add(e) } } + jarOutput.close() mod.remappedModFile = modifiedJarFile @@ -344,8 +359,7 @@ object KiltRemapper { val workaround = if (!ignoreWorkaround) kiltWorkaroundTree.classes.firstOrNull { it.getRawName("forge") == name }?.getRawName("kilt") else null - val intermediary = mcRemapper.map(name) - + val intermediary = srgIntermediaryMapping.remapClass(name.replace(".", "/")) if (toIntermediary) { return workaround ?: intermediary ?: name } @@ -357,57 +371,23 @@ object KiltRemapper { fun unmapClass(name: String): String { val intermediary = mappingResolver.unmapClassName("intermediary", name.replace("/", ".")) - return srgIntermediaryTree.classes.firstOrNull { it.getName("intermediary") == intermediary }?.getName("searge") ?: name - } - - val gameJar = getMCGameJar() - - fun getKiltClassNode(className: String): ClassNode? { - if (!className.startsWith("net/minecraftforge/") && !className.startsWith("xyz/bluspring/kilt/")) - return null - - KiltHelper.getForgeClassNodes().forEach { - if (it.name == className) - return it - } - - return null + return intermediarySrgMapping.remapClass(intermediary) } - fun getGameClassNode(className: String): ClassNode? { - if (gameJar == null) - return null - - if (className.startsWith("net/minecraftforge/") || className.startsWith("xyz/bluspring/kilt/")) - return getKiltClassNode(className) - - if (!className.startsWith("com/mojang/") && !className.startsWith("net/minecraft/")) - return null - - val entry = gameJar.getJarEntry("$className.class") - - if (entry != null) { - val classReader = ClassReader(gameJar.getInputStream(entry)) - val classNode = ClassNode(Opcodes.ASM9) - classReader.accept(classNode, 0) - - return classNode - } + val gameFile = getMCGameFile() + val srgGamePath = remapMinecraft() - return null - } - - private fun getMCGameJar(): JarFile? { + private fun getMCGameFile(): File? { if (!FabricLoader.getInstance().isDevelopmentEnvironment) { val commonJar = GameProviderHelper.getCommonGameJar() if (commonJar != null) - return JarFile(commonJar.toFile()) + return commonJar.toFile() val sidedJar = GameProviderHelper.getEnvGameJar(FabricLoader.getInstance().environmentType) if (sidedJar != null) - return JarFile(sidedJar.toFile()) + return sidedJar.toFile() } else { // TODO: is there a better way of doing this? val possibleMcGameJar = FabricLauncherBase.getLauncher().classPath.firstOrNull { path -> @@ -415,15 +395,17 @@ object KiltRemapper { str.contains("net") && str.contains("minecraft") && str.contains("-loom.mappings.") && str.contains("minecraft-merged-") } ?: return null - return JarFile(possibleMcGameJar.toFile()) + return possibleMcGameJar.toFile() } - return null } - private fun getMCGameClassPath(): Array { + private fun getGameClassPath(): Array { return if (!FabricLoader.getInstance().isDevelopmentEnvironment) - arrayOf(FabricLoader.getInstance().objectShare.get("fabric-loader:inputGameJar") as Path) + arrayOf( + FabricLoader.getInstance().objectShare.get("fabric-loader:inputGameJar") as Path, + Kilt::class.java.protectionDomain.codeSource.location.toURI().toPath() + ) else mutableListOf().apply { val remapClasspathFile = System.getProperty(SystemProperties.REMAP_CLASSPATH_FILE) @@ -436,10 +418,69 @@ object KiltRemapper { .map { first -> Paths.get(first) } - .toList()) + .collect(Collectors.toList())) + + this.add(Kilt::class.java.protectionDomain.codeSource.location.toURI().toPath()) }.toTypedArray() } + private fun remapMinecraft(): Path { + val srgFile = File(KiltLoader.kiltCacheDir, "minecraft_${FabricLoader.getInstance().getModContainer("minecraft").orElseThrow().metadata.version.friendlyString}-srg.jar") + + if (srgFile.exists()) + return srgFile.toPath() + + if (gameFile == null) { + throw IllegalStateException("Minecraft JAR was not found!") + } + + logger.info("Creating SRG-mapped Minecraft JAR for remapping Forge mods...") + val startTime = System.currentTimeMillis() + + val classProvider = ClassProvider.builder().apply { + this.addLibrary(gameFile.toPath()) + for (path in getGameClassPath()) { + this.addLibrary(path) + } + }.build() + val srgRemapper = EnhancedRemapper(classProvider, intermediarySrgMapping, logConsumer) + + val gameJar = JarFile(gameFile) + srgFile.createNewFile() + val outputJar = JarOutputStream(srgFile.outputStream()) + + for (entry in gameJar.entries()) { + if (entry.name.endsWith(".class")) { + val classReader = ClassReader(gameJar.getInputStream(entry)) + + val classNode = ClassNode(Opcodes.ASM9) + classReader.accept(classNode, 0) + + val classWriter = ClassWriter(0) + + val visitor = EnhancedClassRemapper(classWriter, srgRemapper, RenamingTransformer(srgRemapper, false)) + classNode.accept(visitor) + + // We need to remap to the SRG name, otherwise the remapper completely fails in production environments. + val srgName = intermediarySrgMapping.remapClass(entry.name.removePrefix("/").removeSuffix(".class")) + + outputJar.putNextEntry(JarEntry("$srgName.class")) + outputJar.write(classWriter.toByteArray()) + outputJar.closeEntry() + } else { + outputJar.putNextEntry(entry) + outputJar.write(gameJar.getInputStream(entry).readAllBytes()) + outputJar.closeEntry() + } + } + + outputJar.close() + + logger.info("Remapped Minecraft from Intermediary to SRG. (took ${System.currentTimeMillis() - startTime} ms)") + + return srgFile.toPath() + } + fun remapDescriptor(descriptor: String, reverse: Boolean = false, toIntermediary: Boolean = false): String { var formedString = ""