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