diff --git a/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.java b/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.java deleted file mode 100644 index 7d17c599..00000000 --- a/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.java +++ /dev/null @@ -1,241 +0,0 @@ -package dev.openrs2.deob.remap; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableSet; -import dev.openrs2.asm.MemberDesc; -import dev.openrs2.asm.MemberRef; -import dev.openrs2.asm.classpath.ClassMetadata; -import dev.openrs2.asm.classpath.ClassPath; -import dev.openrs2.common.StringUtilsKt; -import dev.openrs2.common.collect.DisjointSet; -import kotlin.text.StringsKt; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.Remapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class TypedRemapper extends Remapper { - private static final Logger logger = LoggerFactory.getLogger(TypedRemapper.class); - - public static final ImmutableSet EXCLUDED_CLASSES = ImmutableSet.of( - "client", - "jagex3/jagmisc/jagmisc", - "loader", - "unpack", - "unpackclass" - ); - private static final ImmutableSet EXCLUDED_METHODS = ImmutableSet.of( - "", - "", - "main", - "providesignlink", - "quit" - ); - private static final ImmutableSet EXCLUDED_FIELDS = ImmutableSet.of( - "cache" - ); - private static final int MAX_OBFUSCATED_NAME_LEN = 2; - - public static TypedRemapper create(ClassPath classPath) { - var inheritedFieldSets = classPath.createInheritedFieldSets(); - var inheritedMethodSets = classPath.createInheritedMethodSets(); - - var classes = createClassMapping(classPath.getLibraryClasses()); - var fields = createFieldMapping(classPath, inheritedFieldSets, classes); - var methods = createMethodMapping(classPath, inheritedMethodSets); - - verifyMapping(classes); - verifyMemberMapping(fields); - verifyMemberMapping(methods); - - return new TypedRemapper(classes, fields, methods); - } - - private static void verifyMapping(Map mapping) { - for (var entry : mapping.entrySet()) { - verifyMapping(entry.getKey(), entry.getValue()); - } - } - - private static void verifyMemberMapping(Map mapping) { - for (var entry : mapping.entrySet()) { - verifyMapping(entry.getKey().getName(), entry.getValue()); - } - } - - private static void verifyMapping(String name, String mappedName) { - name = name.replaceAll("^(?:loader|unpacker)_", ""); - if (name.length() > MAX_OBFUSCATED_NAME_LEN && !name.equals(mappedName)) { - logger.warn("Remapping probably unobfuscated name {} to {}", name, mappedName); - } - } - - private static String generateName(Map prefixes, String prefix) { - return prefix + prefixes.merge(prefix, 1, Integer::sum); - } - - private static Map createClassMapping(List classes) { - var mapping = new HashMap(); - var prefixes = new HashMap(); - - for (var clazz : classes) { - populateClassMapping(mapping, prefixes, clazz); - } - - return mapping; - } - - private static String populateClassMapping(Map mapping, Map prefixes, ClassMetadata clazz) { - var name = clazz.getName(); - if (mapping.containsKey(name) || EXCLUDED_CLASSES.contains(name) || clazz.getDependency()) { - return mapping.getOrDefault(name, name); - } - - var mappedName = name.substring(0, name.lastIndexOf('/') + 1); - - var superClass = clazz.getSuperClass(); - if (superClass != null && !superClass.getName().equals("java/lang/Object")) { - var superName = populateClassMapping(mapping, prefixes, superClass); - superName = superName.substring(superName.lastIndexOf('/') + 1); - - mappedName += generateName(prefixes, superName + "_Sub"); - } else if (clazz.getInterface()) { - mappedName += generateName(prefixes, "Interface"); - } else { - mappedName += generateName(prefixes, "Class"); - } - - mapping.put(name, mappedName); - return mappedName; - } - - private static Map createFieldMapping(ClassPath classPath, DisjointSet disjointSet, Map classMapping) { - var mapping = new HashMap(); - var prefixes = new HashMap(); - - for (var partition : disjointSet) { - boolean skip = false; - - for (var field : partition) { - var clazz = classPath.get(field.getOwner()); - - if (EXCLUDED_FIELDS.contains(field.getName())) { - skip = true; - break; - } - - if (clazz.getDependency()) { - skip = true; - break; - } - } - - if (skip) { - continue; - } - - var prefix = ""; - - var type = Type.getType(partition.iterator().next().getDesc()); - if (type.getSort() == Type.ARRAY) { - prefix = Strings.repeat("Array", type.getDimensions()); - type = type.getElementType(); - } - - switch (type.getSort()) { - case Type.BOOLEAN: - case Type.BYTE: - case Type.CHAR: - case Type.SHORT: - case Type.INT: - case Type.LONG: - case Type.FLOAT: - case Type.DOUBLE: - prefix = type.getClassName() + prefix; - break; - case Type.OBJECT: - var className = classMapping.getOrDefault(type.getInternalName(), type.getInternalName()); - className = className.substring(className.lastIndexOf('/') + 1); - prefix = className + prefix; - break; - default: - throw new IllegalArgumentException("Unknown field type " + type); - } - - prefix = StringUtilsKt.indefiniteArticle(prefix) + StringsKt.capitalize(prefix); - - var mappedName = generateName(prefixes, prefix); - for (var field : partition) { - mapping.put(field, mappedName); - } - } - - return mapping; - } - - public static boolean isMethodImmutable(ClassPath classPath, DisjointSet.Partition partition) { - for (var method : partition) { - var clazz = classPath.get(method.getOwner()); - - if (EXCLUDED_METHODS.contains(method.getName())) { - return true; - } - - if (clazz.getDependency()) { - return true; - } - - if (clazz.isNative(new MemberDesc(method))) { - return true; - } - } - - return false; - } - - private static Map createMethodMapping(ClassPath classPath, DisjointSet disjointSet) { - var mapping = new HashMap(); - var id = 0; - - for (var partition : disjointSet) { - if (isMethodImmutable(classPath, partition)) { - continue; - } - - var mappedName = "method" + (++id); - for (var method : partition) { - mapping.put(method, mappedName); - } - } - - return mapping; - } - - private final Map classes; - private final Map fields, methods; - - private TypedRemapper(Map classes, Map fields, Map methods) { - this.classes = classes; - this.fields = fields; - this.methods = methods; - } - - @Override - public String map(String internalName) { - return classes.getOrDefault(internalName, internalName); - } - - @Override - public String mapFieldName(String owner, String name, String descriptor) { - return fields.getOrDefault(new MemberRef(owner, name, descriptor), name); - } - - @Override - public String mapMethodName(String owner, String name, String descriptor) { - return methods.getOrDefault(new MemberRef(owner, name, descriptor), name); - } -} diff --git a/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt b/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt new file mode 100644 index 00000000..01d9edce --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt @@ -0,0 +1,229 @@ +package dev.openrs2.deob.remap + +import com.github.michaelbull.logging.InlineLogger +import com.google.common.base.Strings +import dev.openrs2.asm.MemberDesc +import dev.openrs2.asm.MemberRef +import dev.openrs2.asm.classpath.ClassMetadata +import dev.openrs2.asm.classpath.ClassPath +import dev.openrs2.common.collect.DisjointSet +import dev.openrs2.common.indefiniteArticle +import org.objectweb.asm.Type +import org.objectweb.asm.commons.Remapper + +class TypedRemapper private constructor( + private val classes: Map, + private val fields: Map, + private val methods: Map +) : Remapper() { + override fun map(internalName: String): String { + return classes.getOrDefault(internalName, internalName) + } + + override fun mapFieldName(owner: String, name: String, descriptor: String): String { + return fields.getOrDefault(MemberRef(owner, name, descriptor), name) + } + + override fun mapMethodName(owner: String, name: String, descriptor: String): String { + return methods.getOrDefault(MemberRef(owner, name, descriptor), name) + } + + companion object { + private val logger = InlineLogger() + + val EXCLUDED_CLASSES = setOf( + "client", + "jagex3/jagmisc/jagmisc", + "loader", + "unpack", + "unpackclass" + ) + private val EXCLUDED_METHODS = setOf( + "", + "", + "main", + "providesignlink", + "quit" + ) + private val EXCLUDED_FIELDS = setOf( + "cache" + ) + + private const val MAX_OBFUSCATED_NAME_LEN = 2 + + fun create(classPath: ClassPath): TypedRemapper { + val inheritedFieldSets = classPath.createInheritedFieldSets() + val inheritedMethodSets = classPath.createInheritedMethodSets() + + val classes = createClassMapping(classPath.libraryClasses) + val fields = createFieldMapping(classPath, inheritedFieldSets, classes) + val methods = createMethodMapping(classPath, inheritedMethodSets) + + verifyMapping(classes) + verifyMemberMapping(fields) + verifyMemberMapping(methods) + + return TypedRemapper(classes, fields, methods) + } + + private fun verifyMapping(mapping: Map) { + for ((key, value) in mapping) { + verifyMapping(key, value) + } + } + + private fun verifyMemberMapping(mapping: Map) { + for ((key, value) in mapping) { + verifyMapping(key.name, value) + } + } + + private fun verifyMapping(name: String, mappedName: String) { + val originalName = name.replace("^(?:loader|unpacker)_".toRegex(), "") + if (originalName.length > MAX_OBFUSCATED_NAME_LEN && originalName != mappedName) { + logger.warn { "Remapping probably unobfuscated name $originalName to $mappedName" } + } + } + + private fun generateName(prefixes: MutableMap, prefix: String): String { + return prefix + prefixes.merge(prefix, 1, Integer::sum) + } + + private fun createClassMapping(classes: List): Map { + val mapping = mutableMapOf() + val prefixes = mutableMapOf() + for (clazz in classes) { + populateClassMapping(mapping, prefixes, clazz) + } + return mapping + } + + private fun populateClassMapping( + mapping: MutableMap, + prefixes: MutableMap, + clazz: ClassMetadata + ): String { + val name = clazz.name + if (mapping.containsKey(name) || EXCLUDED_CLASSES.contains(name) || clazz.dependency) { + return mapping.getOrDefault(name, name) + } + + var mappedName = name.substring(0, name.lastIndexOf('/') + 1) + + val superClass = clazz.superClass + if (superClass != null && superClass.name != "java/lang/Object") { + var superName = populateClassMapping(mapping, prefixes, superClass) + superName = superName.substring(superName.lastIndexOf('/') + 1) + mappedName += generateName(prefixes, superName + "_Sub") + } else if (clazz.`interface`) { + mappedName += generateName(prefixes, "Interface") + } else { + mappedName += generateName(prefixes, "Class") + } + + mapping[name] = mappedName + return mappedName + } + + private fun createFieldMapping( + classPath: ClassPath, + disjointSet: DisjointSet, + classMapping: Map + ): Map { + val mapping = mutableMapOf() + val prefixes = mutableMapOf() + + for (partition in disjointSet) { + var skip = false + + for ((owner, name) in partition) { + val clazz = classPath[owner] + + if (EXCLUDED_FIELDS.contains(name)) { + skip = true + break + } + + if (clazz.dependency) { + skip = true + break + } + } + + if (skip) { + continue + } + + var prefix = "" + + var type = Type.getType(partition.iterator().next().desc) + if (type.sort == Type.ARRAY) { + prefix = Strings.repeat("Array", type.dimensions) + type = type.elementType + } + + when (type.sort) { + Type.BOOLEAN, Type.BYTE, Type.CHAR, Type.SHORT, Type.INT, Type.LONG, Type.FLOAT, Type.DOUBLE -> { + prefix = type.className + prefix + } + Type.OBJECT -> { + var className = classMapping.getOrDefault(type.internalName, type.internalName) + className = className.substring(className.lastIndexOf('/') + 1) + prefix = className + prefix + } + else -> throw IllegalArgumentException("Unknown field type $type") + } + + prefix = prefix.indefiniteArticle() + prefix.capitalize() + + val mappedName = generateName(prefixes, prefix) + for (field in partition) { + mapping[field] = mappedName + } + } + + return mapping + } + + fun isMethodImmutable(classPath: ClassPath, partition: DisjointSet.Partition): Boolean { + for (method in partition) { + val clazz = classPath[method.owner] + + if (EXCLUDED_METHODS.contains(method.name)) { + return true + } + + if (clazz.dependency) { + return true + } + + if (clazz.isNative(MemberDesc(method))) { + return true + } + } + + return false + } + + private fun createMethodMapping( + classPath: ClassPath, + disjointSet: DisjointSet + ): Map { + val mapping = mutableMapOf() + var id = 0 + + for (partition in disjointSet) { + if (isMethodImmutable(classPath, partition)) { + continue + } + + val mappedName = "method" + ++id + for (method in partition) { + mapping[method] = mappedName + } + } + + return mapping + } + } +}