Beginning work on inject replace

This commit is contained in:
Adam
2016-03-11 14:31:31 -05:00
parent 61f6191228
commit 2cbdec1798
7 changed files with 369 additions and 2 deletions

View File

@@ -14,6 +14,9 @@ import java.util.List;
public class ClassFile
{
private static final int MAGIC = 0xcafebabe;
private static final short ACC_FINAL = 0x0010;
private static final short ACC_ABSTRACT = 0x0400;
private ClassGroup group;
@@ -282,4 +285,14 @@ public class ClassFile
{
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;
}
}

View File

@@ -135,6 +135,16 @@ public class Method
{
return (accessFlags & ACC_SYNCHRONIZED) != 0;
}
public boolean isFinal()
{
return (accessFlags & ACC_FINAL) != 0;
}
public boolean isPrivate()
{
return (accessFlags & ACC_PRIVATE) != 0;
}
public Exceptions getExceptions()
{

View File

@@ -38,6 +38,14 @@ public class InvokeSpecial extends Instruction implements InvokeInstruction
{
super(instructions, type, pc);
}
public InvokeSpecial(Instructions instructions, Method method)
{
super(instructions, InstructionType.INVOKESPECIAL, -1);
this.method = method;
length += 2;
}
@Override
public void load(DataInputStream is) throws IOException
@@ -136,6 +144,11 @@ public class InvokeSpecial extends Instruction implements InvokeInstruction
{
return method;
}
public void setMethod(Method method)
{
this.method = method;
}
@Override
public void lookup()

View File

@@ -59,6 +59,11 @@ public class New extends Instruction
{
return clazz;
}
public void setNewClass(Class clazz)
{
this.clazz = clazz;
}
@Override
public void lookup()

View File

@@ -13,6 +13,8 @@ public class AnnotationMapper
{
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 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 ParallelExecutorMapping mapping;
@@ -114,6 +116,6 @@ public class AnnotationMapper
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);
}
}

View File

@@ -44,6 +44,8 @@ public class Inject
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_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;");
private static final String API_PACKAGE_BASE = "net.runelite.rs.api.";
private static java.lang.Class<?> clientClass;
@@ -214,7 +216,7 @@ public class Inject
if (a == 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);
Interfaces interfaces = other.getInterfaces();

View 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()
)
);
}
}
}
}
}
}