Compare commits

...

3 commits
main ... work

Author SHA1 Message Date
Kyle Wood
0ae3814cf5
broken 2021-01-17 02:46:32 -08:00
Kyle Wood
2c4b1e1409 Run paperclip patch process in separate process 2021-01-02 23:06:31 -08:00
Kyle Wood
c4eb91fe8f work 2021-01-02 21:55:01 -08:00
11 changed files with 547 additions and 307 deletions

View file

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

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -92,10 +92,12 @@ class Paperweight : Plugin<Project> {
val tasks = target.createTasks() val tasks = target.createTasks()
val generatePaperclipPatch by target.tasks.registering<GeneratePaperclipPatch>()
// Setup the server jar // Setup the server jar
target.afterEvaluate { target.afterEvaluate {
target.ext.serverProject.forUseAtConfigurationTime().orNull?.setupServerProject(target, tasks.spigotTasks)?.let { reobfJar -> target.ext.serverProject.forUseAtConfigurationTime().orNull?.setupServerProject(target, tasks.spigotTasks)?.let { reobfJar ->
val generatePaperclipPatch by target.tasks.registering<GeneratePaperclipPatch> { generatePaperclipPatch.configure {
originalJar.set(tasks.generalTasks.downloadServerJar.flatMap { it.outputJar }) originalJar.set(tasks.generalTasks.downloadServerJar.flatMap { it.outputJar })
patchedJar.set(reobfJar.flatMap { it.outputJar }) patchedJar.set(reobfJar.flatMap { it.outputJar })
mcVersion.set(target.ext.minecraftVersion) mcVersion.set(target.ext.minecraftVersion)
@ -103,13 +105,13 @@ class Paperweight : Plugin<Project> {
target.tasks.named("jar", Jar::class) { target.tasks.named("jar", Jar::class) {
val paperclipConfig = target.configurations.named(Constants.PAPERCLIP_CONFIG) val paperclipConfig = target.configurations.named(Constants.PAPERCLIP_CONFIG)
dependsOn(paperclipConfig) dependsOn(paperclipConfig, generatePaperclipPatch)
val paperclipZip = target.zipTree(paperclipConfig.map { it.singleFile }) val paperclipZip = target.zipTree(paperclipConfig.map { it.singleFile })
from(paperclipZip) { from(paperclipZip) {
exclude("META-INF/MANIFEST.MF") exclude("META-INF/MANIFEST.MF")
} }
from(target.zipTree(generatePaperclipPatch.flatMap { it.outputZip }.get())) from(target.zipTree(generatePaperclipPatch.flatMap { it.outputZip }))
manifest.from(paperclipZip.matching { include("META-INF/MANIFEST.MF") }.singleFile) manifest.from(paperclipZip.matching { include("META-INF/MANIFEST.MF") }.singleFile)
} }
@ -150,7 +152,7 @@ class Paperweight : Plugin<Project> {
val initialTasks = createInitialTasks() val initialTasks = createInitialTasks()
val generalTasks = createGeneralTasks() val generalTasks = createGeneralTasks()
val vanillaTasks = createVanillaTasks(initialTasks, generalTasks) val vanillaTasks = createVanillaTasks(initialTasks, generalTasks)
val spigotTasks = createSpigotTasks(initialTasks, generalTasks, vanillaTasks) val spigotTasks = createSpigotTasks(generalTasks, vanillaTasks)
val applyMergedAt by tasks.registering<ApplyAccessTransform> { val applyMergedAt by tasks.registering<ApplyAccessTransform> {
inputJar.set(vanillaTasks.fixJar.flatMap { it.outputJar }) inputJar.set(vanillaTasks.fixJar.flatMap { it.outputJar })
@ -236,6 +238,7 @@ class Paperweight : Plugin<Project> {
) )
data class VanillaTasks( data class VanillaTasks(
val inspectVanillaJar: TaskProvider<InspectVanillaJar>,
val generateMappings: TaskProvider<GenerateMappings>, val generateMappings: TaskProvider<GenerateMappings>,
val fixJar: TaskProvider<FixJar>, val fixJar: TaskProvider<FixJar>,
val downloadMcLibraries: TaskProvider<DownloadMcLibraries> val downloadMcLibraries: TaskProvider<DownloadMcLibraries>
@ -334,11 +337,29 @@ class Paperweight : Plugin<Project> {
val cache: File = layout.cache val cache: File = layout.cache
val downloadService = download 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> { val generateMappings by tasks.registering<GenerateMappings> {
vanillaJar.set(generalTasks.filterVanillaJar.flatMap { it.outputJar }) vanillaJar.set(generalTasks.filterVanillaJar.flatMap { it.outputJar })
vanillaMappings.set(initialTasks.downloadMappings.flatMap { it.outputFile }) vanillaMappings.set(initialTasks.downloadMappings.flatMap { it.outputFile })
paramMappings.fileProvider(configurations.named(Constants.PARAM_MAPPINGS_CONFIG).map { it.singleFile }) 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)) outputMappings.set(cache.resolve(Constants.MOJANG_YARN_MAPPINGS))
} }
@ -356,25 +377,16 @@ class Paperweight : Plugin<Project> {
vanillaJar.set(generalTasks.downloadServerJar.flatMap { it.outputJar }) vanillaJar.set(generalTasks.downloadServerJar.flatMap { it.outputJar })
} }
val downloadMcLibraries by tasks.registering<DownloadMcLibraries> { return VanillaTasks(inspectVanillaJar, generateMappings, fixJar, 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)
} }
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 cache: File = layout.cache
val extension: PaperweightExtension = ext val extension: PaperweightExtension = ext
val downloadService = download val downloadService = download
val (buildDataInfo, downloadServerJar, filterVanillaJar) = generalTasks val (buildDataInfo, downloadServerJar, filterVanillaJar) = generalTasks
val (generateMappings, _, _) = vanillaTasks val (inspectVanillaJar, generateMappings, _, _) = vanillaTasks
val addAdditionalSpigotMappings by tasks.registering<AddAdditionalSpigotMappings> { val addAdditionalSpigotMappings by tasks.registering<AddAdditionalSpigotMappings> {
classSrg.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.classMappings })) classSrg.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.classMappings }))
@ -383,14 +395,6 @@ class Paperweight : Plugin<Project> {
additionalMemberEntriesSrg.set(extension.paper.additionalSpigotMemberMappings) 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> { val generateSpigotMappings by tasks.registering<GenerateSpigotMappings> {
classMappings.set(addAdditionalSpigotMappings.flatMap { it.outputClassSrg }) classMappings.set(addAdditionalSpigotMappings.flatMap { it.outputClassSrg })
memberMappings.set(addAdditionalSpigotMappings.flatMap { it.outputMemberSrg }) memberMappings.set(addAdditionalSpigotMappings.flatMap { it.outputMemberSrg })
@ -399,6 +403,7 @@ class Paperweight : Plugin<Project> {
loggerFields.set(inspectVanillaJar.flatMap { it.loggerFile }) loggerFields.set(inspectVanillaJar.flatMap { it.loggerFile })
paramIndexes.set(inspectVanillaJar.flatMap { it.paramIndexes }) paramIndexes.set(inspectVanillaJar.flatMap { it.paramIndexes })
syntheticMethods.set(inspectVanillaJar.flatMap { it.syntheticMethods }) syntheticMethods.set(inspectVanillaJar.flatMap { it.syntheticMethods })
methodOverrides.set(inspectVanillaJar.flatMap { it.methodOverrides })
sourceMappings.set(generateMappings.flatMap { it.outputMappings }) sourceMappings.set(generateMappings.flatMap { it.outputMappings })

View file

@ -23,6 +23,7 @@
package io.papermc.paperweight.tasks package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.AsmUtil import io.papermc.paperweight.util.AsmUtil
import io.papermc.paperweight.util.ClassNodeCache
import io.papermc.paperweight.util.SyntheticUtil import io.papermc.paperweight.util.SyntheticUtil
import io.papermc.paperweight.util.defaultOutput import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file 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.InputFile
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type import org.objectweb.asm.Type
@ -75,8 +75,7 @@ abstract class FixJar : BaseTask(), AsmUtil {
} }
try { try {
val node = val node = classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
ParameterAnnotationFixer(node).visitNode() ParameterAnnotationFixer(node).visitNode()
OverrideAnnotationAdder(node, classNodeCache).visitNode() OverrideAnnotationAdder(node, classNodeCache).visitNode()
@ -230,49 +229,3 @@ class OverrideAnnotationAdder(private val node: ClassNode, private val classNode
return result 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 io.papermc.paperweight.util.path
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Files import java.nio.file.Files
import java.util.zip.ZipFile
import javax.inject.Inject import javax.inject.Inject
import org.cadixdev.atlas.Atlas import org.cadixdev.atlas.Atlas
import org.cadixdev.bombe.asm.jar.JarEntryRemappingTransformer import org.cadixdev.bombe.asm.jar.JarEntryRemappingTransformer
@ -41,6 +42,7 @@ import org.cadixdev.lorenz.merge.MappingSetMergerHandler
import org.cadixdev.lorenz.merge.MergeConfig import org.cadixdev.lorenz.merge.MergeConfig
import org.cadixdev.lorenz.merge.MergeContext import org.cadixdev.lorenz.merge.MergeContext
import org.cadixdev.lorenz.merge.MergeResult import org.cadixdev.lorenz.merge.MergeResult
import org.cadixdev.lorenz.merge.MethodMergeStrategy
import org.cadixdev.lorenz.model.ClassMapping import org.cadixdev.lorenz.model.ClassMapping
import org.cadixdev.lorenz.model.FieldMapping import org.cadixdev.lorenz.model.FieldMapping
import org.cadixdev.lorenz.model.InnerClassMapping import org.cadixdev.lorenz.model.InnerClassMapping
@ -66,6 +68,8 @@ abstract class GenerateMappings : DefaultTask() {
abstract val vanillaMappings: RegularFileProperty abstract val vanillaMappings: RegularFileProperty
@get:InputFile @get:InputFile
abstract val paramMappings: RegularFileProperty abstract val paramMappings: RegularFileProperty
@get:InputFile
abstract val methodOverrides: RegularFileProperty
@get:OutputFile @get:OutputFile
abstract val outputMappings: RegularFileProperty abstract val outputMappings: RegularFileProperty
@ -86,16 +90,17 @@ abstract class GenerateMappings : DefaultTask() {
vanillaMappings, vanillaMappings,
paramMappings, paramMappings,
MergeConfig.builder() MergeConfig.builder()
.withMethodMergeStrategy(MethodMergeStrategy.STRICT)
.withFieldMergeStrategy(FieldMergeStrategy.STRICT) .withFieldMergeStrategy(FieldMergeStrategy.STRICT)
.withMergeHandler(ParamsMergeHandler()) .withMergeHandler(ParamsMergeHandler())
.build() .build()
).merge() ).merge()
ensureParentExists(outputMappings)
// Fill out any missing inheritance info in the mappings // Fill out any missing inheritance info in the mappings
val tempMappingsFile = Files.createTempFile("mappings", "tiny") val tempMappingsFile = Files.createTempFile("mappings", "tiny")
val tempMappingsOutputFile = Files.createTempFile("mappings-out", "tiny") try {
val filledMerged = try {
MappingFormats.TINY.write(merged, tempMappingsFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE) MappingFormats.TINY.write(merged, tempMappingsFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
val queue = workerExecutor.processIsolation { val queue = workerExecutor.processIsolation {
@ -105,21 +110,65 @@ abstract class GenerateMappings : DefaultTask() {
queue.submit(AtlasAction::class) { queue.submit(AtlasAction::class) {
inputJar.set(vanillaJar.file) inputJar.set(vanillaJar.file)
mappingsFile.set(tempMappingsFile.toFile()) mappingsFile.set(tempMappingsFile.toFile())
outputMappingsFile.set(tempMappingsOutputFile.toFile()) outputMappingsFile.set(outputMappings.file)
} }
queue.await() queue.await()
MappingFormats.TINY.read(tempMappingsOutputFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
} finally { } finally {
Files.deleteIfExists(tempMappingsFile) 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) for (classMapping in mappings.topLevelClassMappings) {
MappingFormats.TINY.write(filledMerged, outputMappings.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE) 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> { abstract class AtlasAction : WorkAction<AtlasParameters> {
override fun execute() { override fun execute() {
val mappings = MappingFormats.TINY.read(parameters.mappingsFile.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE) val mappings = MappingFormats.TINY.read(parameters.mappingsFile.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
@ -237,7 +286,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): MergeResult<MethodMapping?> { ): MergeResult<MethodMapping?> {
return MergeResult( return MergeResult(
target.createMethodMapping(left.signature, left.deobfuscatedName), target.createMethodMapping(left.signature, left.deobfuscatedName),
listOfNotNull(standardRightDuplicate, wiggledRightDuplicate) listOfNotNull(standardRightDuplicate)
) )
} }

View file

@ -36,11 +36,16 @@ import java.nio.file.StandardCopyOption
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.Properties import java.util.Properties
import javax.inject.Inject
import kotlin.experimental.and import kotlin.experimental.and
import org.gradle.api.file.RegularFileProperty import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFile
import org.gradle.kotlin.dsl.submit
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
abstract class GeneratePaperclipPatch : ZippedTask() { abstract class GeneratePaperclipPatch : ZippedTask() {
@ -51,88 +56,122 @@ abstract class GeneratePaperclipPatch : ZippedTask() {
@get:Input @get:Input
abstract val mcVersion: Property<String> abstract val mcVersion: Property<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun run(rootDir: File) { override fun run(rootDir: File) {
val patchFile = rootDir.resolve("paperMC.patch").toPath() val queue = workerExecutor.processIsolation {
val propFile = rootDir.resolve("patch.properties").toPath() forkOptions.jvmArgs("-Xmx2G")
val protocol = rootDir.resolve("META-INF/$PROTOCOL_FILE").toPath()
val zipUri = try {
val jarUri = patchedJar.file.toURI()
URI("jar:${jarUri.scheme}", jarUri.path, null)
} catch (e: URISyntaxException) {
throw PaperweightException("Failed to create jar URI for $patchedJar", e)
} }
try { queue.submit(PaperclipAction::class) {
FileSystems.newFileSystem(zipUri, mapOf<String, Any>()).use { zipFs -> zipRootDir.set(rootDir)
val protocolPath = zipFs.getPath("META-INF", PROTOCOL_FILE) originalJar.set(this@GeneratePaperclipPatch.originalJar.file)
if (Files.notExists(protocolPath)) { patchedJar.set(this@GeneratePaperclipPatch.patchedJar.file)
Files.deleteIfExists(protocol) mcVersion.set(this@GeneratePaperclipPatch.mcVersion.get())
return@use }
queue.await()
}
abstract class PaperclipAction : WorkAction<PaperclipParameters> {
override fun execute() {
val rootDir = parameters.zipRootDir.path
val originalJarPath = parameters.originalJar.path
val patchedJarPath = parameters.patchedJar.path
val patchFile = rootDir.resolve("paperMC.patch")
val propFile = rootDir.resolve("patch.properties")
val protocol = rootDir.resolve("META-INF/$PROTOCOL_FILE")
val zipUri = try {
val jarUri = patchedJarPath.toUri()
URI("jar:${jarUri.scheme}", jarUri.path, null)
} catch (e: URISyntaxException) {
throw PaperweightException("Failed to create jar URI for $patchedJarPath", e)
}
try {
FileSystems.newFileSystem(zipUri, mapOf<String, Any>()).use { zipFs ->
val protocolPath = zipFs.getPath("META-INF", PROTOCOL_FILE)
if (Files.notExists(protocolPath)) {
Files.deleteIfExists(protocol)
return@use
}
Files.createDirectories(protocol.parent)
Files.copy(protocolPath, protocol, StandardCopyOption.REPLACE_EXISTING)
} }
} catch (e: IOException) {
Files.createDirectories(protocol.parent) throw PaperweightException("Failed to read $patchedJarPath contents", e)
Files.copy(protocolPath, protocol, StandardCopyOption.REPLACE_EXISTING)
} }
} catch (e: IOException) {
throw PaperweightException("Failed to read $patchedJar contents", e)
}
// Read the files into memory // Read the files into memory
println("Reading jars into memory") println("Reading jars into memory")
val originalBytes = Files.readAllBytes(originalJar.path) val originalBytes = Files.readAllBytes(originalJarPath)
val patchedBytes = Files.readAllBytes(patchedJar.path) val patchedBytes = Files.readAllBytes(patchedJarPath)
println("Creating Paperclip patch") println("Creating Paperclip patch")
try { try {
Files.newOutputStream(patchFile).use { patchOutput -> Files.newOutputStream(patchFile).use { patchOutput ->
Diff.diff(originalBytes, patchedBytes, patchOutput) Diff.diff(originalBytes, patchedBytes, patchOutput)
}
} catch (e: Exception) {
throw PaperweightException("Error creating patch between $originalJarPath and $patchedJarPath", e)
}
// Add the SHA-256 hashes for the files
val digestSha256 = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw PaperweightException("Could not create SHA-256 hasher", e)
}
// Vanilla's URL uses a SHA1 hash of the vanilla server jar
val digestSha1 = try {
MessageDigest.getInstance("SHA1")
} catch (e: NoSuchAlgorithmException) {
throw PaperweightException("Could not create SHA1 hasher", e)
}
println("Hashing files")
val originalSha1 = digestSha1.digest(originalBytes)
val originalSha256 = digestSha256.digest(originalBytes)
val patchedSha256 = digestSha256.digest(patchedBytes)
val prop = Properties()
prop["originalHash"] = toHex(originalSha256)
prop["patchedHash"] = toHex(patchedSha256)
prop["patch"] = "paperMC.patch"
prop["sourceUrl"] = "https://launcher.mojang.com/v1/objects/" + toHex(originalSha1).toLowerCase() + "/server.jar"
prop["version"] = parameters.mcVersion.get()
println("Writing properties file")
Files.newBufferedWriter(propFile).use { writer ->
prop.store(
writer,
"Default Paperclip launch values. Can be overridden by placing a paperclip.properties file in the server directory."
)
} }
} catch (e: Exception) {
throw PaperweightException("Error creating patch between ${originalJar.path} and ${patchedJar.path}", e)
} }
// Add the SHA-256 hashes for the files private fun toHex(hash: ByteArray): String {
val digestSha256 = try { val sb: StringBuilder = StringBuilder(hash.size * 2)
MessageDigest.getInstance("SHA-256") for (aHash in hash) {
} catch (e: NoSuchAlgorithmException) { sb.append("%02X".format(aHash and 0xFF.toByte()))
throw PaperweightException("Could not create SHA-256 hasher", e) }
return sb.toString()
} }
// Vanilla's URL uses a SHA1 hash of the vanilla server jar companion object {
val digestSha1 = try { private const val PROTOCOL_FILE = "io.papermc.paper.daemon.protocol"
MessageDigest.getInstance("SHA1")
} catch (e: NoSuchAlgorithmException) {
throw PaperweightException("Could not create SHA1 hasher", e)
}
println("Hashing files")
val originalSha1 = digestSha1.digest(originalBytes)
val originalSha256 = digestSha256.digest(originalBytes)
val patchedSha256 = digestSha256.digest(patchedBytes)
val prop = Properties()
prop["originalHash"] = toHex(originalSha256)
prop["patchedHash"] = toHex(patchedSha256)
prop["patch"] = "paperMC.patch"
prop["sourceUrl"] = "https://launcher.mojang.com/v1/objects/" + toHex(originalSha1).toLowerCase() + "/server.jar"
prop["version"] = mcVersion.get()
println("Writing properties file")
Files.newBufferedWriter(propFile).use { writer ->
prop.store(writer, "Default Paperclip launch values. Can be overridden by placing a paperclip.properties file in the server directory.")
} }
} }
private fun toHex(hash: ByteArray): String { interface PaperclipParameters : WorkParameters {
val sb: StringBuilder = StringBuilder(hash.size * 2) val zipRootDir: RegularFileProperty
for (aHash in hash) { val originalJar: RegularFileProperty
sb.append("%02X".format(aHash and 0xFF.toByte())) val patchedJar: RegularFileProperty
} val mcVersion: Property<String>
return sb.toString()
}
companion object {
const val PROTOCOL_FILE = "io.papermc.paper.daemon.protocol"
} }
} }

View file

@ -22,14 +22,16 @@
package io.papermc.paperweight.tasks package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Constants 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.MappingFormats
import io.papermc.paperweight.util.MethodRef
import io.papermc.paperweight.util.emptyMergeResult import io.papermc.paperweight.util.emptyMergeResult
import io.papermc.paperweight.util.file import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.orNull 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 io.papermc.paperweight.util.path
import org.cadixdev.bombe.type.signature.MethodSignature
import org.cadixdev.lorenz.MappingSet import org.cadixdev.lorenz.MappingSet
import org.cadixdev.lorenz.merge.MappingSetMerger import org.cadixdev.lorenz.merge.MappingSetMerger
import org.cadixdev.lorenz.merge.MappingSetMergerHandler import org.cadixdev.lorenz.merge.MappingSetMergerHandler
@ -46,6 +48,8 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.jgrapht.graph.SimpleDirectedGraph
import org.jgrapht.traverse.DepthFirstIterator
abstract class GenerateSpigotMappings : DefaultTask() { abstract class GenerateSpigotMappings : DefaultTask() {
@ -62,6 +66,8 @@ abstract class GenerateSpigotMappings : DefaultTask() {
abstract val paramIndexes: RegularFileProperty abstract val paramIndexes: RegularFileProperty
@get:InputFile @get:InputFile
abstract val syntheticMethods: RegularFileProperty abstract val syntheticMethods: RegularFileProperty
@get:InputFile
abstract val methodOverrides: RegularFileProperty
@get:InputFile @get:InputFile
abstract val sourceMappings: RegularFileProperty abstract val sourceMappings: RegularFileProperty
@ -84,13 +90,10 @@ abstract class GenerateSpigotMappings : DefaultTask() {
// Get the new package name // Get the new package name
val newPackage = packageMappings.asFile.get().readLines()[0].split(Regex("\\s+"))[1] val newPackage = packageMappings.asFile.get().readLines()[0].split(Regex("\\s+"))[1]
val sourceMappings = MappingFormats.TINY.read( val sourceMappings = MappingFormats.TINY.read(sourceMappings.path, OBF_NAMESPACE, DEOBF_NAMESPACE)
sourceMappings.path,
Constants.OBF_NAMESPACE,
Constants.DEOBF_NAMESPACE
)
val synths = hashMapOf<String, MutableMap<String, MutableMap<String, String>>>() /*
val synths = newSynths()
syntheticMethods.file.useLines { lines -> syntheticMethods.file.useLines { lines ->
for (line in lines) { for (line in lines) {
val (className, desc, synthName, baseName) = line.split(" ") val (className, desc, synthName, baseName) = line.split(" ")
@ -98,12 +101,15 @@ abstract class GenerateSpigotMappings : DefaultTask() {
.computeIfAbsent(desc) { hashMapOf() }[baseName] = synthName .computeIfAbsent(desc) { hashMapOf() }[baseName] = synthName
} }
} }
*/
val overrides = readOverrides(methodOverrides)
val notchToSpigotSet = MappingSetMerger.create( val notchToSpigotSet = MappingSetMerger.create(
mergedMappingSet, mergedMappingSet,
sourceMappings, sourceMappings,
MergeConfig.builder() MergeConfig.builder()
.withMergeHandler(SpigotMappingsMergerHandler(newPackage, synths)) .withMergeHandler(SpigotMappingsMergerHandler(newPackage, overrides))
.build() .build()
).merge() ).merge()
@ -111,12 +117,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
val cleanedSourceMappings = removeLambdaMappings(adjustedSourceMappings) val cleanedSourceMappings = removeLambdaMappings(adjustedSourceMappings)
val spigotToNamedSet = notchToSpigotSet.reverse().merge(cleanedSourceMappings) val spigotToNamedSet = notchToSpigotSet.reverse().merge(cleanedSourceMappings)
MappingFormats.TINY.write( MappingFormats.TINY.write(spigotToNamedSet, outputMappings.path, SPIGOT_NAMESPACE, DEOBF_NAMESPACE)
spigotToNamedSet,
outputMappings.path,
Constants.SPIGOT_NAMESPACE,
Constants.DEOBF_NAMESPACE
)
} }
private fun adjustParamIndexes(mappings: MappingSet): MappingSet { private fun adjustParamIndexes(mappings: MappingSet): MappingSet {
@ -151,7 +152,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
params: Map<String, Map<String, Map<Int, Int>>> params: Map<String, Map<String, Map<Int, Int>>>
) { ) {
for (mapping in from.fieldMappings) { for (mapping in from.fieldMappings) {
to.createFieldMapping(mapping.signature, mapping.deobfuscatedName) mapping.copy(to)
} }
for (mapping in from.innerClassMappings) { for (mapping in from.innerClassMappings) {
val newMapping = to.createInnerClassMapping(mapping.obfuscatedName, mapping.deobfuscatedName) val newMapping = to.createInnerClassMapping(mapping.obfuscatedName, mapping.deobfuscatedName)
@ -169,7 +170,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
val methodMap = classMap[mapping.signature.toJvmsIdentifier()] ?: continue val methodMap = classMap[mapping.signature.toJvmsIdentifier()] ?: continue
for (paramMapping in paramMappings) { for (paramMapping in paramMappings) {
val i = methodMap[paramMapping.index] ?: paramMapping.index val i = methodMap[paramMapping.index] ?: continue
newMapping.createParameterMapping(i, paramMapping.deobfuscatedName) newMapping.createParameterMapping(i, paramMapping.deobfuscatedName)
} }
} }
@ -210,11 +211,14 @@ abstract class GenerateSpigotMappings : DefaultTask() {
} }
} }
/*
typealias Synths = Map<String, Map<String, Map<String, String>>> typealias Synths = Map<String, Map<String, Map<String, String>>>
fun newSynths() = hashMapOf<String, MutableMap<String, MutableMap<String, String>>>()
*/
class SpigotMappingsMergerHandler( class SpigotMappingsMergerHandler(
private val newPackage: String, private val newPackage: String,
private val synths: Synths private val methodOverrides: SimpleDirectedGraph<MethodRef, *>
) : MappingSetMergerHandler { ) : MappingSetMergerHandler {
override fun mergeTopLevelClassMappings( override fun mergeTopLevelClassMappings(
@ -298,7 +302,7 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>, target: ClassMapping<*, *>,
context: MergeContext context: MergeContext
): MergeResult<InnerClassMapping?> { ): MergeResult<InnerClassMapping?> {
// We want to get all of the inner classes from SRG, but not the SRG names // We want to get all of the members from mojmap, but not the mojmap names
return MergeResult(target.createInnerClassMapping(right.obfuscatedName, right.obfuscatedName), right) return MergeResult(target.createInnerClassMapping(right.obfuscatedName, right.obfuscatedName), right)
} }
@ -320,6 +324,8 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>, target: ClassMapping<*, *>,
context: MergeContext context: MergeContext
): FieldMapping { ): FieldMapping {
// Spigot mappings don't include signature, so see if we can pull the signature out of the mojmap
// Regardless, still keep Spigot's name
val right = strictRightDuplicate ?: looseRightDuplicate ?: strictRightContinuation ?: looseRightContinuation ?: left val right = strictRightDuplicate ?: looseRightDuplicate ?: strictRightContinuation ?: looseRightContinuation ?: left
return target.createFieldMapping(right.signature, left.deobfuscatedName) return target.createFieldMapping(right.signature, left.deobfuscatedName)
} }
@ -342,33 +348,81 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>, target: ClassMapping<*, *>,
context: MergeContext context: MergeContext
): MergeResult<MethodMapping?> { ): 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 // Check if Spigot calls this mapping something else
val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor) val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor)
val newName = synthMethods?.get(left.obfuscatedName) val newName = synthMethods?.get(left.obfuscatedName)
return if (newName != null) { return if (newName != null) {
// Spigot does call this mapping something else, we need to find it first
val newLeftMapping = left.parentClass.getMethodMapping(MethodSignature(newName, left.descriptor)).orNull val newLeftMapping = left.parentClass.getMethodMapping(MethodSignature(newName, left.descriptor)).orNull
val newMapping = if (newLeftMapping != null) { val newMapping = if (newLeftMapping != null) {
// We found it, use it for the left side instead
target.getOrCreateMethodMapping(newLeftMapping.signature).also { target.getOrCreateMethodMapping(newLeftMapping.signature).also {
it.deobfuscatedName = left.deobfuscatedName it.deobfuscatedName = left.deobfuscatedName
} }
} else { } else {
// 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 { target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = newName 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) MergeResult(newMapping)
} else { } else {
// normal mapping
val newMapping = target.getOrCreateMethodMapping(left.signature).also { val newMapping = target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName it.deobfuscatedName = left.deobfuscatedName
} }
return MergeResult(newMapping) return MergeResult(newMapping)
} }
*/
} }
override fun addLeftMethodMapping( override fun addLeftMethodMapping(
left: MethodMapping, left: MethodMapping,
target: ClassMapping<*, *>, target: ClassMapping<*, *>,
context: MergeContext context: MergeContext
): MergeResult<MethodMapping?> { ): 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 // Check if Spigot maps this from a synthetic method name
var obfName: String? = null var obfName: String? = null
val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor) val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor)
@ -383,12 +437,14 @@ class SpigotMappingsMergerHandler(
} }
if (obfName == null) { if (obfName == null) {
// This mapping doesn't actually exist, drop it
return emptyMergeResult() return emptyMergeResult()
} }
val newMapping = target.getOrCreateMethodMapping(obfName, left.descriptor) val newMapping = target.getOrCreateMethodMapping(obfName, left.descriptor)
newMapping.deobfuscatedName = left.deobfuscatedName newMapping.deobfuscatedName = left.deobfuscatedName
return MergeResult(newMapping) return MergeResult(newMapping)
*/
} }
override fun addLeftFieldMapping(left: FieldMapping, target: ClassMapping<*, *>, context: MergeContext): FieldMapping? { override fun addLeftFieldMapping(left: FieldMapping, target: ClassMapping<*, *>, context: MergeContext): FieldMapping? {
@ -410,6 +466,20 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>, target: ClassMapping<*, *>,
context: MergeContext context: MergeContext
): MergeResult<MethodMapping?> { ): 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 // Check if spigot changes this method automatically
val synthMethods = synths[right.parentClass.fullObfuscatedName]?.get(right.obfuscatedDescriptor) val synthMethods = synths[right.parentClass.fullObfuscatedName]?.get(right.obfuscatedDescriptor)
val newName = synthMethods?.get(right.obfuscatedName) ?: return emptyMergeResult() val newName = synthMethods?.get(right.obfuscatedName) ?: return emptyMergeResult()
@ -423,6 +493,7 @@ class SpigotMappingsMergerHandler(
newMapping.deobfuscatedName = newName newMapping.deobfuscatedName = newName
} }
return MergeResult(newMapping) return MergeResult(newMapping)
*/
} }
private fun prependPackage(name: String): String { private fun prependPackage(name: String): String {

View file

@ -23,11 +23,16 @@
package io.papermc.paperweight.tasks package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.AsmUtil import io.papermc.paperweight.util.AsmUtil
import io.papermc.paperweight.util.ClassNodeCache
import io.papermc.paperweight.util.MavenArtifact import io.papermc.paperweight.util.MavenArtifact
import io.papermc.paperweight.util.MethodRef
import io.papermc.paperweight.util.SyntheticUtil import io.papermc.paperweight.util.SyntheticUtil
import io.papermc.paperweight.util.defaultOutput import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.isLibraryJar 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 java.util.zip.ZipFile
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty 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.InputFile
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader import org.jgrapht.graph.DefaultEdge
import org.objectweb.asm.ClassVisitor import org.jgrapht.graph.SimpleDirectedGraph
import org.objectweb.asm.FieldVisitor import org.jgrapht.nio.Attribute
import org.objectweb.asm.MethodVisitor 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.Opcodes
import org.objectweb.asm.Type import org.objectweb.asm.Type
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.ClassNode
abstract class InspectVanillaJar : BaseTask() { abstract class InspectVanillaJar : BaseTask() {
@ -59,12 +66,15 @@ abstract class InspectVanillaJar : BaseTask() {
@get:OutputFile @get:OutputFile
abstract val syntheticMethods: RegularFileProperty abstract val syntheticMethods: RegularFileProperty
@get:OutputFile @get:OutputFile
abstract val methodOverrides: RegularFileProperty
@get:OutputFile
abstract val serverLibraries: RegularFileProperty abstract val serverLibraries: RegularFileProperty
override fun init() { override fun init() {
loggerFile.convention(defaultOutput("$name-loggerFields", "txt")) loggerFile.convention(defaultOutput("$name-loggerFields", "txt"))
paramIndexes.convention(defaultOutput("$name-paramIndexes", "txt")) paramIndexes.convention(defaultOutput("$name-paramIndexes", "txt"))
syntheticMethods.convention(defaultOutput("$name-syntheticMethods", "txt")) syntheticMethods.convention(defaultOutput("$name-syntheticMethods", "txt"))
methodOverrides.convention(defaultOutput("$name-methodOverrides", "json"))
} }
@TaskAction @TaskAction
@ -72,28 +82,32 @@ abstract class InspectVanillaJar : BaseTask() {
val loggers = mutableListOf<LoggerFields.Data>() val loggers = mutableListOf<LoggerFields.Data>()
val params = mutableListOf<ParamIndexes.Data>() val params = mutableListOf<ParamIndexes.Data>()
val synthMethods = mutableListOf<SyntheticMethods.Data>() val synthMethods = mutableListOf<SyntheticMethods.Data>()
val overrides = SimpleDirectedGraph<MethodRef, DefaultEdge>(DefaultEdge::class.java)
var visitor: ClassVisitor JarFile(inputJar.file).use { jarFile ->
visitor = LoggerFields.Visitor(null, loggers) val classNodeCache = ClassNodeCache(jarFile)
visitor = ParamIndexes.Visitor(visitor, params)
visitor = SyntheticMethods.Visitor(visitor, synthMethods)
archives.zipTree(inputJar.file).matching { for (entry in jarFile.entries()) {
include("/*.class") if (!entry.name.endsWith(".class")) {
include("/net/minecraft/**/*.class") continue
}.forEach { file -> }
if (file.isDirectory) { if (entry.name.count { it == '/' } > 0 && !entry.name.startsWith("net/minecraft/")) {
return@forEach 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() val serverLibs = checkLibraries()
loggerFile.file.bufferedWriter(Charsets.UTF_8).use { writer -> loggers.sort()
loggers.sort() loggerFile.file.bufferedWriter().use { writer ->
for (loggerField in loggers) { for (loggerField in loggers) {
writer.append(loggerField.className) writer.append(loggerField.className)
writer.append(' ') 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) { for (methodData in params) {
writer.append(methodData.className) writer.append(methodData.className)
writer.append(' ') 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) { for ((className, desc, synthName, baseName) in synthMethods) {
writer.append(className) writer.append(className)
writer.append(' ') writer.append(' ')
@ -134,8 +148,11 @@ abstract class InspectVanillaJar : BaseTask() {
} }
} }
serverLibraries.file.bufferedWriter(Charsets.UTF_8).use { writer -> writeOverrides(overrides, methodOverrides)
serverLibs.map { it.toString() }.sorted().forEach { artifact ->
val libs = serverLibs.map { it.toString() }.sorted()
serverLibraries.file.bufferedWriter().use { writer ->
libs.forEach { artifact ->
writer.appendln(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 * 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 * to make a note of all of those fields
*/ */
object LoggerFields { object LoggerFields {
class Visitor( object Visitor : AsmUtil {
classVisitor: ClassVisitor?,
private val fields: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
override fun visitField( fun visit(node: ClassNode, fields: MutableList<Data>) {
access: Int, for (field in node.fields) {
name: String, if (Opcodes.ACC_STATIC !in field.access || Opcodes.ACC_FINAL !in field.access) {
descriptor: String, continue
signature: String?, }
value: Any? if (field.desc == "Lorg/apache/logging/log4j/Logger;") {
): FieldVisitor? { fields += Data(node.name, field.name)
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
} }
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. * actual bytecode to translate the LVT-based indexes back to 0-based param indexes.
*/ */
object ParamIndexes { object ParamIndexes {
class Visitor( object Visitor : AsmUtil {
classVisitor: ClassVisitor?,
private val methods: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
override fun visitMethod( fun visit(node: ClassNode, methods: MutableList<Data>) {
access: Int, for (method in node.methods) {
name: String, val isStatic = Opcodes.ACC_STATIC in method.access
descriptor: String, var currentIndex = if (isStatic) 0 else 1
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val ret = super.visitMethod(access, name, descriptor, signature, exceptions)
val className = currentClass ?: return ret
val isStatic = access and Opcodes.ACC_STATIC != 0 val types = Type.getArgumentTypes(method.desc)
var currentIndex = if (isStatic) 0 else 1 if (types.isEmpty()) {
continue
}
val types = Type.getArgumentTypes(descriptor) val params = ArrayList<ParamTarget>(types.size)
if (types.isEmpty()) { val data = Data(node.name, method.name, method.desc, params)
return ret methods += data
}
val params = ArrayList<ParamTarget>(types.size) for (i in types.indices) {
val data = Data(className, name, descriptor, params) params += ParamTarget(currentIndex, i)
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) {
currentIndex++ 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. * can handle it in our generated mappings.
*/ */
object SyntheticMethods { object SyntheticMethods {
class Visitor( object Visitor : AsmUtil {
classVisitor: ClassVisitor?,
private val methods: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
override fun visitMethod( fun visit(node: ClassNode, methods: MutableList<Data>) {
access: Int, for (method in node.methods) {
name: String, if (Opcodes.ACC_SYNTHETIC !in method.access || Opcodes.ACC_BRIDGE in method.access || method.name.contains('$')) {
descriptor: String, continue
signature: String?, }
exceptions: Array<out String>?
): MethodVisitor? {
val ret = super.visitMethod(access, name, descriptor, signature, exceptions)
val className = currentClass ?: return ret
if (Opcodes.ACC_SYNTHETIC !in access || Opcodes.ACC_BRIDGE in access || name.contains('$')) { val (baseName, baseDesc) = SyntheticUtil.findBaseMethod(method, node.name)
return ret
}
return SynthMethodVisitor(access, name, descriptor, signature, exceptions, className, methods) if (baseName != method.name || baseDesc != method.desc) {
} // Add this method as a synthetic for baseName
} methods += Data(node.name, baseDesc, method.name, baseName)
}
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)
} }
} }
} }
@ -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
}

View file

@ -168,16 +168,21 @@ fun <T> emptyMergeResult(): MergeResult<T?> {
} }
// We have to create our own task delegate because the ones Gradle provides don't work in plugin dev environments // We have to create our own task delegate because the ones Gradle provides don't work in plugin dev environments
inline fun <reified T : Task> TaskContainer.registering(noinline configure: T.() -> Unit): TaskDelegateProvider<T> { inline fun <reified T : Task> TaskContainer.registering(noinline configure: (T.() -> Unit)? = null): TaskDelegateProvider<T> {
return TaskDelegateProvider(this, T::class, configure) return TaskDelegateProvider(this, T::class, configure)
} }
class TaskDelegateProvider<T : Task>( class TaskDelegateProvider<T : Task>(
private val container: TaskContainer, private val container: TaskContainer,
private val type: KClass<T>, private val type: KClass<T>,
private val configure: T.() -> Unit private val configure: (T.() -> Unit)?
) { ) {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): TaskDelegate<T> { operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): TaskDelegate<T> {
val provider = container.register(property.name, type.java, configure) val provider = if (configure != null) {
container.register(property.name, type.java, configure)
} else {
container.register(property.name, type.java) {
}
}
return TaskDelegate(provider) return TaskDelegate(provider)
} }
} }