From 92ddde57b04c6240fa835dd091e31af3a42e443a Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 9 Apr 2016 23:03:55 -0400 Subject: [PATCH] RSCanvas stuff, mostly from runeloader. I assume this breaks resizable. I can't tell whether or not this defers all rendering by 1 frame. Fix various injectreplace bugs. I need to make the unused parameter annotate methods with ObfuscatedSignature or I can't export methods that had an unused parameter. This is going to all break if I ever need to export something with an unused parameter that isn't at the end. Should consider only removing unused parameters that I can detect as being garbage. --- pom.xml | 5 + src/main/java/net/runelite/asm/ClassFile.java | 11 ++ src/main/java/net/runelite/asm/Method.java | 13 ++ .../code/instructions/AConstNull.java | 5 + .../code/instructions/InvokeInterface.java | 10 -- .../code/instructions/InvokeSpecial.java | 2 + .../asm/attributes/code/instructions/New.java | 1 + .../net/runelite/deob/injection/Inject.java | 25 ++- .../deob/injection/InjectReplace.java | 157 ++++++++++++++++-- .../java/net/runelite/inject/RSCanvas.java | 27 +++ 10 files changed, 228 insertions(+), 28 deletions(-) create mode 100644 src/main/java/net/runelite/inject/RSCanvas.java diff --git a/pom.xml b/pom.xml index 8a79b3a249..52d0d038c0 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ api 1.0.0-SNAPSHOT + + net.runelite + client + 1.0.0-SNAPSHOT + org.slf4j diff --git a/src/main/java/net/runelite/asm/ClassFile.java b/src/main/java/net/runelite/asm/ClassFile.java index 1f24533ee4..ddf2670672 100644 --- a/src/main/java/net/runelite/asm/ClassFile.java +++ b/src/main/java/net/runelite/asm/ClassFile.java @@ -10,6 +10,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import net.runelite.asm.signature.Signature; public class ClassFile { @@ -246,6 +247,11 @@ public class ClassFile { return methods.findMethod(name); } + + public Method findMethod(String name, Signature signature) + { + return methods.findMethod(new NameAndType(name, signature)); + } public Method findMethodDeep(String name) { @@ -300,4 +306,9 @@ public class ClassFile { this.access_flags &= ~ACC_FINAL; } + + public void clearAbstract() + { + this.access_flags &= ~ACC_ABSTRACT; + } } diff --git a/src/main/java/net/runelite/asm/Method.java b/src/main/java/net/runelite/asm/Method.java index f740fd4dcc..72aab017b3 100644 --- a/src/main/java/net/runelite/asm/Method.java +++ b/src/main/java/net/runelite/asm/Method.java @@ -143,6 +143,14 @@ public class Method return (accessFlags & ACC_FINAL) != 0; } + public void setFinal(boolean f) + { + if (f) + accessFlags |= ACC_FINAL; + else + accessFlags &= ~ACC_FINAL; + } + public boolean isPrivate() { return (accessFlags & ACC_PRIVATE) != 0; @@ -152,6 +160,11 @@ public class Method { accessFlags = (short) ((accessFlags & ~ACCESS_MODIFIERS) | ACC_PRIVATE); } + + public void setPublic() + { + accessFlags = (short) ((accessFlags & ~ACCESS_MODIFIERS) | ACC_PUBLIC); + } public Exceptions getExceptions() { diff --git a/src/main/java/net/runelite/asm/attributes/code/instructions/AConstNull.java b/src/main/java/net/runelite/asm/attributes/code/instructions/AConstNull.java index cc9da792f5..1da3d2f384 100644 --- a/src/main/java/net/runelite/asm/attributes/code/instructions/AConstNull.java +++ b/src/main/java/net/runelite/asm/attributes/code/instructions/AConstNull.java @@ -16,6 +16,11 @@ public class AConstNull extends Instruction super(instructions, type, pc); } + public AConstNull(Instructions instructions) + { + super(instructions, InstructionType.ACONST_NULL, -1); + } + @Override public InstructionContext execute(Frame frame) { diff --git a/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeInterface.java b/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeInterface.java index b0fd4240a9..324a7d1c29 100644 --- a/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeInterface.java +++ b/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeInterface.java @@ -68,16 +68,6 @@ public class InvokeInterface extends Instruction implements InvokeInstruction { return myMethods != null ? myMethods : Arrays.asList(); } - - private void findMethodFromClass(List list, ClassFile clazz) - { - net.runelite.asm.Method m = clazz.findMethodDeep(method.getNameAndType()); - if (m != null && !list.contains(m)) - list.add(m); - - for (ClassFile cf : clazz.getChildren()) - findMethodFromClass(list, cf); - } @Override public InstructionContext execute(Frame frame) diff --git a/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeSpecial.java b/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeSpecial.java index 58de97394a..22853d1a76 100644 --- a/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeSpecial.java +++ b/src/main/java/net/runelite/asm/attributes/code/instructions/InvokeSpecial.java @@ -141,11 +141,13 @@ public class InvokeSpecial extends Instruction implements InvokeInstruction public void setMethod(Method method) { this.method = method; + lookup(); } @Override public void lookup() { + myMethods = null; ClassGroup group = this.getInstructions().getCode().getAttributes().getClassFile().getGroup(); ClassFile otherClass = group.findClass(method.getClassEntry().getName()); diff --git a/src/main/java/net/runelite/asm/attributes/code/instructions/New.java b/src/main/java/net/runelite/asm/attributes/code/instructions/New.java index 7ff6edbe82..466531da1f 100644 --- a/src/main/java/net/runelite/asm/attributes/code/instructions/New.java +++ b/src/main/java/net/runelite/asm/attributes/code/instructions/New.java @@ -62,6 +62,7 @@ public class New extends Instruction public void setNewClass(Class clazz) { this.clazz = clazz; + lookup(); } @Override diff --git a/src/main/java/net/runelite/deob/injection/Inject.java b/src/main/java/net/runelite/deob/injection/Inject.java index fbe053cbdc..cfccdec11f 100644 --- a/src/main/java/net/runelite/deob/injection/Inject.java +++ b/src/main/java/net/runelite/deob/injection/Inject.java @@ -11,6 +11,7 @@ import net.runelite.asm.attributes.Annotations; import net.runelite.asm.attributes.Attributes; import net.runelite.asm.attributes.Code; import net.runelite.asm.attributes.annotation.Annotation; +import net.runelite.asm.attributes.annotation.Element; import net.runelite.asm.attributes.code.Instruction; import net.runelite.asm.attributes.code.InstructionType; import net.runelite.asm.attributes.code.Instructions; @@ -172,7 +173,12 @@ public class Inject assert !m.isStatic(); String exportedName = an.find(EXPORT).getElement().getString(); - obfuscatedName = an.find(OBFUSCATED_NAME).getElement().getString(); + + Annotation obAn = an.find(OBFUSCATED_NAME); + if (obAn != null) + obfuscatedName = obAn.getElement().getString(); + else + obfuscatedName = m.getName(); Method otherm; @@ -181,8 +187,11 @@ public class Inject String garbage = null; if (obfuscatedSignature != null) { - String signatureString = obfuscatedSignature.getElements().get(0).getString(); - garbage = obfuscatedSignature.getElements().get(1).getString(); + List elements = obfuscatedSignature.getElements(); + + String signatureString = elements.get(0).getString(); + if (elements.size() == 2) + garbage = obfuscatedSignature.getElements().get(1).getString(); Signature signature = new Signature(signatureString); // parse signature @@ -349,7 +358,11 @@ public class Inject // deobfuscatedMethod = deobfuscated method, used to get the deobfuscated signature // invokeMethod = method to invoke, obfuscated - assert clazz.findMethod(method.getName()) == null; + if (clazz.findMethod(method.getName()) != null) + { + return; // hmm. this might be due to an export/import of a non obfuscated method + } + assert !invokeMethod.isStatic(); assert invokeMethod.getMethods().getClassFile() == clazz; @@ -423,6 +436,10 @@ public class Inject { // function requires garbage value + // if garbage is null here it might just be an unused parameter, not part of the obfuscation + if (garbage == null) + garbage = "0"; + switch (lastGarbageArgumentType.getType()) { case "Z": diff --git a/src/main/java/net/runelite/deob/injection/InjectReplace.java b/src/main/java/net/runelite/deob/injection/InjectReplace.java index 5b579623af..90da6e6a75 100644 --- a/src/main/java/net/runelite/deob/injection/InjectReplace.java +++ b/src/main/java/net/runelite/deob/injection/InjectReplace.java @@ -14,24 +14,31 @@ import net.runelite.asm.attributes.annotation.Annotation; import net.runelite.asm.attributes.code.Instruction; import net.runelite.asm.attributes.code.InstructionType; import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.AConstNull; import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.BiPush; import net.runelite.asm.attributes.code.instructions.DLoad; import net.runelite.asm.attributes.code.instructions.FLoad; import net.runelite.asm.attributes.code.instructions.ILoad; import net.runelite.asm.attributes.code.instructions.InvokeSpecial; import net.runelite.asm.attributes.code.instructions.InvokeVirtual; +import net.runelite.asm.attributes.code.instructions.LDC2_W; +import net.runelite.asm.attributes.code.instructions.LDC_W; import net.runelite.asm.attributes.code.instructions.LLoad; import net.runelite.asm.attributes.code.instructions.New; import net.runelite.asm.attributes.code.instructions.Pop; import net.runelite.asm.attributes.code.instructions.Return; +import net.runelite.asm.attributes.code.instructions.SiPush; import net.runelite.asm.pool.NameAndType; +import net.runelite.asm.signature.Signature; import net.runelite.asm.signature.Type; public class InjectReplace { private static final Type REPLACE = new Type("Lnet/runelite/mapping/Replace;"); private static final Type OBFUSCATED_OVERRIDE = new Type("Lnet/runelite/mapping/ObfuscatedOverride;"); - //private static final Type OBFUSCATED_NAME = new Type("Lnet/runelite/mapping/ObfuscatedName;"); + private static final Type OBFUSCATED_NAME = new Type("Lnet/runelite/mapping/ObfuscatedName;"); + private static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;"); private static final Type EXPORT = new Type("Lnet/runelite/mapping/Export;"); private ClassFile cf, vanilla; @@ -79,6 +86,7 @@ public class InjectReplace // set parent classToInject.setParentClass(vanilla.getPoolClass()); vanilla.clearFinal(); // can't be final anymore now that we inherit from it + classToInject.clearAbstract(); // this is being instantiated now, so is no longer abstract injectConstructors(classToInject); @@ -94,7 +102,7 @@ public class InjectReplace private void injectConstructors(ClassFile classToInject) { - // Delete compiler generate constructors + // Delete compiler generated constructors Methods methods = classToInject.getMethods(); Methods vanillaMethods = vanilla.getMethods(); @@ -139,6 +147,9 @@ public class InjectReplace Method constructor = new Method(methods, "", m.getDescriptor()); constructor.setAccessFlags(Method.ACC_PUBLIC); + // ensure vanilla ctor is public too + m.setAccessFlags(Method.ACC_PUBLIC); + Attributes methodAttributes = constructor.getAttributes(); // create code attribute @@ -222,41 +233,102 @@ public class InjectReplace String overridenMethod = annotation.getElement().getString(); // name of @Exported method to override // Find method with exported name on 'cf' - Method obfuscatedMethodToOverride = findMethodByExportedName(overridenMethod); + Method obfuscatedMethodToOverride = findMethodByExportedName(overridenMethod); // this is from the deobfuscated jar + Method vanillaMethodToOverride = findVanillaMethodFromDeobfuscatedMethod(obfuscatedMethodToOverride); NameAndType deobfuscatedNat = m.getNameAndType(); assert obfuscatedMethodToOverride != null; - assert !obfuscatedMethodToOverride.isFinal(); - assert !obfuscatedMethodToOverride.isPrivate(); + assert vanillaMethodToOverride != null; + + vanillaMethodToOverride.setFinal(false); + vanillaMethodToOverride.setPublic(); + + assert !vanillaMethodToOverride.isFinal(); + assert !vanillaMethodToOverride.isPrivate(); // Rename method to override - m.setName(obfuscatedMethodToOverride.getName()); + m.setName(vanillaMethodToOverride.getName()); - assert false; - if (!m.getDescriptor().equals(obfuscatedMethodToOverride.getDescriptor())) + String garbageValue = null; + Signature originalSignature = null; + if (!m.getDescriptor().equals(vanillaMethodToOverride.getDescriptor())) { // Obfuscation can add garbage parameter. - assert m.getDescriptor().size() + 1 == obfuscatedMethodToOverride.getDescriptor().size(); + assert m.getDescriptor().size() + 1 == vanillaMethodToOverride.getDescriptor().size(); - // Either we have to modify the bytecode when it is copied over to include this, - // or maybe can inject overloaded function into superclass if it doesn't cause a signature collision - assert false; + originalSignature = m.getDescriptor(); + + m.arguments = vanillaMethodToOverride.getDescriptor(); // is this right? + + garbageValue = this.getGarbage(obfuscatedMethodToOverride); } // This means method is overriden. It is possible that the return value is a child class // of the parents overriden method, and it will still override the method however the signatures won't match, // but we don't do that. - assert m.getDescriptor().equals(obfuscatedMethodToOverride.getDescriptor()); + assert m.getDescriptor().equals(vanillaMethodToOverride.getDescriptor()); // Now that the function is overriden, when the invoke injector is called, it turns around and invokevirtuals // the parent method, which hits ours. // locate super.method() calls and modify... - for (Instruction i : m.getCode().getInstructions().getInstructions()) + for (Instruction i : new ArrayList<>(m.getCode().getInstructions().getInstructions())) { if (!(i instanceof InvokeSpecial)) continue; + if (originalSignature != null) + { + assert originalSignature.size() + 1 == m.getDescriptor().size(); + + Instructions instructions = m.getCode().getInstructions(); + List ins = instructions.getInstructions(); + Type type = m.getDescriptor().getTypeOfArg(m.getDescriptor().size() - 1); + int offset = ins.indexOf(i); + + assert offset != -1; + + // XXX we could maybe just pull the variable off of the lvt here, instead + // if we know we haven't overwritten it? + if (type.getArrayDims() > 0 || !type.isPrimitive()) + { + ins.add(offset, new AConstNull(instructions)); + } + else + { + if (garbageValue == null) + { + garbageValue = "0"; + } + + switch (type.getType()) + { + case "Z": + case "B": + case "C": + ins.add(offset, new BiPush(instructions, Byte.parseByte(garbageValue))); + break; + case "S": + ins.add(offset, new SiPush(instructions, Short.parseShort(garbageValue))); + break; + case "I": + ins.add(offset, new LDC_W(instructions, Integer.parseInt(garbageValue))); + break; + case "D": + ins.add(offset, new LDC2_W(instructions, Double.parseDouble(garbageValue))); + break; + case "F": + ins.add(offset, new LDC_W(instructions, Float.parseFloat(garbageValue))); + break; + case "J": + ins.add(offset, new LDC2_W(instructions, Long.parseLong(garbageValue))); + break; + default: + throw new RuntimeException("Unknown type"); + } + } + } + InvokeSpecial is = (InvokeSpecial) i; net.runelite.asm.pool.Method invokedMethod = (net.runelite.asm.pool.Method) is.getMethod(); @@ -293,6 +365,60 @@ public class InjectReplace return null; } + private Method findVanillaMethodFromDeobfuscatedMethod(Method method) + { + String name = getObfuscatedName(method); + Signature sig = getObfuscatedSignature(method); + return vanilla.findMethod(name, sig); + } + + private String getObfuscatedName(Method method) + { + Annotations an = method.getAttributes().getAnnotations(); + if (an == null) + return method.getName(); + + Annotation a = an.find(OBFUSCATED_NAME); + if (a == null) + return method.getName(); + + return a.getElement().getString(); + } + + private Signature getObfuscatedSignature(Method method) + { + Annotations an = method.getAttributes().getAnnotations(); + if (an == null) + { + return method.getDescriptor(); + } + + Annotation obSig = an.find(OBFUSCATED_SIGNATURE); + if (obSig == null) + { + return method.getDescriptor(); + } + + return new Signature(obSig.getElement().getString()); + } + + private String getGarbage(Method method) + { + Annotations an = method.getAttributes().getAnnotations(); + if (an == null) + { + return null; + } + + Annotation obSig = an.find(OBFUSCATED_SIGNATURE); + if (obSig == null || obSig.getElements().size() < 2) + { + return null; + } + + return obSig.getElements().get(1).getString(); + } + private void replaceSuperclass(ClassFile classToInject) { for (ClassFile cf : vanilla.getGroup().getClasses()) @@ -382,6 +508,9 @@ public class InjectReplace InvokeSpecial is = (InvokeSpecial) i; net.runelite.asm.pool.Method method = (net.runelite.asm.pool.Method) is.getMethod(); + if (!method.getNameAndType().getName().equals("") || !method.getClassEntry().equals(vanilla.getPoolClass())) + continue; + is.setMethod(new net.runelite.asm.pool.Method( classToInject.getPoolClass(), method.getNameAndType() diff --git a/src/main/java/net/runelite/inject/RSCanvas.java b/src/main/java/net/runelite/inject/RSCanvas.java new file mode 100644 index 0000000000..004d140106 --- /dev/null +++ b/src/main/java/net/runelite/inject/RSCanvas.java @@ -0,0 +1,27 @@ +package net.runelite.inject; + +import java.awt.Canvas; +import java.awt.Graphics; +import java.awt.image.BufferedImage; + +public abstract class RSCanvas extends Canvas +{ + private final BufferedImage clientBuffer = new BufferedImage(756, 503, BufferedImage.TYPE_INT_RGB); + private final BufferedImage gameBuffer = new BufferedImage(756, 503, BufferedImage.TYPE_INT_RGB); + + @Override + public Graphics getGraphics() + { + Graphics clientGraphics = clientBuffer.getGraphics(); + clientGraphics.drawImage(gameBuffer, 0, 0, null); + //clientGraphics.dispose(); + + //clientGraphics = clientBuffer.getGraphics(); + clientGraphics.drawString("something, something", 42, 42); + + Graphics superGraphics = super.getGraphics(); + superGraphics.drawImage(clientBuffer, 0, 0, null); + + return gameBuffer.getGraphics(); + } +}