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)