start implementing javadoc and parameter name injection using parchmentmc
All checks were successful
Publish to snapshot maven / build (push) Successful in 21s
All checks were successful
Publish to snapshot maven / build (push) Successful in 21s
This commit is contained in:
parent
187b62ef9e
commit
83aa861eb9
|
@ -21,7 +21,7 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.ecorous.esnesnon:mojmap-patcher:1.0.0-SNAPSHOT")
|
||||
implementation("org.ecorous.esnesnon:nonsense-remapper:1.0.0-SNAPSHOT")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
implementation("org.vineflower:vineflower:1.10.1")
|
||||
testImplementation(kotlin("test"))
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
package org.ecorous.esnesnon.gradle
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider
|
||||
import org.ecorous.esnesnon.gradle.parchment.ParchmentProvider
|
||||
import org.ecorous.esnesnon.gradle.vineflower.ParchmentJavadocProvider
|
||||
import org.ecorous.esnesnon.gradle.vineflower.RenamingPlugin
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler
|
||||
import org.jetbrains.java.decompiler.main.Fernflower
|
||||
import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger
|
||||
import org.jetbrains.java.decompiler.main.decompiler.SingleFileSaver
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
||||
import java.io.PrintStream
|
||||
import java.net.URI
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.deleteExisting
|
||||
import kotlin.io.path.exists
|
||||
|
@ -47,25 +51,44 @@ class NonsenseGradlePlugin : Plugin<Project> {
|
|||
group = "nonsense"
|
||||
doFirst {
|
||||
val fileName = remappedGameJarPath.fileName.toString()
|
||||
val output = remappedGameJarPath.resolveSibling(fileName.substring(0, fileName.length-4)+"-sources.jar")
|
||||
if (output.exists()){
|
||||
val output =
|
||||
remappedGameJarPath.resolveSibling(fileName.substring(0, fileName.length - 4) + "-sources.jar")
|
||||
if (output.exists()) {
|
||||
println("Output $output already exists, deleting!")
|
||||
output.deleteExisting()
|
||||
}
|
||||
val options = mutableMapOf<String, Any>()
|
||||
if (useParchment) {
|
||||
println("Preparing Parchment...")
|
||||
val parchment = ParchmentProvider.getParchment(
|
||||
minecraftVersion,
|
||||
parchmentVersion,
|
||||
project.gradle.gradleUserHomeDir.resolve("caches/nonsense-gradle/").toPath()
|
||||
)
|
||||
RenamingPlugin.parchment = parchment
|
||||
/*options["variable-renaming"] = "parchment"
|
||||
options["rename-parameters"] = "1"*/
|
||||
options[IFabricJavadocProvider.PROPERTY_NAME] = ParchmentJavadocProvider(parchment)
|
||||
}
|
||||
println("Decompiling...")
|
||||
val logger = PrintStreamLogger(System.out)
|
||||
val options = mapOf(
|
||||
IFabricJavadocProvider.PROPERTY_NAME to ParchmentJavadocProvider(),
|
||||
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES to "1",
|
||||
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING to "1",
|
||||
IFernflowerPreferences.REMOVE_SYNTHETIC to "1",
|
||||
IFernflowerPreferences.LOG_LEVEL to "warn",
|
||||
IFernflowerPreferences.THREADS to Runtime.getRuntime().availableProcessors().toString(),
|
||||
IFernflowerPreferences.INDENT_STRING to "\t"
|
||||
val log = project.rootDir.resolve("vineflower.log").toPath() // TODO temporarily log VF output more verbosely to a separate file to have more debug information
|
||||
Files.deleteIfExists(log)
|
||||
val logger = PrintStreamLogger(PrintStream(Files.newOutputStream(log)))
|
||||
options.putAll(
|
||||
mapOf(
|
||||
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES to "1",
|
||||
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING to "1",
|
||||
IFernflowerPreferences.REMOVE_SYNTHETIC to "1",
|
||||
IFernflowerPreferences.LOG_LEVEL to "info", // TODO set to 'warn' before commit/push! (related to the comment above)
|
||||
IFernflowerPreferences.THREADS to Runtime.getRuntime().availableProcessors().toString(),
|
||||
IFernflowerPreferences.INDENT_STRING to "\t"
|
||||
)
|
||||
)
|
||||
|
||||
val decomp = BaseDecompiler(SingleFileSaver(output.toFile()),
|
||||
options, logger)
|
||||
val decomp = Fernflower(
|
||||
SingleFileSaver(output.toFile()),
|
||||
options, logger
|
||||
)
|
||||
decomp.addSource(remappedGameJarPath.toFile())
|
||||
|
||||
decomp.decompileContext()
|
||||
|
@ -89,5 +112,7 @@ class NonsenseGradlePlugin : Plugin<Project> {
|
|||
companion object {
|
||||
lateinit var minecraftVersion: String
|
||||
lateinit var remappedGameJarPath: Path
|
||||
var useParchment = false
|
||||
lateinit var parchmentVersion: String
|
||||
}
|
||||
}
|
|
@ -2,11 +2,12 @@ package org.ecorous.esnesnon.gradle.ext
|
|||
|
||||
import org.ecorous.esnesnon.gradle.NonsenseGradlePlugin
|
||||
import org.ecorous.esnesnon.gradle.VersionChecker
|
||||
import org.ecorous.esnesnon.mojmap_patcher.MojMapPatcher
|
||||
import org.ecorous.esnesnon.gradle.parchment.ParchmentProvider
|
||||
import org.ecorous.esnesnon.nonsense_remapper.NonsenseRemapper
|
||||
import org.gradle.api.Project
|
||||
import kotlin.io.path.notExists
|
||||
|
||||
fun Project.minecraft(version: String) {
|
||||
fun Project.minecraft(version: String): Project { // return self to allow for chaining
|
||||
if (VersionChecker.validateVersion(version)) {
|
||||
NonsenseGradlePlugin.minecraftVersion = version
|
||||
println("Valid version! $version")
|
||||
|
@ -20,7 +21,7 @@ fun Project.minecraft(version: String) {
|
|||
NonsenseGradlePlugin.remappedGameJarPath = remappedJar
|
||||
println("Time to setup Minecraft!")
|
||||
if (remappedJar.notExists()) {
|
||||
MojMapPatcher.run(version, clientJar, remappedJar, true)
|
||||
NonsenseRemapper.run(version, clientJar, remappedJar, true)
|
||||
}
|
||||
VersionChecker.getDependencies(version){
|
||||
dependencies.add("implementation", it)
|
||||
|
@ -31,4 +32,10 @@ fun Project.minecraft(version: String) {
|
|||
error("Invalid minecraft version provided: $version")
|
||||
}
|
||||
println("Minecraft!")
|
||||
return this
|
||||
}
|
||||
|
||||
fun Project.parchment(version: String = ParchmentProvider.findLatestValidVersion()) {
|
||||
NonsenseGradlePlugin.useParchment = true
|
||||
NonsenseGradlePlugin.parchmentVersion = version
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package org.ecorous.esnesnon.gradle.parchment
|
||||
|
||||
import com.google.gson.Gson
|
||||
import org.ecorous.esnesnon.gradle.NonsenseGradlePlugin
|
||||
import java.net.URI
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import kotlin.io.path.createParentDirectories
|
||||
import kotlin.io.path.notExists
|
||||
|
||||
object ParchmentProvider {
|
||||
const val PARCHMENT_URL: String = "https://maven.parchmentmc.org/org/parchmentmc/data"
|
||||
|
||||
private fun getParchmentUrl(gameVersion: String): String {
|
||||
return "$PARCHMENT_URL/parchment-$gameVersion"
|
||||
}
|
||||
|
||||
private fun findParchmentVersion(gameVersion: String): String {
|
||||
val url = getParchmentUrl(gameVersion) + "/maven-metadata.xml"
|
||||
val stream = URI.create(url).toURL().openStream()
|
||||
val document = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().parse(stream)
|
||||
stream?.close()
|
||||
return document.getElementsByTagName("release").item(0).textContent
|
||||
}
|
||||
|
||||
fun getParchment(gameVersion: String, cacheDir: Path): Parchment {
|
||||
return getParchment(gameVersion, findParchmentVersion(gameVersion), cacheDir)
|
||||
}
|
||||
|
||||
fun getParchment(gameVersion: String, parchmentVer: String, cacheDir: Path): Parchment {
|
||||
val cachePath = cacheDir.resolve("parchment/$gameVersion/$parchmentVer/parchment-$gameVersion-$parchmentVer.zip")
|
||||
cachePath.createParentDirectories()
|
||||
|
||||
if (cachePath.notExists()){
|
||||
val url = "${getParchmentUrl(gameVersion)}/$parchmentVer/parchment-$gameVersion-$parchmentVer.zip"
|
||||
Files.copy(URI.create(url).toURL().openStream(), cachePath)
|
||||
}
|
||||
|
||||
// TODO hash verification
|
||||
FileSystems.newFileSystem(cachePath).use {fs ->
|
||||
val mappings = Files.readString(fs.getPath("parchment.json"))
|
||||
return Gson().fromJson(mappings, Parchment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
fun findLatestValidVersion(): String {
|
||||
return findForMinecraftVersion(NonsenseGradlePlugin.minecraftVersion)
|
||||
}
|
||||
|
||||
fun findForMinecraftVersion(minecraftVersion: String): String {
|
||||
return findParchmentVersion(minecraftVersion)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Parchment(val version: String, val packages: List<Package>, val classes: List<Class>?){
|
||||
fun getClass(name: String): Class? {
|
||||
classes?.forEach {
|
||||
if(it.name == name){
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
class Package(val name: String, val javadoc: List<String>?)
|
||||
class Class(val name: String, val javadoc: List<String>?, val fields: List<Field>?, val methods: List<Method>?){
|
||||
fun getField(name: String, descriptor: String): Field? {
|
||||
fields?.forEach {
|
||||
if(it.name == name && it.descriptor == descriptor){
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getMethod(name: String, descriptor: String): Method? {
|
||||
methods?.forEach {
|
||||
if(it.name == name && it.descriptor == descriptor){
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
class Field(val name: String, val descriptor: String, val javadoc: List<String>?)
|
||||
class Method(val name: String, val descriptor: String, val javadoc: List<String>?, val parameters: List<Parameter>?){
|
||||
fun getParameter(index: Int): Parameter? {
|
||||
parameters?.forEach {
|
||||
if (it.index == index){
|
||||
return it
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
class Parameter(val index: Int, val name: String)
|
||||
|
|
@ -1,20 +1,23 @@
|
|||
package org.ecorous.esnesnon.gradle.vineflower
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider
|
||||
import org.ecorous.esnesnon.gradle.parchment.Parchment
|
||||
import org.jetbrains.java.decompiler.struct.StructClass
|
||||
import org.jetbrains.java.decompiler.struct.StructField
|
||||
import org.jetbrains.java.decompiler.struct.StructMethod
|
||||
|
||||
class ParchmentJavadocProvider : IFabricJavadocProvider {
|
||||
override fun getClassDoc(structClass: StructClass): String {
|
||||
return ""
|
||||
class ParchmentJavadocProvider(val parchment: Parchment) : IFabricJavadocProvider {
|
||||
override fun getClassDoc(structClass: StructClass): String? {
|
||||
return parchment.getClass(structClass.qualifiedName)?.javadoc?.joinToString("\n")
|
||||
}
|
||||
|
||||
override fun getFieldDoc(structClass: StructClass, structField: StructField): String {
|
||||
return ""
|
||||
override fun getFieldDoc(structClass: StructClass, structField: StructField): String? {
|
||||
return parchment.getClass(structClass.qualifiedName)
|
||||
?.getField(structField.name, structField.descriptor)?.javadoc?.joinToString("\n")
|
||||
}
|
||||
|
||||
override fun getMethodDoc(structClass: StructClass, structMethod: StructMethod): String {
|
||||
return ""
|
||||
override fun getMethodDoc(structClass: StructClass, structMethod: StructMethod): String? {
|
||||
return parchment.getClass(structClass.qualifiedName)
|
||||
?.getMethod(structMethod.name, structMethod.descriptor)?.javadoc?.joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
package org.ecorous.esnesnon.gradle.vineflower
|
||||
|
||||
import org.ecorous.esnesnon.gradle.parchment.Parchment
|
||||
import org.jetbrains.java.decompiler.code.CodeConstants
|
||||
import org.jetbrains.java.decompiler.main.DecompilerContext
|
||||
import org.jetbrains.java.decompiler.main.extern.IVariableNameProvider
|
||||
import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory
|
||||
import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor
|
||||
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair
|
||||
import org.jetbrains.java.decompiler.struct.StructMethod
|
||||
import org.jetbrains.java.decompiler.struct.gen.CodeType
|
||||
import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor
|
||||
import org.jetbrains.java.decompiler.struct.gen.VarType
|
||||
import org.jetbrains.java.decompiler.util.Pair
|
||||
import org.jetbrains.java.decompiler.util.TextUtil
|
||||
|
||||
// Adapted from https://github.com/Vineflower/vineflower/blob/ddf84710f8521ce62d1e3e55a39d300432d6b729/plugins/variable-renaming/src/main/java/org/vineflower/variablerenaming/TinyNameProvider.java
|
||||
class ParchmentNameProvider(private val parchment: Parchment, val method: StructMethod) : IVariableNameProvider {
|
||||
private val parameters: MutableMap<Int, String?> = mutableMapOf()
|
||||
|
||||
private val renameParameters = true
|
||||
private val usedNames: MutableSet<String> = mutableSetOf()
|
||||
|
||||
override fun rename(entries: Map<VarVersionPair, Pair<VarType, String>>): Map<VarVersionPair, String?> {
|
||||
var params = 0
|
||||
if ((this.method.accessFlags and CodeConstants.ACC_STATIC) != CodeConstants.ACC_STATIC) {
|
||||
params++
|
||||
}
|
||||
|
||||
val md = MethodDescriptor.parseDescriptor(this.method.descriptor)
|
||||
for (param in md.params) {
|
||||
params += param.stackSize
|
||||
}
|
||||
|
||||
val keys: MutableList<VarVersionPair> = ArrayList(entries.keys)
|
||||
keys.sortWith { o1: VarVersionPair, o2: VarVersionPair -> if ((o1.`var` != o2.`var`)) o1.`var` - o2.`var` else o1.version - o2.version }
|
||||
|
||||
val result: MutableMap<VarVersionPair, String?> = LinkedHashMap()
|
||||
for (ver in keys) {
|
||||
// note: entries[ver]!!.a is currently null for references that are not parameter definitions. Figure this out. It currently causes errors further down the line
|
||||
// and prevents VF from working correctly.
|
||||
|
||||
val type = cleanType(entries[ver]!!.b)
|
||||
|
||||
if (ver.`var` >= params) {
|
||||
result[ver] = getNewName(
|
||||
Pair.of(
|
||||
entries[ver]!!.a, type
|
||||
)
|
||||
)
|
||||
} else if (renameParameters) {
|
||||
result[ver] = parameters.computeIfAbsent(
|
||||
ver.`var`
|
||||
) {
|
||||
parchment.getClass(method.classQualifiedName)?.getMethod(method.name, method.descriptor)
|
||||
?.getParameter(ver.`var`)?.name ?: getNewName(
|
||||
Pair.of(
|
||||
entries[ver]!!.a, type
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getNewName(pair: Pair<VarType, String>): String? {
|
||||
val type = pair.a
|
||||
|
||||
usedNames.add(pair.b)
|
||||
|
||||
var increment = true
|
||||
var name: String
|
||||
|
||||
when (type.type) {
|
||||
CodeType.BYTECHAR, CodeType.SHORTCHAR, CodeType.INT -> name = "i"
|
||||
CodeType.LONG -> name = "l"
|
||||
CodeType.BYTE -> name = "b"
|
||||
CodeType.SHORT -> name = "s"
|
||||
CodeType.CHAR -> name = "c"
|
||||
CodeType.FLOAT -> name = "f"
|
||||
CodeType.DOUBLE -> name = "d"
|
||||
CodeType.BOOLEAN -> {
|
||||
name = "bl"
|
||||
increment = false
|
||||
}
|
||||
|
||||
CodeType.OBJECT, CodeType.GENVAR -> {
|
||||
name = pair.b
|
||||
|
||||
// Lowercase first letter
|
||||
if (Character.isUpperCase(name[0])) {
|
||||
name = name[0].lowercaseChar().toString() + name.substring(1)
|
||||
}
|
||||
|
||||
if (type.arrayDim > 0 && !name.endsWith("s")) {
|
||||
name += "s"
|
||||
}
|
||||
|
||||
increment = false
|
||||
}
|
||||
|
||||
else -> return null
|
||||
}
|
||||
if (increment) {
|
||||
// Must be of length 1
|
||||
var idxStart = name[0].code - 'a'.code
|
||||
while (usedNames.contains(name)) {
|
||||
name = convertToName(idxStart++)
|
||||
}
|
||||
} else {
|
||||
// Increment via numbers
|
||||
val oname = name
|
||||
var idx = 0
|
||||
while (usedNames.contains(name)) {
|
||||
name = oname + (++idx)
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtil.isKeyword(name, method.bytecodeVersion, method)) {
|
||||
name += "_"
|
||||
}
|
||||
|
||||
usedNames.add(name)
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
override fun renameParameter(flags: Int, type: VarType, name: String?, index: Int): String? {
|
||||
var typeName: String
|
||||
DecompilerContext.getImportCollector().lock().use {
|
||||
typeName = ExprProcessor.getCastTypeName(type)
|
||||
}
|
||||
if (!this.renameParameters) {
|
||||
return super.renameParameter(flags, type, name, index)
|
||||
}
|
||||
|
||||
return parameters.computeIfAbsent(
|
||||
index
|
||||
) {
|
||||
parchment.getClass(method.classQualifiedName)?.getMethod(method.name, method.descriptor)
|
||||
?.getParameter(index)?.name ?: getNewName(
|
||||
Pair.of(
|
||||
type,
|
||||
cleanType(typeName)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertToName(idx: Int): String {
|
||||
// Convert to base 26
|
||||
val str = idx.toString(26)
|
||||
|
||||
// Remap start of name from '0' to 'a'
|
||||
val res = StringBuilder()
|
||||
for (i in str.length - 1 downTo 0) {
|
||||
var c = str[i]
|
||||
// If we're numerical, remap to lowercase ascii range
|
||||
if (c <= '9') {
|
||||
c = ('a'.code + (c.code - '0'.code)).toChar()
|
||||
} else {
|
||||
// If we're not, simply shift up 10 ascii characters
|
||||
c += 10
|
||||
}
|
||||
|
||||
// TODO: idx 26 starts at 'ba', when it should start at 'aa'
|
||||
res.insert(0, c)
|
||||
}
|
||||
return res.toString()
|
||||
}
|
||||
|
||||
private fun cleanType(type: String): String {
|
||||
var cleaned = type
|
||||
if (cleaned.indexOf('<') != -1) {
|
||||
cleaned = cleaned.substring(0, cleaned.indexOf('<'))
|
||||
}
|
||||
|
||||
if (cleaned.indexOf('.') != -1) {
|
||||
cleaned = cleaned.substring(cleaned.lastIndexOf('.') + 1)
|
||||
}
|
||||
|
||||
if (cleaned.indexOf('$') != -1) {
|
||||
cleaned = cleaned.substring(cleaned.lastIndexOf('$') + 1)
|
||||
}
|
||||
|
||||
cleaned = cleaned.replace("\\[]".toRegex(), "")
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
override fun addParentContext(renamer: IVariableNameProvider) {
|
||||
val prov = renamer as ParchmentNameProvider
|
||||
usedNames.addAll(prov.usedNames)
|
||||
}
|
||||
|
||||
class ParchmentNameProviderFactory(private val parchment: Parchment) : IVariableNamingFactory {
|
||||
override fun createFactory(structMethod: StructMethod?): IVariableNameProvider {
|
||||
return ParchmentNameProvider(parchment, structMethod!!)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.ecorous.esnesnon.gradle.vineflower
|
||||
|
||||
import org.ecorous.esnesnon.gradle.parchment.Parchment
|
||||
import org.jetbrains.java.decompiler.api.plugin.Plugin
|
||||
import org.jetbrains.java.decompiler.main.extern.IVariableNamingFactory
|
||||
|
||||
class RenamingPlugin : Plugin {
|
||||
override fun id(): String {
|
||||
return "NonsenseGradle"
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return "Renaming of parameters using the parchment mappings set"
|
||||
}
|
||||
|
||||
override fun getRenamingFactory(): IVariableNamingFactory {
|
||||
return ParchmentNameProvider.ParchmentNameProviderFactory(parchment)
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var parchment: Parchment
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.ecorous.esnesnon.gradle.vineflower.RenamingPlugin
|
Loading…
Reference in a new issue