This commit is contained in:
Kyle Wood 2021-01-17 02:46:32 -08:00
parent 2c4b1e1409
commit 0ae3814cf5
No known key found for this signature in database
GPG key ID: 86186EDFECC585CB
9 changed files with 410 additions and 219 deletions

View file

@ -30,7 +30,7 @@ tasks.withType<KotlinCompile> {
}
gradlePlugin {
// we handle publications ourselves
// we handle publications manually
isAutomatedPublishing = false
}
@ -79,6 +79,10 @@ dependencies {
shade("net.fabricmc:lorenz-tiny:3.0.0")
shade("io.sigpipe:jbsdiff:1.0")
val jgraphtVersion = "1.5.0"
shade("org.jgrapht:jgrapht-core:$jgraphtVersion")
shade("org.jgrapht:jgrapht-io:$jgraphtVersion")
}
ktlint {
@ -124,13 +128,17 @@ tasks.shadowJar {
"io.sigpipe",
"me.jamiemansfield",
"net.fabricmc",
"org.antlr",
"org.apache.commons.codec",
"org.apache.commons.compress",
"org.apache.commons.logging",
"org.apache.commons.text",
"org.apache.felix",
"org.apache.http",
"org.cadixdev",
"org.eclipse",
"org.jgrapht",
"org.jheaps",
"org.objectweb",
"org.osgi",
"org.tukaani"

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -152,7 +152,7 @@ class Paperweight : Plugin<Project> {
val initialTasks = createInitialTasks()
val generalTasks = createGeneralTasks()
val vanillaTasks = createVanillaTasks(initialTasks, generalTasks)
val spigotTasks = createSpigotTasks(initialTasks, generalTasks, vanillaTasks)
val spigotTasks = createSpigotTasks(generalTasks, vanillaTasks)
val applyMergedAt by tasks.registering<ApplyAccessTransform> {
inputJar.set(vanillaTasks.fixJar.flatMap { it.outputJar })
@ -238,6 +238,7 @@ class Paperweight : Plugin<Project> {
)
data class VanillaTasks(
val inspectVanillaJar: TaskProvider<InspectVanillaJar>,
val generateMappings: TaskProvider<GenerateMappings>,
val fixJar: TaskProvider<FixJar>,
val downloadMcLibraries: TaskProvider<DownloadMcLibraries>
@ -336,11 +337,29 @@ class Paperweight : Plugin<Project> {
val cache: File = layout.cache
val downloadService = download
val downloadMcLibraries by tasks.registering<DownloadMcLibraries> {
mcLibrariesFile.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
mcRepo.set(Constants.MC_LIBRARY_URL)
outputDir.set(cache.resolve(Constants.MINECRAFT_JARS_PATH))
sourcesOutputDir.set(cache.resolve(Constants.MINECRAFT_SOURCES_PATH))
downloader.set(downloadService)
}
val inspectVanillaJar by tasks.registering<InspectVanillaJar> {
inputJar.set(generalTasks.downloadServerJar.flatMap { it.outputJar })
librariesDir.set(downloadMcLibraries.flatMap { it.outputDir })
mcLibraries.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
serverLibraries.set(cache.resolve(Constants.SERVER_LIBRARIES))
}
val generateMappings by tasks.registering<GenerateMappings> {
vanillaJar.set(generalTasks.filterVanillaJar.flatMap { it.outputJar })
vanillaMappings.set(initialTasks.downloadMappings.flatMap { it.outputFile })
paramMappings.fileProvider(configurations.named(Constants.PARAM_MAPPINGS_CONFIG).map { it.singleFile })
methodOverrides.set(inspectVanillaJar.flatMap { it.methodOverrides })
outputMappings.set(cache.resolve(Constants.MOJANG_YARN_MAPPINGS))
}
@ -358,25 +377,16 @@ class Paperweight : Plugin<Project> {
vanillaJar.set(generalTasks.downloadServerJar.flatMap { it.outputJar })
}
val downloadMcLibraries by tasks.registering<DownloadMcLibraries> {
mcLibrariesFile.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
mcRepo.set(Constants.MC_LIBRARY_URL)
outputDir.set(cache.resolve(Constants.MINECRAFT_JARS_PATH))
sourcesOutputDir.set(cache.resolve(Constants.MINECRAFT_SOURCES_PATH))
downloader.set(downloadService)
}
return VanillaTasks(generateMappings, fixJar, downloadMcLibraries)
return VanillaTasks(inspectVanillaJar, generateMappings, fixJar, downloadMcLibraries)
}
private fun Project.createSpigotTasks(initialTasks: InitialTasks, generalTasks: GeneralTasks, vanillaTasks: VanillaTasks): SpigotTasks {
private fun Project.createSpigotTasks(generalTasks: GeneralTasks, vanillaTasks: VanillaTasks): SpigotTasks {
val cache: File = layout.cache
val extension: PaperweightExtension = ext
val downloadService = download
val (buildDataInfo, downloadServerJar, filterVanillaJar) = generalTasks
val (generateMappings, _, _) = vanillaTasks
val (inspectVanillaJar, generateMappings, _, _) = vanillaTasks
val addAdditionalSpigotMappings by tasks.registering<AddAdditionalSpigotMappings> {
classSrg.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.classMappings }))
@ -385,14 +395,6 @@ class Paperweight : Plugin<Project> {
additionalMemberEntriesSrg.set(extension.paper.additionalSpigotMemberMappings)
}
val inspectVanillaJar by tasks.registering<InspectVanillaJar> {
inputJar.set(downloadServerJar.flatMap { it.outputJar })
librariesDir.set(vanillaTasks.downloadMcLibraries.flatMap { it.outputDir })
mcLibraries.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
serverLibraries.set(cache.resolve(Constants.SERVER_LIBRARIES))
}
val generateSpigotMappings by tasks.registering<GenerateSpigotMappings> {
classMappings.set(addAdditionalSpigotMappings.flatMap { it.outputClassSrg })
memberMappings.set(addAdditionalSpigotMappings.flatMap { it.outputMemberSrg })
@ -401,6 +403,7 @@ class Paperweight : Plugin<Project> {
loggerFields.set(inspectVanillaJar.flatMap { it.loggerFile })
paramIndexes.set(inspectVanillaJar.flatMap { it.paramIndexes })
syntheticMethods.set(inspectVanillaJar.flatMap { it.syntheticMethods })
methodOverrides.set(inspectVanillaJar.flatMap { it.methodOverrides })
sourceMappings.set(generateMappings.flatMap { it.outputMappings })

View file

@ -23,6 +23,7 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.AsmUtil
import io.papermc.paperweight.util.ClassNodeCache
import io.papermc.paperweight.util.SyntheticUtil
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
@ -33,7 +34,6 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
@ -75,8 +75,7 @@ abstract class FixJar : BaseTask(), AsmUtil {
}
try {
val node =
classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
val node = classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
ParameterAnnotationFixer(node).visitNode()
OverrideAnnotationAdder(node, classNodeCache).visitNode()
@ -230,49 +229,3 @@ class OverrideAnnotationAdder(private val node: ClassNode, private val classNode
return result
}
}
class ClassNodeCache(private val jarFile: JarFile, private val fallbackJar: JarFile) {
private val classNodeMap = hashMapOf<String, ClassNode?>()
fun findClass(name: String): ClassNode? {
return classNodeMap.computeIfAbsent(normalize(name)) { fileName ->
val classData = findClassData(fileName) ?: return@computeIfAbsent null
val classReader = ClassReader(classData)
val node = ClassNode(Opcodes.ASM9)
classReader.accept(node, 0)
return@computeIfAbsent node
}
}
private fun findClassData(className: String): ByteArray? {
val entry = ZipEntry(className)
return (
jarFile.getInputStream(entry) // remapped class
?: fallbackJar.getInputStream(entry) // library class
?: ClassLoader.getSystemResourceAsStream(className)
)?.use { it.readBytes() } // JDK class
}
private fun normalize(name: String): String {
var workingName = name
if (workingName.endsWith(".class")) {
workingName = workingName.substring(0, workingName.length - 6)
}
var startIndex = 0
var endIndex = workingName.length
if (workingName.startsWith('L')) {
startIndex = 1
}
if (workingName.endsWith(';')) {
endIndex--
}
return workingName.substring(startIndex, endIndex).replace('.', '/') + ".class"
}
fun clear() {
classNodeMap.clear()
}
}

View file

@ -30,6 +30,7 @@ import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.path
import java.nio.file.FileSystems
import java.nio.file.Files
import java.util.zip.ZipFile
import javax.inject.Inject
import org.cadixdev.atlas.Atlas
import org.cadixdev.bombe.asm.jar.JarEntryRemappingTransformer
@ -67,6 +68,8 @@ abstract class GenerateMappings : DefaultTask() {
abstract val vanillaMappings: RegularFileProperty
@get:InputFile
abstract val paramMappings: RegularFileProperty
@get:InputFile
abstract val methodOverrides: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@ -93,11 +96,11 @@ abstract class GenerateMappings : DefaultTask() {
.build()
).merge()
ensureParentExists(outputMappings)
// Fill out any missing inheritance info in the mappings
val tempMappingsFile = Files.createTempFile("mappings", "tiny")
val tempMappingsOutputFile = Files.createTempFile("mappings-out", "tiny")
val filledMerged = try {
try {
MappingFormats.TINY.write(merged, tempMappingsFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
val queue = workerExecutor.processIsolation {
@ -107,21 +110,65 @@ abstract class GenerateMappings : DefaultTask() {
queue.submit(AtlasAction::class) {
inputJar.set(vanillaJar.file)
mappingsFile.set(tempMappingsFile.toFile())
outputMappingsFile.set(tempMappingsOutputFile.toFile())
outputMappingsFile.set(outputMappings.file)
}
queue.await()
MappingFormats.TINY.read(tempMappingsOutputFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
} finally {
Files.deleteIfExists(tempMappingsFile)
Files.deleteIfExists(tempMappingsOutputFile)
}
}
/*
private fun copyOverridenParamMappings(mappings: MappingSet) {
val methods = hashMapOf<String, String>()
methodOverrides.file.reader(Charsets.UTF_8).useLines { lines ->
lines.map { line ->
val (method, superMethod) = line.split("|")
methods[method] = superMethod
}
}
ensureParentExists(outputMappings)
MappingFormats.TINY.write(filledMerged, outputMappings.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
for (classMapping in mappings.topLevelClassMappings) {
copyOverridenParamMappings(mappings, classMapping, methods)
}
}
private fun copyOverridenParamMappings(mappingSet: MappingSet, classMapping: ClassMapping<*, *>, methods: Map<String, String>) {
methodLoop@for (mapping in classMapping.methodMappings) {
if (mapping.parameterMappings.isNotEmpty()) {
continue
}
var baseClassName = classMapping.obfuscatedName
var baseMethod = mapping.obfuscatedName
var baseDesc = mapping.obfuscatedDescriptor
while (true) {
val superMethod = methods["$baseClassName,$baseMethod,$baseDesc"] ?: continue@methodLoop
val (className, methodName, methodDesc) = superMethod.split(",")
val superMethodMapping = mappingSet.getClassMapping(className).orNull?.getMethodMapping(methodName, methodDesc)?.orNull
if (superMethodMapping != null) {
superMethodMapping.parameterMappings.forEach { it.copy(mapping) }
break
}
// prevent possible infinite loops
if (baseClassName == className) {
break
}
baseClassName = className
baseMethod = methodName
baseDesc = methodDesc
}
}
classMapping.innerClassMappings.forEach { copyOverridenParamMappings(mappingSet, it, methods) }
}
*/
abstract class AtlasAction : WorkAction<AtlasParameters> {
override fun execute() {
val mappings = MappingFormats.TINY.read(parameters.mappingsFile.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)

View file

@ -26,12 +26,12 @@ import io.papermc.paperweight.util.Constants.DEOBF_NAMESPACE
import io.papermc.paperweight.util.Constants.OBF_NAMESPACE
import io.papermc.paperweight.util.Constants.SPIGOT_NAMESPACE
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.MethodRef
import io.papermc.paperweight.util.emptyMergeResult
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.orNull
import io.papermc.paperweight.util.parentClass
import io.papermc.paperweight.util.readOverrides
import io.papermc.paperweight.util.path
import org.cadixdev.bombe.type.signature.MethodSignature
import org.cadixdev.lorenz.MappingSet
import org.cadixdev.lorenz.merge.MappingSetMerger
import org.cadixdev.lorenz.merge.MappingSetMergerHandler
@ -48,6 +48,8 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.jgrapht.graph.SimpleDirectedGraph
import org.jgrapht.traverse.DepthFirstIterator
abstract class GenerateSpigotMappings : DefaultTask() {
@ -64,6 +66,8 @@ abstract class GenerateSpigotMappings : DefaultTask() {
abstract val paramIndexes: RegularFileProperty
@get:InputFile
abstract val syntheticMethods: RegularFileProperty
@get:InputFile
abstract val methodOverrides: RegularFileProperty
@get:InputFile
abstract val sourceMappings: RegularFileProperty
@ -88,6 +92,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
val sourceMappings = MappingFormats.TINY.read(sourceMappings.path, OBF_NAMESPACE, DEOBF_NAMESPACE)
/*
val synths = newSynths()
syntheticMethods.file.useLines { lines ->
for (line in lines) {
@ -96,12 +101,15 @@ abstract class GenerateSpigotMappings : DefaultTask() {
.computeIfAbsent(desc) { hashMapOf() }[baseName] = synthName
}
}
*/
val overrides = readOverrides(methodOverrides)
val notchToSpigotSet = MappingSetMerger.create(
mergedMappingSet,
sourceMappings,
MergeConfig.builder()
.withMergeHandler(SpigotMappingsMergerHandler(newPackage, synths))
.withMergeHandler(SpigotMappingsMergerHandler(newPackage, overrides))
.build()
).merge()
@ -144,7 +152,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
params: Map<String, Map<String, Map<Int, Int>>>
) {
for (mapping in from.fieldMappings) {
to.createFieldMapping(mapping.signature, mapping.deobfuscatedName)
mapping.copy(to)
}
for (mapping in from.innerClassMappings) {
val newMapping = to.createInnerClassMapping(mapping.obfuscatedName, mapping.deobfuscatedName)
@ -162,7 +170,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
val methodMap = classMap[mapping.signature.toJvmsIdentifier()] ?: continue
for (paramMapping in paramMappings) {
val i = methodMap[paramMapping.index] ?: paramMapping.index
val i = methodMap[paramMapping.index] ?: continue
newMapping.createParameterMapping(i, paramMapping.deobfuscatedName)
}
}
@ -203,12 +211,14 @@ abstract class GenerateSpigotMappings : DefaultTask() {
}
}
/*
typealias Synths = Map<String, Map<String, Map<String, String>>>
fun newSynths() = hashMapOf<String, MutableMap<String, MutableMap<String, String>>>()
*/
class SpigotMappingsMergerHandler(
private val newPackage: String,
private val synths: Synths
private val methodOverrides: SimpleDirectedGraph<MethodRef, *>
) : MappingSetMergerHandler {
override fun mergeTopLevelClassMappings(
@ -338,6 +348,24 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
val ref = MethodRef.from(left.parent.obfuscatedName, left.signature)
val newMapping = if (methodOverrides.containsVertex(ref)) {
val methodRef = DepthFirstIterator(methodOverrides, ref).asSequence().firstOrNull() ?: ref
target.getOrCreateMethodMapping(methodRef.methodName, methodRef.methodDesc).also {
it.deobfuscatedName = left.deobfuscatedName
}
} else {
target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
}
return MergeResult(newMapping)
/*
val newMapping = target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
// Check if Spigot calls this mapping something else
val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor)
val newName = synthMethods?.get(left.obfuscatedName)
@ -350,11 +378,21 @@ class SpigotMappingsMergerHandler(
it.deobfuscatedName = left.deobfuscatedName
}
} else {
// Can't find the name spigot uses, just use the original mapping
// Can't find the name spigot uses, check the hierarchy
val methodRef = MethodRef(newMapping.parent.obfuscatedName, newMapping.obfuscatedName, newMapping.descriptor.toString())
target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = newName
}
}
val methodRef = MethodRef(newMapping.parent.obfuscatedName, newMapping.obfuscatedName, newMapping.descriptor.toString())
if (methodOverrides.containsVertex(methodRef)) {
for ((superClass, superName, superDesc) in BreadthFirstIterator(methodOverrides, methodRef)) {
}
}
MergeResult(newMapping)
} else {
// normal mapping
@ -363,12 +401,28 @@ class SpigotMappingsMergerHandler(
}
return MergeResult(newMapping)
}
*/
}
override fun addLeftMethodMapping(
left: MethodMapping,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
val ref = MethodRef.from(left.parent.obfuscatedName, left.signature)
val newMapping = if (methodOverrides.containsVertex(ref)) {
val methodRef = DepthFirstIterator(methodOverrides, ref).asSequence().firstOrNull() ?: ref
target.getOrCreateMethodMapping(methodRef.methodName, methodRef.methodDesc).also {
it.deobfuscatedName = left.deobfuscatedName
}
} else {
target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
}
return MergeResult(newMapping)
/*
// Check if Spigot maps this from a synthetic method name
var obfName: String? = null
val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor)
@ -390,6 +444,7 @@ class SpigotMappingsMergerHandler(
val newMapping = target.getOrCreateMethodMapping(obfName, left.descriptor)
newMapping.deobfuscatedName = left.deobfuscatedName
return MergeResult(newMapping)
*/
}
override fun addLeftFieldMapping(left: FieldMapping, target: ClassMapping<*, *>, context: MergeContext): FieldMapping? {
@ -411,6 +466,20 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
val ref = MethodRef.from(right.parent.obfuscatedName, right.signature)
if (methodOverrides.containsVertex(ref)) {
val methodRef = DepthFirstIterator(methodOverrides, ref).asSequence().firstOrNull() ?: ref
val originalMapping = context.left.getClassMapping(methodRef.className).orNull
?.getMethodMapping(methodRef.methodName, methodRef.methodDesc)?.orNull ?: return emptyMergeResult()
val newMapping = target.getOrCreateMethodMapping(right.signature).also {
it.deobfuscatedName = originalMapping.deobfuscatedName
}
return MergeResult(newMapping)
} else {
return emptyMergeResult()
}
/*
// Check if spigot changes this method automatically
val synthMethods = synths[right.parentClass.fullObfuscatedName]?.get(right.obfuscatedDescriptor)
val newName = synthMethods?.get(right.obfuscatedName) ?: return emptyMergeResult()
@ -424,6 +493,7 @@ class SpigotMappingsMergerHandler(
newMapping.deobfuscatedName = newName
}
return MergeResult(newMapping)
*/
}
private fun prependPackage(name: String): String {

View file

@ -23,11 +23,16 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.AsmUtil
import io.papermc.paperweight.util.ClassNodeCache
import io.papermc.paperweight.util.MavenArtifact
import io.papermc.paperweight.util.MethodRef
import io.papermc.paperweight.util.SyntheticUtil
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.isLibraryJar
import io.papermc.paperweight.util.writeOverrides
import java.util.concurrent.atomic.AtomicInteger
import java.util.jar.JarFile
import java.util.zip.ZipFile
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
@ -35,13 +40,15 @@ import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
import org.jgrapht.graph.DefaultEdge
import org.jgrapht.graph.SimpleDirectedGraph
import org.jgrapht.nio.Attribute
import org.jgrapht.nio.DefaultAttribute
import org.jgrapht.nio.dot.DOTExporter
import org.jgrapht.nio.dot.DOTImporter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.ClassNode
abstract class InspectVanillaJar : BaseTask() {
@ -59,12 +66,15 @@ abstract class InspectVanillaJar : BaseTask() {
@get:OutputFile
abstract val syntheticMethods: RegularFileProperty
@get:OutputFile
abstract val methodOverrides: RegularFileProperty
@get:OutputFile
abstract val serverLibraries: RegularFileProperty
override fun init() {
loggerFile.convention(defaultOutput("$name-loggerFields", "txt"))
paramIndexes.convention(defaultOutput("$name-paramIndexes", "txt"))
syntheticMethods.convention(defaultOutput("$name-syntheticMethods", "txt"))
methodOverrides.convention(defaultOutput("$name-methodOverrides", "json"))
}
@TaskAction
@ -72,28 +82,32 @@ abstract class InspectVanillaJar : BaseTask() {
val loggers = mutableListOf<LoggerFields.Data>()
val params = mutableListOf<ParamIndexes.Data>()
val synthMethods = mutableListOf<SyntheticMethods.Data>()
val overrides = SimpleDirectedGraph<MethodRef, DefaultEdge>(DefaultEdge::class.java)
var visitor: ClassVisitor
visitor = LoggerFields.Visitor(null, loggers)
visitor = ParamIndexes.Visitor(visitor, params)
visitor = SyntheticMethods.Visitor(visitor, synthMethods)
JarFile(inputJar.file).use { jarFile ->
val classNodeCache = ClassNodeCache(jarFile)
archives.zipTree(inputJar.file).matching {
include("/*.class")
include("/net/minecraft/**/*.class")
}.forEach { file ->
if (file.isDirectory) {
return@forEach
for (entry in jarFile.entries()) {
if (!entry.name.endsWith(".class")) {
continue
}
if (entry.name.count { it == '/' } > 0 && !entry.name.startsWith("net/minecraft/")) {
continue
}
val node = classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
LoggerFields.Visitor.visit(node, loggers)
ParamIndexes.Visitor.visit(node, params)
SyntheticMethods.Visitor.visit(node, synthMethods)
MethodOverrides.Visitor.visit(node, classNodeCache, overrides)
}
val classData = file.readBytes()
ClassReader(classData).accept(visitor, 0)
}
val serverLibs = checkLibraries()
loggerFile.file.bufferedWriter(Charsets.UTF_8).use { writer ->
loggers.sort()
loggers.sort()
loggerFile.file.bufferedWriter().use { writer ->
for (loggerField in loggers) {
writer.append(loggerField.className)
writer.append(' ')
@ -102,8 +116,8 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
paramIndexes.file.bufferedWriter(Charsets.UTF_8).use { writer ->
params.sort()
params.sort()
paramIndexes.file.bufferedWriter().use { writer ->
for (methodData in params) {
writer.append(methodData.className)
writer.append(' ')
@ -120,8 +134,8 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
syntheticMethods.file.bufferedWriter(Charsets.UTF_8).use { writer ->
synthMethods.sort()
synthMethods.sort()
syntheticMethods.file.bufferedWriter().use { writer ->
for ((className, desc, synthName, baseName) in synthMethods) {
writer.append(className)
writer.append(' ')
@ -134,8 +148,11 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
serverLibraries.file.bufferedWriter(Charsets.UTF_8).use { writer ->
serverLibs.map { it.toString() }.sorted().forEach { artifact ->
writeOverrides(overrides, methodOverrides)
val libs = serverLibs.map { it.toString() }.sorted()
serverLibraries.file.bufferedWriter().use { writer ->
libs.forEach { artifact ->
writer.appendln(artifact)
}
}
@ -173,50 +190,22 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
abstract class BaseClassVisitor(classVisitor: ClassVisitor?) : ClassVisitor(Opcodes.ASM8, classVisitor), AsmUtil {
protected var currentClass: String? = null
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
this.currentClass = name
super.visit(version, access, name, signature, superName, interfaces)
}
}
/*
* SpecialSource2 automatically maps all Logger fields to the name LOGGER, without needing mappings defined, so we need
* to make a note of all of those fields
*/
object LoggerFields {
class Visitor(
classVisitor: ClassVisitor?,
private val fields: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
object Visitor : AsmUtil {
override fun visitField(
access: Int,
name: String,
descriptor: String,
signature: String?,
value: Any?
): FieldVisitor? {
val ret = super.visitField(access, name, descriptor, signature, value)
val className = currentClass ?: return ret
if (Opcodes.ACC_STATIC !in access || Opcodes.ACC_FINAL !in access) {
return ret
fun visit(node: ClassNode, fields: MutableList<Data>) {
for (field in node.fields) {
if (Opcodes.ACC_STATIC !in field.access || Opcodes.ACC_FINAL !in field.access) {
continue
}
if (field.desc == "Lorg/apache/logging/log4j/Logger;") {
fields += Data(node.name, field.name)
}
}
if (descriptor != "Lorg/apache/logging/log4j/Logger;") {
return ret
}
fields += Data(className, name)
return ret
}
}
@ -235,45 +224,33 @@ object LoggerFields {
* actual bytecode to translate the LVT-based indexes back to 0-based param indexes.
*/
object ParamIndexes {
class Visitor(
classVisitor: ClassVisitor?,
private val methods: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
object Visitor : AsmUtil {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val ret = super.visitMethod(access, name, descriptor, signature, exceptions)
val className = currentClass ?: return ret
fun visit(node: ClassNode, methods: MutableList<Data>) {
for (method in node.methods) {
val isStatic = Opcodes.ACC_STATIC in method.access
var currentIndex = if (isStatic) 0 else 1
val isStatic = access and Opcodes.ACC_STATIC != 0
var currentIndex = if (isStatic) 0 else 1
val types = Type.getArgumentTypes(method.desc)
if (types.isEmpty()) {
continue
}
val types = Type.getArgumentTypes(descriptor)
if (types.isEmpty()) {
return ret
}
val params = ArrayList<ParamTarget>(types.size)
val data = Data(node.name, method.name, method.desc, params)
methods += data
val params = ArrayList<ParamTarget>(types.size)
val data = Data(className, name, descriptor, params)
methods += data
for (i in types.indices) {
params += ParamTarget(currentIndex, i)
currentIndex++
// Figure out if we should skip the next index
val type = types[i]
if (type === Type.LONG_TYPE || type === Type.DOUBLE_TYPE) {
for (i in types.indices) {
params += ParamTarget(currentIndex, i)
currentIndex++
// Figure out if we should skip the next index
val type = types[i]
if (type === Type.LONG_TYPE || type === Type.DOUBLE_TYPE) {
currentIndex++
}
}
}
return ret
}
}
@ -303,45 +280,20 @@ object ParamIndexes {
* can handle it in our generated mappings.
*/
object SyntheticMethods {
class Visitor(
classVisitor: ClassVisitor?,
private val methods: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
object Visitor : AsmUtil {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val ret = super.visitMethod(access, name, descriptor, signature, exceptions)
val className = currentClass ?: return ret
fun visit(node: ClassNode, methods: MutableList<Data>) {
for (method in node.methods) {
if (Opcodes.ACC_SYNTHETIC !in method.access || Opcodes.ACC_BRIDGE in method.access || method.name.contains('$')) {
continue
}
if (Opcodes.ACC_SYNTHETIC !in access || Opcodes.ACC_BRIDGE in access || name.contains('$')) {
return ret
}
val (baseName, baseDesc) = SyntheticUtil.findBaseMethod(method, node.name)
return SynthMethodVisitor(access, name, descriptor, signature, exceptions, className, methods)
}
}
private class SynthMethodVisitor(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?,
private val className: String,
private val methods: MutableList<Data>
) : MethodNode(Opcodes.ASM9, access, name, descriptor, signature, exceptions) {
override fun visitEnd() {
val (baseName, baseDesc) = SyntheticUtil.findBaseMethod(this, className)
if (baseName != name || baseDesc != desc) {
// Add this method as a synthetic for baseName
methods += Data(className, baseDesc, name, baseName)
if (baseName != method.name || baseDesc != method.desc) {
// Add this method as a synthetic for baseName
methods += Data(node.name, baseDesc, method.name, baseName)
}
}
}
}
@ -362,3 +314,68 @@ object SyntheticMethods {
)
}
}
/*
* There's no direct marker in bytecode to know if a
*/
object MethodOverrides {
object Visitor : AsmUtil {
fun visit(
node: ClassNode,
classNodeCache: ClassNodeCache,
methodOverrides: SimpleDirectedGraph<MethodRef, DefaultEdge>
) {
val superMethods = collectSuperMethods(node, classNodeCache)
val disqualifiedMethods = Opcodes.ACC_STATIC or Opcodes.ACC_PRIVATE
for (method in node.methods) {
if (method.access in disqualifiedMethods) {
continue
}
if (method.name == "<init>" || method.name == "<clinit>") {
continue
}
val (name, desc) = SyntheticUtil.findBaseMethod(method, node.name)
superMethods[method.name + method.desc]?.let { className ->
val targetMethod = node.methods.firstOrNull { it.name == name && it.desc == desc } ?: method
val methodRef = MethodRef(node.name, method.name, method.desc)
val targetMethodRef = MethodRef(className, targetMethod.name, targetMethod.desc)
methodOverrides.addVertex(methodRef)
methodOverrides.addVertex(targetMethodRef)
methodOverrides.addEdge(methodRef, targetMethodRef)
}
}
}
private fun collectSuperMethods(node: ClassNode, classNodeCache: ClassNodeCache): Map<String, String> {
fun collectSuperMethods(node: ClassNode, superMethods: HashMap<String, String>) {
val supers = listOfNotNull(node.superName, *node.interfaces.toTypedArray())
if (supers.isEmpty()) {
return
}
val disqualifiedMethods = Opcodes.ACC_STATIC or Opcodes.ACC_PRIVATE
val superNodes = supers.mapNotNull { classNodeCache.findClass(it) }
for (superNode in superNodes) {
superNode.methods.asSequence()
.filter { method -> method.access !in disqualifiedMethods }
.filter { method -> method.name != "<init>" && method.name != "<clinit>" }
.map { method -> method.name + method.desc to superNode.name }
.toMap(superMethods)
}
for (superNode in superNodes) {
collectSuperMethods(superNode, superMethods)
}
}
val result = hashMapOf<String, String>()
collectSuperMethods(node, result)
return result
}
}
}

View file

@ -0,0 +1,53 @@
package io.papermc.paperweight.util
import java.util.jar.JarFile
import java.util.zip.ZipEntry
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
class ClassNodeCache(private val jarFile: JarFile, private val fallbackJar: JarFile? = null) {
private val classNodeMap = hashMapOf<String, ClassNode?>()
fun findClass(name: String): ClassNode? {
return classNodeMap.computeIfAbsent(normalize(name)) { fileName ->
val classData = findClassData(fileName) ?: return@computeIfAbsent null
val classReader = ClassReader(classData)
val node = ClassNode(Opcodes.ASM9)
classReader.accept(node, 0)
return@computeIfAbsent node
}
}
private fun findClassData(className: String): ByteArray? {
val entry = ZipEntry(className)
return (
jarFile.getInputStream(entry) // remapped class
?: fallbackJar?.getInputStream(entry) // library class
?: ClassLoader.getSystemResourceAsStream(className) // JDK class
)?.use { it.readBytes() }
}
private fun normalize(name: String): String {
var workingName = name
if (workingName.endsWith(".class")) {
workingName = workingName.substring(0, workingName.length - 6)
}
var startIndex = 0
var endIndex = workingName.length
if (workingName.startsWith('L')) {
startIndex = 1
}
if (workingName.endsWith(';')) {
endIndex--
}
return workingName.substring(startIndex, endIndex).replace('.', '/') + ".class"
}
fun clear() {
classNodeMap.clear()
}
}

View file

@ -0,0 +1,40 @@
package io.papermc.paperweight.util
import org.cadixdev.bombe.type.signature.MethodSignature
import org.jgrapht.Graph
import org.jgrapht.graph.DefaultEdge
import org.jgrapht.graph.SimpleDirectedGraph
import org.jgrapht.nio.dot.DOTImporter
import org.jgrapht.nio.gml.GmlExporter
import org.jgrapht.nio.gml.GmlImporter
import org.jgrapht.nio.json.JSONExporter
import org.jgrapht.nio.json.JSONImporter
data class MethodRef(val className: String, val methodName: String, val methodDesc: String) {
companion object {
fun from(className: String, sig: MethodSignature): MethodRef {
return MethodRef(className, sig.name, sig.descriptor.toString())
}
}
}
fun writeOverrides(graph: Graph<MethodRef, DefaultEdge>, file: Any) {
val exporter = JSONExporter<MethodRef, DefaultEdge> { ref -> "${ref.className} ${ref.methodName} ${ref.methodDesc}" }
file.convertToFile().bufferedWriter().use { writer ->
exporter.exportGraph(graph, writer)
}
}
fun readOverrides(file: Any): SimpleDirectedGraph<MethodRef, *> {
val importer = JSONImporter<MethodRef, DefaultEdge>()
importer.setVertexFactory {
val (className, methodName, methodDesc) = it.split(" ")
MethodRef(className, methodName, methodDesc)
}
val overrides = SimpleDirectedGraph<MethodRef, DefaultEdge>(DefaultEdge::class.java)
file.convertToFile().bufferedReader().use { reader ->
importer.importGraph(overrides, reader)
}
return overrides
}