diff --git a/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java b/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java index d5f0db55..6ae5b323 100644 --- a/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java +++ b/asm/src/main/java/dev/openrs2/asm/InsnNodeUtils.java @@ -35,6 +35,26 @@ public final class InsnNodeUtils { return insn; } + public static boolean isIntConstant(AbstractInsnNode insn) { + switch (insn.getOpcode()) { + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + return true; + case Opcodes.LDC: + var ldc = (LdcInsnNode) insn; + return ldc.cst instanceof Integer; + } + + return false; + } + public static int getIntConstant(AbstractInsnNode insn) { switch (insn.getOpcode()) { case Opcodes.ICONST_M1: diff --git a/deob/src/main/java/dev/openrs2/deob/analysis/IntInterpreter.java b/deob/src/main/java/dev/openrs2/deob/analysis/IntInterpreter.java new file mode 100644 index 00000000..30fc754c --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/analysis/IntInterpreter.java @@ -0,0 +1,167 @@ +package dev.openrs2.deob.analysis; + +import java.util.List; +import java.util.stream.Collectors; + +import dev.openrs2.asm.InsnNodeUtils; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.Interpreter; + +public final class IntInterpreter extends Interpreter { + private final Interpreter basicInterpreter = new BasicInterpreter(); + + public IntInterpreter() { + super(Opcodes.ASM7); + } + + @Override + public IntValue newValue(Type type) { + var basicValue = basicInterpreter.newValue(type); + if (basicValue == null) { + return null; + } + + return IntValue.newUnknown(basicValue); + } + + @Override + public IntValue newOperation(AbstractInsnNode insn) throws AnalyzerException { + var basicValue = basicInterpreter.newOperation(insn); + if (basicValue == null) { + return null; + } + + if (InsnNodeUtils.isIntConstant(insn)) { + return IntValue.newConstant(basicValue, InsnNodeUtils.getIntConstant(insn)); + } + + return IntValue.newUnknown(basicValue); + } + + @Override + public IntValue copyOperation(AbstractInsnNode insn, IntValue value) throws AnalyzerException { + return value; + } + + @Override + public IntValue unaryOperation(AbstractInsnNode insn, IntValue value) throws AnalyzerException { + var basicValue = basicInterpreter.unaryOperation(insn, value.getBasicValue()); + if (basicValue == null) { + return null; + } + + if (value.isConstant()) { + var v = value.getIntValue(); + + switch (insn.getOpcode()) { + case Opcodes.INEG: + return IntValue.newConstant(basicValue, -v); + case Opcodes.IINC: + var iinc = (IincInsnNode) insn; + return IntValue.newConstant(basicValue, v + iinc.incr); + case Opcodes.I2B: + return IntValue.newConstant(basicValue, (byte) v); + case Opcodes.I2C: + return IntValue.newConstant(basicValue, (char) v); + case Opcodes.I2S: + return IntValue.newConstant(basicValue, (short) v); + } + } + + return IntValue.newUnknown(basicValue); + } + + @Override + public IntValue binaryOperation(AbstractInsnNode insn, IntValue value1, IntValue value2) throws AnalyzerException { + var basicValue = basicInterpreter.binaryOperation(insn, value1.getBasicValue(), value2.getBasicValue()); + if (basicValue == null) { + return null; + } + + if (value1.isConstant() && value2.isConstant()) { + var v1 = value1.getIntValue(); + var v2 = value2.getIntValue(); + + switch (insn.getOpcode()) { + case Opcodes.IADD: + return IntValue.newConstant(basicValue, v1 + v2); + case Opcodes.ISUB: + return IntValue.newConstant(basicValue, v1 - v2); + case Opcodes.IMUL: + return IntValue.newConstant(basicValue, v1 * v2); + case Opcodes.IDIV: + if (v2 == 0) { + return IntValue.newUnknown(basicValue); + } + return IntValue.newConstant(basicValue, v1 / v2); + case Opcodes.IREM: + if (v2 == 0) { + return IntValue.newUnknown(basicValue); + } + return IntValue.newConstant(basicValue, v1 % v2); + case Opcodes.ISHL: + return IntValue.newConstant(basicValue, v1 << v2); + case Opcodes.ISHR: + return IntValue.newConstant(basicValue, v1 >> v2); + case Opcodes.IUSHR: + return IntValue.newConstant(basicValue, v1 >>> v2); + case Opcodes.IAND: + return IntValue.newConstant(basicValue, v1 & v2); + case Opcodes.IOR: + return IntValue.newConstant(basicValue, v1 | v2); + case Opcodes.IXOR: + return IntValue.newConstant(basicValue, v1 ^ v2); + } + } + + return IntValue.newUnknown(basicValue); + } + + @Override + public IntValue ternaryOperation(AbstractInsnNode insn, IntValue value1, IntValue value2, IntValue value3) throws AnalyzerException { + var basicValue = basicInterpreter.ternaryOperation(insn, value1.getBasicValue(), value2.getBasicValue(), value3.getBasicValue()); + if (basicValue == null) { + return null; + } + + return IntValue.newUnknown(basicValue); + } + + @Override + public IntValue naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { + var args = values.stream() + .map(IntValue::getBasicValue) + .collect(Collectors.toUnmodifiableList()); + var basicValue = basicInterpreter.naryOperation(insn, args); + if (basicValue == null) { + return null; + } + + return IntValue.newUnknown(basicValue); + } + + @Override + public void returnOperation(AbstractInsnNode insn, IntValue value, IntValue expected) throws AnalyzerException { + basicInterpreter.returnOperation(insn, value.getBasicValue(), expected.getBasicValue()); + } + + @Override + public IntValue merge(IntValue value1, IntValue value2) { + var basicValue = basicInterpreter.merge(value1.getBasicValue(), value2.getBasicValue()); + if (basicValue == null) { + return null; + } + + if (value1.isConstant() && value2.isConstant() && value1.getIntValue() == value2.getIntValue()) { + return IntValue.newConstant(basicValue, value1.getIntValue()); + } + + return IntValue.newUnknown(basicValue); + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/analysis/IntValue.java b/deob/src/main/java/dev/openrs2/deob/analysis/IntValue.java new file mode 100644 index 00000000..deee8619 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/analysis/IntValue.java @@ -0,0 +1,63 @@ +package dev.openrs2.deob.analysis; + +import java.util.Objects; + +import com.google.common.base.Preconditions; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.Value; + +public final class IntValue implements Value { + public static IntValue newConstant(BasicValue basicValue, int intValue) { + Preconditions.checkArgument(basicValue == BasicValue.INT_VALUE); + return new IntValue(basicValue, intValue); + } + + public static IntValue newUnknown(BasicValue basicValue) { + Preconditions.checkNotNull(basicValue); + return new IntValue(basicValue, null); + } + + private final BasicValue basicValue; + private final Integer intValue; + + private IntValue(BasicValue basicValue, Integer intValue) { + this.basicValue = basicValue; + this.intValue = intValue; + } + + public BasicValue getBasicValue() { + return basicValue; + } + + public boolean isConstant() { + return intValue != null; + } + + public int getIntValue() { + Preconditions.checkState(intValue != null); + return intValue; + } + + @Override + public int getSize() { + return basicValue.getSize(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IntValue intValue1 = (IntValue) o; + return basicValue.equals(intValue1.basicValue) && + Objects.equals(intValue, intValue1.intValue); + } + + @Override + public int hashCode() { + return Objects.hash(basicValue, intValue); + } +}