diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ed9f1f3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.tiny] +indent_style = tab \ No newline at end of file diff --git a/src/main/kotlin/de/skyrising/guardian/gen/json.kt b/src/main/kotlin/de/skyrising/guardian/gen/json.kt index add717d..a40897c 100644 --- a/src/main/kotlin/de/skyrising/guardian/gen/json.kt +++ b/src/main/kotlin/de/skyrising/guardian/gen/json.kt @@ -2,6 +2,7 @@ package de.skyrising.guardian.gen import com.google.gson.* import com.google.gson.reflect.TypeToken +import de.skyrising.guardian.gen.mappings.MappingProvider import java.io.Reader import java.lang.reflect.Type import java.net.URI diff --git a/src/main/kotlin/de/skyrising/guardian/gen/mappings/formats.kt b/src/main/kotlin/de/skyrising/guardian/gen/mappings/formats.kt new file mode 100644 index 0000000..7cef724 --- /dev/null +++ b/src/main/kotlin/de/skyrising/guardian/gen/mappings/formats.kt @@ -0,0 +1,37 @@ +package de.skyrising.guardian.gen.mappings + +import java.io.BufferedReader +import java.util.stream.Stream + +interface MappingsParser { + fun parse(reader: BufferedReader): MappingTree +} + +interface MappingsWriter { + fun write(mappings: MappingTree, writer: Appendable) +} + +interface MappingsFormat : MappingsParser, MappingsWriter + +abstract class LineBasedMappingFormat : MappingsFormat { + fun parse(initialState: T?, reader: BufferedReader): MappingTree { + var state: T? = initialState + reader.lineSequence().forEach { + state = readLine(state, it) + } + return decodeState(state!!) + } + + override fun parse(reader: BufferedReader) = parse(null, reader) + + override fun write(mappings: MappingTree, writer: Appendable) { + getLines(mappings).forEach { writer.appendln(it) } + } + + abstract fun readLine(state: T?, line: String): T + abstract fun decodeState(state: T): MappingTree + abstract fun getLines(tree: MappingTree): Stream +} + +inline fun String.toDotted() = this.replace('/', '.') +inline fun String.toSlashed() = this.replace('.', '/') \ No newline at end of file diff --git a/src/main/kotlin/de/skyrising/guardian/gen/mappings.kt b/src/main/kotlin/de/skyrising/guardian/gen/mappings/mappings.kt similarity index 98% rename from src/main/kotlin/de/skyrising/guardian/gen/mappings.kt rename to src/main/kotlin/de/skyrising/guardian/gen/mappings/mappings.kt index 06b3762..9e14123 100644 --- a/src/main/kotlin/de/skyrising/guardian/gen/mappings.kt +++ b/src/main/kotlin/de/skyrising/guardian/gen/mappings/mappings.kt @@ -1,4 +1,4 @@ -package de.skyrising.guardian.gen +package de.skyrising.guardian.gen.mappings import com.google.gson.JsonArray import cuchaz.enigma.ProgressListener @@ -6,6 +6,7 @@ import cuchaz.enigma.translation.mapping.EntryMapping import cuchaz.enigma.translation.mapping.serde.MappingFormat import cuchaz.enigma.translation.mapping.tree.EntryTree import cuchaz.enigma.translation.mapping.tree.HashEntryTree +import de.skyrising.guardian.gen.* import java.net.URI import java.nio.file.FileSystem import java.nio.file.Files @@ -14,7 +15,6 @@ import java.util.concurrent.CompletableFuture val JARS_MAPPED_DIR: Path = JARS_DIR.resolve("mapped") - interface MappingProvider { val name: String val format: MappingFormat diff --git a/src/main/kotlin/de/skyrising/guardian/gen/mappings/proguard.kt b/src/main/kotlin/de/skyrising/guardian/gen/mappings/proguard.kt new file mode 100644 index 0000000..20ba577 --- /dev/null +++ b/src/main/kotlin/de/skyrising/guardian/gen/mappings/proguard.kt @@ -0,0 +1,144 @@ +package de.skyrising.guardian.gen.mappings + +import jdk.internal.org.objectweb.asm.Type +import java.util.stream.Stream + +object ProguardMappings : LineBasedMappingFormat>() { + override fun readLine(state: Pair?, line: String): Pair { + val mappings = state?.first ?: MappingTree(arrayOf("named", "official")) + var currentClass = state?.second + when(line[0]) { + '#' -> {} + ' ' -> parseMember(currentClass, line) + else -> { + val cls = parseClass(line) + currentClass = cls + mappings.classes.add(cls) + } + } + return Pair(mappings, currentClass) + } + + override fun decodeState(state: Pair) = state.first + + override fun getLines(tree: MappingTree): Stream = tree.classes.stream().flatMap { cls -> + Stream.of( + Stream.of("${cls[0].toDotted()} -> ${cls[1].toDotted()}:"), + cls.fields.stream().map { f -> + " ${getTypeName(f.defaultName.type)} ${f[0]} -> ${f[1]}" + }, + cls.methods.stream().map { m -> + val lineFrom = if (m is ProguardMethodMapping) m.lineFrom else 0 + val lineTo = if (m is ProguardMethodMapping) m.lineTo else 0 + val type = Type.getMethodType(m.defaultName.type) + val sb = StringBuilder(" ") + sb.append(lineFrom).append(':').append(lineTo).append(':') + sb.append(getTypeName(type.returnType.descriptor)).append(' ') + sb.append(m[0]).append('(') + var first = true + for (arg in type.argumentTypes) { + if (first) first = false + else sb.append(',') + sb.append(getTypeName(arg.descriptor)) + } + sb.append(") -> ").append(m[1]) + sb.toString() + } + ).flatMap { it } + } + + private fun parseClass(line: String): ClassMapping { + val arrow = line.indexOf(" -> ") + if (arrow < 0 || line[line.lastIndex] != ':') throw IllegalArgumentException("Expected '->' and ':' for class mapping") + val from = line.substring(0, arrow).toSlashed() + val to = line.substring(arrow + 4, line.lastIndex).toSlashed() + return ClassMapping(arrayOf(from, to)) + } + + private fun parseMember(currentClass: ClassMapping?, line: String) { + if (currentClass == null) throw IllegalStateException("Cannot parse class member without a class") + if (!line.startsWith(" ")) throw IllegalArgumentException("Expected line to start with ' '") + when (line[4]) { + in '0' .. '9' -> parseMethod(currentClass, line) + in 'a' .. 'z', in 'A' .. 'Z' -> parseField(currentClass, line) + else -> throw IllegalArgumentException("Expected method or field mapping, got '$line'") + } + } + + private fun parseField(currentClass: ClassMapping, line: String) { + val space = line.indexOf(' ', 4) + val arrow = line.indexOf(" -> ", space + 1) + if (space < 0 || arrow < 0) throw IllegalArgumentException("Invalid field mapping '$line'") + val fieldType = getTypeDescriptor(line.substring(4, space)) + val from = line.substring(space + 1, arrow) + val to = line.substring(arrow + 4) + currentClass.fields.add(FieldMappingImpl(MemberDescriptor(from, fieldType), arrayOf(from, to))) + } + + private fun parseMethod(currentClass: ClassMapping, line: String) { + val colon1 = line.indexOf(':', 4) + val colon2 = line.indexOf(':', colon1 + 1) + val space = line.indexOf(' ', colon2 + 1) + val openParen = line.indexOf('(', space + 1) + val closeParenArrow = line.indexOf(") -> ", openParen + 1) + if (colon1 < 0 || colon2 < 0 || space < 0 || openParen < 0 || closeParenArrow < 0) { + throw IllegalArgumentException("Invalid method mapping '$line'") + } + val lineFrom = line.substring(4, colon1).toInt() + val lineTo = line.substring(colon1 + 1, colon2).toInt() + val from = line.substring(space + 1, openParen) + val desc = StringBuilder("(") + var i = openParen + 1 + while (i < closeParenArrow) { + var argEnd = line.indexOf(',', i) + if (argEnd < 0) argEnd = closeParenArrow + desc.append(getTypeDescriptor(line.substring(i, argEnd))) + i = argEnd + 1 + } + desc.append(")").append(getTypeDescriptor(line.substring(colon2 + 1, space))) + val to = line.substring(closeParenArrow + 5) + currentClass.methods.add(ProguardMethodMapping(lineFrom, lineTo, MemberDescriptor(from, desc.toString()), arrayOf(from, to))) + } + + private fun getTypeDescriptor(type: String): String = when { + type.endsWith("[]") -> "[" + getTypeDescriptor(type.substring(0, type.length - 2)) + type in PRIMITIVE_DESCRIPTORS -> PRIMITIVE_DESCRIPTORS[type]!! + else -> "L" + type.toSlashed() + ";" + } + + private fun getTypeName(type: String): String = when { + type.startsWith("[") -> getTypeName(type.substring(1)) + "[]" + type in PRIMITIVE_NAMES -> PRIMITIVE_NAMES[type]!! + type.startsWith("L") -> type.substring(1, type.length - 1).toDotted() + else -> throw IllegalArgumentException(type) + } + + private val PRIMITIVE_DESCRIPTORS = mapOf( + "byte" to "B", + "char" to "C", + "double" to "D", + "float" to "F", + "int" to "I", + "long" to "J", + "short" to "S", + "void" to "V", + "boolean" to "Z" + ) + + private val PRIMITIVE_NAMES = mapOf( + "B" to "byte", + "C" to "char", + "D" to "double", + "F" to "float", + "I" to "int", + "J" to "long", + "S" to "short", + "V" to "void", + "Z" to "boolean" + ) +} + +class ProguardMethodMapping(val lineFrom: Int, val lineTo: Int, defaultName: MemberDescriptor, names: Array) + : ArrayMemberMapping(defaultName, names), MethodMapping { + override fun invert(size: Int, index: Int, tree: MappingTree): ProguardMethodMapping = ProguardMethodMapping(lineFrom, lineTo, invertDefaultName(index, tree), invertNames(size, index)) +} \ No newline at end of file diff --git a/src/main/kotlin/de/skyrising/guardian/gen/remapper.kt b/src/main/kotlin/de/skyrising/guardian/gen/mappings/remapper.kt similarity index 98% rename from src/main/kotlin/de/skyrising/guardian/gen/remapper.kt rename to src/main/kotlin/de/skyrising/guardian/gen/mappings/remapper.kt index e14d3d0..bab76da 100644 --- a/src/main/kotlin/de/skyrising/guardian/gen/remapper.kt +++ b/src/main/kotlin/de/skyrising/guardian/gen/mappings/remapper.kt @@ -1,4 +1,4 @@ -package de.skyrising.guardian.gen +package de.skyrising.guardian.gen.mappings import cuchaz.enigma.translation.mapping.EntryMapping import cuchaz.enigma.translation.mapping.tree.EntryTree @@ -7,6 +7,7 @@ import cuchaz.enigma.translation.representation.TypeDescriptor import cuchaz.enigma.translation.representation.entry.ClassEntry import cuchaz.enigma.translation.representation.entry.FieldEntry import cuchaz.enigma.translation.representation.entry.MethodEntry +import de.skyrising.guardian.gen.* import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes diff --git a/src/main/kotlin/de/skyrising/guardian/gen/mappings/tiny.kt b/src/main/kotlin/de/skyrising/guardian/gen/mappings/tiny.kt new file mode 100644 index 0000000..0e0a5f7 --- /dev/null +++ b/src/main/kotlin/de/skyrising/guardian/gen/mappings/tiny.kt @@ -0,0 +1,183 @@ +package de.skyrising.guardian.gen.mappings + +import java.io.BufferedReader +import java.util.stream.Stream + +object GenericTinyReader : MappingsParser { + override fun parse(reader: BufferedReader): MappingTree { + val headerLine = reader.readLine() ?: throw IllegalStateException("Tiny file is empty") + val header = readHeader(headerLine) + return when(header.major) { + 1 -> { + if (header.minor != 0) throw UnsupportedOperationException("Tiny version ${header.major}.${header.minor} not supported") + TinyMappingsV1.parse(TinyStateV1(header), reader) + } + 2 -> { + if (header.minor != 0) throw UnsupportedOperationException("Tiny version ${header.major}.${header.minor} not supported") + TinyMappingsV2.parse(TinyStateV2(header), reader) + } + else -> throw UnsupportedOperationException("Tiny version ${header.major}.${header.minor} not supported") + } + } + + fun readHeader(line: String): TinyHeader { + val parts = line.split("\t") + if (parts.isEmpty()) throw IllegalArgumentException("Invalid tiny header: empty") + return when (parts[0]) { + "v1" -> TinyHeader(1, 0, parts.subList(1, parts.size)) + "tiny" -> TinyHeader(parts[1].toInt(), parts[2].toInt(), parts.subList(3, parts.size)) + else -> throw IllegalArgumentException("Invalid tiny header: '${parts[0]}'") + } + } +} + +object TinyMappingsV1 : LineBasedMappingFormat() { + override fun readLine(state: TinyStateV1?, line: String): TinyStateV1 { + if (state == null) { + val header = GenericTinyReader.readHeader(line) + if (header.major != 1 || header.minor != 0) throw UnsupportedOperationException("Tiny version ${header.major}.${header.minor} not supported") + return TinyStateV1(header) + } + val parts = line.split("\t") + if (parts.isEmpty()) return state + val tree = state.tree + when (parts[0]) { + "CLASS" -> { + tree.classes.add(ClassMapping(parts.subList(1, parts.size).toTypedArray())) + } + "FIELD" -> { + val classMapping = tree.classes[parts[1]] ?: throw IllegalStateException("Class ${parts[1]} for field ${parts[3]} not defined") + classMapping.fields.add(FieldMappingImpl(MemberDescriptor(parts[3], parts[2]), parts.subList(3, parts.size).toTypedArray())) + } + "METHOD" -> { + val classMapping = tree.classes[parts[1]] ?: throw IllegalStateException("Class ${parts[1]} for method ${parts[3]} not defined") + classMapping.methods.add(MethodMappingImpl(MemberDescriptor(parts[3], parts[2]), parts.subList(3, parts.size).toTypedArray())) + } + } + return state + } + + override fun decodeState(state: TinyStateV1) = state.tree + + override fun getLines(tree: MappingTree): Stream { + TODO("Not yet implemented") + } +} + +open class TinyState(val header: TinyHeader, val tree: T) +class TinyStateV1(header: TinyHeader) : TinyState(header, MappingTree(header.namespaces.toTypedArray())) + +object TinyMappingsV2 : LineBasedMappingFormat() { + override fun readLine(state: TinyStateV2?, line: String): TinyStateV2 { + if (state == null) { + val header = GenericTinyReader.readHeader(line) + if (header.major != 2 || header.minor != 0) throw UnsupportedOperationException("Tiny version ${header.major}.${header.minor} not supported") + return TinyStateV2(header) + } + val parts = line.split('\t') + var indent = 0 + while (parts[indent] == "") indent++ + if (indent > state.indent + 1) throw IllegalArgumentException("Invalid indent level") + if (indent == 0) state.currentClass = null + if (indent <= 1) state.currentMember = null + when (parts[indent]) { + "c" -> { + if (indent == 0) { + val names = parts.subList(1, parts.size).map(state::unescape) + val c = ClassMapping(names.toTypedArray()) + state.currentClass = c + if (state.tree.classes.containsKey(c.defaultName)) { + throw IllegalArgumentException("Duplicate class name ${c.defaultName}") + } + state.tree.classes.add(c) + } + } + "f" -> { + if (indent == 1) { + val type = parts[2] + val names = parts.subList(3, parts.size).map(state::unescape) + val f = FieldMappingImpl(MemberDescriptor(names[0], type), names.toTypedArray()) + state.currentMember = f + val c = state.currentClass ?: throw IllegalStateException("Field without a class") + if (c.fields.containsKey(f.defaultName)) { + throw IllegalArgumentException("Duplicate field name ${f.defaultName}") + } + c.fields.add(f) + } + } + "m" -> { + if (indent == 1) { + val type = parts[2] + val names = parts.subList(3, parts.size).map(state::unescape) + val m = MethodMappingImpl(MemberDescriptor(names[0], type), names.toTypedArray()) + state.currentMember = m + val c = state.currentClass ?: throw IllegalStateException("Method without a class") + if (c.methods.containsKey(m.defaultName)) { + throw IllegalArgumentException("Duplicate method name ${m.defaultName}") + } + c.methods.add(m) + } + } + else -> println(parts) + } + return state + } + + private fun countIndent(line: String): Int { + for (i in line.indices) if (line[i] != '\t') return i + return line.length + } + + override fun decodeState(state: TinyStateV2) = state.tree + + override fun getLines(tree: MappingTree): Stream { + TODO("Not yet implemented") + } + + fun unescape(s: String): String { + if (!s.contains('\\')) return s + val sb = StringBuilder(s.length) + var escaped = false + for (c in s) { + if (escaped) { + sb.append(when (c) { + '\\' -> '\\' + 'n' -> '\n' + 'r' -> '\r' + 't' -> '\t' + '0' -> '\u0000' + else -> throw IllegalArgumentException("Invalid escape code\\$c") + }) + escaped = false + } else { + if (c == '\\') { + escaped = true + continue + } + sb.append(c) + } + } + if (escaped) throw IllegalArgumentException("Unterminated escape code") + return sb.toString() + } +} + +class TinyStateV2(header: TinyHeader) : TinyState(header, TinyMappingTreeV2(header.namespaces.toTypedArray())) { + var currentClass: ClassMapping? = null + var currentMember: MemberMapping? = null + var indent = 0 + + val top get() = when { + currentMember != null -> currentMember!! + currentClass != null -> currentClass!! + else -> throw IllegalStateException("No parent") + } + + fun unescape(s: String) = if (tree.properties.containsKey("escaped-names")) TinyMappingsV2.unescape(s) else s +} + +data class TinyHeader(val major: Int, val minor: Int, val namespaces: List) + +class TinyMappingTreeV2(namespaces: Array) : MappingTree(namespaces) { + val properties = linkedMapOf() +} \ No newline at end of file diff --git a/src/main/kotlin/de/skyrising/guardian/gen/mappings/tree.kt b/src/main/kotlin/de/skyrising/guardian/gen/mappings/tree.kt new file mode 100644 index 0000000..e6d9898 --- /dev/null +++ b/src/main/kotlin/de/skyrising/guardian/gen/mappings/tree.kt @@ -0,0 +1,221 @@ +package de.skyrising.guardian.gen.mappings + +import org.objectweb.asm.Type +import java.io.StringWriter + +open class MappingTree(val namespaces: Array) { + val classes = IndexedMemberList() + + fun invert(namespace: String) = invert(this.namespaces.indexOf(namespace)) + fun invert(index: Int): MappingTree { + val size = namespaces.size + val inverted = MappingTree(Array(size) { + when { + it == 0 -> namespaces[index] + it > index -> namespaces[it] + else -> namespaces[it - 1] + } + }) + for (cls in classes) { + inverted.classes.add(cls.invert(size, index, this)) + } + return inverted + } + + fun mapType(name: String, index: Int) = classes[name]?.getName(index) + fun mapType(type: Type, index: Int): Type = when(type.sort) { + Type.ARRAY -> { + val remappedDesc = StringBuilder() + for (i in 0 until type.dimensions) remappedDesc.append('[') + remappedDesc.append(mapType(type.elementType, index).descriptor) + Type.getType(remappedDesc.toString()) + } + Type.OBJECT -> { + val mapped = mapType(type.internalName, index) + if (mapped != null) Type.getObjectType(mapped) else type + } + Type.METHOD -> { + val retType = mapType(type.returnType, index) + val argTypes = type.argumentTypes.map { mapType(it, index) }.toTypedArray() + Type.getMethodType(retType, *argTypes) + } + else -> type + } + + fun toString(format: MappingsFormat): String { + val writer = StringWriter() + format.write(this, writer) + return writer.toString() + } + + override fun toString() = "MappingTree$classes" + override fun equals(other: Any?) = other is MappingTree && other.namespaces.contentEquals(namespaces) && other.classes == classes + override fun hashCode() = namespaces.contentHashCode() * 31 + classes.hashCode() +} + +class ClassMapping(private val names: Array) : Mapping { + val methods = IndexedMemberList() + val fields = IndexedMemberList() + + override val size get() = names.size + override val defaultName get() = names[0] + override fun getRawName(index: Int) = names[index] + + override fun invertDefaultName(index: Int, tree: MappingTree) = names[index] + override fun invert(size: Int, index: Int, tree: MappingTree): ClassMapping { + val inverted = ClassMapping(invertNames(size, index)) + for (m in methods) inverted.methods.add(m.invert(size, index, tree)) + for (f in fields) inverted.fields.add(f.invert(size, index, tree)) + return inverted + } + + override fun equals(other: Any?) = + other is ClassMapping && names.contentEquals(other.names) && fields == other.fields && methods == other.methods + override fun hashCode() = names.contentHashCode() + override fun toString() = "ClassMapping[$defaultName,fields=$fields,methods=$methods]" +} + +data class MemberDescriptor(val name: String, val type: String) { + override fun toString() = "$name:$type" +} + +interface Mapping { + val size: Int + val defaultName: T + fun getName(index: Int): String { + if (size <= 0) throw IllegalStateException("At least one name is required") + return getRawName(minOf(index, size - 1)) + } + + fun getRawName(index: Int): String + fun invert(size: Int, index: Int, tree: MappingTree): Mapping + fun invertDefaultName(index: Int, tree: MappingTree): T + fun invertNames(size: Int, index: Int) = Array(size) { + when { + it == 0 -> getName(index) + it > index -> getName(it) + else -> getName(it - 1) + } + } + + operator fun get(index: Int) = getName(index) +} + + +interface MemberMapping : Mapping { + override fun invertDefaultName(index: Int, tree: MappingTree): MemberDescriptor { + val newName = getName(index) + val newType = tree.mapType(Type.getType(defaultName.type), index).descriptor + return MemberDescriptor(newName, newType) + } +} +interface MethodMapping : MemberMapping { + override fun invert(size: Int, index: Int, tree: MappingTree): MethodMapping +} +interface FieldMapping : MemberMapping { + override fun invert(size: Int, index: Int, tree: MappingTree): FieldMapping +} + +abstract class ArrayMemberMapping(override val defaultName: MemberDescriptor, private val names: Array) : MemberMapping { + override val size get() = names.size + override fun getRawName(index: Int) = names[index] + + override fun equals(other: Any?): Boolean { + if (other !is MemberMapping) return false + if (other.size != size || other.defaultName != defaultName) return false + for (i in 0 until size) { + if (other.getRawName(i) != names[i]) return false + } + return true + } + + override fun hashCode(): Int { + var result = defaultName.hashCode() + result = 31 * result + names.contentHashCode() + return result + } + + override fun toString() = "${javaClass.simpleName}[$defaultName, ${names.joinToString()}]" +} + +open class MethodMappingImpl(defaultName: MemberDescriptor, names: Array) : ArrayMemberMapping(defaultName, names), MethodMapping { + override fun invert(size: Int, index: Int, tree: MappingTree): MethodMapping = MethodMappingImpl(invertDefaultName(index, tree), invertNames(size, index)) + + override fun equals(other: Any?) = other is MethodMapping && super.equals(other) + override fun hashCode() = super.hashCode() +} +open class FieldMappingImpl(defaultName: MemberDescriptor, names: Array) : ArrayMemberMapping(defaultName, names), FieldMapping { + override fun invert(size: Int, index: Int, tree: MappingTree): FieldMapping = FieldMappingImpl(invertDefaultName(index, tree), invertNames(size, index)) + + override fun equals(other: Any?) = other is FieldMapping && super.equals(other) + override fun hashCode() = super.hashCode() +} + + +class IndexedMemberList> : AbstractMutableSet() { + private val members: MutableSet = linkedSetOf() + private val index = mutableMapOf() + override val size get() = members.size + override fun contains(element: M) = members.contains(element) + override fun containsAll(elements: Collection) = members.containsAll(elements) + override fun isEmpty() = members.isEmpty() + override fun iterator(): MutableIterator { + val iter = members.iterator() + return object : MutableIterator { + private var prev: M? = null + override fun hasNext() = iter.hasNext() + override fun next(): M { + prev = iter.next() + return prev!! + } + + override fun remove() { + if (prev == null) throw IllegalStateException() + iter.remove() + index.remove(prev!!.defaultName) + } + } + } + + override fun add(element: M): Boolean { + index[element.defaultName] = element + return members.add(element) + } + + override fun addAll(elements: Collection): Boolean { + for (element in elements) { + this.index[element.defaultName] = element + } + return members.addAll(elements) + } + + override fun clear() { + index.clear() + members.clear() + } + + override fun remove(element: M): Boolean { + index.remove(element.defaultName) + return members.remove(element) + } + + override fun removeAll(elements: Collection): Boolean { + for (element in elements) { + index.remove(element.defaultName) + } + return members.removeAll(elements) + } + + override fun retainAll(elements: Collection): Boolean { + index.clear() + for (element in elements) { + this.index[element.defaultName] = element + } + return members.retainAll(elements) + } + + operator fun get(key: T) = index[key] + fun containsKey(key: T) = index.containsKey(key) +} + +inline operator fun IndexedMemberList.get(name: String, type: String) = get(MemberDescriptor(name, type)) \ No newline at end of file diff --git a/src/main/kotlin/de/skyrising/guardian/gen/mojang.kt b/src/main/kotlin/de/skyrising/guardian/gen/mojang.kt index 7ac21e1..caa3f0f 100644 --- a/src/main/kotlin/de/skyrising/guardian/gen/mojang.kt +++ b/src/main/kotlin/de/skyrising/guardian/gen/mojang.kt @@ -2,6 +2,7 @@ package de.skyrising.guardian.gen import com.google.gson.JsonArray import com.google.gson.JsonObject +import de.skyrising.guardian.gen.mappings.MappingTarget import java.io.PrintWriter import java.net.URI import java.nio.charset.StandardCharsets diff --git a/src/main/kotlin/de/skyrising/guardian/gen/monument.kt b/src/main/kotlin/de/skyrising/guardian/gen/monument.kt index 7b6c960..0ca585f 100644 --- a/src/main/kotlin/de/skyrising/guardian/gen/monument.kt +++ b/src/main/kotlin/de/skyrising/guardian/gen/monument.kt @@ -1,6 +1,10 @@ package de.skyrising.guardian.gen import com.google.common.jimfs.Jimfs +import de.skyrising.guardian.gen.mappings.MappingProvider +import de.skyrising.guardian.gen.mappings.MappingTarget +import de.skyrising.guardian.gen.mappings.getMappings +import de.skyrising.guardian.gen.mappings.mapJar import joptsimple.OptionException import joptsimple.OptionParser import org.tomlj.Toml diff --git a/src/test/kotlin/de/skyrising/guardian/gen/mergerTest.kt b/src/test/kotlin/de/skyrising/guardian/gen/MergerTest.kt similarity index 100% rename from src/test/kotlin/de/skyrising/guardian/gen/mergerTest.kt rename to src/test/kotlin/de/skyrising/guardian/gen/MergerTest.kt diff --git a/src/test/kotlin/de/skyrising/guardian/gen/mappings/MappingsTest.kt b/src/test/kotlin/de/skyrising/guardian/gen/mappings/MappingsTest.kt new file mode 100644 index 0000000..8e3a047 --- /dev/null +++ b/src/test/kotlin/de/skyrising/guardian/gen/mappings/MappingsTest.kt @@ -0,0 +1,143 @@ +package de.skyrising.guardian.gen.mappings + +import java.io.BufferedReader +import java.io.StringReader +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class MappingsTest { + private fun checkCommonTree(tree: MappingTree) { + val cls1 = tree.classes["pkg/Class1"] + assertNotNull(cls1) + assertEquals("pkg/Class1", cls1[0]) + assertEquals("a", cls1[1]) + val intField = cls1.fields["IntField", "I"] + assertNotNull(intField) + assertEquals("IntField", intField[0]) + assertEquals("a", intField[1]) + val floatField = cls1.fields["FloatField", "F"] + assertNotNull(floatField) + assertEquals("FloatField", floatField[0]) + assertEquals("b", floatField[1]) + val cls2Field = cls1.fields["Cls2Field", "Lpkg/Class2;"] + assertNotNull(cls2Field) + assertEquals("Cls2Field", cls2Field[0]) + assertEquals("c", cls2Field[1]) + val initMethod = cls1.methods["", "()V"] + assertNotNull(initMethod) + assertEquals("", initMethod[0]) + assertEquals("", initMethod[1]) + val method1 = cls1.methods["method1", "([Lpkg/Class1;I)Lpkg/Class2;"] + assertNotNull(method1) + assertEquals("method1", method1[0]) + assertEquals("a", method1[1]) + assertEquals(3, cls1.fields.size) + assertEquals(2, cls1.methods.size) + val cls2 = tree.classes["pkg/Class2"] + assertNotNull(cls2) + assertEquals("pkg/Class2", cls2[0]) + assertEquals("b", cls2[1]) + assertEquals(0, cls2.fields.size) + assertEquals(0, cls2.methods.size) + } + + @Test + fun parseProguard() { + val tree = MappingsTest::class.java.getResourceAsStream("/test_proguard.txt")?.reader()?.buffered().use { + assertNotNull(it) + ProguardMappings.parse(BufferedReader(it)) + } + checkCommonTree(tree) + val cls1 = tree.classes["pkg/Class1"]!! + val initMethod = cls1.methods["", "()V"]!! + assertTrue(initMethod is ProguardMethodMapping) + assertEquals(11, initMethod.lineFrom) + assertEquals(14, initMethod.lineTo) + val method1 = cls1.methods["method1", "([Lpkg/Class1;I)Lpkg/Class2;"]!! + assertTrue(method1 is ProguardMethodMapping) + assertEquals(16, method1.lineFrom) + assertEquals(24, method1.lineTo) + } + + @Test + fun testTinyV1() { + MappingsTest::class.java.getResourceAsStream("/test_tiny_v1.tiny")?.reader()?.buffered().use { + assertNotNull(it) + it.mark(8192) + val v1 = TinyMappingsV1.parse(it) + checkCommonTree(v1) + it.reset() + val generic = GenericTinyReader.parse(it) + assertEquals(v1, generic) + } + } + + @Test + fun testTinyV2() { + MappingsTest::class.java.getResourceAsStream("/test_tiny_v2.tiny")?.reader()?.buffered().use { + assertNotNull(it) + it.mark(8192) + val v2 = TinyMappingsV2.parse(it) + checkCommonTree(v2) + it.reset() + val generic = GenericTinyReader.parse(it) + assertEquals(v2, generic) + } + } + + @Test + fun testInvert() { + val reader = MappingsTest::class.java.getResourceAsStream("/test_proguard.txt")?.reader() + assertNotNull(reader) + val tree = ProguardMappings.parse(BufferedReader(reader)).invert(1) + val cls1 = tree.classes["a"] + assertNotNull(cls1) + assertEquals("a", cls1[0]) + assertEquals("pkg/Class1", cls1[1]) + val intField = cls1.fields["a", "I"] + assertNotNull(intField) + assertEquals("a", intField[0]) + assertEquals("IntField", intField[1]) + val floatField = cls1.fields["b", "F"] + assertNotNull(floatField) + assertEquals("b", floatField[0]) + assertEquals("FloatField", floatField[1]) + val cls2Field = cls1.fields["c", "Lb;"] + assertNotNull(cls2Field) + assertEquals("c", cls2Field[0]) + assertEquals("Cls2Field", cls2Field[1]) + val initMethod = cls1.methods["", "()V"] + assertNotNull(initMethod) + assertTrue(initMethod is ProguardMethodMapping) + assertEquals(11, initMethod.lineFrom) + assertEquals(14, initMethod.lineTo) + assertEquals("", initMethod[0]) + assertEquals("", initMethod[1]) + val method1 = cls1.methods["a", "([La;I)Lb;"] + assertNotNull(method1) + assertTrue(method1 is ProguardMethodMapping) + assertEquals(16, method1.lineFrom) + assertEquals(24, method1.lineTo) + assertEquals("a", method1[0]) + assertEquals("method1", method1[1]) + assertEquals(3, cls1.fields.size) + assertEquals(2, cls1.methods.size) + val cls2 = tree.classes["b"] + assertNotNull(cls2) + assertEquals("b", cls2[0]) + assertEquals("pkg/Class2", cls2[1]) + assertEquals(0, cls2.fields.size) + assertEquals(0, cls2.methods.size) + } + + @Test + fun writeProguard() { + val txt = MappingsTest::class.java.getResourceAsStream("/test_proguard.txt")?.readBytes()?.toString(Charsets.UTF_8) + assertNotNull(txt) + val tree = ProguardMappings.parse(BufferedReader(StringReader(txt))) + val result = tree.toString(ProguardMappings) + assertEquals(txt.split('\n').let { it.subList(1, it.size) }, result.split('\n')) + } +} \ No newline at end of file diff --git a/src/test/resources/test_proguard.txt b/src/test/resources/test_proguard.txt new file mode 100644 index 0000000..6bd925b --- /dev/null +++ b/src/test/resources/test_proguard.txt @@ -0,0 +1,8 @@ +# Comment +pkg.Class1 -> a: + int IntField -> a + float FloatField -> b + pkg.Class2 Cls2Field -> c + 11:14:void () -> + 16:24:pkg.Class2 method1(pkg.Class1[],int) -> a +pkg.Class2 -> b: diff --git a/src/test/resources/test_tiny_v1.tiny b/src/test/resources/test_tiny_v1.tiny new file mode 100644 index 0000000..fede411 --- /dev/null +++ b/src/test/resources/test_tiny_v1.tiny @@ -0,0 +1,8 @@ +v1 official named +CLASS pkg/Class1 a +CLASS pkg/Class2 b +FIELD pkg/Class1 I IntField a +FIELD pkg/Class1 F FloatField b +FIELD pkg/Class1 Lpkg/Class2; Cls2Field c +METHOD pkg/Class1 ()V +METHOD pkg/Class1 ([Lpkg/Class1;I)Lpkg/Class2; method1 a diff --git a/src/test/resources/test_tiny_v2.tiny b/src/test/resources/test_tiny_v2.tiny new file mode 100644 index 0000000..1951ebf --- /dev/null +++ b/src/test/resources/test_tiny_v2.tiny @@ -0,0 +1,8 @@ +tiny 2 0 official named +c pkg/Class1 a + f I IntField a + f F FloatField b + f Lpkg/Class2; Cls2Field c + m ()V + m ([Lpkg/Class1;I)Lpkg/Class2; method1 a +c pkg/Class2 b