Implement map{Field,Method}Owner and getFieldInitializer

Signed-off-by: Graham <gpe@openrs2.dev>
Graham 5 years ago
parent 4f17d67039
commit cccbbe4d19
  1. 20
      deob/src/main/java/dev/openrs2/deob/remap/StaticClassGenerator.kt
  2. 5
      deob/src/main/java/dev/openrs2/deob/remap/StaticField.kt
  3. 119
      deob/src/main/java/dev/openrs2/deob/remap/StaticFieldUnscrambler.kt
  4. 51
      deob/src/main/java/dev/openrs2/deob/remap/StaticMethodUnscrambler.kt
  5. 47
      deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt

@ -0,0 +1,20 @@
package dev.openrs2.deob.remap
class StaticClassGenerator(private val generator: NameGenerator, private val maxMembers: Int) {
private var lastClass: String? = null
private var members = 0
fun generate(): String {
var clazz = lastClass
if (clazz == null || members >= maxMembers) {
clazz = generator.generate("Static")
lastClass = clazz
members = 1
} else {
members++
}
return clazz
}
}

@ -0,0 +1,5 @@
package dev.openrs2.deob.remap
import org.objectweb.asm.tree.AbstractInsnNode
class StaticField(val owner: String, val initializer: List<AbstractInsnNode>?)

@ -0,0 +1,119 @@
package dev.openrs2.deob.remap
import dev.openrs2.asm.MemberDesc
import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.filter.MemberFilter
import dev.openrs2.asm.getExpression
import dev.openrs2.asm.isSequential
import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.MethodNode
class StaticFieldUnscrambler(
private val classPath: ClassPath,
private val excludedFields: MemberFilter,
private val inheritedFieldSets: DisjointSet<MemberRef>,
staticClassNameGenerator: NameGenerator
) {
private val generator = StaticClassGenerator(staticClassNameGenerator, MAX_FIELDS_PER_CLASS)
fun unscramble(): Map<DisjointSet.Partition<MemberRef>, StaticField> {
val fields = mutableMapOf<DisjointSet.Partition<MemberRef>, StaticField>()
for (library in classPath.libraries) {
if ("client" !in library) {
// TODO(gpe): improve detection of the client library
continue
}
for (clazz in library) {
// TODO(gpe): exclude the JSObject class
val clinit = clazz.methods.find { it.name == "<clinit>" }
val (simpleInitializers, complexInitializers) = clinit?.extractInitializers(clazz.name)
?: Pair(emptyMap(), emptySet())
for (field in clazz.fields) {
if (field.access and Opcodes.ACC_STATIC == 0) {
continue
} else if (excludedFields.matches(clazz.name, field.name, field.desc)) {
continue
}
val desc = MemberDesc(field)
if (complexInitializers.contains(desc)) {
continue
}
val member = MemberRef(clazz, field)
val partition = inheritedFieldSets[member]!!
fields[partition] = StaticField(generator.generate(), simpleInitializers[desc])
}
}
}
return fields
}
private fun MethodNode.extractEntryExitBlocks(): List<AbstractInsnNode> {
/*
* Most (or all?) of the <clinit> methods have "simple" initializers
* that we're capable of moving in the first and last basic blocks of
* the method. The last basic block is always at the end of the code
* and ends in a RETURN. This allows us to avoid worrying about making
* a full basic block control flow graph here.
*/
val entry = instructions.takeWhile { it.isSequential }
val last = instructions.lastOrNull()
if (last == null || last.opcode != Opcodes.RETURN) {
return entry
}
val exit = instructions.toList()
.dropLast(1)
.takeLastWhile { it.isSequential }
return entry.plus(exit)
}
private fun MethodNode.extractInitializers(
owner: String
): Pair<Map<MemberDesc, List<AbstractInsnNode>>, Set<MemberDesc>> {
val entryExitBlocks = extractEntryExitBlocks()
val simpleInitializers = mutableMapOf<MemberDesc, List<AbstractInsnNode>>()
val complexInitializers = instructions.asSequence()
.filter { !entryExitBlocks.contains(it) }
.filterIsInstance<FieldInsnNode>()
.filter { it.opcode == Opcodes.GETSTATIC && it.owner == owner }
.filter { !excludedFields.matches(it.owner, it.name, it.desc) }
.map(::MemberDesc)
.toSet()
val putstatics = entryExitBlocks
.filterIsInstance<FieldInsnNode>()
.filter { it.opcode == Opcodes.PUTSTATIC && it.owner == owner }
.filter { !excludedFields.matches(it.owner, it.name, it.desc) }
for (putstatic in putstatics) {
val desc = MemberDesc(putstatic)
if (simpleInitializers.containsKey(desc) || complexInitializers.contains(desc)) {
continue
}
// TODO(gpe): use a filter here (pure with no *LOADs?)
val expr = getExpression(putstatic) ?: continue
simpleInitializers[desc] = expr.plus(putstatic)
}
return Pair(simpleInitializers, complexInitializers)
}
private companion object {
private const val MAX_FIELDS_PER_CLASS = 500
}
}

