Injector, deob, mixins
This commit is contained in:
104
injector-plugin/pom.xml
Normal file
104
injector-plugin/pom.xml
Normal 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>
|
||||
629
injector-plugin/src/main/java/net/runelite/injector/Inject.java
Normal file
629
injector-plugin/src/main/java/net/runelite/injector/Inject.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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]));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user