From b6bba954359a3a507fe69d03a493c3f2c47c4021 Mon Sep 17 00:00:00 2001 From: Graham Date: Sun, 1 Mar 2020 17:29:01 +0000 Subject: [PATCH] Simplify static field scrambling By moving all the static fields in a single class in one go, we can just combine the 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. --- .../transform/StaticScramblingTransformer.kt | 160 +++++++----------- 1 file changed, 57 insertions(+), 103 deletions(-) diff --git a/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt index 7c81875e..1a9d9794 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt +++ b/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 dev.openrs2.asm.ClassVersionUtils -import dev.openrs2.asm.MemberDesc import dev.openrs2.asm.MemberRef import dev.openrs2.asm.classpath.ClassPath 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.deob.remap.TypedRemapper import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.FieldNode import org.objectweb.asm.tree.InsnList 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.MethodNode import kotlin.math.max class StaticScramblingTransformer : Transformer() { - private data class Field(val node: FieldNode, val initializer: InsnList, val version: Int, val maxStack: Int) { - val dependencies = initializer.asSequence() - .filterIsInstance() - .filter { it.opcode == Opcodes.GETSTATIC } - .map(::MemberRef) - .toSet() + private class FieldSet(val owner: ClassNode, val fields: List, val clinit: MethodNode?) { + val dependencies = clinit?.instructions + ?.filterIsInstance() + ?.filter { it.opcode == Opcodes.GETSTATIC && it.owner != owner.name } + ?.map(::MemberRef) + ?.toSet() ?: emptySet() } - private val fields = mutableMapOf() + private val fieldSets = mutableMapOf() private val fieldClasses = mutableMapOf() private val methodClasses = mutableMapOf() private var nextStaticClass: ClassNode? = null @@ -70,94 +68,61 @@ class StaticScramblingTransformer : Transformer() { return Pair(clazz, clinit) } - private fun MethodNode.extractEntryExitBlocks(): List { - /* - * Most (or all?) of the 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.sequential } - - val last = instructions.lastOrNull() - if (last == null || last.opcode != Opcodes.RETURN) { - return entry + private fun spliceFields() { + val done = mutableSetOf
() + for (fieldSet in fieldSets.values) { + spliceFields(done, fieldSet) } - - val exit = instructions.toList() - .dropLast(1) - .takeLastWhile { it.sequential } - - return entry.plus(exit) } - private fun MethodNode.extractInitializers(owner: String): Pair, Set> { - val entryExitBlocks = extractEntryExitBlocks() + private fun spliceFields(done: MutableSet
, fieldSet: FieldSet) { + if (!done.add(fieldSet)) { + return + } - val simpleInitializers = mutableMapOf() - val complexInitializers = instructions.asSequence() - .filter { !entryExitBlocks.contains(it) } - .filterIsInstance() - .filter { it.opcode == Opcodes.GETSTATIC && it.owner == owner && it.name !in TypedRemapper.EXCLUDED_FIELDS } - .map(::MemberDesc) - .toSet() + for (dependency in fieldSet.dependencies) { + val dependencyFieldSet = fieldSets[dependency] ?: continue + spliceFields(done, dependencyFieldSet) + } - val putstatics = entryExitBlocks - .filterIsInstance() - .filter { it.opcode == Opcodes.PUTSTATIC && it.owner == owner && it.name !in TypedRemapper.EXCLUDED_FIELDS } + val (staticClass, staticClinit) = nextClass() + staticClass.fields.addAll(fieldSet.fields) + staticClass.version = ClassVersionUtils.maxVersion(staticClass.version, fieldSet.owner.version) - for (putstatic in putstatics) { - val desc = MemberDesc(putstatic) - if (simpleInitializers.containsKey(desc) || complexInitializers.contains(desc)) { - continue + 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) } - // TODO(gpe): use a filter here (pure with no *LOADs?) - val expr = getExpression(putstatic) ?: continue + // replace any remaining RETURNs with a GOTO to the end of the method + val end = LabelNode() + insns.add(end) - val initializer = InsnList() - for (insn in expr) { - instructions.remove(insn) - initializer.add(insn) + for (insn in insns) { + if (insn.opcode == Opcodes.RETURN) { + insns.set(insn, JumpInsnNode(Opcodes.GOTO, end)) + } } - instructions.remove(putstatic) - initializer.add(putstatic) - simpleInitializers[desc] = initializer - } - - return Pair(simpleInitializers, complexInitializers) - } + // append just before the end of the static RETURN + staticClinit.instructions.insertBefore(staticClinit.instructions.last, insns) - private fun spliceInitializers() { - val done = mutableSetOf() - for ((ref, field) in fields) { - spliceInitializers(done, ref, field) - } - } - - private fun spliceInitializers(done: MutableSet, ref: MemberRef, field: Field) { - if (!done.add(ref)) { - return + staticClinit.tryCatchBlocks.addAll(fieldSet.clinit.tryCatchBlocks) + staticClinit.maxStack = max(staticClinit.maxStack, fieldSet.clinit.maxStack) + staticClinit.maxLocals = max(staticClinit.maxLocals, fieldSet.clinit.maxLocals) } - for (dependency in field.dependencies) { - val dependencyField = fields[dependency] ?: continue - spliceInitializers(done, dependency, dependencyField) + for (field in fieldSet.fields) { + val ref = MemberRef(fieldSet.owner, field) + fieldClasses[ref] = staticClass.name } - - val (clazz, clinit) = nextClass() - clazz.fields.add(field.node) - clazz.version = ClassVersionUtils.maxVersion(clazz.version, field.version) - clinit.instructions.insertBefore(clinit.instructions.last, field.initializer) - clinit.maxStack = max(clinit.maxStack, field.maxStack) - - fieldClasses[ref] = clazz.name } override fun preTransform(classPath: ClassPath) { - fields.clear() + fieldSets.clear() fieldClasses.clear() methodClasses.clear() nextStaticClass = null @@ -171,33 +136,22 @@ class StaticScramblingTransformer : Transformer() { for (clazz in library) { // TODO(gpe): exclude the JSObject class - if (clazz.name in TypedRemapper.EXCLUDED_CLASSES) { + if (clazz.name == "jagex3/jagmisc/jagmisc") { continue } - val clinit = clazz.methods.find { it.name == "" } - val (simpleInitializers, complexInitializers) = clinit?.extractInitializers(clazz.name) - ?: 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) - if (complexInitializers.contains(desc)) { - return@removeIf false - } + val fields = clazz.fields.filter { it.access and Opcodes.ACC_STATIC != 0 } + clazz.fields.removeAll(fields) - val initializer = simpleInitializers[desc] ?: InsnList() - val maxStack = clinit?.maxStack ?: 0 + val clinit = clazz.methods.find { it.name == "" } + if (clinit != null) { + clazz.methods.remove(clinit) + } + val fieldSet = FieldSet(clazz, fields, clinit) + for (field in fields) { val ref = MemberRef(clazz, field) - fields[ref] = Field(field, initializer, clazz.version, maxStack) - - return@removeIf true + fieldSets[ref] = fieldSet } clazz.methods.removeIf { method -> @@ -218,7 +172,7 @@ class StaticScramblingTransformer : Transformer() { } } - spliceInitializers() + spliceFields() for (clazz in staticClasses) { library.add(clazz)