@ -0,0 +1,51 @@
package dev.openrs2.deob.remap
import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.filter.MemberFilter
import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes
class StaticMethodUnscrambler(
private val classPath: ClassPath,
private val excludedMethods: MemberFilter,
private val inheritedMethodSets: DisjointSet<MemberRef>,
staticClassNameGenerator: NameGenerator
) {
private val generator = StaticClassGenerator(staticClassNameGenerator, MAX_METHODS_PER_CLASS)
fun unscramble(): Map<DisjointSet.Partition<MemberRef>, String> {
val owners = mutableMapOf<DisjointSet.Partition<MemberRef>, String>()
for (library in classPath.libraries) {
if ("client" !in library) {
// TODO(gpe): improve detection of the client library
continue
}
for (clazz in library) {
// TODO(gpe): exclude the JSObject class
for (method in clazz.methods) {
if (method.access and Opcodes.ACC_STATIC == 0) {
continue
} else if (method.access and Opcodes.ACC_NATIVE != 0) {
continue
} else if (excludedMethods.matches(clazz.name, method.name, method.desc)) {
continue
}
val member = MemberRef(clazz, method)
val partition = inheritedMethodSets[member]!!
owners[partition] = generator.generate()
}
}
}
return owners
}
private companion object {
private const val MAX_METHODS_PER_CLASS = 50
}
}

@ -6,13 +6,16 @@ import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.ExtendedRemapper import dev.openrs2.asm.classpath.ExtendedRemapper
import dev.openrs2.deob.Profile import dev.openrs2.deob.Profile
import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.tree.AbstractInsnNode
class TypedRemapper private constructor( class TypedRemapper private constructor(
private val inheritedFieldSets: DisjointSet<MemberRef>, private val inheritedFieldSets: DisjointSet<MemberRef>,
private val inheritedMethodSets: DisjointSet<MemberRef>, private val inheritedMethodSets: DisjointSet<MemberRef>,
private val classes: Map<String, String>, private val classes: Map<String, String>,
private val fields: Map<DisjointSet.Partition<MemberRef>, String>, private val fields: Map<DisjointSet.Partition<MemberRef>, String>,
private val methods: Map<DisjointSet.Partition<MemberRef>, String> private val methods: Map<DisjointSet.Partition<MemberRef>, String>,
private val staticFields: Map<DisjointSet.Partition<MemberRef>, StaticField>,
private val staticMethods: Map<DisjointSet.Partition<MemberRef>, String>
) : ExtendedRemapper() { ) : ExtendedRemapper() {
override fun map(internalName: String): String { override fun map(internalName: String): String {
return classes.getOrDefault(internalName, internalName) return classes.getOrDefault(internalName, internalName)
@ -24,12 +27,30 @@ class TypedRemapper private constructor(
return fields.getOrDefault(partition, name) return fields.getOrDefault(partition, name)
} }
override fun mapFieldOwner(owner: String, name: String, descriptor: String): String {
val member = MemberRef(owner, name, descriptor)
val partition = inheritedFieldSets[member] ?: return mapType(owner)
return staticFields[partition]?.owner ?: mapType(owner)
}
override fun getFieldInitializer(owner: String, name: String, descriptor: String): List<AbstractInsnNode>? {
val member = MemberRef(owner, name, descriptor)
val partition = inheritedFieldSets[member] ?: return null
return staticFields[partition]?.initializer
}
override fun mapMethodName(owner: String, name: String, descriptor: String): String { override fun mapMethodName(owner: String, name: String, descriptor: String): String {
val member = MemberRef(owner, name, descriptor) val member = MemberRef(owner, name, descriptor)
val partition = inheritedMethodSets[member] ?: return name val partition = inheritedMethodSets[member] ?: return name
return methods.getOrDefault(partition, name) return methods.getOrDefault(partition, name)
} }
override fun mapMethodOwner(owner: String, name: String, descriptor: String): String {
val member = MemberRef(owner, name, descriptor)
val partition = inheritedMethodSets[member] ?: return mapType(owner)
return staticMethods.getOrDefault(partition, mapType(owner))
}
companion object { companion object {
private val logger = InlineLogger() private val logger = InlineLogger()
@ -56,7 +77,29 @@ class TypedRemapper private constructor(
verifyMemberMapping(fields, profile.maxObfuscatedNameLen) verifyMemberMapping(fields, profile.maxObfuscatedNameLen)
verifyMemberMapping(methods, profile.maxObfuscatedNameLen) verifyMemberMapping(methods, profile.maxObfuscatedNameLen)
return TypedRemapper(inheritedFieldSets, inheritedMethodSets, classes, fields, methods) val staticClassNameGenerator = NameGenerator()
val staticFields = StaticFieldUnscrambler(
classPath,
profile.excludedFields,
inheritedFieldSets,
staticClassNameGenerator
).unscramble()
val staticMethods = StaticMethodUnscrambler(
classPath,
profile.excludedMethods,
inheritedMethodSets,
staticClassNameGenerator
).unscramble()
return TypedRemapper(
inheritedFieldSets,
inheritedMethodSets,
classes,
fields,
methods,
staticFields,
staticMethods
)
} }
private fun verifyMapping(mapping: Map<String, String>, maxObfuscatedNameLen: Int) { private fun verifyMapping(mapping: Map<String, String>, maxObfuscatedNameLen: Int) {

Loading…
Cancel
Save