Simplify static field scrambling

By moving all the static fields in a single class in one go, we can just
combine the <clinit> methods together. This allows us to remove all the
really complicated logic for extracting initializers, and also allows us
to move the last few array fields with complex initializers.
bzip2
Graham 5 years ago
parent c70d810057
commit b6bba95435
  1. 156
      deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt

@ -2,35 +2,33 @@ package dev.openrs2.deob.transform
import com.github.michaelbull.logging.InlineLogger import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.ClassVersionUtils import dev.openrs2.asm.ClassVersionUtils
import dev.openrs2.asm.MemberDesc
import dev.openrs2.asm.MemberRef import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.getExpression
import dev.openrs2.asm.sequential
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.remap.TypedRemapper import dev.openrs2.deob.remap.TypedRemapper
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.FieldNode import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.InsnList import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.InsnNode import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.MethodNode
import kotlin.math.max import kotlin.math.max
class StaticScramblingTransformer : Transformer() { class StaticScramblingTransformer : Transformer() {
private data class Field(val node: FieldNode, val initializer: InsnList, val version: Int, val maxStack: Int) { private class FieldSet(val owner: ClassNode, val fields: List<FieldNode>, val clinit: MethodNode?) {
val dependencies = initializer.asSequence() val dependencies = clinit?.instructions
.filterIsInstance<FieldInsnNode>() ?.filterIsInstance<FieldInsnNode>()
.filter { it.opcode == Opcodes.GETSTATIC } ?.filter { it.opcode == Opcodes.GETSTATIC && it.owner != owner.name }
.map(::MemberRef) ?.map(::MemberRef)
.toSet() ?.toSet() ?: emptySet()
} }
private val fields = mutableMapOf<MemberRef, Field>() private val fieldSets = mutableMapOf<MemberRef, FieldSet>()
private val fieldClasses = mutableMapOf<MemberRef, String>() private val fieldClasses = mutableMapOf<MemberRef, String>()
private val methodClasses = mutableMapOf<MemberRef, String>() private val methodClasses = mutableMapOf<MemberRef, String>()
private var nextStaticClass: ClassNode? = null private var nextStaticClass: ClassNode? = null
@ -70,94 +68,61 @@ class StaticScramblingTransformer : Transformer() {
return Pair(clazz, clinit) return Pair(clazz, clinit)
} }
private fun MethodNode.extractEntryExitBlocks(): List<AbstractInsnNode> { private fun spliceFields() {
/* val done = mutableSetOf<FieldSet>()
* Most (or all?) of the <clinit> methods have "simple" initializers for (fieldSet in fieldSets.values) {
* that we're capable of moving in the first and last basic blocks of spliceFields(done, fieldSet)
* 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.sequential }
val last = instructions.lastOrNull()
if (last == null || last.opcode != Opcodes.RETURN) {
return entry
} }
val exit = instructions.toList()
.dropLast(1)
.takeLastWhile { it.sequential }
return entry.plus(exit)
} }
private fun MethodNode.extractInitializers(owner: String): Pair<Map<MemberDesc, InsnList>, Set<MemberDesc>> { private fun spliceFields(done: MutableSet<FieldSet>, fieldSet: FieldSet) {
val entryExitBlocks = extractEntryExitBlocks() if (!done.add(fieldSet)) {
return
val simpleInitializers = mutableMapOf<MemberDesc, InsnList>()
val complexInitializers = instructions.asSequence()
.filter { !entryExitBlocks.contains(it) }
.filterIsInstance<FieldInsnNode>()
.filter { it.opcode == Opcodes.GETSTATIC && it.owner == owner && it.name !in TypedRemapper.EXCLUDED_FIELDS }
.map(::MemberDesc)
.toSet()
val putstatics = entryExitBlocks
.filterIsInstance<FieldInsnNode>()
.filter { it.opcode == Opcodes.PUTSTATIC && it.owner == owner && it.name !in TypedRemapper.EXCLUDED_FIELDS }
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?) for (dependency in fieldSet.dependencies) {
val expr = getExpression(putstatic) ?: continue val dependencyFieldSet = fieldSets[dependency] ?: continue
spliceFields(done, dependencyFieldSet)
val initializer = InsnList()
for (insn in expr) {
instructions.remove(insn)
initializer.add(insn)
} }
instructions.remove(putstatic)
initializer.add(putstatic)
simpleInitializers[desc] = initializer val (staticClass, staticClinit) = nextClass()
} staticClass.fields.addAll(fieldSet.fields)
staticClass.version = ClassVersionUtils.maxVersion(staticClass.version, fieldSet.owner.version)
return Pair(simpleInitializers, complexInitializers) if (fieldSet.clinit != null) {
// remove tail RETURN
val insns = fieldSet.clinit.instructions
val last = insns.lastOrNull()
if (last != null && last.opcode == Opcodes.RETURN) {
insns.remove(last)
} }
private fun spliceInitializers() { // replace any remaining RETURNs with a GOTO to the end of the method
val done = mutableSetOf<MemberRef>() val end = LabelNode()
for ((ref, field) in fields) { insns.add(end)
spliceInitializers(done, ref, field)
for (insn in insns) {
if (insn.opcode == Opcodes.RETURN) {
insns.set(insn, JumpInsnNode(Opcodes.GOTO, end))
} }
} }
private fun spliceInitializers(done: MutableSet<MemberRef>, ref: MemberRef, field: Field) { // append just before the end of the static <clinit> RETURN
if (!done.add(ref)) { staticClinit.instructions.insertBefore(staticClinit.instructions.last, insns)
return
}
for (dependency in field.dependencies) { staticClinit.tryCatchBlocks.addAll(fieldSet.clinit.tryCatchBlocks)
val dependencyField = fields[dependency] ?: continue staticClinit.maxStack = max(staticClinit.maxStack, fieldSet.clinit.maxStack)
spliceInitializers(done, dependency, dependencyField) staticClinit.maxLocals = max(staticClinit.maxLocals, fieldSet.clinit.maxLocals)
} }
val (clazz, clinit) = nextClass() for (field in fieldSet.fields) {
clazz.fields.add(field.node) val ref = MemberRef(fieldSet.owner, field)
clazz.version = ClassVersionUtils.maxVersion(clazz.version, field.version) fieldClasses[ref] = staticClass.name
clinit.instructions.insertBefore(clinit.instructions.last, field.initializer) }
clinit.maxStack = max(clinit.maxStack, field.maxStack)
fieldClasses[ref] = clazz.name
} }
override fun preTransform(classPath: ClassPath) { override fun preTransform(classPath: ClassPath) {
fields.clear() fieldSets.clear()
fieldClasses.clear() fieldClasses.clear()
methodClasses.clear() methodClasses.clear()
nextStaticClass = null nextStaticClass = null
@ -171,33 +136,22 @@ class StaticScramblingTransformer : Transformer() {
for (clazz in library) { for (clazz in library) {
// TODO(gpe): exclude the JSObject class // TODO(gpe): exclude the JSObject class
if (clazz.name in TypedRemapper.EXCLUDED_CLASSES) { if (clazz.name == "jagex3/jagmisc/jagmisc") {
continue continue
} }
val clinit = clazz.methods.find { it.name == "<clinit>" } val fields = clazz.fields.filter { it.access and Opcodes.ACC_STATIC != 0 }
val (simpleInitializers, complexInitializers) = clinit?.extractInitializers(clazz.name) clazz.fields.removeAll(fields)
?: Pair(emptyMap(), emptySet())
clazz.fields.removeIf { field ->
if (field.access and Opcodes.ACC_STATIC == 0) {
return@removeIf false
} else if (field.name in TypedRemapper.EXCLUDED_METHODS) {
return@removeIf false
}
val desc = MemberDesc(field) val clinit = clazz.methods.find { it.name == "<clinit>" }
if (complexInitializers.contains(desc)) { if (clinit != null) {
return@removeIf false clazz.methods.remove(clinit)
} }
val initializer = simpleInitializers[desc] ?: InsnList() val fieldSet = FieldSet(clazz, fields, clinit)
val maxStack = clinit?.maxStack ?: 0 for (field in fields) {
val ref = MemberRef(clazz, field) val ref = MemberRef(clazz, field)
fields[ref] = Field(field, initializer, clazz.version, maxStack) fieldSets[ref] = fieldSet
return@removeIf true
} }
clazz.methods.removeIf { method -> clazz.methods.removeIf { method ->
@ -218,7 +172,7 @@ class StaticScramblingTransformer : Transformer() {
} }
} }
spliceInitializers() spliceFields()
for (clazz in staticClasses) { for (clazz in staticClasses) {
library.add(clazz) library.add(clazz)

Loading…
Cancel
Save