forked from openrs2/openrs2
parent
4f17d67039
commit
cccbbe4d19
@ -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 |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue