Injector, deob, mixins

This commit is contained in:
Lucas
2019-06-08 09:33:50 +02:00
parent 276ff1995d
commit 49afdf7dc7
599 changed files with 84012 additions and 1821 deletions

104
injector-plugin/pom.xml Normal file
View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId>
<version>1.5.27-SNAPSHOT</version>
</parent>
<groupId>net.runelite.rs</groupId>
<artifactId>injector-plugin</artifactId>
<name>Injector</name>
<packaging>maven-plugin</packaging>
<dependencies>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>deobfuscator</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>mixins</artifactId>
<version>1.5.27-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.runelite.rs</groupId>
<artifactId>runescape-api</artifactId>
<version>1.5.27-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>deobfuscator</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.4</version>
<executions>
<execution>
<id>default-descriptor</id>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,629 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.util.HashMap;
import java.util.Map;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Interfaces;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.ALoad;
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.LLoad;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.deobfuscators.arithmetic.DMath;
import net.runelite.injector.raw.ClearColorBuffer;
import net.runelite.injector.raw.DrawAfterWidgets;
//import net.runelite.injector.raw.DrawMenu;
import net.runelite.injector.raw.RasterizerHook;
import net.runelite.injector.raw.RenderDraw;
import net.runelite.injector.raw.ScriptVM;
import net.runelite.mapping.Import;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.runelite.rs.api.RSClient;
public class Inject
{
private static final Logger logger = LoggerFactory.getLogger(Inject.class);
public static final java.lang.Class<?> CLIENT_CLASS = RSClient.class;
public static final String API_PACKAGE_BASE = "net.runelite.rs.api.RS";
public static final String RL_API_PACKAGE_BASE = "net.runelite.api.";
private final InjectHookMethod hookMethod = new InjectHookMethod(this);
private final InjectGetter getters = new InjectGetter(this);
private final InjectSetter setters = new InjectSetter(this);
private final InjectInvoker invokes = new InjectInvoker(this);
private final InjectConstruct construct = new InjectConstruct(this);
private final RasterizerHook rasterizerHook = new RasterizerHook(this);
private final MixinInjector mixinInjector = new MixinInjector(this);
private final DrawAfterWidgets drawAfterWidgets = new DrawAfterWidgets(this);
private final ScriptVM scriptVM = new ScriptVM(this);
private final ClearColorBuffer clearColorBuffer = new ClearColorBuffer(this);
private final RenderDraw renderDraw = new RenderDraw(this);
//private final DrawMenu drawMenu = new DrawMenu(this);
// deobfuscated contains exports etc to apply to vanilla
private final ClassGroup deobfuscated, vanilla;
public Inject(ClassGroup deobfuscated, ClassGroup vanilla)
{
this.deobfuscated = deobfuscated;
this.vanilla = vanilla;
}
public Type getFieldType(Field f)
{
Type type = f.getType();
Annotation obfSignature = f.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE);
if (obfSignature != null)
{
//Annotation exists. Type was updated by us during deobfuscation
type = DeobAnnotations.getObfuscatedType(f);
}
return type;
}
public Signature getMethodSignature(Method m)
{
Signature signature = m.getDescriptor();
Annotation obfSignature = m.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE);
if (obfSignature != null)
{
//Annotation exists. Signature was updated by us during deobfuscation
signature = DeobAnnotations.getObfuscatedSignature(m);
}
return signature;
}
/**
* Convert a java.lang.Class to a Type
*
* @param c
* @return
*/
public static Type classToType(java.lang.Class<?> c)
{
int dimms = 0;
while (c.isArray())
{
c = c.getComponentType();
++dimms;
}
if (c.isPrimitive())
{
String s;
switch (c.getName())
{
case "int":
s = "I";
break;
case "long":
s = "J";
break;
case "boolean":
s = "Z";
break;
case "char":
s = "C";
break;
case "short":
s = "S";
break;
case "float":
s = "F";
break;
case "double":
s = "D";
break;
case "byte":
s = "B";
break;
case "void":
s = "V";
break;
default:
throw new RuntimeException("unknown primitive type " + c.getName());
}
return Type.getType(s, dimms);
}
return Type.getType("L" + c.getName().replace('.', '/') + ";", dimms);
}
/**
* Build a Signature from a java method
*
* @param method
* @return
*/
public Signature javaMethodToSignature(java.lang.reflect.Method method)
{
Signature.Builder builder = new Signature.Builder()
.setReturnType(classToType(method.getReturnType()));
for (java.lang.Class<?> clazz : method.getParameterTypes())
{
builder.addArgument(classToType(clazz));
}
return builder.build();
}
public void run() throws InjectionException
{
Map<ClassFile, java.lang.Class> implemented = new HashMap<>();
// inject interfaces first, so the validateTypeIsConvertibleTo
// check below works
for (ClassFile cf : deobfuscated.getClasses())
{
Annotations an = cf.getAnnotations();
if (an == null || an.size() == 0)
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(an);
if (obfuscatedName == null)
{
obfuscatedName = cf.getName();
}
ClassFile other = vanilla.findClass(obfuscatedName);
assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName;
java.lang.Class implementingClass = injectInterface(cf, other);
// it can not implement an interface but still have exported static fields, which are
// moved to client
implemented.put(cf, implementingClass);
}
// Has to be done before mixins
// well, can be done after really
// but why do that when you can do it before
rasterizerHook.inject();
// requires interfaces to be injected
mixinInjector.inject();
construct.inject(implemented);
for (ClassFile cf : deobfuscated.getClasses())
{
java.lang.Class implementingClass = implemented.get(cf);
Annotations an = cf.getAnnotations();
if (an == null || an.size() == 0)
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(an);
if (obfuscatedName == null)
{
obfuscatedName = cf.getName();
}
ClassFile other = vanilla.findClass(obfuscatedName);
assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName;
for (Field f : cf.getFields())
{
an = f.getAnnotations();
if (an == null || an.find(DeobAnnotations.EXPORT) == null)
{
continue; // not an exported field
}
Annotation exportAnnotation = an.find(DeobAnnotations.EXPORT);
String exportedName = exportAnnotation.getElement().getString();
obfuscatedName = DeobAnnotations.getObfuscatedName(an);
Annotation getterAnnotation = an.find(DeobAnnotations.OBFUSCATED_GETTER);
Number getter = null;
if (getterAnnotation != null)
{
getter = (Number) getterAnnotation.getElement().getValue();
}
// the ob jar is the same as the vanilla so this field must exist in this class.
Type obType = getFieldType(f);
Field otherf = other.findField(obfuscatedName, obType);
assert otherf != null;
assert f.isStatic() == otherf.isStatic();
ClassFile targetClass = f.isStatic() ? vanilla.findClass("client") : other; // target class for getter
java.lang.Class targetApiClass = f.isStatic() ? CLIENT_CLASS : implementingClass; // target api class for getter
if (targetApiClass == null)
{
assert !f.isStatic();
// non static field exported on non exported interface
// logger.debug("Non static exported field {} on non exported interface", exportedName);
continue;
}
java.lang.reflect.Method apiMethod = findImportMethodOnApi(targetApiClass, exportedName, true);
if (apiMethod != null)
{
Number setter = null;
if (getter != null)
{
setter = DMath.modInverse(getter); // inverse getter to get the setter
}
setters.injectSetter(targetClass, targetApiClass, otherf, exportedName, setter);
}
apiMethod = findImportMethodOnApi(targetApiClass, exportedName, false);
if (apiMethod == null)
{
// logger.debug("Unable to find import method on api class {} with imported name {}, not injecting getter", targetApiClass, exportedName);
continue;
}
// check that otherf is converable to apiMethod's
// return type
Type fieldType = otherf.getType();
Type returnType = classToType(apiMethod.getReturnType());
if (!validateTypeIsConvertibleTo(fieldType, returnType))
{
throw new InjectionException("Type " + fieldType + " is not convertable to " + returnType + " for getter " + apiMethod);
}
getters.injectGetter(targetClass, apiMethod, otherf, getter);
}
for (Method m : cf.getMethods())
{
hookMethod.process(m);
invokes.process(m, other, implementingClass);
}
}
logger.info("Injected {} getters, {} setters, {} invokers",
getters.getInjectedGetters(),
setters.getInjectedSetters(), invokes.getInjectedInvokers());
drawAfterWidgets.inject();
scriptVM.inject();
clearColorBuffer.inject();
renderDraw.inject();
//drawMenu.inject();
}
private java.lang.Class injectInterface(ClassFile cf, ClassFile other)
{
Annotations an = cf.getAnnotations();
if (an == null)
{
return null;
}
Annotation a = an.find(DeobAnnotations.IMPLEMENTS);
if (a == null)
{
return null;
}
String ifaceName = API_PACKAGE_BASE + a.getElement().getString();
java.lang.Class<?> apiClass;
try
{
apiClass = java.lang.Class.forName(ifaceName);
}
catch (ClassNotFoundException ex)
{
logger.trace("Class {} implements nonexistent interface {}, skipping interface injection",
cf.getName(),
ifaceName);
return null;
}
String ifaceNameInternal = ifaceName.replace('.', '/'); // to internal name
Class clazz = new Class(ifaceNameInternal);
Interfaces interfaces = other.getInterfaces();
interfaces.addInterface(clazz);
return apiClass;
}
public java.lang.reflect.Method findImportMethodOnApi(java.lang.Class<?> clazz, String name, Boolean setter)
{
for (java.lang.reflect.Method method : clazz.getDeclaredMethods())
{
if (method.isSynthetic())
{
/*
* If you override an interface method in another interface
* with a return type that is a child of the overriden methods
* return type, both methods end up in the interface, and both
* are *annotated*. But the base one is synthetic.
*/
continue;
}
Import i = method.getAnnotation(Import.class);
if (i == null || !name.equals(i.value()) || (setter != null && (method.getParameterCount() > 0) != setter))
{
continue;
}
return method;
}
return null;
}
/**
* create a load instruction for a variable of type from a given index
*
* @param instructions
* @param type
* @param index
* @return
*/
public Instruction createLoadForTypeIndex(Instructions instructions, Type type, int index)
{
if (type.getDimensions() > 0 || !type.isPrimitive())
{
return new ALoad(instructions, index);
}
switch (type.toString())
{
case "B":
case "C":
case "I":
case "S":
case "Z":
return new ILoad(instructions, index);
case "D":
return new DLoad(instructions, index);
case "F":
return new FLoad(instructions, index);
case "J":
return new LLoad(instructions, index);
default:
throw new RuntimeException("Unknown type");
}
}
ClassFile toDeobClass(ClassFile obClass)
{
for (ClassFile cf : deobfuscated.getClasses())
{
String obfuscatedName = DeobAnnotations.getObfuscatedName(cf.getAnnotations());
if (obClass.getName().equalsIgnoreCase(obfuscatedName))
{
return cf;
}
}
return null;
}
public ClassFile toObClass(ClassFile deobClass)
{
String obfuscatedName = DeobAnnotations.getObfuscatedName(deobClass.getAnnotations());
for (ClassFile cf : vanilla.getClasses())
{
if (cf.getName().equalsIgnoreCase(obfuscatedName))
{
return cf;
}
}
return null;
}
public Field toObField(Field field)
{
String obfuscatedClassName = DeobAnnotations.getObfuscatedName(field.getClassFile().getAnnotations());
String obfuscatedFieldName = DeobAnnotations.getObfuscatedName(field.getAnnotations()); // obfuscated name of field
Type type = getFieldType(field);
ClassFile obfuscatedClass = vanilla.findClass(obfuscatedClassName);
assert obfuscatedClass != null;
Field obfuscatedField = obfuscatedClass.findFieldDeep(obfuscatedFieldName, type);
assert obfuscatedField != null;
return obfuscatedField;
}
Type deobfuscatedTypeToApiType(Type type) throws InjectionException
{
if (type.isPrimitive())
{
return type;
}
ClassFile cf = deobfuscated.findClass(type.getInternalName());
if (cf == null)
{
return type; // not my type
}
java.lang.Class<?> rsApiType;
try
{
rsApiType = java.lang.Class.forName(API_PACKAGE_BASE + cf.getName().replace("/", "."));
}
catch (ClassNotFoundException ex)
{
throw new InjectionException("Deobfuscated type " + type.getInternalName() + " has no API type", ex);
}
java.lang.Class<?> rlApiType = null;
for (java.lang.Class<?> inter : rsApiType.getInterfaces())
{
if (inter.getName().startsWith(RL_API_PACKAGE_BASE))
{
rlApiType = inter;
}
}
// if (rlApiType == null)
// {
// throw new InjectionException("RS API type " + rsApiType + " does not extend RL API interface");
// }
final java.lang.Class<?> finalType = rlApiType == null ? rsApiType : rlApiType;
return Type.getType("L" + finalType.getName().replace('.', '/') + ";", type.getDimensions());
}
Type apiTypeToDeobfuscatedType(Type type) throws InjectionException
{
if (type.isPrimitive())
{
return type;
}
String internalName = type.getInternalName().replace('/', '.');
if (!internalName.startsWith(API_PACKAGE_BASE))
{
return type; // not an rs api type
}
return Type.getType("L" + type.getInternalName().substring(API_PACKAGE_BASE.length()) + ";", type.getDimensions());
}
ClassFile findVanillaForInterface(java.lang.Class<?> clazz)
{
String className = clazz.getName().replace('.', '/');
for (ClassFile cf : getVanilla().getClasses())
{
for (net.runelite.asm.pool.Class cl : cf.getInterfaces().getInterfaces())
{
if (cl.getName().equals(className))
{
return cf;
}
}
}
return null;
}
private boolean validateTypeIsConvertibleTo(Type from, Type to) throws InjectionException
{
if (from.getDimensions() != to.getDimensions())
{
throw new InjectionException("Array dimension mismatch");
}
if (from.isPrimitive())
{
return true;
}
ClassFile vanillaClass = vanilla.findClass(from.getInternalName());
if (vanillaClass == null)
{
return true;
}
boolean okay = false;
for (Class inter : vanillaClass.getInterfaces().getInterfaces())
{
java.lang.Class c;
try
{
c = java.lang.Class.forName(inter.getName().replace('/', '.'));
}
catch (ClassNotFoundException ex)
{
continue;
}
okay |= check(c, to);
}
return okay;
}
private boolean check(java.lang.Class c, Type type)
{
String s = type.getInternalName()
.replace('/', '.');
if (c.getName().equals(s))
{
return true;
}
for (java.lang.Class c2 : c.getInterfaces())
{
if (check(c2, type))
{
return true;
}
}
return false;
}
public ClassGroup getDeobfuscated()
{
return deobfuscated;
}
public ClassGroup getVanilla()
{
return vanilla;
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.attributes.code.instructions.Dup;
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
import net.runelite.asm.attributes.code.instructions.New;
import net.runelite.asm.attributes.code.instructions.Return;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.mapping.Construct;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InjectConstruct
{
private static final Logger logger = LoggerFactory.getLogger(InjectConstruct.class);
private final Inject inject;
public InjectConstruct(Inject inject)
{
this.inject = inject;
}
public void inject(Map<ClassFile, java.lang.Class> implemented) throws InjectionException
{
for (Entry<ClassFile, java.lang.Class> entry : implemented.entrySet())
{
Class<?> clazz = entry.getValue();
ClassFile cf = entry.getKey();
if (clazz == null)
{
continue;
}
for (java.lang.reflect.Method method : clazz.getDeclaredMethods())
{
if (method.isSynthetic())
{
continue;
}
Construct construct = method.getAnnotation(Construct.class);
if (construct == null)
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(cf.getAnnotations());
if (obfuscatedName == null)
{
obfuscatedName = cf.getName();
}
ClassGroup vanilla = inject.getVanilla();
ClassFile other = vanilla.findClass(obfuscatedName);
assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName;
injectConstruct(other, method);
}
}
}
public void injectConstruct(ClassFile targetClass, java.lang.reflect.Method apiMethod) throws InjectionException
{
logger.info("Injecting construct for {}", apiMethod);
assert targetClass.findMethod(apiMethod.getName()) == null;
Class<?> typeToConstruct = apiMethod.getReturnType();
ClassFile vanillaClass = inject.findVanillaForInterface(typeToConstruct);
if (vanillaClass == null)
{
throw new InjectionException("Unable to find vanilla class which implements interface " + typeToConstruct);
}
Signature sig = inject.javaMethodToSignature(apiMethod);
Signature constructorSig = new Signature.Builder()
.addArguments(Stream.of(apiMethod.getParameterTypes())
.map(arg ->
{
ClassFile vanilla = inject.findVanillaForInterface(arg);
if (vanilla != null)
{
return new Type("L" + vanilla.getName() + ";");
}
return Inject.classToType(arg);
})
.collect(Collectors.toList()))
.setReturnType(Type.VOID)
.build();
Method vanillaConstructor = vanillaClass.findMethod("<init>", constructorSig);
if (vanillaConstructor == null)
{
throw new InjectionException("Unable to find constructor for " + vanillaClass.getName() + ".<init>" + constructorSig);
}
Method setterMethod = new Method(targetClass, apiMethod.getName(), sig);
setterMethod.setAccessFlags(ACC_PUBLIC);
targetClass.addMethod(setterMethod);
Code code = new Code(setterMethod);
setterMethod.setCode(code);
Instructions instructions = code.getInstructions();
List<Instruction> ins = instructions.getInstructions();
ins.add(new New(instructions, vanillaClass.getPoolClass()));
ins.add(new Dup(instructions));
int idx = 1;
int parameter = 0;
for (Type type : vanillaConstructor.getDescriptor().getArguments())
{
Instruction load = inject.createLoadForTypeIndex(instructions, type, idx);
idx += type.getSize();
ins.add(load);
Type paramType = sig.getTypeOfArg(parameter);
if (!type.equals(paramType))
{
CheckCast checkCast = new CheckCast(instructions);
checkCast.setType(type);
ins.add(checkCast);
}
++parameter;
}
ins.add(new InvokeSpecial(instructions, vanillaConstructor.getPoolMethod()));
ins.add(new Return(instructions));
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
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.ALoad;
import net.runelite.asm.attributes.code.instructions.GetField;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.attributes.code.instructions.Return;
import net.runelite.asm.signature.Signature;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InjectGetter
{
private static final Logger logger = LoggerFactory.getLogger(InjectGetter.class);
private final Inject inject;
private int injectedGetters;
public InjectGetter(Inject inject)
{
this.inject = inject;
}
public void injectGetter(ClassFile clazz, java.lang.reflect.Method method, Field field, Number getter)
{
// clazz = class file we're injecting the method into.
// method = api method (java reflect) that we're overriding
// field = field we're getting. might not be in this class if static.
// getter = encryption getter
assert clazz.findMethod(method.getName()) == null;
assert field.isStatic() || field.getClassFile() == clazz;
Signature sig = new Signature.Builder()
.setReturnType(Inject.classToType(method.getReturnType()))
.build();
Method getterMethod = new Method(clazz, method.getName(), sig);
getterMethod.setAccessFlags(ACC_PUBLIC);
// create code
Code code = new Code(getterMethod);
getterMethod.setCode(code);
Instructions instructions = code.getInstructions();
List<Instruction> ins = instructions.getInstructions();
if (field.isStatic())
{
code.setMaxStack(1);
ins.add(new GetStatic(instructions, field.getPoolField()));
}
else
{
code.setMaxStack(2);
ins.add(new ALoad(instructions, 0));
ins.add(new GetField(instructions, field.getPoolField()));
}
if (getter != null)
{
code.setMaxStack(2);
assert getter instanceof Integer || getter instanceof Long;
if (getter instanceof Integer)
{
ins.add(new LDC(instructions, (int) getter));
ins.add(new IMul(instructions));
}
else
{
ins.add(new LDC(instructions, (long) getter));
ins.add(new LMul(instructions));
}
}
InstructionType returnType;
if (field.getType().isPrimitive() && field.getType().getDimensions() == 0)
{
switch (field.getType().toString())
{
case "B":
case "C":
case "I":
case "S":
case "Z":
returnType = InstructionType.IRETURN;
break;
case "D":
returnType = InstructionType.DRETURN;
break;
case "F":
returnType = InstructionType.FRETURN;
break;
case "J":
returnType = InstructionType.LRETURN;
break;
default:
throw new RuntimeException("Unknown type");
}
}
else
{
returnType = InstructionType.ARETURN;
}
ins.add(new Return(instructions, returnType));
clazz.addMethod(getterMethod);
++injectedGetters;
}
public int getInjectedGetters()
{
return injectedGetters;
}
}

View File

@@ -0,0 +1,384 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import com.google.common.collect.Lists;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
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.instruction.types.DupInstruction;
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
import net.runelite.asm.attributes.code.instructions.ArrayStore;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.attributes.code.instructions.Dup;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.PutField;
import net.runelite.asm.attributes.code.instructions.Swap;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.signature.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InjectHook
{
private static final Logger logger = LoggerFactory.getLogger(InjectHook.class);
static class HookInfo
{
String fieldName;
String clazz;
Method method;
boolean before;
}
private static final String HOOK_METHOD_SIGNATURE = "(I)V";
private static final String CLINIT = "<clinit>";
private final Inject inject;
private final Map<Field, HookInfo> hooked = new HashMap<>();
private int injectedHooks;
public InjectHook(Inject inject)
{
this.inject = inject;
}
void hook(Field field, HookInfo hookInfo)
{
hooked.put(field, hookInfo);
}
public void run()
{
Execution e = new Execution(inject.getVanilla());
e.populateInitialMethods();
Set<Instruction> done = new HashSet<>();
Set<Instruction> doneIh = new HashSet<>();
e.addExecutionVisitor((InstructionContext ic) ->
{
Instruction i = ic.getInstruction();
Instructions ins = i.getInstructions();
Code code = ins.getCode();
Method method = code.getMethod();
if (method.getName().equals(CLINIT))
{
return;
}
if (!(i instanceof SetFieldInstruction))
{
return;
}
if (!done.add(i))
{
return;
}
SetFieldInstruction sfi = (SetFieldInstruction) i;
Field fieldBeingSet = sfi.getMyField();
if (fieldBeingSet == null)
{
return;
}
HookInfo hookInfo = hooked.get(fieldBeingSet);
if (hookInfo == null)
{
return;
}
String hookName = hookInfo.fieldName;
assert hookName != null;
logger.trace("Found injection location for hook {} at instruction {}", hookName, sfi);
++injectedHooks;
StackContext value = ic.getPops().get(0);
StackContext objectStackContext = null;
if (sfi instanceof PutField)
{
StackContext objectStack = ic.getPops().get(1); // Object being set on
objectStackContext = objectStack;
}
int idx = ins.getInstructions().indexOf(sfi);
assert idx != -1;
try
{
if (hookInfo.before)
{
injectCallbackBefore(ins, idx, hookInfo, null, objectStackContext, value);
}
else
{
// idx + 1 to insert after the set
injectCallback(ins, idx + 1, hookInfo, null, objectStackContext);
}
}
catch (InjectionException ex)
{
throw new RuntimeException(ex);
}
});
// these look like:
// getfield
// iload_0
// iconst_0
// iastore
e.addExecutionVisitor((InstructionContext ic) ->
{
Instruction i = ic.getInstruction();
Instructions ins = i.getInstructions();
Code code = ins.getCode();
Method method = code.getMethod();
if (method.getName().equals(CLINIT))
{
return;
}
if (!(i instanceof ArrayStore))
{
return;
}
if (!doneIh.add(i))
{
return;
}
ArrayStore as = (ArrayStore) i;
Field fieldBeingSet = as.getMyField(ic);
if (fieldBeingSet == null)
{
return;
}
HookInfo hookInfo = hooked.get(fieldBeingSet);
if (hookInfo == null)
{
return;
}
String hookName = hookInfo.fieldName;
StackContext value = ic.getPops().get(0);
StackContext index = ic.getPops().get(1);
StackContext arrayReference = ic.getPops().get(2);
InstructionContext arrayReferencePushed = arrayReference.getPushed();
StackContext objectStackContext = null;
if (arrayReferencePushed.getInstruction().getType() == InstructionType.GETFIELD)
{
StackContext objectReference = arrayReferencePushed.getPops().get(0);
objectStackContext = objectReference;
}
// inject hook after 'i'
logger.info("Found array injection location for hook {} at instruction {}", hookName, i);
++injectedHooks;
int idx = ins.getInstructions().indexOf(i);
assert idx != -1;
try
{
if (hookInfo.before)
{
injectCallbackBefore(ins, idx, hookInfo, index, objectStackContext, value);
}
else
{
injectCallback(ins, idx + 1, hookInfo, index, objectStackContext);
}
}
catch (InjectionException ex)
{
throw new RuntimeException(ex);
}
});
e.run();
}
private void injectCallbackBefore(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext object, StackContext value) throws InjectionException
{
Signature signature = hookInfo.method.getDescriptor();
Type methodArgumentType = signature.getTypeOfArg(0);
if (!hookInfo.method.isStatic())
{
if (object == null)
{
throw new InjectionException("null object");
}
ins.getInstructions().add(idx++, new Dup(ins)); // dup value
idx = recursivelyPush(ins, idx, object);
ins.getInstructions().add(idx++, new Swap(ins));
if (!value.type.equals(methodArgumentType))
{
CheckCast checkCast = new CheckCast(ins);
checkCast.setType(methodArgumentType);
ins.getInstructions().add(idx++, checkCast);
}
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
InvokeVirtual invoke = new InvokeVirtual(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
signature
)
);
ins.getInstructions().add(idx++, invoke);
}
else
{
ins.getInstructions().add(idx++, new Dup(ins)); // dup value
if (!value.type.equals(methodArgumentType))
{
CheckCast checkCast = new CheckCast(ins);
checkCast.setType(methodArgumentType);
ins.getInstructions().add(idx++, checkCast);
}
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
InvokeStatic invoke = new InvokeStatic(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
signature
)
);
ins.getInstructions().add(idx++, invoke);
}
}
private int recursivelyPush(Instructions ins, int idx, StackContext sctx)
{
InstructionContext ctx = sctx.getPushed();
if (ctx.getInstruction() instanceof DupInstruction)
{
DupInstruction dupInstruction = (DupInstruction) ctx.getInstruction();
sctx = dupInstruction.getOriginal(sctx);
ctx = sctx.getPushed();
}
for (StackContext s : Lists.reverse(ctx.getPops()))
{
idx = recursivelyPush(ins, idx, s);
}
ins.getInstructions().add(idx++, ctx.getInstruction().clone());
return idx;
}
private void injectCallback(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext objectPusher) throws InjectionException
{
if (!hookInfo.method.isStatic())
{
if (objectPusher == null)
{
throw new InjectionException("Null object pusher");
}
idx = recursivelyPush(ins, idx, objectPusher);
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
else
{
ins.getInstructions().add(idx++, new LDC(ins, -1));
}
InvokeVirtual invoke = new InvokeVirtual(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
new Signature(HOOK_METHOD_SIGNATURE)
)
);
ins.getInstructions().add(idx++, invoke);
}
else
{
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
else
{
ins.getInstructions().add(idx++, new LDC(ins, -1));
}
InvokeStatic invoke = new InvokeStatic(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
new Signature(HOOK_METHOD_SIGNATURE)
)
);
ins.getInstructions().add(idx++, invoke);
}
}
public int getInjectedHooks()
{
return injectedHooks;
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotations;
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.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InjectHookMethod
{
private static final Logger logger = LoggerFactory.getLogger(InjectHookMethod.class);
public static final String HOOKS = "net/runelite/client/callback/Hooks";
private final Inject inject;
public InjectHookMethod(Inject inject)
{
this.inject = inject;
}
public void process(Method method) throws InjectionException
{
Annotations an = method.getAnnotations();
if (an == null)
{
return;
}
Annotation a = an.find(DeobAnnotations.HOOK);
if (a == null)
{
return;
}
String hookName = a.getElement().getString();
boolean end = a.getElements().size() == 2 && a.getElements().get(1).getValue().equals(true);
inject(null, method, hookName, end, true);
}
public void inject(Method hookMethod, Method method, String name, boolean end, boolean useHooks) throws InjectionException
{
Annotations an = method.getAnnotations();
// Method is hooked
// Find equivalent method in vanilla, and insert callback at the beginning
ClassFile cf = method.getClassFile();
String obfuscatedMethodName = DeobAnnotations.getObfuscatedName(an),
obfuscatedClassName = DeobAnnotations.getObfuscatedName(cf.getAnnotations());
// might be a constructor
if (obfuscatedMethodName == null && method.getName().equals("<init>"))
{
obfuscatedMethodName = "<init>";
}
assert obfuscatedClassName != null : "hook on method in class with no obfuscated name";
assert obfuscatedMethodName != null : "hook on method with no obfuscated name";
Signature obfuscatedSignature = inject.getMethodSignature(method);
ClassGroup vanilla = inject.getVanilla();
ClassFile vanillaClass = vanilla.findClass(obfuscatedClassName);
Method vanillaMethod = vanillaClass.findMethod(obfuscatedMethodName, obfuscatedSignature);
assert method.isStatic() == vanillaMethod.isStatic();
// Insert instructions at beginning of method
injectHookMethod(hookMethod, name, end, method, vanillaMethod, useHooks);
}
private void injectHookMethod(Method hookMethod, String hookName, boolean end, Method deobMethod, Method vanillaMethod, boolean useHooks) throws InjectionException
{
Instructions instructions = vanillaMethod.getCode().getInstructions();
Signature.Builder builder = new Signature.Builder()
.setReturnType(Type.VOID); // Hooks always return void
for (Type type : deobMethod.getDescriptor().getArguments())
{
builder.addArgument(inject.deobfuscatedTypeToApiType(type));
}
assert deobMethod.isStatic() == vanillaMethod.isStatic();
boolean modifiedSignature = false;
if (!deobMethod.isStatic() && useHooks)
{
// Add variable to signature
builder.addArgument(0, inject.deobfuscatedTypeToApiType(new Type(deobMethod.getClassFile().getName())));
modifiedSignature = true;
}
Signature signature = builder.build();
List<Integer> insertIndexes = findHookLocations(hookName, end, vanillaMethod);
insertIndexes.sort((a, b) -> Integer.compare(b, a));
for (int insertPos : insertIndexes)
{
if (!deobMethod.isStatic())
{
instructions.addInstruction(insertPos++, new ALoad(instructions, 0));
}
int signatureStart = modifiedSignature ? 1 : 0;
int index = deobMethod.isStatic() ? 0 : 1; // current variable index
for (int i = signatureStart; i < signature.size(); ++i)
{
Type type = signature.getTypeOfArg(i);
Instruction load = inject.createLoadForTypeIndex(instructions, type, index);
instructions.addInstruction(insertPos++, load);
index += type.getSize();
}
InvokeInstruction invoke;
// use old Hooks callback
if (useHooks)
{
// Invoke callback
invoke = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(HOOKS),
hookName,
signature
)
);
}
else
{
// Invoke methodhook
assert hookMethod != null;
if (vanillaMethod.isStatic())
{
invoke = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("client"), // Static methods are in client
hookMethod.getName(),
signature
)
);
}
else
{
// otherwise invoke member function
//instructions.addInstruction(insertPos++, new ALoad(instructions, 0));
invoke = new InvokeVirtual(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(vanillaMethod.getClassFile().getName()),
hookMethod.getName(),
signature
)
);
}
}
instructions.addInstruction(insertPos++, (Instruction) invoke);
}
logger.info("Injected method hook {} in {} with {} args: {}",
hookName, vanillaMethod, signature.size(),
signature.getArguments());
}
private List<Integer> findHookLocations(String hookName, boolean end, Method vanillaMethod) throws InjectionException
{
Instructions instructions = vanillaMethod.getCode().getInstructions();
if (end)
{
// find return
List<Instruction> returns = instructions.getInstructions().stream()
.filter(i -> i instanceof ReturnInstruction)
.collect(Collectors.toList());
List<Integer> indexes = new ArrayList<>();
for (Instruction ret : returns)
{
int idx = instructions.getInstructions().indexOf(ret);
assert idx != -1;
indexes.add(idx);
}
return indexes;
}
if (!vanillaMethod.getName().equals("<init>"))
{
return Arrays.asList(0);
}
// Find index after invokespecial
for (int i = 0; i < instructions.getInstructions().size(); ++i)
{
Instruction in = instructions.getInstructions().get(i);
if (in.getType() == InstructionType.INVOKESPECIAL)
{
return Arrays.asList(i + 1); // one after
}
}
throw new IllegalStateException("constructor with no invokespecial");
}
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.Code;
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.ALoad;
import net.runelite.asm.attributes.code.instructions.BiPush;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.attributes.code.instructions.DLoad;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.LLoad;
import net.runelite.asm.attributes.code.instructions.Return;
import net.runelite.asm.attributes.code.instructions.SiPush;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import static net.runelite.deob.DeobAnnotations.EXPORT;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InjectInvoker
{
private static final Logger logger = LoggerFactory.getLogger(InjectInvoker.class);
private final Inject inject;
private int injectedInvokers;
public InjectInvoker(Inject inject)
{
this.inject = inject;
}
/**
* Inject an invoker for a method
*
* @param m Method in the deobfuscated client to inject an invoker for
* @param other Class in the vanilla client of the same class m is a
* member of
* @param implementingClass Java class for the API interface the class
* will implement
*/
public void process(Method m, ClassFile other, java.lang.Class<?> implementingClass)
{
Annotations an = m.getAnnotations();
if (an == null || an.find(EXPORT) == null)
{
return; // not an exported method
}
String exportedName = DeobAnnotations.getExportedName(an);
String obfuscatedName = DeobAnnotations.getObfuscatedName(an);
if (obfuscatedName == null)
{
obfuscatedName = m.getName();
}
String garbage = DeobAnnotations.getObfuscatedValue(m);
Method otherm = other.findMethod(obfuscatedName, inject.getMethodSignature(m));
assert otherm != null;
assert m.isStatic() == otherm.isStatic();
ClassGroup vanilla = inject.getVanilla();
ClassFile targetClass = m.isStatic() ? vanilla.findClass("client") : other;
// Place into implementing class, unless the method is static
java.lang.Class<?> targetClassJava = m.isStatic() ? Inject.CLIENT_CLASS : implementingClass;
if (targetClassJava == null)
{
assert !m.isStatic();
// non static exported method on non exported interface, weird.
// logger.debug("Non static exported method {} on non exported interface", exportedName);
return;
}
java.lang.reflect.Method apiMethod = inject.findImportMethodOnApi(targetClassJava, exportedName, null); // api method to invoke 'otherm'
if (apiMethod == null)
{
// logger.debug("Unable to find api method on {} with imported name {}, not injecting invoker", targetClassJava, exportedName);
return;
}
injectInvoker(targetClass, apiMethod, m, otherm, garbage);
++injectedInvokers;
}
private void injectInvoker(ClassFile clazz, java.lang.reflect.Method method, Method deobfuscatedMethod, Method invokeMethod, String garbage)
{
// clazz = clazz to add invoker to
// method = api method to override
// deobfuscatedMethod = deobfuscated method, used to get the deobfuscated signature
// invokeMethod = method to invoke, obfuscated
if (clazz.findMethod(method.getName(), deobfuscatedMethod.getDescriptor()) != null)
{
logger.warn("Not injecting method {} because it already exists!", method);
return; // this can happen from exporting a field and method with the same name
}
assert invokeMethod.isStatic() == deobfuscatedMethod.isStatic();
assert invokeMethod.isStatic() || invokeMethod.getClassFile() == clazz;
Type lastGarbageArgumentType = null;
if (deobfuscatedMethod.getDescriptor().getArguments().size() != invokeMethod.getDescriptor().getArguments().size())
{
// allow for obfuscated method to have a single bogus signature at the end
assert deobfuscatedMethod.getDescriptor().size() + 1 == invokeMethod.getDescriptor().size();
List<Type> arguments = invokeMethod.getDescriptor().getArguments();
lastGarbageArgumentType = arguments.get(arguments.size() - 1);
}
// Injected method signature is always the same as the API
Signature apiSignature = inject.javaMethodToSignature(method);
Method invokerMethodSignature = new Method(clazz, method.getName(), apiSignature);
invokerMethodSignature.setAccessFlags(ACC_PUBLIC);
// create code attribute
Code code = new Code(invokerMethodSignature);
invokerMethodSignature.setCode(code);
Instructions instructions = code.getInstructions();
List<Instruction> ins = instructions.getInstructions();
code.setMaxStack(1 + invokeMethod.getDescriptor().size()); // this + arguments
// load function arguments onto the stack.
int index = 0;
if (!invokeMethod.isStatic())
{
ins.add(new ALoad(instructions, index++)); // this
}
else
{
++index; // this method is always non static
}
for (int i = 0; i < deobfuscatedMethod.getDescriptor().size(); ++i)
{
Type type = deobfuscatedMethod.getDescriptor().getTypeOfArg(i);
Instruction loadInstruction = inject.createLoadForTypeIndex(instructions, type, index);
ins.add(loadInstruction);
Signature invokeDesc = invokeMethod.getDescriptor();
Type obType = invokeDesc.getTypeOfArg(i);
if (!type.equals(obType))
{
CheckCast checkCast = new CheckCast(instructions);
checkCast.setType(obType);
ins.add(checkCast);
}
if (loadInstruction instanceof DLoad || loadInstruction instanceof LLoad)
{
index += 2;
}
else
{
index += 1;
}
}
if (lastGarbageArgumentType != null)
{
// 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.toString())
{
case "Z":
case "B":
case "C":
ins.add(new BiPush(instructions, Byte.parseByte(garbage)));
break;
case "S":
ins.add(new SiPush(instructions, Short.parseShort(garbage)));
break;
case "I":
ins.add(new LDC(instructions, Integer.parseInt(garbage)));
break;
case "D":
ins.add(new LDC(instructions, Double.parseDouble(garbage)));
break;
case "F":
ins.add(new LDC(instructions, Float.parseFloat(garbage)));
break;
case "J":
ins.add(new LDC(instructions, Long.parseLong(garbage)));
break;
default:
throw new RuntimeException("Unknown type");
}
}
if (invokeMethod.isStatic())
{
ins.add(new InvokeStatic(instructions, invokeMethod.getPoolMethod()));
}
else
{
ins.add(new InvokeVirtual(instructions, invokeMethod.getPoolMethod()));
}
Type returnValue = invokeMethod.getDescriptor().getReturnValue();
InstructionType returnType;
if (returnValue.isPrimitive() && returnValue.getDimensions() == 0)
{
switch (returnValue.toString())
{
case "Z":
case "I":
returnType = InstructionType.IRETURN;
break;
case "J":
returnType = InstructionType.LRETURN;
break;
case "F":
returnType = InstructionType.FRETURN;
break;
case "D":
returnType = InstructionType.DRETURN;
break;
case "V":
returnType = InstructionType.RETURN;
break;
default:
assert false;
return;
}
}
else
{
returnType = InstructionType.ARETURN;
}
ins.add(new Return(instructions, returnType));
clazz.addMethod(invokerMethodSignature);
}
public int getInjectedInvokers()
{
return injectedInvokers;
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.deob.clientver.ClientVersion;
import net.runelite.deob.util.JarUtil;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
@Mojo(
name = "runelite-injector",
defaultPhase = LifecyclePhase.GENERATE_RESOURCES
)
public class InjectMojo extends AbstractMojo
{
@Parameter(defaultValue = "${project.build.outputDirectory}")
private File outputDirectory;
@Parameter(defaultValue = "./rs-client/target/rs-client-1.5.27-SNAPSHOT.jar", readonly = true, required = true)
private String rsClientPath;
@Parameter(defaultValue = "${net.runelite.rs:vanilla:jar}", readonly = true, required = true)
private String vanillaPath;
private final Log log = getLog();
@Override
public void execute() throws MojoExecutionException, MojoFailureException
{
ClientVersion ver = new ClientVersion(new File(vanillaPath));
int version;
try
{
version = ver.getVersion();
}
catch (IOException ex)
{
throw new MojoExecutionException("Unable to read vanilla client version", ex);
}
log.info("Vanilla client version " + version);
ClassGroup rs;
ClassGroup vanilla;
try
{
rs = JarUtil.loadJar(new File(rsClientPath));
vanilla = JarUtil.loadJar(new File(vanillaPath));
}
catch (IOException ex)
{
throw new MojoExecutionException("Unable to load dependency jars", ex);
}
Injector injector = new Injector(rs, vanilla);
try
{
injector.inject();
}
catch (InjectionException ex)
{
throw new MojoExecutionException("Error injecting client", ex);
}
InjectorValidator iv = new InjectorValidator(vanilla);
iv.validate();
if (iv.getError() > 0)
{
throw new MojoExecutionException("Error building injected jar");
}
if (iv.getMissing() > 0)
{
throw new MojoExecutionException("Unable to inject all methods");
}
try
{
writeClasses(vanilla, outputDirectory);
}
catch (IOException ex)
{
throw new MojoExecutionException("Unable to write classes", ex);
}
log.info("Injector wrote " + vanilla.getClasses().size() + " classes, " + iv.getOkay() + " injected methods");
}
private void writeClasses(ClassGroup group, File outputDirectory) throws IOException
{
for (ClassFile cf : group.getClasses())
{
File classFile = getClassFile(outputDirectory, cf);
byte[] classData = JarUtil.writeClass(group, cf);
try (FileOutputStream fout = new FileOutputStream(classFile, false))
{
fout.write(classData);
}
}
}
private File getClassFile(File base, ClassFile cf)
{
File f = base;
String[] parts = cf.getName().split("/");
for (int i = 0; i < parts.length - 1; ++i)
{
String part = parts[i];
f = new File(f, part);
}
f.mkdirs();
f = new File(f, parts[parts.length - 1] + ".class");
return f;
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.attributes.code.instructions.PutField;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import net.runelite.asm.attributes.code.instructions.VReturn;
import net.runelite.asm.signature.Signature;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InjectSetter
{
private static final Logger logger = LoggerFactory.getLogger(InjectSetter.class);
private final Inject inject;
private int injectedSetters;
public InjectSetter(Inject inject)
{
this.inject = inject;
}
/**
* inject a setter into the vanilla classgroup
*
* @param targetClass Class where to inject the setter (field's class,
* or client)
* @param targetApiClass API targetClass implements, which may have the
* setter declared
* @param field Field of vanilla that will be set
* @param exportedName exported name of field
* @param setter
*/
public void injectSetter(ClassFile targetClass, Class<?> targetApiClass, Field field, String exportedName, Number setter)
{
java.lang.reflect.Method method = inject.findImportMethodOnApi(targetApiClass, exportedName, true);
if (method == null)
{
logger.warn("Setter injection for field {} but an API method was not found on {}", exportedName, targetApiClass);
return;
}
if (method.getParameterCount() != 1)
{
logger.warn("Setter {} with not parameter count != 1?", exportedName);
return;
}
logger.info("Injecting setter for {} on {}", exportedName, targetApiClass);
assert targetClass.findMethod(method.getName()) == null;
assert field.isStatic() || field.getClassFile() == targetClass;
Signature sig = new Signature.Builder()
.setReturnType(Type.VOID)
.addArgument(Inject.classToType(method.getParameterTypes()[0]))
.build();
Method setterMethod = new Method(targetClass, method.getName(), sig);
setterMethod.setAccessFlags(ACC_PUBLIC);
targetClass.addMethod(setterMethod);
++injectedSetters;
Code code = new Code(setterMethod);
setterMethod.setCode(code);
Instructions instructions = code.getInstructions();
List<Instruction> ins = instructions.getInstructions();
// load this
if (!field.isStatic())
{
ins.add(new ALoad(instructions, 0));
}
// load argument
Type argumentType = sig.getTypeOfArg(0);
ins.add(inject.createLoadForTypeIndex(instructions, argumentType, 1));
// cast argument to field type
Type fieldType = field.getType();
if (!argumentType.equals(fieldType))
{
CheckCast checkCast = new CheckCast(instructions);
checkCast.setType(fieldType);
ins.add(checkCast);
}
if (setter != null)
{
assert setter instanceof Integer || setter instanceof Long;
if (setter instanceof Integer)
{
ins.add(new LDC(instructions, (int) setter));
ins.add(new IMul(instructions));
}
else
{
ins.add(new LDC(instructions, (long) setter));
ins.add(new LMul(instructions));
}
}
if (field.isStatic())
{
ins.add(new PutStatic(instructions, field));
}
else
{
ins.add(new PutField(instructions, field));
}
ins.add(new VReturn(instructions));
}
public int getInjectedSetters()
{
return injectedSetters;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
public class InjectionException extends Exception
{
public InjectionException(String message)
{
super(message);
}
public InjectionException(Throwable cause)
{
super(cause);
}
public InjectionException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.io.File;
import java.io.IOException;
import net.runelite.asm.ClassGroup;
import net.runelite.deob.util.JarUtil;
public class Injector
{
private final ClassGroup deobfuscated, vanilla;
public Injector(ClassGroup deobfuscated, ClassGroup vanilla)
{
this.deobfuscated = deobfuscated;
this.vanilla = vanilla;
}
public void inject() throws InjectionException
{
Inject instance = new Inject(deobfuscated, vanilla);
instance.run();
}
public void save(File out) throws IOException
{
JarUtil.saveJar(vanilla, out);
}
public static void main(String[] args) throws IOException, InjectionException
{
if (args.length < 3)
{
System.exit(-1);
}
ClassGroup deobfuscated = JarUtil.loadJar(new File(args[0]));
ClassGroup vanilla = JarUtil.loadJar(new File(args[1]));
Injector u = new Injector(
deobfuscated,
vanilla
);
u.inject();
InjectorValidator iv = new InjectorValidator(vanilla);
iv.validate();
u.save(new File(args[2]));
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.signature.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Verifies the injected jar is valid
*
* @author Adam
*/
public class InjectorValidator
{
private static final Logger logger = LoggerFactory.getLogger(InjectorValidator.class);
private static final String API_PACKAGE_BASE = "net/runelite/rs/api/";
private final ClassGroup group;
private int error, missing, okay;
public InjectorValidator(ClassGroup group)
{
this.group = group;
}
public void validate()
{
for (ClassFile cf : group.getClasses())
{
validate(cf);
}
logger.info("{} overridden methods, {} missing", okay, missing);
}
private void validate(ClassFile cf)
{
// find methods of the interface not implemented in the class
for (net.runelite.asm.pool.Class clazz : cf.getInterfaces().getInterfaces())
{
if (!clazz.getName().startsWith(API_PACKAGE_BASE))
{
continue;
}
Class<?> c;
try
{
c = Class.forName(clazz.getName().replace('/', '.'));
}
catch (ClassNotFoundException ex)
{
logger.warn(null, ex);
continue;
}
if (cf.isAbstract())
{
// Abstract classes don't have to implement anything
continue;
}
for (Method method : c.getMethods())
{
if (method.isSynthetic() || method.isDefault())
{
continue;
}
// could check method signature here too but it is
// annoying to deal with both runelite api and java
// reflection api
if (cf.findMethodDeep(method.getName()) == null)
{
logger.warn("Class {} implements interface {} but not does implement method {}",
cf.getName(), c.getSimpleName(), method);
++missing;
}
else
{
++okay;
}
}
}
Set<NameAndSignature> signatures = new HashSet<>();
for (net.runelite.asm.Method method : cf.getMethods())
{
NameAndSignature nas = new NameAndSignature(method.getName(), method.getDescriptor());
if (signatures.contains(nas))
{
logger.error("Class {} has duplicate method with same name and signature {} {}",
cf.getName(), method.getName(), method.getDescriptor());
++error;
}
signatures.add(nas);
}
}
public int getError()
{
return error;
}
public int getMissing()
{
return missing;
}
public int getOkay()
{
return okay;
}
static final class NameAndSignature
{
String name;
Signature signature;
NameAndSignature(String name, Signature signature)
{
this.name = name;
this.signature = signature;
}
@Override
public int hashCode()
{
int hash = 3;
hash = 67 * hash + Objects.hashCode(this.name);
hash = 67 * hash + Objects.hashCode(this.signature);
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final NameAndSignature other = (NameAndSignature) obj;
if (!Objects.equals(this.name, other.name))
{
return false;
}
if (!Objects.equals(this.signature, other.signature))
{
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,982 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import net.runelite.api.mixins.Mixin;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.attributes.code.instructions.GetField;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.InvokeDynamic;
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.Pop;
import net.runelite.asm.attributes.code.instructions.PutField;
import net.runelite.asm.signature.Signature;
import net.runelite.asm.visitors.ClassFileVisitor;
import net.runelite.deob.DeobAnnotations;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MixinInjector
{
private static final Logger logger = LoggerFactory.getLogger(MixinInjector.class);
private static final Type INJECT = new Type("Lnet/runelite/api/mixins/Inject;");
private static final Type SHADOW = new Type("Lnet/runelite/api/mixins/Shadow;");
private static final Type COPY = new Type("Lnet/runelite/api/mixins/Copy;");
private static final Type REPLACE = new Type("Lnet/runelite/api/mixins/Replace;");
private static final Type FIELDHOOK = new Type("Lnet/runelite/api/mixins/FieldHook;");
private static final Type METHODHOOK = new Type("Lnet/runelite/api/mixins/MethodHook;");
private static final Type JAVAX_INJECT = new Type("Ljavax/inject/Inject;");
private static final Type NAMED = new Type("Ljavax/inject/Named;");
private static final String MIXIN_BASE = "net.runelite.mixins";
private static final String ASSERTION_FIELD = "$assertionsDisabled";
private final Inject inject;
// field name -> Field of injected fields
private final Map<String, Field> injectedFields = new HashMap<>();
// Use net.runelite.asm.pool.Field instead of Field because the pool version has hashcode implemented
private final Map<net.runelite.asm.pool.Field, Field> shadowFields = new HashMap<>();
public MixinInjector(Inject inject)
{
this.inject = inject;
}
public void inject() throws InjectionException
{
ClassPath classPath;
try
{
classPath = ClassPath.from(this.getClass().getClassLoader());
}
catch (IOException ex)
{
throw new InjectionException(ex);
}
// key: mixin class
// value: mixin targets
Map<Class<?>, List<ClassFile>> mixinClasses = new HashMap<>();
// Find mixins and populate mixinClasses
for (ClassInfo classInfo : classPath.getTopLevelClasses(MIXIN_BASE))
{
Class<?> mixinClass = classInfo.load();
List<ClassFile> mixinTargets = new ArrayList<>();
for (Mixin mixin : mixinClass.getAnnotationsByType(Mixin.class))
{
Class<?> implementInto = mixin.value();
ClassFile targetCf = inject.findVanillaForInterface(implementInto);
if (targetCf == null)
{
throw new InjectionException("No class implements " + implementInto + " for mixin " + mixinClass);
}
mixinTargets.add(targetCf);
}
mixinClasses.put(mixinClass, mixinTargets);
}
inject(mixinClasses);
}
public void inject(Map<Class<?>, List<ClassFile>> mixinClasses) throws InjectionException
{
injectFields(mixinClasses);
findShadowFields(mixinClasses);
for (Class<?> mixinClass : mixinClasses.keySet())
{
try
{
for (ClassFile cf : mixinClasses.get(mixinClass))
{
// Make a new mixin ClassFile copy every time,
// so they don't share Code references
ClassFile mixinCf = loadClass(mixinClass);
injectMethods(mixinCf, cf, shadowFields);
}
}
catch (IOException ex)
{
throw new InjectionException(ex);
}
}
injectFieldHooks(mixinClasses);
injectMethodHooks(mixinClasses);
}
/**
* Finds fields that are marked @Inject and inject them into the target
*
* @param mixinClasses
* @throws InjectionException
*/
private void injectFields(Map<Class<?>, List<ClassFile>> mixinClasses) throws InjectionException
{
// Inject fields, and put them in injectedFields if they can be used by other mixins
for (Class<?> mixinClass : mixinClasses.keySet())
{
ClassFile mixinCf;
try
{
mixinCf = loadClass(mixinClass);
}
catch (IOException ex)
{
throw new InjectionException(ex);
}
List<ClassFile> targetCfs = mixinClasses.get(mixinClass);
for (ClassFile cf : targetCfs)
{
for (Field field : mixinCf.getFields())
{
// Always inject $assertionsEnabled if its missing.
if (ASSERTION_FIELD.equals(field.getName()))
{
if (cf.findField(ASSERTION_FIELD, Type.BOOLEAN) != null)
{
continue;
}
}
else
{
Annotation inject = field.getAnnotations().find(INJECT);
if (inject == null)
{
continue;
}
}
Field copy = new Field(cf, field.getName(), field.getType());
copy.setAccessFlags(field.getAccessFlags());
copy.setPublic();
copy.setValue(field.getValue());
Annotation jInject = field.getAnnotations().find(JAVAX_INJECT);
if (jInject != null)
{
copy.getAnnotations().addAnnotation(jInject);
logger.info("Added javax inject to {}.{}", cf.getClassName(), copy.getName());
Annotation named = field.getAnnotations().find(NAMED);
if (named != null)
{
copy.getAnnotations().addAnnotation(named);
logger.info("Added javax named to {}.{}", cf.getClassName(), copy.getName());
}
}
cf.addField(copy);
if (injectedFields.containsKey(field.getName()))
{
java.util.logging.Logger.getAnonymousLogger().severe("Duplicate field : "+ field.getName());
//throw new InjectionException("Injected field names must be globally unique");
}
injectedFields.put(field.getName(), copy);
}
}
}
}
/**
* Find fields which are marked @Shadow, and what they shadow
*
* @param mixinClasses
* @throws InjectionException
*/
private void findShadowFields(Map<Class<?>, List<ClassFile>> mixinClasses) throws InjectionException
{
// Find shadow fields
// Injected static fields take precedence when looking up shadowed fields
for (Class<?> mixinClass : mixinClasses.keySet())
{
ClassFile mixinCf;
try
{
mixinCf = loadClass(mixinClass);
}
catch (IOException ex)
{
throw new InjectionException(ex);
}
for (Field field : mixinCf.getFields())
{
Annotation shadow = field.getAnnotations().find(SHADOW);
if (shadow != null)
{
if (!field.isStatic())
{
throw new InjectionException("Can only shadow static fields");
}
String shadowName = shadow.getElement().getString(); // shadow this field
Field injectedField = injectedFields.get(shadowName);
if (injectedField != null)
{
// Shadow a field injected by a mixin
shadowFields.put(field.getPoolField(), injectedField);
}
else
{
// Shadow a field already in the gamepack
Field shadowField = findDeobField(shadowName);
if (shadowField == null)
{
throw new InjectionException("Shadow of nonexistent field " + shadowName);
}
Field obShadow = inject.toObField(shadowField);
assert obShadow != null;
shadowFields.put(field.getPoolField(), obShadow);
}
}
}
}
}
private ClassFile loadClass(Class<?> clazz) throws IOException
{
try (InputStream is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class"))
{
ClassReader reader = new ClassReader(is);
ClassFileVisitor cv = new ClassFileVisitor();
reader.accept(cv, 0);
return cv.getClassFile();
}
}
private Field findDeobField(String name)
{
for (ClassFile cf : inject.getDeobfuscated().getClasses())
{
for (Field f : cf.getFields())
{
if (f.getName().equals(name) && f.isStatic())
{
return f;
}
}
}
return null;
}
private void injectMethods(ClassFile mixinCf, ClassFile cf, Map<net.runelite.asm.pool.Field, Field> shadowFields)
throws InjectionException
{
// Keeps mappings between methods annotated with @Copy -> the copied method within the vanilla pack
Map<net.runelite.asm.pool.Method, CopiedMethod> copiedMethods = new HashMap<>();
// Handle the copy mixins first, so all other mixins know of the copies
for (Method method : mixinCf.getMethods())
{
Annotation copyAnnotation = method.getAnnotations().find(COPY);
if (copyAnnotation == null)
{
continue;
}
String deobMethodName = (String) copyAnnotation.getElement().getValue();
ClassFile deobCf = inject.toDeobClass(cf);
Method deobMethod = findDeobMethod(deobCf, deobMethodName, method.getDescriptor());
if (deobMethod == null)
{
throw new InjectionException("Failed to find the deob method " + deobMethodName + " for mixin " + mixinCf);
}
if (method.isStatic() != deobMethod.isStatic())
{
throw new InjectionException("Mixin method " + method + " should be " + (deobMethod.isStatic() ? "static" : "non-static"));
}
// Find the vanilla class where the method to copy is in
String obClassName = DeobAnnotations.getObfuscatedName(deobMethod.getClassFile().getAnnotations());
ClassFile obCf = inject.getVanilla().findClass(obClassName);
assert obCf != null : "unable to find vanilla class from obfuscated name " + obClassName;
String obMethodName = DeobAnnotations.getObfuscatedName(deobMethod.getAnnotations());
Signature obMethodSignature = DeobAnnotations.getObfuscatedSignature(deobMethod);
if (obMethodName == null)
{
obMethodName = deobMethod.getName();
}
if (obMethodSignature == null)
{
obMethodSignature = deobMethod.getDescriptor();
}
Method obMethod = obCf.findMethod(obMethodName, obMethodSignature);
if (obMethod == null)
{
throw new InjectionException("Failed to find the ob method " + obMethodName + " for mixin " + mixinCf);
}
if (method.getDescriptor().size() > obMethod.getDescriptor().size())
{
throw new InjectionException("Mixin methods cannot have more parameters than their corresponding ob method");
}
Method copy = new Method(cf, "copy$" + deobMethodName, obMethodSignature);
moveCode(copy, obMethod.getCode());
copy.setAccessFlags(obMethod.getAccessFlags());
copy.setPublic();
copy.getExceptions().getExceptions().addAll(obMethod.getExceptions().getExceptions());
copy.getAnnotations().getAnnotations().addAll(obMethod.getAnnotations().getAnnotations());
cf.addMethod(copy);
/*
If the desc for the mixin method and the desc for the ob method
are the same in length, assume that the mixin method is taking
care of the garbage parameter itself.
*/
boolean hasGarbageValue = method.getDescriptor().size() != obMethod.getDescriptor().size()
&& deobMethod.getDescriptor().size() < obMethodSignature.size();
copiedMethods.put(method.getPoolMethod(), new CopiedMethod(copy, hasGarbageValue));
logger.debug("Injected copy of {} to {}", obMethod, copy);
}
// Handle the rest of the mixin types
for (Method method : mixinCf.getMethods())
{
boolean isClinit = "<clinit>".equals(method.getName());
boolean isInit = "<init>".equals(method.getName());
boolean hasInject = method.getAnnotations().find(INJECT) != null;
// You can't annotate clinit, so its always injected
if ((hasInject && isInit) || isClinit)
{
if (!"()V".equals(method.getDescriptor().toString()))
{
throw new InjectionException("Injected constructors cannot have arguments");
}
Method[] originalMethods = cf.getMethods().stream()
.filter(n -> n.getName().equals(method.getName()))
.toArray(Method[]::new);
// If there isn't a <clinit> already just inject ours, otherwise rename it
// This is always true for <init>
String name = method.getName();
if (originalMethods.length > 0)
{
name = "rl$$" + (isInit ? "init" : "clinit");
}
String numberlessName = name;
for (int i = 1; cf.findMethod(name, method.getDescriptor()) != null; i++)
{
name = numberlessName + i;
}
Method copy = new Method(cf, name, method.getDescriptor());
moveCode(copy, method.getCode());
copy.setAccessFlags(method.getAccessFlags());
copy.setPrivate();
assert method.getExceptions().getExceptions().isEmpty();
// Remove the call to the superclass's ctor
if (isInit)
{
Instructions instructions = copy.getCode().getInstructions();
ListIterator<Instruction> listIter = instructions.getInstructions().listIterator();
for (; listIter.hasNext(); )
{
Instruction instr = listIter.next();
if (instr instanceof InvokeSpecial)
{
InvokeSpecial invoke = (InvokeSpecial) instr;
assert invoke.getMethod().getName().equals("<init>");
listIter.remove();
int pops = invoke.getMethod().getType().getArguments().size() + 1;
for (int i = 0; i < pops; i++)
{
listIter.add(new Pop(instructions));
}
break;
}
}
}
setOwnersToTargetClass(mixinCf, cf, copy, shadowFields, copiedMethods);
cf.addMethod(copy);
// Call our method at the return point of the matching method(s)
for (Method om : originalMethods)
{
Instructions instructions = om.getCode().getInstructions();
ListIterator<Instruction> listIter = instructions.getInstructions().listIterator();
for (; listIter.hasNext(); )
{
Instruction instr = listIter.next();
if (instr instanceof ReturnInstruction)
{
listIter.previous();
if (isInit)
{
listIter.add(new ALoad(instructions, 0));
listIter.add(new InvokeSpecial(instructions, copy.getPoolMethod()));
}
else if (isClinit)
{
listIter.add(new InvokeStatic(instructions, copy.getPoolMethod()));
}
listIter.next();
}
}
}
logger.debug("Injected mixin method {} to {}", copy, cf);
}
else if (hasInject)
{
// Make sure the method doesn't invoke copied methods
for (Instruction i : method.getCode().getInstructions().getInstructions())
{
if (i instanceof InvokeInstruction)
{
InvokeInstruction ii = (InvokeInstruction) i;
if (copiedMethods.containsKey(ii.getMethod()))
{
throw new InjectionException("Injected methods cannot invoke copied methods");
}
}
}
Method copy = new Method(cf, method.getName(), method.getDescriptor());
moveCode(copy, method.getCode());
copy.setAccessFlags(method.getAccessFlags());
copy.setPublic();
assert method.getExceptions().getExceptions().isEmpty();
setOwnersToTargetClass(mixinCf, cf, copy, shadowFields, copiedMethods);
cf.addMethod(copy);
logger.debug("Injected mixin method {} to {}", copy, cf);
}
else if (method.getAnnotations().find(REPLACE) != null)
{
Annotation replaceAnnotation = method.getAnnotations().find(REPLACE);
String deobMethodName = (String) replaceAnnotation.getElement().getValue();
ClassFile deobCf = inject.toDeobClass(cf);
Method deobMethod = findDeobMethod(deobCf, deobMethodName, method.getDescriptor());
if (deobMethod == null)
{
throw new InjectionException("Failed to find the deob method " + deobMethodName + " for mixin " + mixinCf);
}
if (method.isStatic() != deobMethod.isStatic())
{
throw new InjectionException("Mixin method " + method + " should be "
+ (deobMethod.isStatic() ? "static" : "non-static"));
}
String obMethodName = DeobAnnotations.getObfuscatedName(deobMethod.getAnnotations());
Signature obMethodSignature = DeobAnnotations.getObfuscatedSignature(deobMethod);
// Deob signature is the same as ob signature
if (obMethodName == null)
{
obMethodName = deobMethod.getName();
}
if (obMethodSignature == null)
{
obMethodSignature = deobMethod.getDescriptor();
}
// Find the vanilla class where the method to copy is in
String obClassName = DeobAnnotations.getObfuscatedName(deobMethod.getClassFile().getAnnotations());
ClassFile obCf = inject.getVanilla().findClass(obClassName);
Method obMethod = obCf.findMethod(obMethodName, obMethodSignature);
assert obMethod != null : "obfuscated method " + obMethodName + obMethodSignature + " does not exist";
if (method.getDescriptor().size() > obMethod.getDescriptor().size())
{
throw new InjectionException("Mixin methods cannot have more parameters than their corresponding ob method");
}
Type returnType = method.getDescriptor().getReturnValue();
Type deobReturnType = inject.apiTypeToDeobfuscatedType(returnType);
if (!returnType.equals(deobReturnType))
{
ClassFile deobReturnTypeClassFile = inject.getDeobfuscated()
.findClass(deobReturnType.getInternalName());
if (deobReturnTypeClassFile != null)
{
ClassFile obReturnTypeClass = inject.toObClass(deobReturnTypeClassFile);
Instructions instructions = method.getCode().getInstructions();
ListIterator<Instruction> listIter = instructions.getInstructions().listIterator();
for (; listIter.hasNext(); )
{
Instruction instr = listIter.next();
if (instr instanceof ReturnInstruction)
{
listIter.previous();
CheckCast checkCast = new CheckCast(instructions);
checkCast.setType(new Type(obReturnTypeClass.getName()));
listIter.add(checkCast);
listIter.next();
}
}
}
}
moveCode(obMethod, method.getCode());
boolean hasGarbageValue = method.getDescriptor().size() != obMethod.getDescriptor().size()
&& deobMethod.getDescriptor().size() < obMethodSignature.size();
if (hasGarbageValue)
{
int garbageIndex = obMethod.isStatic()
? obMethod.getDescriptor().size() - 1
: obMethod.getDescriptor().size();
/*
If the mixin method doesn't have the garbage parameter,
the compiler will have produced code that uses the garbage
parameter's local variable index for other things,
so we'll have to add 1 to all loads/stores to indices
that are >= garbageIndex.
*/
shiftLocalIndices(obMethod.getCode().getInstructions(), garbageIndex);
}
setOwnersToTargetClass(mixinCf, cf, obMethod, shadowFields, copiedMethods);
logger.debug("Replaced method {} with mixin method {}", obMethod, method);
}
}
}
private void moveCode(Method method, Code code)
{
Code newCode = new Code(method);
newCode.setMaxStack(code.getMaxStack());
newCode.getInstructions().getInstructions().addAll(code.getInstructions().getInstructions());
// Update instructions for each instruction
for (Instruction i : newCode.getInstructions().getInstructions())
{
i.setInstructions(newCode.getInstructions());
}
newCode.getExceptions().getExceptions().addAll(code.getExceptions().getExceptions());
for (net.runelite.asm.attributes.code.Exception e : newCode.getExceptions().getExceptions())
{
e.setExceptions(newCode.getExceptions());
}
method.setCode(newCode);
}
private void setOwnersToTargetClass(ClassFile mixinCf, ClassFile cf, Method method,
Map<net.runelite.asm.pool.Field, Field> shadowFields,
Map<net.runelite.asm.pool.Method, CopiedMethod> copiedMethods)
throws InjectionException
{
ListIterator<Instruction> iterator = method.getCode().getInstructions().getInstructions().listIterator();
while (iterator.hasNext())
{
Instruction i = iterator.next();
if (i instanceof InvokeInstruction)
{
InvokeInstruction ii = (InvokeInstruction) i;
CopiedMethod copiedMethod = copiedMethods.get(ii.getMethod());
if (copiedMethod != null)
{
ii.setMethod(copiedMethod.obMethod.getPoolMethod());
// Pass through garbage value if the method has one
if (copiedMethod.hasGarbageValue)
{
int garbageIndex = copiedMethod.obMethod.isStatic()
? copiedMethod.obMethod.getDescriptor().size() - 1
: copiedMethod.obMethod.getDescriptor().size();
iterator.previous();
iterator.add(new ILoad(method.getCode().getInstructions(), garbageIndex));
iterator.next();
}
}
else if (ii.getMethod().getClazz().getName().equals(mixinCf.getName()))
{
ii.setMethod(new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(cf.getName()),
ii.getMethod().getName(),
ii.getMethod().getType()
));
}
}
else if (i instanceof FieldInstruction)
{
FieldInstruction fi = (FieldInstruction) i;
Field shadowed = shadowFields.get(fi.getField());
if (shadowed != null)
{
fi.setField(shadowed.getPoolField());
}
else if (fi.getField().getClazz().getName().equals(mixinCf.getName()))
{
fi.setField(new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(cf.getName()),
fi.getField().getName(),
fi.getField().getType()
));
}
}
else if (i instanceof PushConstantInstruction)
{
PushConstantInstruction pi = (PushConstantInstruction) i;
if (mixinCf.getPoolClass().equals(pi.getConstant()))
{
pi.setConstant(cf.getPoolClass());
}
}
verify(mixinCf, i);
}
}
private void shiftLocalIndices(Instructions instructions, int startIdx)
{
for (Instruction i : instructions.getInstructions())
{
if (i instanceof LVTInstruction)
{
LVTInstruction lvti = (LVTInstruction) i;
if (lvti.getVariableIndex() >= startIdx)
{
lvti.setVariableIndex(lvti.getVariableIndex() + 1);
}
}
}
}
private Method findDeobMethod(ClassFile deobCf, String deobMethodName, Signature descriptor)
throws InjectionException
{
List<Method> matchingMethods = new ArrayList<>();
for (Method m : deobCf.getMethods())
{
if (!deobMethodName.equals(m.getName()))
{
continue;
}
Type returnType = inject.apiTypeToDeobfuscatedType(descriptor.getReturnValue());
Type returnType2 = m.getDescriptor().getReturnValue();
if (!returnType.equals(returnType2))
{
continue;
}
List<Type> args = descriptor.getArguments();
List<Type> args2 = m.getDescriptor().getArguments();
if (args.size() > args2.size())
{
continue;
}
boolean matchingArgs = true;
for (int i = 0; i < args.size(); i++)
{
Type type = inject.apiTypeToDeobfuscatedType(args.get(i));
Type type2 = args2.get(i);
if (!type.equals(type2))
{
matchingArgs = false;
break;
}
}
if (!matchingArgs)
{
continue;
}
matchingMethods.add(m);
}
if (matchingMethods.size() > 1)
{
// this happens when it has found several deob methods for some mixin method,
// to get rid of the error, refine your search by making your mixin method have more parameters
throw new InjectionException("There are several matching methods when there should only be one");
}
else if (matchingMethods.size() == 1)
{
return matchingMethods.get(0);
}
Method method = deobCf.findMethod(deobMethodName);
if (method == null)
{
// Look for static methods if an instance method couldn't be found
for (ClassFile deobCf2 : inject.getDeobfuscated().getClasses())
{
if (deobCf2 != deobCf)
{
method = deobCf2.findMethod(deobMethodName);
if (method != null)
{
break;
}
}
}
}
return method;
}
private void verify(ClassFile mixinCf, Instruction i) throws InjectionException
{
if (i instanceof FieldInstruction)
{
FieldInstruction fi = (FieldInstruction) i;
if (fi.getField().getClazz().getName().equals(mixinCf.getName()))
{
if (i instanceof PutField || i instanceof GetField)
{
throw new InjectionException("Access to non static member field of mixin");
}
Field field = fi.getMyField();
if (field != null && !field.isPublic())
{
throw new InjectionException("Static access to non public field " + field);
}
}
}
else if (i instanceof InvokeStatic)
{
InvokeStatic is = (InvokeStatic) i;
if (is.getMethod().getClazz() != mixinCf.getPoolClass()
&& is.getMethod().getClazz().getName().startsWith(MIXIN_BASE.replace(".", "/")))
{
throw new InjectionException("Invoking static methods of other mixins is not supported");
}
}
else if (i instanceof InvokeDynamic)
{
// RS classes don't verify under java 7+ due to the
// super() invokespecial being inside of a try{}
throw new InjectionException("Injected bytecode must be Java 6 compatible");
}
}
private void injectFieldHooks(Map<Class<?>, List<ClassFile>> mixinClasses) throws InjectionException
{
InjectHook injectHook = new InjectHook(inject);
for (Class<?> mixinClass : mixinClasses.keySet())
{
ClassFile mixinCf;
try
{
mixinCf = loadClass(mixinClass);
}
catch (IOException ex)
{
throw new InjectionException(ex);
}
List<ClassFile> targetCfs = mixinClasses.get(mixinClass);
for (ClassFile cf : targetCfs)
{
for (Method method : mixinCf.getMethods())
{
Annotation fieldHook = method.getAnnotations().find(FIELDHOOK);
if (fieldHook != null)
{
String hookName = fieldHook.getElement().getString();
boolean before = fieldHook.getElements().size() == 2 && fieldHook.getElements().get(1).getValue().equals(true);
ClassFile deobCf = inject.toDeobClass(cf);
Field targetField = deobCf.findField(hookName);
if (targetField == null)
{
// first try non static fields, then static
targetField = findDeobField(hookName);
}
if (targetField == null)
{
throw new InjectionException("Field hook for nonexistent field " + hookName + " on " + method);
}
Field obField = inject.toObField(targetField);
if (method.isStatic() != targetField.isStatic())
{
throw new InjectionException("Field hook method static flag must match target field");
}
// cf is the target class to invoke
InjectHook.HookInfo hookInfo = new InjectHook.HookInfo();
hookInfo.clazz = cf.getName();
hookInfo.fieldName = hookName;
hookInfo.method = method;
hookInfo.before = before;
injectHook.hook(obField, hookInfo);
}
}
}
}
injectHook.run();
logger.info("Injected {} field hooks", injectHook.getInjectedHooks());
}
private void injectMethodHooks(Map<Class<?>, List<ClassFile>> mixinClasses) throws InjectionException
{
InjectHookMethod injectHookMethod = new InjectHookMethod(inject);
for (Class<?> mixinClass : mixinClasses.keySet())
{
ClassFile mixinCf;
try
{
mixinCf = loadClass(mixinClass);
}
catch (IOException ex)
{
throw new InjectionException(ex);
}
List<ClassFile> targetCfs = mixinClasses.get(mixinClass);
for (ClassFile cf : targetCfs)
{
for (Method method : mixinCf.getMethods())
{
Annotation methodHook = method.getAnnotations().find(METHODHOOK);
if (methodHook == null)
{
continue;
}
String hookName = methodHook.getElement().getString();
boolean end = methodHook.getElements().size() == 2 && methodHook.getElements().get(1).getValue().equals(true);
ClassFile deobCf = inject.toDeobClass(cf);
Method targetMethod = findDeobMethod(deobCf, hookName, method.getDescriptor());
if (targetMethod == null)
{
throw new InjectionException("Method hook for nonexistent method " + hookName + " on " + method);
}
if (method.isStatic() != targetMethod.isStatic())
{
throw new InjectionException("Method hook static flag must match target - " + hookName);
}
injectHookMethod.inject(method, targetMethod, hookName, end, false);
}
}
}
}
private static class CopiedMethod
{
private Method obMethod;
private boolean hasGarbageValue;
private CopiedMethod(Method obMethod, boolean hasGarbageValue)
{
this.obMethod = obMethod;
this.hasGarbageValue = hasGarbageValue;
}
}
}

View File

@@ -0,0 +1,93 @@
package net.runelite.injector.raw;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.injector.Inject;
import net.runelite.injector.InjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClearColorBuffer
{
private static final Logger log = LoggerFactory.getLogger(ClearColorBuffer.class);
private static final net.runelite.asm.pool.Method clearBuffer = new net.runelite.asm.pool.Method(
new Class("net.runelite.client.callback.Hooks"),
"clearColorBuffer",
new Signature("(IIIII)V")
);
private final Inject inject;
public ClearColorBuffer(Inject inject)
{
this.inject = inject;
}
public void inject() throws InjectionException
{
injectColorBufferHooks();
}
private void injectColorBufferHooks() throws InjectionException
{
Method obmethod = findStaticMethod("drawEntities");
net.runelite.asm.pool.Method fillRectangle = findStaticMethod("Rasterizer2D_fillRectangle").getPoolMethod();
Instructions ins = obmethod.getCode().getInstructions();
replace(ins, fillRectangle);
}
private void replace(Instructions ins, net.runelite.asm.pool.Method meth)
{
List<Instruction> insList = new ArrayList<>();
for (Instruction i : ins.getInstructions())
{
if (i instanceof InvokeStatic)
{
if (((InvokeStatic) i).getMethod().equals(meth))
{
int index = ins.getInstructions().indexOf(i);
log.info("Found drawRectangle at index {}", index);
insList.add(i);
}
}
}
for (Instruction i : insList)
{
Instruction invoke = new InvokeStatic(ins, clearBuffer);
ins.replace(i, invoke);
}
}
private Method findStaticMethod(String name) throws InjectionException
{
for (ClassFile c : inject.getDeobfuscated().getClasses())
{
for (Method m : c.getMethods())
{
if (!m.getName().equals(name))
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(m.getAnnotations());
Signature obfuscatedSignature = DeobAnnotations.getObfuscatedSignature(m);
ClassFile c2 = inject.toObClass(c);
return c2.findMethod(obfuscatedName, (obfuscatedSignature != null) ? obfuscatedSignature : m.getDescriptor());
}
}
throw new InjectionException("Couldn't find static method " + name);
}
}

View File

@@ -0,0 +1,287 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector.raw;
import java.util.HashSet;
import java.util.ListIterator;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.injector.Inject;
import static net.runelite.injector.InjectHookMethod.HOOKS;
import net.runelite.injector.InjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DrawAfterWidgets
{
private static final Logger logger = LoggerFactory.getLogger(DrawAfterWidgets.class);
private final Inject inject;
public DrawAfterWidgets(Inject inject)
{
this.inject = inject;
}
public void inject() throws InjectionException
{
injectDrawAfterWidgets();
}
private void injectDrawAfterWidgets() throws InjectionException
{
/*
This call has to be injected using raw injection because the
drawWidgets method gets inlined in some revisions. If it wouldn't be,
mixins would be used to add the call to the end of drawWidgets.
--> This hook depends on the positions of "if (535573958 * kl != -1)" and "jz.db();".
Revision 180 - client.gs():
______________________________________________________
@Export("drawLoggedIn")
final void drawLoggedIn() {
if(rootWidgetGroup != -1) {
ClientPreferences.method1809(rootWidgetGroup);
}
int var1;
for(var1 = 0; var1 < rootWidgetCount; ++var1) {
if(__client_od[var1]) {
__client_ot[var1] = true;
}
__client_oq[var1] = __client_od[var1];
__client_od[var1] = false;
}
__client_oo = cycle;
__client_lq = -1;
__client_ln = -1;
UserComparator6.__fg_jh = null;
if(rootWidgetGroup != -1) {
rootWidgetCount = 0;
Interpreter.method1977(rootWidgetGroup, 0, 0, SoundCache.canvasWidth, Huffman.canvasHeight, 0, 0, -1);
}
< -- here appearantly
Rasterizer2D.Rasterizer2D_resetClip();
______________________________________________________
*/
boolean injected = false;
Method noClip = findStaticMethod("Rasterizer2D_resetClip"); // !!!!!
if (noClip == null)
{
throw new InjectionException("Mapped method \"Rasterizer2D_resetClip\" could not be found.");
}
net.runelite.asm.pool.Method poolNoClip = noClip.getPoolMethod();
for (ClassFile c : inject.getVanilla().getClasses())
{
for (Method m : c.getMethods())
{
if (m.getCode() == null)
{
continue;
}
Instructions instructions = m.getCode().getInstructions();
Set<Label> labels = new HashSet<>();
// Let's find "invokestatic <some class>.noClip()" and its label
ListIterator<Instruction> labelIterator = instructions.getInstructions().listIterator();
while (labelIterator.hasNext())
{
Instruction i = labelIterator.next();
if (!(i instanceof InvokeStatic))
{
continue;
}
InvokeStatic is = (InvokeStatic) i;
if (!is.getMethod().equals(poolNoClip))
{
continue;
}
labelIterator.previous();
Instruction i2 = labelIterator.previous();
labelIterator.next();
labelIterator.next();
// Find the label that marks the code path for the instruction
if (!(i2 instanceof Label))
{
continue;
}
// There can be several noClip invocations in a method, so let's catch them all
labels.add((Label) i2);
}
if (labels.isEmpty())
{
// If we get here, we're either in the wrong method
// or Jagex has removed the "if (535573958 * kl != -1)"
logger.debug("Could not find the label for jumping to the " + noClip + " call in " + m);
continue;
}
Set<Label> labelsToInjectAfter = new HashSet<>();
ListIterator<Instruction> jumpIterator = instructions.getInstructions().listIterator();
while (jumpIterator.hasNext())
{
Instruction i = jumpIterator.next();
if (!(i instanceof JumpingInstruction))
{
continue;
}
JumpingInstruction ji = (JumpingInstruction) i;
Label label = null;
for (Label l : labels)
{
if (ji.getJumps().contains(l))
{
label = l;
break;
}
}
if (label == null)
{
continue;
}
jumpIterator.previous();
Set<Instruction> insns = new HashSet<>();
insns.add(jumpIterator.previous());
insns.add(jumpIterator.previous());
insns.add(jumpIterator.previous());
insns.add(jumpIterator.previous());
// Get the iterator back to i's position
jumpIterator.next();
jumpIterator.next();
jumpIterator.next();
jumpIterator.next();
jumpIterator.next();
/*
Check that these instruction types are passed into the if-statement:
ICONST_M1
GETSTATIC client.kr : I
LDC 634425425
IMUL
We cannot depend on the order of these because of the obfuscation,
so let's make it easier by just checking that they are there.
*/
if (insns.stream().filter(i2 -> i2 instanceof PushConstantInstruction).count() != 2
|| insns.stream().filter(i2 -> i2 instanceof IMul).count() != 1
|| insns.stream().filter(i2 -> i2 instanceof GetStatic).count() != 1)
{
continue;
}
// At this point, we have found the real injection point
labelsToInjectAfter.add(label);
}
for (Label l : labelsToInjectAfter)
{
InvokeStatic invoke = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(HOOKS),
"drawAfterWidgets",
new Signature("()V")
)
);
instructions.addInstruction(instructions.getInstructions().indexOf(l) + 1, invoke);
logger.info("injectDrawAfterWidgets injected a call after " + l);
injected = true;
}
}
}
if (!injected)
{
throw new InjectionException("injectDrawAfterWidgets failed to inject!");
}
}
private Method findStaticMethod(String name)
{
for (ClassFile c : inject.getDeobfuscated().getClasses())
{
for (Method m : c.getMethods())
{
if (!m.getName().equals(name))
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(m.getAnnotations());
Signature obfuscatedSignature = DeobAnnotations.getObfuscatedSignature(m);
ClassFile c2 = inject.toObClass(c);
return c2.findMethod(obfuscatedName, (obfuscatedSignature != null) ? obfuscatedSignature : m.getDescriptor());
}
}
return null;
}
}

View File

@@ -0,0 +1,193 @@
/*
package net.runelite.injector.raw;
import com.google.common.base.Strings;
import java.util.HashSet;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IfACmpEq;
import net.runelite.asm.attributes.code.instructions.IfACmpNe;
import net.runelite.asm.attributes.code.instructions.IfEq;
import net.runelite.asm.attributes.code.instructions.IfNe;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Field;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.injector.Inject;
import net.runelite.injector.InjectionException;
public class DrawMenu
{
private final Inject inject;
private static final Field isMenuOpen = new Field(
new Class("Client"),
"isMenuOpen",
Type.BOOLEAN
);
private static final net.runelite.asm.pool.Method hook = new net.runelite.asm.pool.Method(
new Class("net.runelite.client.callback.Hooks"),
"drawMenu",
new Signature("()Z")
);
public DrawMenu(Inject inject)
{
this.inject =inject;
}
public void inject() throws InjectionException
{
Method drawLoggedIn = findDeobThing("drawLoggedIn", "Client", false);
Instructions ins = drawLoggedIn.getCode().getInstructions();
int menuOpenIdx = -1;
Field field = toObField(isMenuOpen).getPoolField();
for (Instruction i : ins.getInstructions())
{
if (!(i instanceof GetStatic))
{
continue;
}
if (((GetStatic) i).getField().equals(field))
{
menuOpenIdx = ins.getInstructions().indexOf(i);
}
}
if (menuOpenIdx == -1)
{
throw new InjectionException("Couldn't find the isMenuOpen check!");
}
// This is where the IFEQ or IFNE will be
final Instruction jump = ins.getInstructions().get(menuOpenIdx + 1);
// We want to inject if it's false so
if (jump instanceof IfEq)
{
// Not this one, but we gotta find out where the paths will intersect
Set<Label> labels = getLabels(jump);
}
}
private Set<Label> getLabels(Instruction i)
{
Set<Label> labels = new HashSet<>();
Execution ex = new Execution(inject.getVanilla());
ex.addMethod(i.getInstructions().getCode().getMethod());
ex.noInvoke = true;
ex.addExecutionVisitor((InstructionContext ic) ->
{
Instruction in = ic.getInstruction();
Instructions ins = in.getInstructions();
Code code = ins.getCode();
Method method = code.getMethod();
//ic.
});
}
private Set<Label> findLabels(Instructions ins, int idx)
{
Set<Label> labels = new HashSet<>();
Instruction i = null;
while (labels.size() < 10 || !(i instanceof ReturnInstruction))
{
i = ins.getInstructions().get(idx);
if (i instanceof JumpingInstruction)
{
Label cur = ((JumpingInstruction) i).getJumps().get(0);
labels.add((Label) cur);
idx = ins.getInstructions().indexOf(cur) + 1;
}
}
return labels;
}
private Method findDeobThing(String name, String hint, boolean isStatic) throws InjectionException
{
if (!Strings.isNullOrEmpty(hint))
{
ClassFile hintCf = inject.getDeobfuscated().findClass(hint);
if (hintCf != null)
{
for (Method m : hintCf.getMethods())
{
if (isStatic != m.isStatic())
{
continue;
}
if (!m.getName().equals(name))
{
continue;
}
Annotations an = m.getAnnotations();
if (an == null || an.find(DeobAnnotations.EXPORT) == null)
{
continue; // not an exported field
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(an);
return inject.toObClass(hintCf).findMethod(obfuscatedName);
}
}
}
for (ClassFile cf : inject.getDeobfuscated().getClasses())
{
for (Method m : cf.getMethods())
{
if (isStatic != m.isStatic())
{
continue;
}
Annotations an = m.getAnnotations();
if (an == null || an.find(DeobAnnotations.EXPORT) == null)
{
continue; // not an exported field
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(an);
return inject.toObClass(cf).findMethod(obfuscatedName);
}
}
throw new InjectionException("Method not found!");
}
private net.runelite.asm.Field toObField(Field field) throws InjectionException
{
ClassFile cf = inject.getDeobfuscated().findClass(field.getClazz().getName());
for (net.runelite.asm.Field f : cf.getFields())
{
if (f.getPoolField().equals(field))
{
return inject.toObField(f);
}
}
throw new InjectionException("Field not found!");
}
}
*/

View File

@@ -0,0 +1,400 @@
package net.runelite.injector.raw;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
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.ArrayStore;
import net.runelite.asm.attributes.code.instructions.GetField;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IALoad;
import net.runelite.asm.attributes.code.instructions.IAStore;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.IOr;
import net.runelite.asm.attributes.code.instructions.ISub;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.injector.Inject;
import net.runelite.injector.InjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RasterizerHook
{
private static final Logger logger = LoggerFactory.getLogger(ClearColorBuffer.class);
private static final int val = -16777216;
private static final String font_alpha = "AbstractFont_placeGlyphAlpha";
private static final String circle_alpha = "Rasterizer2D_drawCircleAlpha";
private static final String line_alpha = "Rasterizer2D_drawHorizontalLineAlpha";
private static final String line_alpha2 = "Rasterizer2D_drawVerticalLineAlpha";
private static final String more_alpha = "Rasterizer2D_moreAlpha";
private static final String r3d_vert = "Rasterizer3D_vertAlpha";
private static final String r3d_horiz = "Rasterizer3D_horizAlpha";
private static final String r3d_field = "Rasterizer3D_alpha";
private static final String font = "AbstractFont_placeGlyph";
private static final String rast3D = "Rasterizer3D_iDontKnow";
private static final String rast3D2 = "Rasterizer3D_textureAlpha";
private static final String sprite = "Sprite_something";
private static final String sprite2 = "Sprite_somethingElse";
private static final String sprite3 = "Sprite_anotherOne";
private static final String sprite4 = "Sprite_andAnotherOne";
private static final String indexedSprite = "IndexedSprite_something";
private static final String indexedSprite2 = "IndexedSprite_two";
private static final net.runelite.asm.pool.Method drawAlpha = new net.runelite.asm.pool.Method(
new Class("client"),
"drawAlpha",
new Signature("([IIII)V")
);
private final Inject inject;
private int count;
public RasterizerHook(Inject inject)
{
this.inject = inject;
}
public void inject() throws InjectionException
{
runDrawAlpha();
logger.info("Injected {} drawAlpha's", count);
count = 0;
runVars();
{
// throw new InjectionException("Not all variable alpha thingshits were found");
}
run();
}
private void runDrawAlpha() throws InjectionException
{
runR3DAlpha(r3d_horiz, 15, r3d_field);
runR3DAlpha(r3d_vert, 12, r3d_field);
runFontAlpha(font_alpha, 1, 9); // speshul cause 255 - var9
runAlpha(circle_alpha, 2, 4);
runAlpha(line_alpha, 1, 4);
runAlpha(line_alpha2, 1, 4);
runAlpha(more_alpha, 1, 5);
}
private void runR3DAlpha(String methodName, int req, String fieldName) throws InjectionException
{
Method meth = findStaticMethod(methodName);
Field field = findDeobField(fieldName);
Instructions ins = meth.getCode().getInstructions();
int varIdx = 0; // This is obviously dumb but I cba making this better
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : meth.findLVTInstructionsForVariable(varIdx))
{
indices.add(ins.getInstructions().indexOf(i));
}
if (indices.isEmpty())
{
throw new InjectionException("Couldn't find hook location in " + methodName);
}
int oldCount = count;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
ins.getInstructions().add(codeIndex, new ISub(ins, InstructionType.ISUB));
ins.getInstructions().add(codeIndex, new GetStatic(ins, field.getPoolField()));
ins.getInstructions().add(codeIndex, new LDC(ins, 255));
added++;
count++;
break;
}
}
}
}
private void runAlpha(String methodName, int req, int extraArg) throws InjectionException
{
final net.runelite.asm.pool.Field pixels = findDeobField("Rasterizer2D_pixels").getPoolField();
Method meth = findStaticMethod(methodName);
Instructions ins = meth.getCode().getInstructions();
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : ins.getInstructions())
{
if (!(i instanceof IALoad) && !(i instanceof GetField))
{
continue;
}
if (i instanceof GetField)
{
if (((GetField) i).getField().equals(pixels))
{
indices.add(ins.getInstructions().indexOf(i));
}
continue;
}
indices.add(ins.getInstructions().indexOf(i));
}
if (indices.isEmpty())
{
throw new InjectionException("Couldn't find hook location in " + methodName);
}
int oldCount = count;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
if (extraArg != -1)
{
ins.getInstructions().add(codeIndex, new ILoad(ins, extraArg));
added++;
}
count++;
break;
}
}
}
if (count - oldCount > req)
{
throw new InjectionException("Too many drawAlpha's were injected into " + methodName);
}
}
private void runFontAlpha(String methodName, int req, int extraArg) throws InjectionException
{
final net.runelite.asm.pool.Field pixels = findDeobField("Rasterizer2D_pixels").getPoolField();
Method meth = findStaticMethod(methodName);
Instructions ins = meth.getCode().getInstructions();
int varIdx = 0; // This is obviously dumb but I cba making this better
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : meth.findLVTInstructionsForVariable(varIdx))
{
indices.add(ins.getInstructions().indexOf(i));
}
if (indices.isEmpty())
{
throw new InjectionException("Couldn't find hook location in " + methodName);
}
int oldCount = count;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
ins.getInstructions().add(codeIndex, new ISub(ins, InstructionType.ISUB));
ins.getInstructions().add(codeIndex, new ILoad(ins, extraArg));
ins.getInstructions().add(codeIndex, new LDC(ins, 255));
added++;
count++;
break;
}
}
}
if (count - oldCount > req)
{
throw new InjectionException("Too many drawAlpha's were injected into " + methodName);
}
if (count == oldCount)
{
throw new InjectionException("Couldn't find any drawAlpha positions in " + methodName);
}
}
private void runVars() throws InjectionException
{
runOnMethodWithVar(rast3D, 0);
runOnMethodWithVar(rast3D2, 0);
// 36 expected
runOnMethodWithVar(font, 0);
// 5 expected
runOnMethodWithVar(sprite, 1);
runOnMethodWithVar(sprite2, 0);
runOnMethodWithVar(sprite3, 0);
runOnMethodWithVar(sprite4, 0);
// 12 expected
runOnMethodWithVar(indexedSprite, 0);
runOnMethodWithVar(indexedSprite2, 0);
// 6 expected
}
private void run() throws InjectionException
{
final int startCount = count; // Cause you can't just count shit ty
final net.runelite.asm.pool.Field pixels = findDeobField("Rasterizer2D_pixels").getPoolField();
Execution ex = new Execution(inject.getVanilla());
ex.populateInitialMethods();
Set<Instruction> done = new HashSet<>();
ex.addExecutionVisitor((InstructionContext ic) ->
{
Instruction i = ic.getInstruction();
Instructions ins = i.getInstructions();
Code code = ins.getCode();
Method method = code.getMethod();
logger.debug(i.toString());
if (!(i instanceof IAStore))
{
return;
}
if (!done.add(i))
{
return;
}
ArrayStore as = (ArrayStore) i;
Field fieldBeingSet = as.getMyField(ic);
if (fieldBeingSet == null)
{
return;
}
if (!fieldBeingSet.getPoolField().equals(pixels))
{
return;
}
int index = ins.getInstructions().indexOf(i);
if (!(ins.getInstructions().get(index - 1) instanceof ILoad) && !ic.getPops().get(0).getValue().isUnknownOrNull())
{
if ((int) ic.getPops().get(0).getValue().getValue() == 0)
{
logger.info("Didn't add hook in method {}.{}. {} added, {} total, value 0", method.getClassFile().getClassName(), method.getName(), count - startCount, count);
return;
}
}
ins.getInstructions().add(index, new IOr(ins, InstructionType.IOR)); // Add instructions backwards
ins.getInstructions().add(index, new LDC(ins, val));
count++;
logger.info("Added hook in method {}.{}. {} added, {} total", method.getClassFile().getClassName(), method.getName(), count - startCount, count);
});
ex.run();
}
private void runOnMethodWithVar(String meth, int varIndex) throws InjectionException
{
Method method = findStaticMethod(meth);
Instructions ins = method.getCode().getInstructions();
List<Integer> indices = new ArrayList<>();
for (Instruction i : method.findLVTInstructionsForVariable(varIndex))
{
int index = ins.getInstructions().indexOf(i);
assert index != -1;
assert ins.getInstructions().get(index + 1) instanceof ILoad; // Index in the array
indices.add(index);
}
int added = 0;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
added += 2;
count++;
ins.addInstruction(codeIndex, new IOr(ins, InstructionType.IOR)); // Add instructions backwards
ins.addInstruction(codeIndex, new LDC(ins, val));
break;
}
}
}
logger.info("Added {} instructions in {}. {} total", added >>> 1, meth, count);
}
private Method findStaticMethod(String name) throws InjectionException
{
for (ClassFile c : inject.getDeobfuscated().getClasses())
{
for (Method m : c.getMethods())
{
if (!m.getName().equals(name))
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(m.getAnnotations());
Signature obfuscatedSignature = DeobAnnotations.getObfuscatedSignature(m);
ClassFile c2 = inject.toObClass(c);
return c2.findMethod(obfuscatedName, (obfuscatedSignature != null) ? obfuscatedSignature : m.getDescriptor());
}
}
throw new InjectionException("Couldn't find static method " + name);
}
private Field findDeobField(String name) throws InjectionException
{
for (ClassFile c : inject.getDeobfuscated().getClasses())
{
for (Field f : c.getFields())
{
if (!f.getName().equals(name))
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(f.getAnnotations());
ClassFile c2 = inject.toObClass(c);
return c2.findField(obfuscatedName);
}
}
throw new InjectionException(String.format("Mapped field \"%s\" could not be found.", name));
}
}

