forked from openrs2/openrs2
parent
0db3d979c9
commit
b6d96ac4e6
@ -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<String> EXCLUDED_CLASSES = ImmutableSet.of( |
||||
"client", |
||||
"jagex3/jagmisc/jagmisc", |
||||
"loader", |
||||
"unpack", |
||||
"unpackclass" |
||||
); |
||||
private static final ImmutableSet<String> EXCLUDED_METHODS = ImmutableSet.of( |
||||
"<clinit>", |
||||
"<init>", |
||||
"main", |
||||
"providesignlink", |
||||
"quit" |
||||
); |
||||
private static final ImmutableSet<String> 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<String, String> mapping) { |
||||
for (var entry : mapping.entrySet()) { |
||||
verifyMapping(entry.getKey(), entry.getValue()); |
||||
} |
||||
} |
||||
|
||||
private static void verifyMemberMapping(Map<MemberRef, String> 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<String, Integer> prefixes, String prefix) { |
||||
return prefix + prefixes.merge(prefix, 1, Integer::sum); |
||||
} |
||||
|
||||
private static Map<String, String> createClassMapping(List<ClassMetadata> classes) { |
||||
var mapping = new HashMap<String, String>(); |
||||
var prefixes = new HashMap<String, Integer>(); |
||||
|
||||
for (var clazz : classes) { |
||||
populateClassMapping(mapping, prefixes, clazz); |
||||
} |
||||
|
||||
return mapping; |
||||
} |
||||
|
||||
private static String populateClassMapping(Map<String, String> mapping, Map<String, Integer> 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<MemberRef, String> createFieldMapping(ClassPath classPath, DisjointSet<MemberRef> disjointSet, Map<String, String> classMapping) { |
||||
var mapping = new HashMap<MemberRef, String>(); |
||||
var prefixes = new HashMap<String, Integer>(); |
||||
|
||||
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<MemberRef> 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<MemberRef, String> createMethodMapping(ClassPath classPath, DisjointSet<MemberRef> disjointSet) { |
||||
var mapping = new HashMap<MemberRef, String>(); |
||||
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<String, String> classes; |
||||
private final Map<MemberRef, String> fields, methods; |
||||
|
||||
private TypedRemapper(Map<String, String> classes, Map<MemberRef, String> fields, Map<MemberRef, String> 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); |
||||
} |
||||
} |
@ -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<String, String>, |
||||
private val fields: Map<MemberRef, String>, |
||||
private val methods: Map<MemberRef, String> |
||||
) : 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( |
||||
"<clinit>", |
||||
"<init>", |
||||
"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<String, String>) { |
||||
for ((key, value) in mapping) { |
||||
verifyMapping(key, value) |
||||
} |
||||
} |
||||
|
||||
private fun verifyMemberMapping(mapping: Map<MemberRef, String>) { |
||||
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<String, Int>, prefix: String): String { |
||||
return prefix + prefixes.merge(prefix, 1, Integer::sum) |
||||
} |
||||
|
||||
private fun createClassMapping(classes: List<ClassMetadata>): Map<String, String> { |
||||
val mapping = mutableMapOf<String, String>() |
||||
val prefixes = mutableMapOf<String, Int>() |
||||
for (clazz in classes) { |
||||
populateClassMapping(mapping, prefixes, clazz) |
||||
} |
||||
return mapping |
||||
} |
||||
|
||||
private fun populateClassMapping( |
||||
mapping: MutableMap<String, String>, |
||||
prefixes: MutableMap<String, Int>, |
||||
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<MemberRef>, |
||||
classMapping: Map<String, String> |
||||
): Map<MemberRef, String> { |
||||
val mapping = mutableMapOf<MemberRef, String>() |
||||
val prefixes = mutableMapOf<String, Int>() |
||||
|
||||
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<MemberRef>): 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<MemberRef> |
||||
): Map<MemberRef, String> { |
||||
val mapping = mutableMapOf<MemberRef, String>() |
||||
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 |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue