diff --git a/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java b/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java index fdb8ea3202..047726b207 100644 --- a/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java +++ b/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java @@ -1,7 +1,10 @@ package dev.openrs2.asm; +import java.util.ArrayList; + import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.IntInsnNode; import org.objectweb.asm.tree.LdcInsnNode; @@ -305,6 +308,31 @@ public final class InsnNodeUtils { throw new IllegalArgumentException(); } + public static boolean deleteSimpleExpression(InsnList list, AbstractInsnNode last) { + var deadInsns = new ArrayList(); + + var height = 0; + var insn = last; + do { + deadInsns.add(insn); + + var metadata = StackMetadata.get(insn); + if (insn != last) { + height -= metadata.getPushes(); + } + height += metadata.getPops(); + + if (height == 0) { + deadInsns.forEach(list::remove); + return true; + } + + insn = insn.getPrevious(); + } while (insn != null && insn.getType() != AbstractInsnNode.LABEL && !hasSideEffects(insn)); + + return false; + } + private InsnNodeUtils() { /* empty */ } diff --git a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.java b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.java index 6875d6fcab..3d9c6fe3b9 100644 --- a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.java +++ b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.java @@ -18,6 +18,7 @@ import dev.openrs2.deob.transform.BitwiseOpTransformer; import dev.openrs2.deob.transform.CanvasTransformer; import dev.openrs2.deob.transform.ClassForNameTransformer; import dev.openrs2.deob.transform.CounterTransformer; +import dev.openrs2.deob.transform.DummyLocalTransformer; import dev.openrs2.deob.transform.DummyTransformer; import dev.openrs2.deob.transform.ExceptionTracingTransformer; import dev.openrs2.deob.transform.FieldOrderTransformer; @@ -40,7 +41,8 @@ public final class Deobfuscator { new CanvasTransformer(), new FieldOrderTransformer(), new BitwiseOpTransformer(), - new DummyTransformer() + new DummyTransformer(), + new DummyLocalTransformer() ); public static void main(String[] args) throws IOException, AnalyzerException { diff --git a/deob/src/main/java/dev/openrs2/deob/transform/DummyLocalTransformer.java b/deob/src/main/java/dev/openrs2/deob/transform/DummyLocalTransformer.java new file mode 100644 index 0000000000..76030d674d --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/transform/DummyLocalTransformer.java @@ -0,0 +1,83 @@ +package dev.openrs2.deob.transform; + +import com.google.common.collect.ImmutableSet; +import dev.openrs2.asm.InsnNodeUtils; +import dev.openrs2.asm.classpath.ClassPath; +import dev.openrs2.asm.transform.Transformer; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class DummyLocalTransformer extends Transformer { + private static final Logger logger = LoggerFactory.getLogger(DummyLocalTransformer.class); + + private static final ImmutableSet LOAD_OPCODES = ImmutableSet.of( + Opcodes.ILOAD, + Opcodes.LLOAD, + Opcodes.FLOAD, + Opcodes.DLOAD, + Opcodes.ALOAD + ); + + private static final ImmutableSet STORE_OPCODES = ImmutableSet.of( + Opcodes.ISTORE, + Opcodes.LSTORE, + Opcodes.FSTORE, + Opcodes.DSTORE, + Opcodes.ASTORE + ); + + private int localsRemoved; + + @Override + protected void preTransform(ClassPath classPath) throws AnalyzerException { + localsRemoved = 0; + } + + @Override + protected boolean transformCode(ClassNode clazz, MethodNode method) throws AnalyzerException { + /* + * XXX(gpe): this is primitive (ideally we'd do a proper data flow + * analysis, but we'd need to do it in reverse and ASM only supports + * forward data flow), however, it seems to be good enough to catch + * everything. + */ + var loads = new boolean[method.maxLocals]; + + for (var it = method.instructions.iterator(); it.hasNext(); ) { + var insn = it.next(); + if (LOAD_OPCODES.contains(insn.getOpcode())) { + var load = (VarInsnNode) insn; + loads[load.var] = true; + } + } + + for (var it = method.instructions.iterator(); it.hasNext(); ) { + var insn = it.next(); + var opcode = insn.getOpcode(); + if (!STORE_OPCODES.contains(opcode)) { + continue; + } + + var store = (VarInsnNode) insn; + if (loads[store.var]) { + continue; + } + + if (InsnNodeUtils.deleteSimpleExpression(method.instructions, insn)) { + localsRemoved++; + } + } + + return false; + } + + @Override + protected void postTransform(ClassPath classPath) throws AnalyzerException { + logger.info("Removed {} dummy local variables", localsRemoved); + } +}