View File

@@ -0,0 +1,92 @@
package net.runelite.injector.raw;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.injector.Inject;
import net.runelite.injector.InjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RenderDraw
{
private static final Logger log = LoggerFactory.getLogger(ClearColorBuffer.class);
private static final net.runelite.asm.pool.Method renderDraw = new net.runelite.asm.pool.Method(
new Class("net.runelite.client.callback.Hooks"),
"renderDraw",
new Signature("(Lapi/Renderable;IIIIIIIIJ)V")
);
private final Inject inject;
public RenderDraw(Inject inject)
{
this.inject = inject;
}
public void inject() throws InjectionException
{
injectColorBufferHooks();
}
private void injectColorBufferHooks() throws InjectionException
{
net.runelite.asm.Method obmethod = findObMethod("drawTile");
Method renderDraw = findObMethod("renderDraw").getPoolMethod();
Instructions ins = obmethod.getCode().getInstructions();
replace(ins, renderDraw);
}
private void replace(Instructions ins, net.runelite.asm.pool.Method meth)
{
List<Instruction> insList = new ArrayList<>();
int count = 0;
for (Instruction i : ins.getInstructions())
{
if (i instanceof InvokeVirtual)
{
if (((InvokeVirtual) i).getMethod().equals(meth))
{
int index = ins.getInstructions().indexOf(i);
count++;
log.info("Found renderDraw at index {}, {} found.", index, count);
insList.add(i);
}
}
}
for (Instruction i : insList)
{
Instruction invoke = new InvokeStatic(ins, renderDraw);
ins.replace(i, invoke);
}
}
private net.runelite.asm.Method findObMethod(String name) throws InjectionException
{
for (ClassFile cf : inject.getDeobfuscated().getClasses())
{
for (net.runelite.asm.Method m : cf.getMethods())
{
if (!m.getName().equals(name))
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(m.getAnnotations());
ClassFile c2 = inject.toObClass(cf);
return c2.findMethod(obfuscatedName);
}
}
throw new InjectionException(String.format("Method \"%s\" could not be found.", name));
}
}

