Beginning work on inject replace
This commit is contained in:
@@ -14,6 +14,9 @@ import java.util.List;
|
|||||||
public class ClassFile
|
public class ClassFile
|
||||||
{
|
{
|
||||||
private static final int MAGIC = 0xcafebabe;
|
private static final int MAGIC = 0xcafebabe;
|
||||||
|
|
||||||
|
private static final short ACC_FINAL = 0x0010;
|
||||||
|
private static final short ACC_ABSTRACT = 0x0400;
|
||||||
|
|
||||||
private ClassGroup group;
|
private ClassGroup group;
|
||||||
|
|
||||||
@@ -282,4 +285,14 @@ public class ClassFile
|
|||||||
{
|
{
|
||||||
return this == other || interfaces.instanceOf(other) || (getParent() != null && getParent().instanceOf(other));
|
return this == other || interfaces.instanceOf(other) || (getParent() != null && getParent().instanceOf(other));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAbstract()
|
||||||
|
{
|
||||||
|
return (this.access_flags & ACC_ABSTRACT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinal()
|
||||||
|
{
|
||||||
|
return (this.access_flags & ACC_FINAL) != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,16 @@ public class Method
|
|||||||
{
|
{
|
||||||
return (accessFlags & ACC_SYNCHRONIZED) != 0;
|
return (accessFlags & ACC_SYNCHRONIZED) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFinal()
|
||||||
|
{
|
||||||
|
return (accessFlags & ACC_FINAL) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrivate()
|
||||||
|
{
|
||||||
|
return (accessFlags & ACC_PRIVATE) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public Exceptions getExceptions()
|
public Exceptions getExceptions()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ public class InvokeSpecial extends Instruction implements InvokeInstruction
|
|||||||
{
|
{
|
||||||
super(instructions, type, pc);
|
super(instructions, type, pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InvokeSpecial(Instructions instructions, Method method)
|
||||||
|
{
|
||||||
|
super(instructions, InstructionType.INVOKESPECIAL, -1);
|
||||||
|
|
||||||
|
this.method = method;
|
||||||
|
length += 2;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load(DataInputStream is) throws IOException
|
public void load(DataInputStream is) throws IOException
|
||||||
@@ -136,6 +144,11 @@ public class InvokeSpecial extends Instruction implements InvokeInstruction
|
|||||||
{
|
{
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMethod(Method method)
|
||||||
|
{
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void lookup()
|
public void lookup()
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ public class New extends Instruction
|
|||||||
{
|
{
|
||||||
return clazz;
|
return clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNewClass(Class clazz)
|
||||||
|
{
|
||||||
|
this.clazz = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void lookup()
|
public void lookup()
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ public class AnnotationMapper
|
|||||||
{
|
{
|
||||||
private static final Type EXPORT = new Type("Lnet/runelite/mapping/Export;");
|
private static final Type EXPORT = new Type("Lnet/runelite/mapping/Export;");
|
||||||
private static final Type IMPLEMENTS = new Type("Lnet/runelite/mapping/Implements;");
|
private static final Type IMPLEMENTS = new Type("Lnet/runelite/mapping/Implements;");
|
||||||
|
private static final Type REPLACE = new Type("Lnet/runelite/mapping/Replace;");
|
||||||
|
private static final Type OBFUSCATED_OVERRIDE = new Type("Lnet/runelite/mapping/ObfuscatedOverride;");
|
||||||
|
|
||||||
private final ClassGroup source, target;
|
private final ClassGroup source, target;
|
||||||
private final ParallelExecutorMapping mapping;
|
private final ParallelExecutorMapping mapping;
|
||||||
@@ -114,6 +116,6 @@ public class AnnotationMapper
|
|||||||
|
|
||||||
private boolean isCopyable(Annotation a)
|
private boolean isCopyable(Annotation a)
|
||||||
{
|
{
|
||||||
return a.getType().equals(EXPORT) || a.getType().equals(IMPLEMENTS);
|
return a.getType().equals(EXPORT) || a.getType().equals(IMPLEMENTS) || a.getType().equals(REPLACE) || a.getType().equals(OBFUSCATED_OVERRIDE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ public class Inject
|
|||||||
private static final Type IMPLEMENTS = new Type("Lnet/runelite/mapping/Implements;");
|
private static final Type IMPLEMENTS = new Type("Lnet/runelite/mapping/Implements;");
|
||||||
private static final Type OBFUSCATED_GETTER = new Type("Lnet/runelite/mapping/ObfuscatedGetter;");
|
private static final Type OBFUSCATED_GETTER = new Type("Lnet/runelite/mapping/ObfuscatedGetter;");
|
||||||
private static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;");
|
private static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;");
|
||||||
|
|
||||||
|
private static final String API_PACKAGE_BASE = "net.runelite.rs.api.";
|
||||||
|
|
||||||
private static java.lang.Class<?> clientClass;
|
private static java.lang.Class<?> clientClass;
|
||||||
|
|
||||||
@@ -214,7 +216,7 @@ public class Inject
|
|||||||
if (a == null)
|
if (a == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
String ifaceName = "net.runelite.rs.api." + a.getElement().getString();
|
String ifaceName = API_PACKAGE_BASE + a.getElement().getString();
|
||||||
Class clazz = new Class(ifaceName);
|
Class clazz = new Class(ifaceName);
|
||||||
|
|
||||||
Interfaces interfaces = other.getInterfaces();
|
Interfaces interfaces = other.getInterfaces();
|
||||||
|
|||||||
322
src/main/java/net/runelite/deob/injection/InjectReplace.java
Normal file
322
src/main/java/net/runelite/deob/injection/InjectReplace.java
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
package net.runelite.deob.injection;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import net.runelite.deob.ClassFile;
|
||||||
|
import net.runelite.deob.Method;
|
||||||
|
import net.runelite.deob.Methods;
|
||||||
|
import net.runelite.deob.attributes.Annotations;
|
||||||
|
import net.runelite.deob.attributes.Attributes;
|
||||||
|
import net.runelite.deob.attributes.Code;
|
||||||
|
import net.runelite.deob.attributes.annotation.Annotation;
|
||||||
|
import net.runelite.deob.attributes.code.Instruction;
|
||||||
|
import net.runelite.deob.attributes.code.InstructionType;
|
||||||
|
import net.runelite.deob.attributes.code.Instructions;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.ALoad;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.DLoad;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.FLoad;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.ILoad;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.InvokeSpecial;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.LLoad;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.New;
|
||||||
|
import net.runelite.deob.attributes.code.instructions.Return;
|
||||||
|
import net.runelite.deob.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 ClassFile cf, vanilla;
|
||||||
|
|
||||||
|
public InjectReplace(ClassFile cf, ClassFile vanilla)
|
||||||
|
{
|
||||||
|
this.cf = cf; // deobfuscated class
|
||||||
|
this.vanilla = vanilla; // vanilla class
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() throws ClassNotFoundException, IOException
|
||||||
|
{
|
||||||
|
Annotations an = cf.getAttributes().getAnnotations();
|
||||||
|
if (an == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Annotation a = an.find(REPLACE);
|
||||||
|
if (a == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// cf = deobfuscated class with @Replace("net.runelite.whatever")
|
||||||
|
|
||||||
|
// generate a new class. make it inherit from 'vanilla'.
|
||||||
|
|
||||||
|
// make all classes which inherit from 'vanilla' instead inherit from the new class
|
||||||
|
// and adjust their constructors
|
||||||
|
|
||||||
|
// add constructors to new class
|
||||||
|
// cf must implement an interface from the api?
|
||||||
|
// methods can have @ObfuscatedOverride("name") to be renamed
|
||||||
|
// to override ob'd method.
|
||||||
|
// replace all instances of new 'vanilla' with new 'new class'
|
||||||
|
|
||||||
|
Class<?> c = Class.forName(a.getElement().getString());
|
||||||
|
ClassFile classToInject;
|
||||||
|
try (DataInputStream dis = new DataInputStream(c.getClassLoader().getResourceAsStream(c.getName().replace('.', '/') + ".class")))
|
||||||
|
{
|
||||||
|
classToInject = new ClassFile(vanilla.getGroup(), dis);
|
||||||
|
vanilla.getGroup().addClass(classToInject);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert classToInject.getParentClass().getName().equals("java/lang/Object");
|
||||||
|
assert classToInject.isAbstract();
|
||||||
|
|
||||||
|
// set parent
|
||||||
|
classToInject.setParentClass(vanilla.getPoolClass());
|
||||||
|
assert !vanilla.isFinal();
|
||||||
|
|
||||||
|
injectConstructors(classToInject);
|
||||||
|
|
||||||
|
overideMethods(classToInject);
|
||||||
|
|
||||||
|
replaceSuperclass(classToInject);
|
||||||
|
|
||||||
|
replaceNew(classToInject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void injectConstructors(ClassFile classToInject)
|
||||||
|
{
|
||||||
|
// Delete compiler generate constructors
|
||||||
|
Methods methods = classToInject.getMethods();
|
||||||
|
Methods vanillaMethods = vanilla.getMethods();
|
||||||
|
|
||||||
|
for (Method m : new ArrayList<>(methods.getMethods()))
|
||||||
|
if (m.getName().equals("<init>"))
|
||||||
|
methods.removeMethod(m);
|
||||||
|
|
||||||
|
// Add constructors
|
||||||
|
for (Method m : vanillaMethods.getMethods())
|
||||||
|
if (m.getName().equals("<init>"))
|
||||||
|
{
|
||||||
|
// create new constructor with same signature
|
||||||
|
Method constructor = new Method(methods, "<init>", m.getDescriptor());
|
||||||
|
constructor.setAccessFlags(Method.ACC_PUBLIC);
|
||||||
|
|
||||||
|
Attributes methodAttributes = constructor.getAttributes();
|
||||||
|
|
||||||
|
// create code attribute
|
||||||
|
Code code = new Code(methodAttributes);
|
||||||
|
methodAttributes.addAttribute(code);
|
||||||
|
|
||||||
|
Instructions instructions = code.getInstructions();
|
||||||
|
List<Instruction> ins = instructions.getInstructions();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
ins.add(new ALoad(instructions, index++)); // this
|
||||||
|
|
||||||
|
// push arguments
|
||||||
|
for (int i = 0; i < m.getDescriptor().size(); ++i)
|
||||||
|
{
|
||||||
|
Type type = m.getDescriptor().getTypeOfArg(i);
|
||||||
|
|
||||||
|
if (type.getArrayDims() > 0 || !type.isPrimitive())
|
||||||
|
{
|
||||||
|
ins.add(new ALoad(instructions, index++));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (type.getType())
|
||||||
|
{
|
||||||
|
case "B":
|
||||||
|
case "C":
|
||||||
|
case "I":
|
||||||
|
case "S":
|
||||||
|
case "Z":
|
||||||
|
ins.add(new ILoad(instructions, index++));
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
ins.add(new DLoad(instructions, index++));
|
||||||
|
++index; // takes two slots
|
||||||
|
break;
|
||||||
|
case "F":
|
||||||
|
ins.add(new FLoad(instructions, index++));
|
||||||
|
break;
|
||||||
|
case "J":
|
||||||
|
ins.add(new LLoad(instructions, index++));
|
||||||
|
++index;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Unknown type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ins.add(new InvokeSpecial(instructions, m.getPoolMethod()));
|
||||||
|
ins.add(new Return(instructions, InstructionType.RETURN));
|
||||||
|
|
||||||
|
methods.addMethod(constructor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void overideMethods(ClassFile classToInject)
|
||||||
|
{
|
||||||
|
// find methods in methods that are supposed to override obfuscated methods, and rename them.
|
||||||
|
|
||||||
|
Methods methods = classToInject.getMethods();
|
||||||
|
|
||||||
|
for (Method m : methods.getMethods())
|
||||||
|
{
|
||||||
|
Attributes attributes = m.getAttributes();
|
||||||
|
Annotations annotations = attributes.getAnnotations();
|
||||||
|
|
||||||
|
if (annotations == null || annotations.find(OBFUSCATED_OVERRIDE) == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Annotation annotation = annotations.find(OBFUSCATED_OVERRIDE);
|
||||||
|
String overridenMethod = annotation.getElement().getString(); // name of @Exported method to override
|
||||||
|
|
||||||
|
// Find method with exported name on 'cf'
|
||||||
|
Method obfuscatedMethodToOverride = findMethodByObfuscatedName(overridenMethod);
|
||||||
|
|
||||||
|
assert obfuscatedMethodToOverride != null;
|
||||||
|
assert !obfuscatedMethodToOverride.isFinal();
|
||||||
|
assert !obfuscatedMethodToOverride.isPrivate();
|
||||||
|
|
||||||
|
// Rename method to override
|
||||||
|
m.setName(obfuscatedMethodToOverride.getName());
|
||||||
|
|
||||||
|
if (!m.getDescriptor().equals(obfuscatedMethodToOverride.getDescriptor()))
|
||||||
|
{
|
||||||
|
// Obfuscation can add garbage parameter.
|
||||||
|
assert m.getDescriptor().size() + 1 == obfuscatedMethodToOverride.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
// Now that the function is overriden, when the invoke injector is called, it turns around and invokevirtuals
|
||||||
|
// the parent method, which hits ours.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findMethodByObfuscatedName(String name)
|
||||||
|
{
|
||||||
|
for (Method m : cf.getMethods().getMethods())
|
||||||
|
{
|
||||||
|
Attributes attributes = m.getAttributes();
|
||||||
|
Annotations annotations = attributes.getAnnotations();
|
||||||
|
|
||||||
|
if (annotations == null || annotations.find(OBFUSCATED_NAME) == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Annotation annotation = annotations.find(OBFUSCATED_NAME);
|
||||||
|
String obfuscatedName = annotation.getElement().getString();
|
||||||
|
|
||||||
|
if (name.equals(obfuscatedName))
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replaceSuperclass(ClassFile classToInject)
|
||||||
|
{
|
||||||
|
// find all classes which inherit from 'vanilla'. replace with classToInject
|
||||||
|
|
||||||
|
for (ClassFile cf : vanilla.getGroup().getClasses())
|
||||||
|
if (cf.getParentClass().equals(vanilla.getPoolClass()))
|
||||||
|
{
|
||||||
|
cf.setParentClass(classToInject.getPoolClass());
|
||||||
|
|
||||||
|
// adjust constructors
|
||||||
|
|
||||||
|
for (Method m : cf.getMethods().getMethods())
|
||||||
|
{
|
||||||
|
if (!m.getName().equals("<init>"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Code code = m.getCode();
|
||||||
|
Instructions ins = code.getInstructions();
|
||||||
|
|
||||||
|
for (Instruction i : ins.getInstructions())
|
||||||
|
{
|
||||||
|
if (!(i instanceof InvokeSpecial))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The super constructor invokespecial will be the first invokespecial instruction encountered
|
||||||
|
InvokeSpecial is = (InvokeSpecial) i;
|
||||||
|
|
||||||
|
net.runelite.deob.pool.Method method = (net.runelite.deob.pool.Method) is.getMethod();
|
||||||
|
assert method.getClassEntry().equals(vanilla.getPoolClass());
|
||||||
|
assert method.getNameAndType().getName().equals("<init>");
|
||||||
|
|
||||||
|
is.setMethod(
|
||||||
|
new net.runelite.deob.pool.Method(
|
||||||
|
classToInject.getPoolClass(),
|
||||||
|
method.getNameAndType()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replaceNew(ClassFile classToInject)
|
||||||
|
{
|
||||||
|
// new vanilla -> new classToInject
|
||||||
|
|
||||||
|
for (ClassFile cf : vanilla.getGroup().getClasses())
|
||||||
|
if (cf.getParentClass().equals(vanilla.getPoolClass()))
|
||||||
|
{
|
||||||
|
for (Method m : cf.getMethods().getMethods())
|
||||||
|
{
|
||||||
|
Code code = m.getCode();
|
||||||
|
Instructions ins = code.getInstructions();
|
||||||
|
|
||||||
|
boolean seen = false, isConstructor = m.getName().equals("<init>");
|
||||||
|
|
||||||
|
for (Instruction i : ins.getInstructions())
|
||||||
|
{
|
||||||
|
if (i instanceof New)
|
||||||
|
{
|
||||||
|
New n = (New) i;
|
||||||
|
if (!n.getNewClass().equals(vanilla.getPoolClass()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
n.setNewClass(classToInject.getPoolClass());
|
||||||
|
}
|
||||||
|
else if (i instanceof InvokeSpecial)
|
||||||
|
{
|
||||||
|
if (isConstructor)
|
||||||
|
{
|
||||||
|
if (!seen)
|
||||||
|
{
|
||||||
|
seen = true;
|
||||||
|
continue; // superclass invoke in constructor of class which inherits classToInject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InvokeSpecial is = (InvokeSpecial) i;
|
||||||
|
net.runelite.deob.pool.Method method = (net.runelite.deob.pool.Method) is.getMethod();
|
||||||
|
|
||||||
|
is.setMethod(
|
||||||
|
new net.runelite.deob.pool.Method(
|
||||||
|
classToInject.getPoolClass(),
|
||||||
|
method.getNameAndType()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user