diff --git a/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/taskcontainers/PatchRemapTasks.kt b/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/taskcontainers/PatchRemapTasks.kt index 6c7c7a73..6745dba5 100644 --- a/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/taskcontainers/PatchRemapTasks.kt +++ b/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/taskcontainers/PatchRemapTasks.kt @@ -83,8 +83,8 @@ open class PatchRemapTasks( val patchCraftBukkit by tasks.registering { // TODO temp to speed stuff up - //sourceJar.set(spigotDecompileJar.flatMap { it.outputJar }) - sourceJar.set(cache.resolve("paperweight/taskCache/spigotDecompileJar.jar")) + sourceJar.set(spigotDecompileJar.flatMap { it.outputJar }) + //sourceJar.set(cache.resolve("paperweight/taskCache/spigotDecompileJar.jar")) cleanDirPath.set("net/minecraft") patchDir.set(extension.patchRemap.patchDir) craftBukkitDir.set(extension.patchRemap.craftBukkitDir) @@ -92,4 +92,35 @@ open class PatchRemapTasks( //dependsOn(cloneForPatchRemap) } + + //val filterSpigotMojMapExcludes by tasks.registering { + // inputZip.set(spigotMojMapRemapJar.flatMap { it.outputJar }) + // excludesFile.set(extension.patchRemap.excludesFile) + //} +// + //val spigotDecompileMojMapJar by tasks.registering { + // inputJar.set(filterSpigotMojMapExcludes.flatMap { it.outputZip }) + // fernFlowerJar.set(extension.patchRemap.fernFlowerJar) + // decompileCommand.set(buildDataInfo.map { it.decompileCommand }) + //} + + val remapCraftBukkitSources by tasks.registering { + vanillaJar.set(allTasks.extractFromBundler.flatMap { it.serverJar }) + //mojangMappedVanillaJar.set(fixJar.flatMap { it.outputJar }) + //vanillaRemappedSpigotJar.set(filterSpigotExcludes.flatMap { it.outputZip }) + //mappings.set(generateSpigotMappings.flatMap { it.outputMappings }) + mappings.set(cache.resolve(SPIGOT_MOJANG_YARN_MAPPINGS)) + //sources.set(patchCraftBukkit.flatMap { it.outputDir }) // todo temp to speed stuff up + sources.set(extension.patchRemap.patchedCraftBukkitDir); + //spigotDeps.from(downloadSpigotDependencies.map { it.outputDir.asFileTree }) + //additionalAts.set(mergePaperAts.flatMap { it.outputFile }) + } + + // todo first try diffing against remapped mcp config source + // if that fails, we have to diff against spigot decompiled mojmap+yarn mapped vanilla jar + val diffCraftBukkitAgainstVanilla by tasks.registering { + craftBukkit.set(remapCraftBukkitSources.flatMap { it.sourcesOutputZip }) + //vanilla.set(allTasks.prepareBase.flatMap { it.output }) + vanilla.set(cache.resolve(BASE_PROJECT)) + } } diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/patchremap/DiffCraftBukkitAgainstVanilla.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/patchremap/DiffCraftBukkitAgainstVanilla.kt new file mode 100644 index 00000000..15cea421 --- /dev/null +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/patchremap/DiffCraftBukkitAgainstVanilla.kt @@ -0,0 +1,56 @@ +package io.papermc.paperweight.tasks.patchremap + +import codechicken.diffpatch.cli.DiffOperation +import io.papermc.paperweight.tasks.BaseTask +import io.papermc.paperweight.util.defaultOutput +import io.papermc.paperweight.util.filesMatchingRecursive +import io.papermc.paperweight.util.path +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import java.nio.file.Files + +abstract class DiffCraftBukkitAgainstVanilla: BaseTask() { + + @get:InputFile + abstract val craftBukkit: RegularFileProperty + + @get:InputDirectory + abstract val vanilla: DirectoryProperty + + @get:OutputDirectory + abstract val patches: RegularFileProperty + + override fun init() { + patches.convention(defaultOutput()) + } + + @TaskAction + open fun run() { + val diffOp = DiffOperation.builder() + .logTo(System.out) + .aPath(vanilla.path.resolve("src/vanilla")) + .bPath(craftBukkit.path) + .outputPath(patches.path.resolve("dum")) + .verbose(false) + .summary(true) + .lineEnding("\n") + //.ignorePattern(ignorePattern.get()) + .build() + + diffOp.operate() + + patches.path.resolve("dum").filesMatchingRecursive("*.patch").forEach { p -> + if (Files.readAllLines(p)[1].contains("+++ /dev/null")) { + Files.delete(p) + } + } + Files.walk(patches.path.resolve("dum")) + .filter { Files.isDirectory(it) } + .filter { it.toFile().listFiles()?.isEmpty() ?: false } + .forEach { Files.delete(it) } + } +} diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/patchremap/RemapSources.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/patchremap/RemapSources.kt new file mode 100644 index 00000000..d0c867a1 --- /dev/null +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/patchremap/RemapSources.kt @@ -0,0 +1,385 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * Contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 only, no later versions. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +package io.papermc.paperweight.tasks.patchremap + +import io.papermc.paperweight.tasks.JavaLauncherTask +import io.papermc.paperweight.util.* +import io.papermc.paperweight.util.constants.* +import java.nio.file.Files +import java.nio.file.Path +import javax.inject.Inject +import kotlin.io.path.* +import kotlin.streams.asSequence +import org.cadixdev.at.AccessTransformSet +import org.cadixdev.at.io.AccessTransformFormats +import org.cadixdev.mercury.Mercury +import org.cadixdev.mercury.RewriteContext +import org.cadixdev.mercury.SourceProcessor +import org.cadixdev.mercury.SourceRewriter +import org.cadixdev.mercury.at.AccessTransformerRewriter +import org.cadixdev.mercury.extra.AccessAnalyzerProcessor +import org.cadixdev.mercury.remapper.MercuryRemapper +import org.eclipse.jdt.core.JavaCore +import org.eclipse.jdt.core.dom.* +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor + +@CacheableTask +abstract class RemapSources : JavaLauncherTask() { + + @get:CompileClasspath + abstract val vanillaJar: RegularFileProperty + + //@get:CompileClasspath + //abstract val mojangMappedVanillaJar: RegularFileProperty + + //@get:CompileClasspath + //abstract val vanillaRemappedSpigotJar: RegularFileProperty + + @get:InputFile + @get:PathSensitive(PathSensitivity.NONE) + abstract val mappings: RegularFileProperty + + //@get:CompileClasspath + //abstract val spigotDeps: ConfigurableFileCollection + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val sources: DirectoryProperty + + //@get:Optional + //@get:InputFile + //@get:PathSensitive(PathSensitivity.NONE) + //abstract val additionalAts: RegularFileProperty + + @get:OutputFile + abstract val generatedAt: RegularFileProperty + + @get:OutputFile + abstract val sourcesOutputZip: RegularFileProperty + + @get:Internal + abstract val jvmargs: ListProperty + + @get:Inject + abstract val workerExecutor: WorkerExecutor + + @get:OutputFile + abstract val spigotRecompiledClasses: RegularFileProperty + + override fun init() { + super.init() + + jvmargs.convention(listOf("-Xmx2G")) + sourcesOutputZip.convention(defaultOutput("$name-sources", "jar")) + generatedAt.convention(defaultOutput("at")) + spigotRecompiledClasses.convention(defaultOutput("spigotRecompiledClasses", "txt")) + } + + @TaskAction + fun run() { + val srcOut = findOutputDir(sourcesOutputZip.path).apply { createDirectories() } + + try { + val queue = workerExecutor.processIsolation { + forkOptions.jvmArgs(jvmargs.get()) + forkOptions.executable(launcher.get().executablePath.path.absolutePathString()) + } + + val srcDir = sources.path.resolve("src/main/java") + + // Remap sources + queue.submit(RemapAction::class) { + //classpath.from(vanillaRemappedSpigotJar.path) + //classpath.from(mojangMappedVanillaJar.path) + classpath.from(vanillaJar.path) + //classpath.from(spigotApiDir.dir("src/main/java").path) + //classpath.from(spigotDeps.files.filter { it.toPath().isLibraryJar }) + //additionalAts.set(this@RemapSources.additionalAts.pathOrNull) + + mappings.set(this@RemapSources.mappings.path) + inputDir.set(srcDir) + + cacheDir.set(this@RemapSources.layout.cache) + + outputDir.set(srcOut) + generatedAtOutput.set(generatedAt.path) + } + queue.await() + + zip(srcOut, sourcesOutputZip) + + writeSpigotRecompiledFiles(srcOut) + } finally { + srcOut.deleteRecursively() + } + } + + private fun writeSpigotRecompiledFiles(srcOut: Path) { + // Write list of java files spigot recompiles + val spigotRecompiled = Files.walk(srcOut).use { stream -> + stream.asSequence().mapNotNull { + if (!it.isRegularFile()) { + return@mapNotNull null + } + if (!it.fileName.pathString.endsWith(".java")) { + return@mapNotNull null + } + val path = srcOut.relativize(it).pathString + if (!path.startsWith("net/minecraft")) { + return@mapNotNull null + } + path.replace(".java", "") + }.sorted().joinToString("\n") + } + spigotRecompiledClasses.path.parent.createDirectories() + spigotRecompiledClasses.path.writeText(spigotRecompiled) + } + + abstract class RemapAction : WorkAction { + override fun execute() { + val mappingSet = MappingFormats.TINY.read( + parameters.mappings.path, + SPIGOT_NAMESPACE, + DEOBF_NAMESPACE + ) + + val additionalAt = parameters.additionalAts.pathOrNull?.let { AccessTransformFormats.FML.read(it) } + + val processAt = AccessTransformSet.create() + val generatedAtOutPath = parameters.generatedAtOutput.pathOrNull + + // Remap any references Spigot maps to mojmap+yarn + Mercury().let { merc -> + merc.sourceCompatibility = JavaCore.VERSION_17 + merc.isGracefulClasspathChecks = true + merc.classPath.addAll(parameters.classpath.map { it.toPath() }) + + if (generatedAtOutPath != null) { + merc.processors += AccessAnalyzerProcessor.create(processAt, mappingSet) + } + + merc.process(parameters.inputDir.path) + + val tempOut = Files.createTempDirectory(parameters.cacheDir.path, "remap") + try { + merc.processors.clear() + merc.processors.addAll( + listOf( + ExplicitThisAdder, + OverrideRemover, // TODO meh + MercuryRemapper.create(mappingSet), + AccessTransformerRewriter.create(processAt) + ) + ) + + if (generatedAtOutPath != null) { + merc.processors.add(AccessTransformerRewriter.create(processAt)) + } + + merc.rewrite(parameters.inputDir.path, tempOut) + + if (additionalAt != null) { + merc.processors.clear() + merc.processors += AccessTransformerRewriter.create(additionalAt) + + merc.rewrite(tempOut, parameters.outputDir.path) + } else { + tempOut.copyRecursivelyTo(parameters.outputDir.path) + } + } finally { + tempOut.deleteRecursively() + } + } + + if (generatedAtOutPath != null) { + AccessTransformFormats.FML.write(generatedAtOutPath, processAt) + } + } + } + + interface RemapParams : WorkParameters { + val classpath: ConfigurableFileCollection + val mappings: RegularFileProperty + val inputDir: RegularFileProperty + val additionalAts: RegularFileProperty + + val cacheDir: RegularFileProperty + val generatedAtOutput: RegularFileProperty + val outputDir: RegularFileProperty + } + + object OverrideRemover : SourceRewriter { + + override fun getFlags(): Int = SourceProcessor.FLAG_RESOLVE_BINDINGS + + override fun rewrite(context: RewriteContext) { + context.compilationUnit.accept(OverrideRemoverVisitor(context)) + } + } + + class OverrideRemoverVisitor(private val context: RewriteContext) : ASTVisitor() { + + override fun visit(node: MarkerAnnotation): Boolean { + if (node.typeName.fullyQualifiedName == "Override") { + context.createASTRewrite().remove(node, null) + } + return super.visit(node) + } + } + + object ExplicitThisAdder : SourceRewriter { + + override fun getFlags(): Int = SourceProcessor.FLAG_RESOLVE_BINDINGS + + override fun rewrite(context: RewriteContext) { + context.compilationUnit.accept(ExplicitThisAdderVisitor(context)) + } + } + + class ExplicitThisAdderVisitor(private val context: RewriteContext) : ASTVisitor() { + + override fun visit(node: SimpleName): Boolean { + val binding = node.resolveBinding() ?: return false + + val name = when (val declaringNode = context.compilationUnit.findDeclaringNode(binding)) { + is VariableDeclarationFragment -> declaringNode.name + is MethodDeclaration -> declaringNode.name + else -> return false + } + if (name === node) { + // this is the actual declaration + return false + } + + visit(node, binding) + return false + } + + private fun visit(node: SimpleName, binding: IBinding) { + if (binding.kind != IBinding.VARIABLE && binding.kind != IBinding.METHOD) { + return + } + + val referringClass = when (binding) { + is IVariableBinding -> { + if (!binding.isField || binding.isEnumConstant) { + return + } + binding.declaringClass + } + is IMethodBinding -> { + if (binding.isConstructor || binding.isSynthetic) { + return + } + binding.declaringClass + } + else -> return + } + val modifiers = when (binding) { + is IVariableBinding -> binding.modifiers + is IMethodBinding -> binding.modifiers + else -> return + } + + when (val p = node.parent) { + is FieldAccess, is SuperFieldAccess, is QualifiedName, is ThisExpression, is MethodReference, is SuperMethodInvocation -> return + is MethodInvocation -> { + if (p.expression != null && p.expression !== node) { + return + } + } + } + + // find declaring method + var parentNode: ASTNode? = node + loop@ while (parentNode != null) { + when (parentNode) { + is MethodDeclaration, is AnonymousClassDeclaration, is LambdaExpression, is Initializer -> break@loop + } + parentNode = parentNode.parent + } + + val rewrite = context.createASTRewrite() + val fieldAccess = rewrite.ast.newFieldAccess() + + val expr: Expression = if (!Modifier.isStatic(modifiers)) { + rewrite.ast.newThisExpression().also { thisExpr -> + if (parentNode is LambdaExpression) { + return@also + } + + if (parentNode is AnonymousClassDeclaration && referringClass.erasure != parentNode.resolveBinding().erasure) { + val name = getNameNode(referringClass) ?: return + thisExpr.qualifier = rewrite.createCopyTarget(name) as Name + return@also + } + + val methodDec = parentNode as? MethodDeclaration ?: return@also + + var methodClass = methodDec.resolveBinding()?.declaringClass ?: return // silently return if binding does not resolve + if (methodClass.isAnonymous) { + val name = getNameNode(referringClass) ?: return + thisExpr.qualifier = rewrite.createCopyTarget(name) as Name + return@also + } + + if (referringClass.erasure != methodClass.erasure && methodClass.isNested && !Modifier.isStatic(methodClass.modifiers)) { + while (true) { + methodClass = methodClass.declaringClass ?: break + } + // Looks like the method is accessing an outer class's fields + if (referringClass.erasure == methodClass.erasure) { + val name = getNameNode(referringClass) ?: return + thisExpr.qualifier = rewrite.createCopyTarget(name) as Name + } + } + } + } else { + if (parentNode is Initializer && Modifier.isStatic(parentNode.modifiers)) { + // Can't provide explicit static receiver here + return + } + val name = getNameNode(referringClass) ?: return + rewrite.createCopyTarget(name) as Name + } + + fieldAccess.expression = expr + fieldAccess.name = rewrite.createMoveTarget(node) as SimpleName + + rewrite.replace(node, fieldAccess, null) + } + + private fun getNameNode(dec: ITypeBinding): Name? { + val typeDec = context.compilationUnit.findDeclaringNode(dec) as? TypeDeclaration ?: return null + return typeDec.name + } + } +}