View File

@@ -0,0 +1,353 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector.raw;
import java.util.HashSet;
import java.util.ListIterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.AStore;
import net.runelite.asm.attributes.code.instructions.Dup;
import net.runelite.asm.attributes.code.instructions.GetField;
import net.runelite.asm.attributes.code.instructions.IALoad;
import net.runelite.asm.attributes.code.instructions.IInc;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.IStore;
import net.runelite.asm.attributes.code.instructions.IfNe;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.PutField;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.deob.DeobAnnotations;
import net.runelite.injector.Inject;
import net.runelite.injector.InjectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ScriptVM
{
private static final Logger log = LoggerFactory.getLogger(ScriptVM.class);
private final Inject inject;
public ScriptVM(Inject inject)
{
this.inject = inject;
}
public void inject() throws InjectionException
{
injectScriptVMHooks();
}
private void injectScriptVMHooks() throws InjectionException
{
/*
This hooks local variable assignments in the copied version of runScript:
- The currently executing script > client.currentScript
- The currently executing script's program counter > client.currentScriptPC
- The currently executing opcode > client.vmExecuteOpcode(I)Z
The currently executing script variable is located as the outermost Script local
The PC is located by its use in PutField ScriptFrame::invokedFromPC
The currently executing opcode is found by searching for iaload with the script's instruction array
The script's instruction array is identified by looking for the getfield from script.instructions
bn.g @ rev 163 :
// Jump back to here if vmExecuteOpcode returns true
aload6 // Script.instructions
iinc 5 1 // ++PC
iload5 // PC
iaload
istore8
// <- Inject here
iload8
bipush 100
if_icmpge L52
*/
String scriptObName = DeobAnnotations.getObfuscatedName(inject.getDeobfuscated().findClass("Script").getAnnotations());
Method runScript = findObMethod("copy$runScript0");
Method vmExecuteOpcode = findObMethod("vmExecuteOpcode");
Field scriptInstructions = findDeobField("opcodes");
Field scriptStatePC = findDeobField("pc");
Field currentScriptField = findObField("currentScript");
Field currentScriptPCField = findObField("currentScriptPC");
Execution e = new Execution(inject.getVanilla());
e.addMethod(runScript);
e.noInvoke = true;
AtomicReference<MethodContext> pcontext = new AtomicReference<>(null);
e.addMethodContextVisitor(pcontext::set);
e.run();
Instructions instrs = runScript.getCode().getInstructions();
Set<AStore> scriptStores = new HashSet<>();
Integer pcLocalVar = null;
Integer instructionArrayLocalVar = null;
IStore currentOpcodeStore = null;
ALoad localInstructionLoad = null;
MethodContext methodContext = pcontext.get();
for (InstructionContext instrCtx : methodContext.getInstructionContexts())
{
Instruction instr = instrCtx.getInstruction();
if (instr instanceof AStore)
{
AStore store = (AStore) instr;
StackContext storedVarCtx = instrCtx.getPops().get(0);
// Find AStores that store a Script
if (storedVarCtx.getType().getInternalName().equals(scriptObName))
{
scriptStores.add(store);
}
// Find AStores that store the instructions
InstructionContext pusherCtx = storedVarCtx.getPushed();
if (pusherCtx.getInstruction() instanceof GetField)
{
GetField getField = (GetField) pusherCtx.getInstruction();
if (getField.getMyField().equals(scriptInstructions))
{
instructionArrayLocalVar = store.getVariableIndex();
}
}
}
// Find the local that invokedFromPc is set from
if (instr instanceof PutField)
{
PutField put = (PutField) instr;
if (put.getMyField() == scriptStatePC)
{
StackContext pc = instrCtx.getPops().get(0);
assert Type.INT.equals(pc.getType()) : pc.getType();
InstructionContext mulctx = pc.pushed;
assert mulctx.getInstruction() instanceof IMul;
pcLocalVar = mulctx.getPops().stream()
.map(StackContext::getPushed)
.filter(i -> i.getInstruction() instanceof ILoad)
.map(i -> ((ILoad) i.getInstruction()).getVariableIndex())
.findFirst()
.orElse(null);
}
}
}
// Find opcode load
// This has to run after the first loop because it relies on instructionArrayLocalVar being set
if (instructionArrayLocalVar == null)
{
throw new InjectionException("Unable to find local instruction array");
}
for (InstructionContext instrCtx : methodContext.getInstructionContexts())
{
Instruction instr = instrCtx.getInstruction();
if (instr instanceof IALoad)
{
StackContext array = instrCtx.getPops().get(1);
// Check where the array came from (looking for a getField scriptInstructions
InstructionContext pushedCtx = array.getPushed();
Instruction pushed = pushedCtx.getInstruction();
if (pushed instanceof ALoad)
{
ALoad arrayLoad = (ALoad) pushed;
if (arrayLoad.getVariableIndex() == instructionArrayLocalVar)
{
//Find the istore
IStore istore = (IStore) instrCtx.getPushes().get(0).getPopped().stream()
.map(InstructionContext::getInstruction)
.filter(i -> i instanceof IStore)
.findFirst()
.orElse(null);
if (istore != null)
{
currentOpcodeStore = istore;
localInstructionLoad = arrayLoad;
}
}
}
}
}
// Add PutStatics to all Script AStores
{
int outerSciptIdx = scriptStores.stream()
.mapToInt(AStore::getVariableIndex)
.reduce(Math::min)
.orElseThrow(() -> new InjectionException("Unable to find any Script AStores in runScript"));
log.debug("Found script index {}", outerSciptIdx);
ListIterator<Instruction> instrIter = instrs.getInstructions().listIterator();
while (instrIter.hasNext())
{
Instruction instr = instrIter.next();
if (instr instanceof AStore)
{
AStore il = (AStore) instr;
if (il.getVariableIndex() == outerSciptIdx)
{
instrIter.previous();
instrIter.add(new Dup(instrs));
instrIter.add(new PutStatic(instrs, currentScriptField));
instrIter.next();
}
}
}
}
// Add PutStatics to all PC IStores and IIncs
{
if (pcLocalVar == null)
{
throw new InjectionException("Unable to find ILoad for invokedFromPc IStore");
}
log.debug("Found pc index {}", pcLocalVar);
ListIterator<Instruction> instrIter = instrs.getInstructions().listIterator();
while (instrIter.hasNext())
{
Instruction instr = instrIter.next();
if (instr instanceof IStore)
{
IStore il = (IStore) instr;
if (il.getVariableIndex() == pcLocalVar)
{
instrIter.previous();
instrIter.add(new Dup(instrs));
instrIter.add(new PutStatic(instrs, currentScriptPCField));
instrIter.next();
}
}
if (instr instanceof IInc)
{
IInc iinc = (IInc) instr;
if (iinc.getVariableIndex() == pcLocalVar)
{
instrIter.add(new ILoad(instrs, pcLocalVar));
instrIter.add(new PutStatic(instrs, currentScriptPCField));
}
}
}
}
// Inject call to vmExecuteOpcode
log.debug("Found instruction array index {}", instructionArrayLocalVar);
if (currentOpcodeStore == null)
{
throw new InjectionException("Unable to find IStore for current opcode");
}
int istorepc = instrs.getInstructions().indexOf(currentOpcodeStore);
assert istorepc >= 0;
Label nextIteration = instrs.createLabelFor(localInstructionLoad);
instrs.addInstruction(istorepc + 1, new ILoad(instrs, currentOpcodeStore.getVariableIndex()));
instrs.addInstruction(istorepc + 2, new InvokeStatic(instrs, vmExecuteOpcode.getPoolMethod()));
instrs.addInstruction(istorepc + 3, new IfNe(instrs, nextIteration));
}
private Method findObMethod(String name) throws InjectionException
{
for (ClassFile c : inject.getVanilla().getClasses())
{
for (Method m : c.getMethods())
{
if (!m.getName().equals(name))
{
continue;
}
return m;
}
}
throw new InjectionException(String.format("Method \"%s\" could not be found.", name));
}
private Field findObField(String name) throws InjectionException
{
for (ClassFile c : inject.getVanilla().getClasses())
{
for (Field f : c.getFields())
{
if (!f.getName().equals(name))
{
continue;
}
return f;
}
}
throw new InjectionException(String.format("Field \"%s\" could not be found.", name));
}
private Field findDeobField(String name) throws InjectionException
{
for (ClassFile c : inject.getDeobfuscated().getClasses())
{
for (Field f : c.getFields())
{
if (!f.getName().equals(name))
{
continue;
}
String obfuscatedName = DeobAnnotations.getObfuscatedName(f.getAnnotations());
ClassFile c2 = inject.toObClass(c);
return c2.findField(obfuscatedName);
}
}
throw new InjectionException(String.format("Mapped field \"%s\" could not be found.", name));
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Method;
import net.runelite.asm.signature.Signature;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.mockito.Matchers;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class InjectConstructTest
{
interface APIClass
{
APIClass create();
}
@Test
public void testInjectConstruct() throws Exception
{
ClassFile targetClass = new ClassFile();
targetClass.setName("test");
ClassFile vanillaClass = new ClassFile();
vanillaClass.setName("ab");
Method constructor = new Method(vanillaClass, "<init>", new Signature("()V"));
vanillaClass.addMethod(constructor);
Inject inject = mock(Inject.class);
when(inject.findVanillaForInterface(Matchers.any(Class.class)))
.thenReturn(vanillaClass);
InjectConstruct injectConstruct = new InjectConstruct(inject);
injectConstruct.injectConstruct(targetClass, APIClass.class.getDeclaredMethod("create"));
assertNotNull(targetClass.findMethod("create"));
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.ClassUtil;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.signature.Signature;
import static net.runelite.injector.Inject.RL_API_PACKAGE_BASE;
import static net.runelite.injector.InjectHookMethod.HOOKS;
import net.runelite.mapping.ObfuscatedName;
import net.runelite.mapping.ObfuscatedSignature;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
abstract class Obfuscated
{
public int foo(Obfuscated o, int i)
{
if (i > 0)
{
return i;
}
else
{
return -i;
}
}
}
@ObfuscatedName("Obfuscated")
class Actor
{
@ObfuscatedName("foo")
@ObfuscatedSignature(
signature = "(LObfuscated;I)I"
)
public int bar(Actor actor, int i)
{
throw new IllegalStateException();
}
}
public class InjectHookMethodTest
{
@Test
public void testProcess() throws IOException, InjectionException
{
InputStream in = getClass().getResourceAsStream("Actor.class");
ClassFile cf = ClassUtil.loadClass(in);
cf.setName("Actor");
cf.findMethod("bar").setDescriptor(new Signature("(LActor;I)I"));
ClassGroup deobfuscated = new ClassGroup();
deobfuscated.addClass(cf);
in = getClass().getResourceAsStream("Obfuscated.class");
ClassFile obcf = ClassUtil.loadClass(in);
obcf.setName("Obfuscated");
obcf.findMethod("foo").setDescriptor(new Signature("(LObfuscated;I)I"));
ClassGroup obfuscated = new ClassGroup();
obfuscated.addClass(obcf);
Method method = cf.findMethod("bar");
assert method != null;
Inject inject = new Inject(deobfuscated, obfuscated);
InjectHookMethod injectHookMethod = new InjectHookMethod(inject);
injectHookMethod.process(method);
method = obcf.findMethod("foo");
assert method != null;
Code code = method.getCode();
List<InvokeStatic> invokeIns = code.getInstructions().getInstructions().stream()
.filter(i -> i instanceof InvokeStatic)
.map(i -> (InvokeStatic) i)
.filter(i -> i.getMethod().getClazz().getName().equals(HOOKS))
.collect(Collectors.toList());
assertTrue(!invokeIns.isEmpty());
assertEquals(2, invokeIns.size());
InvokeStatic invokeStatic = invokeIns.get(0);
Signature signature = invokeStatic.getMethod().getType();
assertEquals(3, signature.size()); // this + patamers
Type arg = signature.getTypeOfArg(1);
assertEquals(RL_API_PACKAGE_BASE.replace('.', '/') + "Actor", arg.getInternalName());
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import static net.runelite.asm.attributes.code.InstructionType.CHECKCAST;
import net.runelite.asm.attributes.code.Instructions;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class InjectSetterTest
{
interface APIClass
{
void setTest(int i);
void setTestObject(Object str);
}
@Test
public void testInjectSetterInt() throws NoSuchMethodException
{
Inject inject = mock(Inject.class);
when(inject.findImportMethodOnApi(any(Class.class), anyString(), any(Boolean.class)))
.thenReturn(APIClass.class.getDeclaredMethod("setTest", int.class));
when(inject.createLoadForTypeIndex(any(Instructions.class), any(Type.class), anyInt()))
.thenReturn(mock(Instruction.class));
InjectSetter instance = new InjectSetter(inject);
ClassFile targetClass = new ClassFile();
targetClass.setName("test");
Field field = new Field(targetClass, "test", Type.INT);
targetClass.addField(field);
instance.injectSetter(targetClass, APIClass.class, field, "test", null);
Method injectedMethod = targetClass.findMethod("setTest");
assertNotNull(injectedMethod);
Code code = injectedMethod.getCode();
Instructions instructions = code.getInstructions();
assertFalse(instructions.getInstructions().stream()
.filter(i -> i.getType() == CHECKCAST)
.findAny()
.isPresent());
}
@Test
public void testInjectSetterObject() throws NoSuchMethodException
{
Inject inject = mock(Inject.class);
when(inject.findImportMethodOnApi(any(Class.class), anyString(), any(Boolean.class)))
.thenReturn(APIClass.class.getDeclaredMethod("setTestObject", Object.class));
when(inject.createLoadForTypeIndex(any(Instructions.class), any(Type.class), anyInt()))
.thenReturn(mock(Instruction.class));
InjectSetter instance = new InjectSetter(inject);
ClassFile targetClass = new ClassFile();
targetClass.setName("test");
Field field = new Field(targetClass, "testObject", Type.STRING);
targetClass.addField(field);
instance.injectSetter(targetClass, APIClass.class, field, "testObject", null);
Method injectedMethod = targetClass.findMethod("setTestObject");
assertNotNull(injectedMethod);
Code code = injectedMethod.getCode();
Instructions instructions = code.getInstructions();
assertTrue(instructions.getInstructions().stream()
.filter(i -> i.getType() == CHECKCAST)
.findAny()
.isPresent());
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.io.File;
import java.io.IOException;
import net.runelite.asm.ClassGroup;
import net.runelite.deob.DeobTestProperties;
import net.runelite.deob.TemporyFolderLocation;
import net.runelite.deob.util.JarUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class InjectTest
{
@Rule
public DeobTestProperties properties = new DeobTestProperties();
@Rule
public TemporaryFolder folder = TemporyFolderLocation.getTemporaryFolder();
private ClassGroup deob, vanilla;
@Before
public void before() throws IOException
{
deob = JarUtil.loadJar(new File(properties.getRsClient()));
vanilla = JarUtil.loadJar(new File(properties.getVanillaClient()));
}
@After
public void after() throws IOException
{
JarUtil.saveJar(vanilla, folder.newFile());
}
@Test
@Ignore
public void testRun() throws InjectionException
{
Inject instance = new Inject(deob, vanilla);
instance.run();
InjectorValidator iv = new InjectorValidator(vanilla);
iv.validate();
}
}

View File

@@ -0,0 +1,268 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.runelite.api.mixins.Copy;
import net.runelite.api.mixins.Replace;
import net.runelite.api.mixins.Shadow;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.ClassUtil;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import static net.runelite.asm.Type.INT;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.signature.Signature;
import net.runelite.mapping.ObfuscatedName;
import net.runelite.mapping.ObfuscatedSignature;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
@ObfuscatedName("net/runelite/injector/VanillaTarget")
class DeobTarget
{
@ObfuscatedName("ob_foo3")
@ObfuscatedSignature(
signature = "(I)V",
garbageValue = "123"
)
private void foo3()
{
// De-obfuscated foo3
System.out.println("foo3");
}
@ObfuscatedName("ob_foo4")
private static int foo4;
}
class VanillaTarget
{
private void ob_foo3(int garbageValue)
{
// Obfuscated foo3
if (garbageValue != 123)
{
return;
}
System.out.println("foo3");
}
private static int ob_foo4;
}
abstract class Source
{
@net.runelite.api.mixins.Inject
private static int foo;
@net.runelite.api.mixins.Inject
private void foo2()
{
}
@Copy("foo3")
abstract void foo3();
@Replace("foo3")
private void rl$foo3()
{
System.out.println("replaced");
System.out.println(foo4);
foo3();
}
@Shadow("foo4")
private static int foo4;
}
// Test shadowing the "foo" field injected by Source
abstract class Source2
{
@Shadow("foo")
private static int foo;
@net.runelite.api.mixins.Inject
private void foo5()
{
System.out.println(foo);
}
}
public class MixinInjectorTest
{
@Test
public void testInject() throws Exception
{
InputStream deobIn = getClass().getResourceAsStream("DeobTarget.class");
ClassFile deobTarget = ClassUtil.loadClass(deobIn);
ClassGroup deob = new ClassGroup();
deob.addClass(deobTarget);
InputStream vanillaIn = getClass().getResourceAsStream("VanillaTarget.class");
ClassFile vanillaTarget = ClassUtil.loadClass(vanillaIn);
ClassGroup vanilla = new ClassGroup();
vanilla.addClass(vanillaTarget);
Map<Class<?>, List<ClassFile>> mixinClasses = new HashMap<>();
mixinClasses.put(Source.class, Collections.singletonList(vanillaTarget));
mixinClasses.put(Source2.class, Collections.singletonList(vanillaTarget));
Inject inject = new Inject(deob, vanilla);
new MixinInjector(inject).inject(mixinClasses);
// Check if "foo" has been injected
Field foo = vanillaTarget.findField("foo");
assertNotNull(foo);
assertEquals(INT, foo.getType());
assertEquals(ACC_PUBLIC | ACC_STATIC, foo.getAccessFlags());
// Check if "foo2()V" has been injected
Method foo2 = vanillaTarget.findMethod("foo2");
assertNotNull(foo2);
assertEquals(new Signature("()V"), foo2.getDescriptor());
assertEquals(ACC_PUBLIC, foo2.getAccessFlags());
// Check if "ob_foo3(I)V" was copied
Method foo3 = vanillaTarget.findMethod("copy$foo3");
assertNotNull(foo3);
assertEquals(new Signature("(I)V"), foo3.getDescriptor());
assertEquals(ACC_PUBLIC, foo3.getAccessFlags());
// Check if "ob_foo3(I)V" was replaced
Method ob_foo3 = vanillaTarget.findMethod("ob_foo3");
assertNotNull(ob_foo3);
assertEquals(new Signature("(I)V"), ob_foo3.getDescriptor());
assertEquals(ob_foo3
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i -> i instanceof LDC && ((LDC) i).getConstant().equals("replaced"))
.count(), 1);
// Check that the "foo4" field access in the new code body was mapped correctly
assertEquals(ob_foo3
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i ->
{
if (!(i instanceof GetStatic))
{
return false;
}
net.runelite.asm.pool.Field field = ((GetStatic) i).getField();
if (!field.getClazz().getName().equals("net/runelite/injector/VanillaTarget"))
{
return false;
}
if (!field.getName().equals("ob_foo4"))
{
return false;
}
return true;
})
.count(), 1);
// Check that the "foo3()" call in the new code body was mapped to the copy
assertEquals(ob_foo3
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i ->
{
if (!(i instanceof InvokeVirtual))
{
return false;
}
net.runelite.asm.pool.Method method = ((InvokeVirtual) i).getMethod();
if (!method.getClazz().getName().equals("net/runelite/injector/VanillaTarget"))
{
return false;
}
if (!method.getName().equals("copy$foo3"))
{
return false;
}
return true;
})
.count(), 1);
// Check if "foo5()V" was injected
Method foo5 = vanillaTarget.findMethod("foo5");
assertNotNull(foo5);
assertEquals(new Signature("()V"), foo5.getDescriptor());
assertEquals(ACC_PUBLIC, foo5.getAccessFlags());
// Check that the shadow "foo" field access was mapped correctly
assertEquals(foo5
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i ->
{
if (!(i instanceof GetStatic))
{
return false;
}
net.runelite.asm.pool.Field field = ((GetStatic) i).getField();
if (!field.getClazz().getName().equals("net/runelite/injector/VanillaTarget"))
{
return false;
}
if (!field.getName().equals("foo"))
{
return false;
}
return true;
})
.count(), 1);
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.injector.raw;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.ClassUtil;
import net.runelite.injector.Inject;
import org.junit.Test;
public class DrawAfterWidgetsTest
{
@Test
public void testInjectDrawWidgetsRev160() throws Exception
{
// Rev 160 does not have the drawWidgets call inlined
ClassFile deobClient = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Client_deob160.class"));
ClassFile deobRasterizer = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_deob160.class"));
ClassGroup deob = new ClassGroup();
deob.addClass(deobClient);
deob.addClass(deobRasterizer);
ClassFile obClient = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Client_ob160.class"));
ClassFile obRasterizer = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_ob160.class"));
ClassGroup vanilla = new ClassGroup();
vanilla.addClass(obClient);
vanilla.addClass(obRasterizer);
Inject inject = new Inject(deob, vanilla);
new DrawAfterWidgets(inject).inject();
}
@Test
public void testInjectDrawWidgetsRev153() throws Exception
{
// Rev 153 has the drawWidgets call inlined
ClassFile deobClient = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Client_deob153.class"));
ClassFile deobRasterizer = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_deob153.class"));
ClassGroup deob = new ClassGroup();
deob.addClass(deobClient);
deob.addClass(deobRasterizer);
ClassFile obClient = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Client_ob153.class"));
ClassFile obRasterizer = ClassUtil.loadClass(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_ob153.class"));
ClassGroup vanilla = new ClassGroup();
vanilla.addClass(obClient);
vanilla.addClass(obRasterizer);
Inject inject = new Inject(deob, vanilla);
new DrawAfterWidgets(inject).inject();
}
}