"finish" injector module, more robust injected-client loading (#2881)

"finish" injector module, more robust injected-client loading
This commit is contained in:
Tyler Bochard
2020-12-22 22:03:29 -05:00
committed by GitHub
parent f415f3cc6e
commit c36be56e26
65 changed files with 96 additions and 1754 deletions

View File

@@ -0,0 +1,44 @@
import ProjectVersions.rsversion
group = "com.openosrs"
version = 1.0
repositories {
mavenCentral()
mavenLocal()
maven {
url = uri("https://repo.runelite.net")
url = uri("https://raw.githubusercontent.com/open-osrs/hosting/master")
url = uri("https://repo.openosrs.com/repository/maven")
}
}
plugins {
java
}
dependencies {
implementation(gradleApi())
annotationProcessor("org.projectlombok:lombok:1.18.12")
compileOnly("org.projectlombok:lombok:1.18.12")
implementation("org.ow2.asm:asm:8.0.1")
implementation("org.ow2.asm:asm-util:8.0.1")
implementation("org.jetbrains:annotations:19.0.0")
implementation("com.google.guava:guava:29.0-jre")
implementation(project(":deobfuscator"))
implementation(project(":runescape-api"))
implementation(project(":runescape-client"))
implementation(project(":runelite-mixins"))
}
tasks.register<JavaExec>("inject") {
main = "com.openosrs.injector.Injector"
classpath = sourceSets["main"].runtimeClasspath
}
tasks {
build {
finalizedBy("inject")
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector;
public class InjectException extends RuntimeException
{
public InjectException(String message)
{
super(message);
}
public InjectException(Throwable cause)
{
super(cause);
}
public InjectException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,555 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.rsapi.RSApiClass;
import com.openosrs.injector.rsapi.RSApiMethod;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.runelite.asm.Annotation;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Named;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotated;
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.DLoad;
import net.runelite.asm.attributes.code.instructions.FLoad;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.IMul;
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.LMul;
import net.runelite.asm.attributes.code.instructions.Return;
import net.runelite.asm.attributes.code.instructions.VReturn;
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 org.jetbrains.annotations.Nullable;
import static com.openosrs.injector.rsapi.RSApi.API_BASE;
import static com.openosrs.injector.rsapi.RSApi.RL_API_BASE;
public interface InjectUtil
{
/**
* Finds a static method in deob and converts it to ob
*
* @param data InjectData instance
* @param name The name of the method you want to find
* @return The obfuscated version of the found method
*/
static Method findMethod(InjectData data, String name)
{
return findMethod(data, name, null, null);
}
/**
* Finds a static method in deob and converts it to ob
*
* @param data InjectData instance
* @param name The name of the method you want to find
* @param classHint The name of the class you expect the method to be in, or null
* @param sig The signature the method has in deob, or null
* @return The obfuscated version of the found method
*/
static Method findMethod(
InjectData data,
String name,
String classHint,
@Nullable Predicate<Signature> sig)
{
return findMethod(data, name, classHint, sig, false, false);
}
/**
* Finds a method in injectData's deobfuscated classes.
*
* @param data InjectData instance
* @param name (Exported) method name
* @param classHint The (exported) name of a class you expect (or know) the method to be in, or null, if you're not sure
* @throws InjectException If the hint class couldn't be found, or no method matching the settings was found
*/
static Method findMethod(
InjectData data,
String name,
@Nullable String classHint)
throws InjectException
{
return findMethod(data, name, classHint, null, false, false);
}
/**
* Finds a method in injectData's deobfuscated classes.
*
* @param data InjectData instance
* @param name (Exported) method name
* @param classHint The (exported) name of a class you expect (or know) the method to be in, or null, if you're not sure
* @param sig The deobfuscated methods' signature, or null, if you're unsure
* @param notStatic If this is true, only check non-static methods. If classHint isn't null, check only subclasses
* @param returnDeob If this is true, this method will return the deobfuscated method, instead of turning it into vanilla first
* @throws InjectException If the hint class couldn't be found, or no method matching the settings was found
*/
static Method findMethod(
InjectData data,
String name,
@Nullable String classHint,
@Nullable Predicate<Signature> sig,
boolean notStatic,
boolean returnDeob)
throws InjectException
{
final ClassGroup deob = data.getDeobfuscated();
if (classHint != null)
{
ClassFile cf;
Method m;
cf = findClassOrThrow(deob, classHint);
if (notStatic)
{
if (sig == null)
{
m = InjectUtil.findMethodDeep(cf, name, s -> true);
}
else
{
m = InjectUtil.findMethodDeep(cf, name, sig);
}
}
else
{
m = cf.findMethod(name);
}
if (m != null)
{
return returnDeob ? m : data.toVanilla(m);
}
}
for (ClassFile cf : deob)
{
for (Method m : cf.getMethods())
{
if (m.getName().equals(name))
{
if (!notStatic || !m.isStatic())
{
if (sig == null || sig.test(m.getDescriptor()))
{
return returnDeob ? m : data.toVanilla(m);
}
}
}
}
}
throw new InjectException(String.format("Couldn't find %s", name));
}
static ClassFile findClassOrThrow(ClassGroup group, String name)
{
ClassFile clazz = group.findClass(name);
if (clazz == null)
{
throw new InjectException("Hint class " + name + " doesn't exist");
}
return clazz;
}
/**
* Fail-fast implementation of ClassFile.findMethodDeep, using a predicate for signature
*/
static Method findMethodDeep(ClassFile clazz, String name, Predicate<Signature> type)
{
do
{
for (Method method : clazz.getMethods())
{
if (method.getName().equals(name))
{
if (type.test(method.getDescriptor()))
{
return method;
}
}
}
}
while ((clazz = clazz.getParent()) != null);
throw new InjectException(String.format("Method %s couldn't be found", name + type.toString()));
}
/**
* Fail-fast implementation of ClassGroup.findStaticField
* <p>
* well...
*/
static Field findStaticField(ClassGroup group, String name)
{
for (ClassFile clazz : group)
{
Field f = clazz.findField(name);
if (f != null && f.isStatic())
{
return f;
}
}
throw new InjectException("Couldn't find static field " + name);
}
/**
* Finds a static field in deob and converts it to ob
*
* @param data InjectData instance
* @param name The name of the field you want to find
* @param classHint The name of the class you expect the field to be in, or null
* @param type The type the method has in deob, or null
* @return The obfuscated version of the found field
*/
static Field findStaticField(InjectData data, String name, String classHint, Type type)
{
final ClassGroup deob = data.getDeobfuscated();
Field field;
if (classHint != null)
{
ClassFile clazz = findClassOrThrow(deob, classHint);
if (type == null)
{
field = clazz.findField(name);
}
else
{
field = clazz.findField(name, type);
}
if (field != null)
{
return field;
}
}
for (ClassFile clazz : deob)
{
if (type == null)
{
field = clazz.findField(name);
}
else
{
field = clazz.findField(name, type);
}
if (field != null)
{
return field;
}
}
throw new InjectException(String.format("Static field %s doesn't exist", (type != null ? type + " " : "") + name));
}
/**
* Fail-fast implementation of ClassGroup.findFieldDeep
*/
static Field findFieldDeep(ClassFile clazz, String name)
{
Field f;
do
{
if ((f = clazz.findField(name)) != null)
{
return f;
}
}
while ((clazz = clazz.getParent()) != null);
throw new InjectException("Couldn't find field " + name);
}
static Field findField(InjectData data, String name, String hintClass)
{
final ClassGroup deob = data.getDeobfuscated();
return data.toVanilla(findField(deob, name, hintClass));
}
static Field findField(ClassGroup group, String name, String hintClass)
{
Field field;
if (hintClass != null)
{
ClassFile clazz = findClassOrThrow(group, hintClass);
field = clazz.findField(name);
if (field != null)
{
return field;
}
}
for (ClassFile clazz : group)
{
if ((field = clazz.findField(name)) != null)
{
return field;
}
}
throw new InjectException("Field " + name + " doesn't exist");
}
static ClassFile deobFromApiMethod(InjectData data, RSApiMethod apiMethod)
{
return data.toDeob(apiMethod.getClazz().getName());
}
static ClassFile vanillaFromApiMethod(InjectData data, RSApiMethod apiMethod)
{
return data.toVanilla(deobFromApiMethod(data, apiMethod));
}
static Signature apiToDeob(InjectData data, Signature api)
{
return new Signature.Builder()
.setReturnType(apiToDeob(data, api.getReturnValue()))
.addArguments(
api.getArguments().stream()
.map(type -> apiToDeob(data, type))
.collect(Collectors.toList())
).build();
}
static Type apiToDeob(InjectData data, Type api)
{
if (api.isPrimitive())
{
return api;
}
final String internalName = api.getInternalName();
if (internalName.startsWith(API_BASE))
{
return Type.getType("L" + api.getInternalName().substring(API_BASE.length()) + ";", api.getDimensions());
}
else if (internalName.startsWith(RL_API_BASE))
{
Class rlApiC = new Class(internalName);
RSApiClass highestKnown = data.getRsApi().withInterface(rlApiC);
// Cheeky unchecked exception
assert highestKnown != null : "No rs api class implements rl api class " + rlApiC.toString();
boolean changed;
do
{
changed = false;
for (RSApiClass interf : highestKnown.getApiInterfaces())
{
if (interf.getInterfaces().contains(rlApiC))
{
highestKnown = interf;
changed = true;
break;
}
}
}
while (changed);
return apiToDeob(data, Type.getType(highestKnown.getName(), api.getDimensions()));
}
return api;
}
static Type deobToVanilla(InjectData data, Type deobT)
{
if (deobT.isPrimitive())
{
return deobT;
}
final ClassFile deobClass = data.getDeobfuscated().findClass(deobT.getInternalName());
if (deobClass == null)
{
return deobT;
}
return Type.getType("L" + data.toVanilla(deobClass).getName() + ";", deobT.getDimensions());
}
static boolean apiToDeobSigEquals(InjectData data, Signature deobSig, Signature apiSig)
{
return deobSig.equals(apiToDeob(data, apiSig));
}
static boolean argsMatch(Signature a, Signature b)
{
List<Type> aa = a.getArguments();
List<Type> bb = b.getArguments();
if (aa.size() != bb.size())
{
return false;
}
for (int i = 0; i < aa.size(); i++)
{
if (!aa.get(i).equals(bb.get(i)))
{
return false;
}
}
return true;
}
/**
* Gets the obfuscated name from something's annotations.
* <p>
* If the annotation doesn't exist return the current name instead.
*/
static <T extends Annotated & Named> String getObfuscatedName(T from)
{
Annotation name = from.findAnnotation(DeobAnnotations.OBFUSCATED_NAME);
return name == null ? from.getName() : name.getValueString();
}
/**
* Gets the value of the @Export annotation on the object.
*/
static String getExportedName(Annotated from)
{
Annotation export = from.findAnnotation(DeobAnnotations.EXPORT);
return export == null ? null : export.getValueString();
}
/**
* Creates the correct load instruction for the variable with Type type and var index index.
*/
static 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 IllegalStateException("Unknown type");
}
}
/**
* Creates the right return instruction for an object with Type type
*/
static Instruction createReturnForType(Instructions instructions, Type type)
{
if (!type.isPrimitive())
{
return new Return(instructions, InstructionType.ARETURN);
}
switch (type.toString())
{
case "B":
case "C":
case "I":
case "S":
case "Z":
return new Return(instructions, InstructionType.IRETURN);
case "D":
return new Return(instructions, InstructionType.DRETURN);
case "F":
return new Return(instructions, InstructionType.FRETURN);
case "J":
return new Return(instructions, InstructionType.LRETURN);
case "V":
return new VReturn(instructions);
default:
throw new IllegalStateException("Unknown type");
}
}
static Instruction createInvokeFor(Instructions instructions, net.runelite.asm.pool.Method method, boolean isStatic)
{
if (isStatic)
{
return new InvokeStatic(instructions, method);
}
else
{
return new InvokeVirtual(instructions, method);
}
}
/**
* Legit fuck annotations
*/
static ClassFile getVanillaClassFromAnnotationString(InjectData data, Annotation annotation)
{
Object v = annotation.getValue();
String str = ((org.objectweb.asm.Type) v).getInternalName();
return data.toVanilla(data.toDeob(str));
}
/**
* Add after the get
*/
static void injectObfuscatedGetter(Number getter, Instructions instrs, Consumer<Instruction> into)
{
into.accept(new LDC(instrs, getter));
if (getter instanceof Integer)
{
into.accept(new IMul(instrs));
}
else if (getter instanceof Long)
{
into.accept(new LMul(instrs));
}
}
/**
* Add IN FRONT of the put
*
* @param getter should be the same value as for the getter (straight from ObfuscatedGetter)
*/
static void injectObfuscatedSetter(Number getter, Instructions instrs, Consumer<Instruction> into)
{
injectObfuscatedGetter(DMath.modInverse(getter), instrs, into);
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injection.InjectTaskHandler;
import com.openosrs.injector.injectors.CreateAnnotations;
import com.openosrs.injector.injectors.InjectConstruct;
import com.openosrs.injector.injectors.InterfaceInjector;
import com.openosrs.injector.injectors.MixinInjector;
import com.openosrs.injector.injectors.RSApiInjector;
import com.openosrs.injector.injectors.raw.AddPlayerToMenu;
import com.openosrs.injector.injectors.raw.ClearColorBuffer;
import com.openosrs.injector.injectors.raw.DrawMenu;
import com.openosrs.injector.injectors.raw.Occluder;
import com.openosrs.injector.injectors.raw.RasterizerAlpha;
import com.openosrs.injector.injectors.raw.RenderDraw;
import com.openosrs.injector.injectors.raw.ScriptVM;
import com.openosrs.injector.rsapi.RSApi;
import com.openosrs.injector.transformers.InjectTransformer;
import com.openosrs.injector.transformers.SourceChanger;
import java.io.File;
import java.io.IOException;
import net.runelite.deob.util.JarUtil;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
public class Injector extends InjectData implements InjectTaskHandler
{
private static final Logger log = Logging.getLogger(Injector.class);
public Injector(File vanilla, File rsclient, File mixins, File[] rsapi) throws IOException
{
super(
JarUtil.loadJar(vanilla),
JarUtil.loadJar(rsclient),
JarUtil.loadJar(mixins),
new RSApi(rsapi)
);
inject();
save(new File("../runelite-client/src/main/resources/net/runelite/client/injected-client.jar"));
}
public static void main(String[] args)
{
try
{
args = new String[]
{
"./vanilla.jar",
"../runescape-client/build/libs/runescape-client-3.5.4.jar",
"../runelite-mixins/build/libs/runelite-mixins-3.5.4.jar",
"../runescape-api/build/classes/java/main/net/runelite/rs/api/"
};
new Injector(new File(args[0]), new File(args[1]), new File(args[2]), new File(args[3]).listFiles());
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void inject()
{
log.debug("[DEBUG] Starting injection");
inject(new CreateAnnotations(this));
inject(new InterfaceInjector(this));
inject(new RasterizerAlpha(this));
inject(new MixinInjector(this));
// This is where field hooks runs
// This is where method hooks runs
inject(new InjectConstruct(this));
inject(new RSApiInjector(this));
//inject(new DrawAfterWidgets(this));
inject(new ScriptVM(this));
// All GPU raw injectors should probably be combined, especially RenderDraw and Occluder
inject(new ClearColorBuffer(this));
inject(new RenderDraw(this));
inject(new Occluder(this));
inject(new DrawMenu(this));
inject(new AddPlayerToMenu(this));
validate(new InjectorValidator(this));
transform(new SourceChanger(this));
}
public void save(File outputJar) throws IOException
{
log.info("[INFO] Saving jar to {}", outputJar.toString());
JarUtil.saveJar(this.getVanilla(), outputJar);
}
private void inject(com.openosrs.injector.injectors.Injector injector)
{
final String name = injector.getName();
log.info("[INFO] Starting {}", name);
injector.start();
injector.inject();
log.lifecycle("{} {}", name, injector.getCompletionMsg());
if (injector instanceof Validator)
{
validate((Validator) injector);
}
}
private void validate(Validator validator)
{
final String name = validator.getName();
if (!validator.validate())
{
throw new InjectException(name + " failed validation");
}
}
private void transform(InjectTransformer transformer)
{
final String name = transformer.getName();
log.info("[INFO] Starting {}", name);
transformer.transform();
log.lifecycle("{} {}", name, transformer.getCompletionMsg());
}
public void runChildInjector(com.openosrs.injector.injectors.Injector injector)
{
inject(injector);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.rsapi.RSApi;
import com.openosrs.injector.rsapi.RSApiClass;
import com.openosrs.injector.rsapi.RSApiMethod;
import lombok.RequiredArgsConstructor;
import net.runelite.asm.ClassFile;
import net.runelite.asm.pool.Class;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import static com.openosrs.injector.rsapi.RSApi.API_BASE;
@RequiredArgsConstructor
public class InjectorValidator implements Validator
{
private static final Logger log = Logging.getLogger(InjectorValidator.class);
private static final String OK = "OK", ERROR = "ERROR", WTF = "WTF";
private final InjectData inject;
private int missing = 0, okay = 0, wtf = 0;
public boolean validate()
{
final RSApi rsApi = inject.getRsApi();
for (ClassFile cf : inject.getVanilla())
{
for (Class intf : cf.getInterfaces())
{
if (!intf.getName().startsWith(API_BASE))
{
continue;
}
RSApiClass apiC = rsApi.findClass(intf.getName());
if (apiC == null)
{
log.error("{} is rs api type implemented by {} but it doesn't exist in rsapi. wtf", intf, cf.getPoolClass());
++wtf;
continue;
}
check(cf, apiC);
}
}
String status = wtf > 0 ? WTF : missing > 0 ? ERROR : OK;
log.info("[INFO] RSApiValidator completed. Status [{}] {} overridden methods, {} missing", status, okay, missing);
// valid, ref to static final field
return status == OK;
}
private void check(ClassFile clazz, RSApiClass apiClass)
{
for (RSApiMethod apiMethod : apiClass)
{
if (apiMethod.isSynthetic() || apiMethod.isDefault())
{
continue;
}
if (clazz.findMethodDeep(apiMethod.getName(), apiMethod.getSignature()) == null)
{
log.error("[WARN] Class {} implements interface {} but doesn't implement {}",
clazz.getPoolClass(), apiClass.getClazz(), apiMethod.getMethod());
++missing;
}
else
{
++okay;
}
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector;
import net.runelite.asm.Named;
public interface Validator extends Named
{
boolean validate();
default String getName()
{
return this.getClass().getSimpleName();
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injection;
import com.google.common.collect.ImmutableMap;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injectors.Injector;
import com.openosrs.injector.rsapi.RSApi;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import lombok.Getter;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.signature.Signature;
/**
* Abstract class meant as the interface of {@link com.openosrs.injector.Injector injection} for injectors
*/
public abstract class InjectData
{
public static final String HOOKS = "net/runelite/client/callback/Hooks";
public static final String CALLBACKS = "net/runelite/api/hooks/Callbacks";
@Getter
private final ClassGroup vanilla;
@Getter
private final ClassGroup deobfuscated;
@Getter
private final ClassGroup mixins;
@Getter
private final RSApi rsApi;
/**
* Deobfuscated ClassFiles -> Vanilla ClassFiles
*/
private final Map<ClassFile, ClassFile> toVanilla;
/**
* Strings -> Deobfuscated ClassFiles
* keys:
* - Obfuscated name
* - RSApi implementing name
*/
private final Map<String, ClassFile> toDeob = new HashMap<>();
public InjectData(ClassGroup vanilla, ClassGroup deobfuscated, ClassGroup mixins, RSApi rsApi)
{
this.vanilla = vanilla;
this.deobfuscated = deobfuscated;
this.rsApi = rsApi;
this.mixins = mixins;
this.toVanilla = initToVanilla();
}
public abstract void runChildInjector(Injector injector);
private Map<ClassFile, ClassFile> initToVanilla()
{
ImmutableMap.Builder<ClassFile, ClassFile> toVanillaB = ImmutableMap.builder();
for (final ClassFile deobClass : deobfuscated)
{
if (deobClass.getName().startsWith("net/runelite/"))
{
continue;
}
final String obName = InjectUtil.getObfuscatedName(deobClass);
if (obName != null)
{
toDeob.put(obName, deobClass);
// Can't be null
final ClassFile obClass = this.vanilla.findClass(obName);
toVanillaB.put(deobClass, obClass);
}
}
return toVanillaB.build();
}
/**
* Deobfuscated ClassFile -> Vanilla ClassFile
*/
public ClassFile toVanilla(ClassFile deobClass)
{
return toVanilla.get(deobClass);
}
/**
* Deobfuscated Method -> Vanilla Method
*/
public Method toVanilla(Method deobMeth)
{
final ClassFile obC = toVanilla(deobMeth.getClassFile());
String name = InjectUtil.getObfuscatedName(deobMeth);
Signature sig = deobMeth.getObfuscatedSignature();
if (sig == null)
{
sig = deobMeth.getDescriptor();
}
return obC.findMethod(name, sig);
}
/**
* Deobfuscated Field -> Vanilla Field
*/
public Field toVanilla(Field deobField)
{
final ClassFile obC = toVanilla(deobField.getClassFile());
String name = InjectUtil.getObfuscatedName(deobField);
Type type = deobField.getObfuscatedType();
return obC.findField(name, type);
}
/**
* Vanilla ClassFile -> Deobfuscated ClassFile
*/
public ClassFile toDeob(String str)
{
return this.toDeob.get(str);
}
/**
* Adds a string mapping for a deobfuscated class
*/
public void addToDeob(String key, ClassFile value)
{
toDeob.put(key, value);
}
/**
* Do something with all paired classes.
* <p>
* Key = deobfuscated, Value = vanilla
*/
public void forEachPair(BiConsumer<ClassFile, ClassFile> action)
{
for (Map.Entry<ClassFile, ClassFile> pair : toVanilla.entrySet())
{
action.accept(pair.getKey(), pair.getValue());
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injection;
import java.io.File;
import java.io.IOException;
/**
* Interface containing all the methods gradle needs to know about
*/
public interface InjectTaskHandler
{
/**
* The actual method that does all the work
*/
void inject();
/**
* Call this to save the injected jar to outputJar
*/
void save(File outputJar) throws IOException;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors;
import com.google.common.base.Stopwatch;
import com.openosrs.injector.injection.InjectData;
import lombok.RequiredArgsConstructor;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
@RequiredArgsConstructor
public abstract class AbstractInjector implements Injector
{
protected final InjectData inject;
protected final Logger log = Logging.getLogger(this.getClass());
private Stopwatch stopwatch;
public void start()
{
stopwatch = Stopwatch.createStarted();
}
public final String getCompletionMsg()
{
return "finished in " + stopwatch.toString();
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors;
import com.openosrs.injector.injection.InjectData;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.deob.DeobAnnotations;
/*
* This handles creating "virtual" annotations to clean up rs-client in the main project
*/
public class CreateAnnotations extends AbstractInjector
{
public CreateAnnotations(InjectData inject)
{
super(inject);
}
public void inject()
{
for (final ClassFile deobClass : inject.getDeobfuscated())
{
injectFields(deobClass);
injectMethods(deobClass);
if (deobClass.getName().startsWith("class"))
{
continue;
}
deobClass.addAnnotation(DeobAnnotations.IMPLEMENTS, deobClass.getName());
}
}
private void injectFields(ClassFile deobClass)
{
for (Field deobField : deobClass.getFields())
{
deobField.addAnnotation(DeobAnnotations.EXPORT, deobField.getName());
}
}
private void injectMethods(ClassFile deobClass)
{
for (Method deobMethod : deobClass.getMethods())
{
deobMethod.addAnnotation(DeobAnnotations.EXPORT, deobMethod.getName());
}
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.rsapi.RSApiMethod;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.Annotation;
import net.runelite.asm.ClassFile;
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.pool.Class;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
import static com.openosrs.injector.rsapi.RSApi.CONSTRUCT;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
public class InjectConstruct extends AbstractInjector
{
private int injected = 0;
public InjectConstruct(InjectData inject)
{
super(inject);
}
@Override
public void inject()
{
for (RSApiMethod apiMethod : inject.getRsApi().getConstructs())
{
Annotation construct = apiMethod.findAnnotation(CONSTRUCT);
if (construct == null)
{
continue;
}
final Method method = apiMethod.getMethod();
final Class clazz = method.getClazz();
final ClassFile deobClass = inject.toDeob(clazz.getName());
final ClassFile vanillaClass = inject.toVanilla(deobClass);
injectConstruct(vanillaClass, method);
apiMethod.setInjected(true);
injected++;
}
log.info("[INFO] Injected {} constructors", injected);
}
private void injectConstruct(ClassFile targetClass, Method apiMethod)
{
log.debug("[DEBUG] Injecting constructor for {} into {}", apiMethod, targetClass.getPoolClass());
final Type returnval = apiMethod.getType().getReturnValue();
final ClassFile deobClass = inject.toDeob(returnval.getInternalName());
final ClassFile classToConstruct = inject.toVanilla(deobClass);
Signature constr = new Signature.Builder()
.addArguments(apiMethod.getType().getArguments().stream()
.map(t -> InjectUtil.apiToDeob(inject, t))
.map(t -> InjectUtil.deobToVanilla(inject, t))
.collect(Collectors.toList()))
.setReturnType(Type.VOID)
.build();
final net.runelite.asm.Method constructor = classToConstruct.findMethod("<init>", constr);
if (constructor == null)
{
throw new InjectException("Unable to find constructor for " + classToConstruct.getName() + ".<init>" + constr);
}
net.runelite.asm.Method setterMethod = new net.runelite.asm.Method(targetClass, apiMethod.getName(), apiMethod.getType());
setterMethod.setAccessFlags(ACC_PUBLIC);
targetClass.addMethod(setterMethod);
final Code code = new Code(setterMethod);
setterMethod.setCode(code);
final Instructions instructions = code.getInstructions();
final List<Instruction> ins = instructions.getInstructions();
ins.add(new New(instructions, classToConstruct.getPoolClass()));
ins.add(new Dup(instructions));
int idx = 1;
int parameter = 0;
for (Type type : constructor.getDescriptor().getArguments())
{
Instruction load = InjectUtil.createLoadForTypeIndex(instructions, type, idx);
idx += type.getSize();
ins.add(load);
Type paramType = apiMethod.getType().getTypeOfArg(parameter);
if (!type.equals(paramType))
{
CheckCast checkCast = new CheckCast(instructions);
checkCast.setType(type);
ins.add(checkCast);
}
++parameter;
}
ins.add(new InvokeSpecial(instructions, constructor.getPoolMethod()));
ins.add(new Return(instructions));
}
}

View File

@@ -0,0 +1,414 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
* * 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 com.openosrs.injector.injectors;
import com.google.common.collect.Lists;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Provider;
import lombok.AllArgsConstructor;
import net.runelite.asm.Annotation;
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.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.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.Swap;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
public class InjectHook extends AbstractInjector
{
private static final String CLINIT = "<clinit>";
private static final Type FIELDHOOK = new Type("Lnet/runelite/api/mixins/FieldHook;");
private final Map<Field, HookInfo> hooked = new HashMap<>();
private final Map<Provider<ClassFile>, List<ClassFile>> mixinTargets;
private int injectedHooks;
InjectHook(final InjectData inject, final Map<Provider<ClassFile>, List<ClassFile>> mixinTargets)
{
super(inject);
this.mixinTargets = mixinTargets;
}
@Override
public void inject()
{
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectMethods(entry.getKey(), entry.getValue());
}
injectHooks();
log.info("[INFO] Injected {} field hooks.", injectedHooks);
}
private void injectMethods(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses)
{
final ClassFile mixinClass = mixinProvider.get();
for (ClassFile targetClass : targetClasses)
{
for (Method mixinMethod : mixinClass.getMethods())
{
final Annotation fieldHook = mixinMethod.findAnnotation(FIELDHOOK);
if (fieldHook == null)
{
continue;
}
final String hookName = fieldHook.getValueString();
final boolean before = isBefore(fieldHook);
final ClassFile deobTarget = inject.toDeob(targetClass.getName());
final Field deobField;
if (mixinMethod.isStatic())
{
deobField = InjectUtil.findStaticField(deobTarget.getGroup(), hookName);
}
else
{
deobField = InjectUtil.findFieldDeep(deobTarget, hookName);
}
assert mixinMethod.isStatic() == deobField.isStatic() : "Mixin method isn't static but deob has a static method named the same as the hook, and I was too lazy to do something about this bug";
final Number getter = DeobAnnotations.getObfuscatedGetter(deobField);
final Field obField = inject.toVanilla(deobField);
final HookInfo info = new HookInfo(targetClass.getPoolClass(), mixinMethod, before, getter);
hooked.put(obField, info);
}
}
}
private void injectHooks()
{
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;
}
log.trace("Found injection location for hook {} at instruction {}", hookInfo.method.getName(), sfi);
++injectedHooks;
StackContext value = ic.getPops().get(0);
StackContext objectStackContext = null;
if (sfi instanceof PutField)
{
objectStackContext = ic.getPops().get(1);
}
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 (InjectException 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;
}
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)
{
objectStackContext = arrayReferencePushed.getPops().get(0);
}
// inject hook after 'i'
log.debug("[DEBUG] Found array injection location for hook {} at instruction {}", hookInfo.method.getName(), 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 (InjectException ex)
{
throw new RuntimeException(ex);
}
});
e.run();
}
private void injectCallbackBefore(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext object, StackContext value)
{
Signature signature = hookInfo.method.getDescriptor();
Type methodArgumentType = signature.getTypeOfArg(0);
if (!hookInfo.method.isStatic())
{
if (object == null)
{
throw new InjectException("null object");
}
ins.getInstructions().add(idx++, new Dup(ins)); // dup value
idx = recursivelyPush(ins, idx, object);
ins.getInstructions().add(idx++, new Swap(ins));
if (hookInfo.getter instanceof Integer)
{
ins.getInstructions().add(idx++, new LDC(ins, hookInfo.getter));
ins.getInstructions().add(idx++, new IMul(ins));
}
else if (hookInfo.getter instanceof Long)
{
ins.getInstructions().add(idx++, new LDC(ins, hookInfo.getter));
ins.getInstructions().add(idx++, new LMul(ins));
}
}
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);
}
Instruction invoke = hookInfo.getInvoke(ins);
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)
{
if (!hookInfo.method.isStatic())
{
if (objectPusher == null)
{
throw new InjectException("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));
}
Instruction invoke = hookInfo.getInvoke(ins);
ins.getInstructions().add(idx, invoke);
}
@AllArgsConstructor
static class HookInfo
{
final Class targetClass;
final Method method;
final boolean before;
final Number getter;
Instruction getInvoke(Instructions instructions)
{
return InjectUtil.createInvokeFor(
instructions,
new net.runelite.asm.pool.Method(
targetClass,
method.getName(),
method.getDescriptor()
),
method.isStatic()
);
}
}
private static boolean isBefore(Annotation a)
{
Object val = a.get("before");
return val instanceof Boolean && (Boolean) val;
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.inject.Provider;
import net.runelite.asm.Annotation;
import net.runelite.asm.ClassFile;
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.instruction.types.ReturnInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
import net.runelite.asm.signature.Signature;
public class InjectHookMethod extends AbstractInjector
{
private static final Type METHODHOOK = new Type("Lnet/runelite/api/mixins/MethodHook;");
private final Map<Provider<ClassFile>, List<ClassFile>> mixinTargets;
private int injected = 0;
InjectHookMethod(final InjectData inject, final Map<Provider<ClassFile>, List<ClassFile>> mixinTargets)
{
super(inject);
this.mixinTargets = mixinTargets;
}
@Override
public void inject()
{
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectMethods(entry.getKey(), entry.getValue());
}
log.info("[INFO] Injected {} method hooks", injected);
}
private void injectMethods(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses)
{
final ClassFile mixinClass = mixinProvider.get();
for (ClassFile targetClass : targetClasses)
{
for (Method mixinMethod : mixinClass.getMethods())
{
final Annotation methodHook = mixinMethod.findAnnotation(METHODHOOK);
if (methodHook == null)
{
continue;
}
if (!mixinMethod.getDescriptor().isVoid())
{
throw new InjectException("Method hook " + mixinMethod.getPoolMethod() + " doesn't have void return type");
}
final String hookName = methodHook.getValueString();
final boolean end = isEnd(methodHook);
final ClassFile deobTarget = inject.toDeob(targetClass.getName());
final Signature deobSig = InjectUtil.apiToDeob(inject, mixinMethod.getDescriptor());
final boolean notStatic = !mixinMethod.isStatic();
final Method targetMethod = InjectUtil.findMethod(inject, hookName, deobTarget.getName(), sig -> InjectUtil.argsMatch(sig, deobSig), notStatic, false);
final net.runelite.asm.pool.Method hookMethod = new net.runelite.asm.pool.Method(
targetClass.getPoolClass(),
mixinMethod.getName(),
mixinMethod.getDescriptor()
);
inject(targetMethod, hookMethod, end);
log.debug("[DEBUG] Injected method hook {} in {}", hookMethod, targetMethod);
++injected;
}
}
}
private void inject(final Method method, final net.runelite.asm.pool.Method hookMethod, boolean end)
{
final Instructions ins = method.getCode().getInstructions();
final ListIterator<Instruction> it;
if (end)
{
it = ins.listIterator(ins.size());
while (it.hasPrevious())
{
if (it.previous() instanceof ReturnInstruction)
{
insertVoke(method, hookMethod, it);
}
}
return;
}
it = ins.listIterator();
if (method.getName().equals("<init>"))
{
while (it.hasNext())
{
if (it.next() instanceof InvokeSpecial)
{
break;
}
}
assert it.hasNext() : "Constructor without invokespecial";
}
insertVoke(method, hookMethod, it);
}
private void insertVoke(final Method method, final net.runelite.asm.pool.Method hookMethod, ListIterator<Instruction> iterator)
{
final Instructions instructions = method.getCode().getInstructions();
int varIdx = 0;
if (!method.isStatic())
{
iterator.add(new ALoad(instructions, varIdx++));
}
for (Type type : hookMethod.getType().getArguments())
{
iterator.add(
InjectUtil.createLoadForTypeIndex(
instructions,
type,
varIdx
)
);
varIdx += type.getSize();
}
iterator.add(
InjectUtil.createInvokeFor(
instructions,
hookMethod,
method.isStatic()
)
);
}
private static boolean isEnd(Annotation annotation)
{
Object val = annotation.get("end");
return val instanceof Boolean && (Boolean) val;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors;
import net.runelite.asm.Named;
public interface Injector extends Named
{
/**
* Where all the injection should be done
*/
void inject();
/**
* Get a name the injector is going to be referred to in logging
*/
default String getName()
{
return this.getClass().getSimpleName();
}
/**
* Called before inject, AbstractInjector currently uses it to start a stopwatch
*/
void start();
/**
* Gets a message logged at quiet level when the injector ends
*/
String getCompletionMsg();
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors;
import com.openosrs.injector.injection.InjectData;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Interfaces;
import net.runelite.asm.pool.Class;
import net.runelite.deob.DeobAnnotations;
import static com.openosrs.injector.rsapi.RSApi.API_BASE;
public class InterfaceInjector extends AbstractInjector
{
private int implemented = 0;
public InterfaceInjector(InjectData inject)
{
super(inject);
}
public void inject()
{
// forEachPair performs actions on a deob-vanilla pair, which is what's needed here
inject.forEachPair(this::injectInterface);
log.info("[INFO] Injected {} interfaces", implemented);
}
private void injectInterface(final ClassFile deobCf, final ClassFile vanillaCf)
{
final String impls = DeobAnnotations.getImplements(deobCf);
if (impls == null)
{
return;
}
final String fullName = API_BASE + impls;
if (!inject.getRsApi().hasClass(fullName))
{
log.error("[DEBUG] Class {} implements nonexistent interface {}, skipping interface injection",
deobCf.getName(),
fullName
);
return;
}
final Interfaces interfaces = vanillaCf.getInterfaces();
System.out.println(fullName);
interfaces.addInterface(new Class(fullName));
implemented++;
inject.addToDeob(fullName, deobCf);
}
}

View File

@@ -0,0 +1,811 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.inject.Provider;
import lombok.Value;
import net.runelite.asm.Annotation;
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.Annotated;
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.instruction.types.FieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
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.instruction.types.SetFieldInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.ANewArray;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.attributes.code.instructions.GetField;
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.LDC;
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.deob.DeobAnnotations;
import net.runelite.deob.util.JarUtil;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
public class MixinInjector extends AbstractInjector
{
private static final Type COPY = new Type("Lnet/runelite/api/mixins/Copy;");
private static final Type INJECT = new Type("Lnet/runelite/api/mixins/Inject;");
private static final Type MIXIN = new Type("Lnet/runelite/api/mixins/Mixin;");
private static final Type MIXINS = new Type("Lnet/runelite/api/mixins/Mixins;");
private static final Type REPLACE = new Type("Lnet/runelite/api/mixins/Replace;");
private static final Type SHADOW = new Type("Lnet/runelite/api/mixins/Shadow;");
private static final String ASSERTION_FIELD = "$assertionsDisabled";
private static final String MIXIN_BASE = "net/runelite/mixins/";
private int injectedInterfaces = 0;
private final Map<String, Field> injectedFields = new HashMap<>();
private final Map<net.runelite.asm.pool.Field, ShadowField> shadowFields = new HashMap<>();
private int copied = 0, replaced = 0, injected = 0;
public MixinInjector(InjectData inject)
{
super(inject);
}
@Override
public void inject()
{
final Map<Provider<ClassFile>, List<ClassFile>> mixinTargets = initTargets();
inject(mixinTargets);
}
@VisibleForTesting
void inject(Map<Provider<ClassFile>, List<ClassFile>> mixinTargets)
{
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectInterfaces(entry.getKey(), entry.getValue());
}
log.info("[INFO] Injected {} interfaces", injectedInterfaces);
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
System.out.println(entry.getKey().get().getName());
injectFields(entry.getKey(), entry.getValue());
}
log.info("[INFO] Injected {} fields", injectedFields.size());
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
findShadowFields(entry.getKey());
}
log.info("[INFO] Shadowed {} fields", shadowFields.size());
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectMethods(entry.getKey(), entry.getValue());
}
log.info("[INFO] Injected {}, copied {}, replaced {} methods", injected, copied, replaced);
inject.runChildInjector(new InjectHook(inject, mixinTargets));
inject.runChildInjector(new InjectHookMethod(inject, mixinTargets));
}
private Map<Provider<ClassFile>, List<ClassFile>> initTargets()
{
ImmutableMap.Builder<Provider<ClassFile>, List<ClassFile>> builder = ImmutableMap.builder();
try
{
for (ClassFile mixinClass : inject.getMixins())
{
System.out.println(mixinClass.getName());
final List<ClassFile> ret = getMixins(mixinClass);
builder.put(
(ret.size() > 1 ? mixinProvider(mixinClass) : () -> mixinClass),
ret
);
}
}
catch (Exception e)
{
e.printStackTrace();
}
return builder.build();
}
private void injectInterfaces(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses)
{
try
{
final ClassFile mixinClass = mixinProvider.get();
for (final ClassFile targetClass : targetClasses)
{
mixinClass.getInterfaces().getInterfaces().forEach((itf) ->
{
if (targetClass.getInterfaces().addInterface(itf))
{
injectedInterfaces++;
}
});
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
private void injectFields(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses)
{
try
{
final ClassFile mixinClass = mixinProvider.get();
for (final ClassFile targetClass : targetClasses)
{
for (Field field : mixinClass.getFields())
{
if (field.findAnnotation(INJECT) == null &&
(!ASSERTION_FIELD.equals(field.getName()) || targetClass.findField(ASSERTION_FIELD, Type.BOOLEAN) != null))
{
continue;
}
Field copy = new Field(targetClass, field.getName(), field.getType());
copy.setAccessFlags(field.getAccessFlags());
copy.setPublic();
copy.setValue(field.getValue());
for (Map.Entry<Type, Annotation> e : field.getAnnotations().entrySet())
{
if (!e.getKey().toString().startsWith("Lnet/runelite/api/mixins"))
{
copy.addAnnotation(e.getValue());
}
}
targetClass.addField(copy);
if (injectedFields.containsKey(field.getName()) && !ASSERTION_FIELD.equals(field.getName()))
{
throw new InjectException("Duplicate field: " + field.getName());
}
injectedFields.put(field.getName(), copy);
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
private void findShadowFields(Provider<ClassFile> mixinProvider)
{
final ClassFile mixinClass = mixinProvider.get();
for (final Field field : mixinClass.getFields())
{
Annotation shadow = field.findAnnotation(SHADOW);
if (shadow == null)
{
continue;
}
if (!field.isStatic())
{
throw new InjectException("Shadowed fields must be static");
}
String shadowed = shadow.getValueString();
Field targetField = injectedFields.get(shadowed);
Number getter = null;
if (targetField == null)
{
final Field deobTargetField = InjectUtil.findStaticField(inject, shadowed, null, InjectUtil.apiToDeob(inject, field.getType()));
targetField = inject.toVanilla(deobTargetField);
getter = DeobAnnotations.getObfuscatedGetter(deobTargetField);
}
if ((targetField.getAccessFlags() & Opcodes.ACC_PRIVATE) != 0)
{
throw new InjectException("Shadowed fields can't be private");
}
shadowFields.put(
field.getPoolField(),
new ShadowField(targetField, getter)
);
}
}
private void injectMethods(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses)
{
for (ClassFile targetClass : targetClasses)
{
final ClassFile mixinClass = mixinProvider.get();
// 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 mixinMethod : mixinClass.getMethods())
{
Annotation copyA = mixinMethod.findAnnotation(COPY);
if (copyA == null)
{
continue;
}
String copiedName = copyA.getValueString();
Signature deobSig = InjectUtil.apiToDeob(inject, mixinMethod.getDescriptor());
boolean notStat = !mixinMethod.isStatic();
Method deobSourceMethod = InjectUtil.findMethod(inject, copiedName, inject.toDeob(targetClass.getName()).getName(), deobSig::equals, notStat, true);
if (mixinMethod.isStatic() != deobSourceMethod.isStatic())
{
throw new InjectException("Mixin method " + mixinMethod + " should be " + (deobSourceMethod.isStatic() ? "static" : "non-static"));
}
// The actual method we're copying, including code etc
Method sourceMethod = inject.toVanilla(deobSourceMethod);
if (mixinMethod.getDescriptor().size() > sourceMethod.getDescriptor().size())
{
throw new InjectException("Mixin methods cannot have more parameters than their corresponding ob method");
}
Method copy = new Method(targetClass, "copy$" + copiedName, sourceMethod.getDescriptor());
moveCode(copy, sourceMethod.getCode());
copy.setAccessFlags(sourceMethod.getAccessFlags());
copy.setPublic();
copy.getExceptions().getExceptions().addAll(sourceMethod.getExceptions().getExceptions());
for (var a : sourceMethod.getAnnotations().values())
{
copy.addAnnotation(a);
}
targetClass.addMethod(copy);
++copied;
/*
* 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 = mixinMethod.getDescriptor().size() != sourceMethod.getDescriptor().size()
&& deobSourceMethod.getDescriptor().size() < copy.getDescriptor().size();
copiedMethods.put(mixinMethod.getPoolMethod(), new CopiedMethod(copy, !hasGarbageValue ? null : Integer.valueOf(DeobAnnotations.getDecoder(deobSourceMethod))));
}
// Handle the rest of the mixin types
for (Method mixinMethod : mixinClass.getMethods())
{
boolean isClinit = "<clinit>".equals(mixinMethod.getName());
boolean isInit = "<init>".equals(mixinMethod.getName());
boolean hasInject = mixinMethod.findAnnotation(INJECT) != null;
// You can't annotate clinit, so its always injected
if ((hasInject && isInit) || isClinit)
{
if (!"()V".equals(mixinMethod.getDescriptor().toString()))
{
throw new InjectException("Injected constructors cannot have arguments");
}
Method[] originalMethods = targetClass.getMethods().stream()
.filter(m -> m.getName().equals(mixinMethod.getName()))
.toArray(Method[]::new);
String name = mixinMethod.getName();
// If there isn't a <clinit> already just inject ours, otherwise rename it
// This is always true for <init>
if (originalMethods.length > 0)
{
name = "rl$$" + (isInit ? "init" : "clinit");
}
String numberlessName = name;
for (int i = 1; targetClass.findMethod(name, mixinMethod.getDescriptor()) != null; i++)
{
name = numberlessName + i;
}
Method copy = new Method(targetClass, name, mixinMethod.getDescriptor());
moveCode(copy, mixinMethod.getCode());
copy.setAccessFlags(mixinMethod.getAccessFlags());
copy.setPrivate();
assert mixinMethod.getExceptions().getExceptions().isEmpty();
// Remove the call to the superclass's ctor
if (isInit)
{
Instructions instructions = copy.getCode().getInstructions();
ListIterator<Instruction> listIter = instructions.listIterator();
while (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(mixinClass, targetClass, copy, copiedMethods);
targetClass.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.listIterator();
while (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();
}
}
}
log.debug("[DEBUG] Injected mixin method {} to {}", copy, targetClass);
++injected;
}
else if (hasInject)
{
// Make sure the method doesn't invoke copied methods
for (Instruction i : mixinMethod.getCode().getInstructions())
{
if (i instanceof InvokeInstruction)
{
InvokeInstruction ii = (InvokeInstruction) i;
if (copiedMethods.containsKey(ii.getMethod()))
{
throw new InjectException("Injected methods cannot invoke copied methods");
}
}
}
Method copy = new Method(targetClass, mixinMethod.getName(), mixinMethod.getDescriptor());
moveCode(copy, mixinMethod.getCode());
copy.setAccessFlags(mixinMethod.getAccessFlags());
copy.setPublic();
assert mixinMethod.getExceptions().getExceptions().isEmpty();
setOwnersToTargetClass(mixinClass, targetClass, copy, copiedMethods);
targetClass.addMethod(copy);
log.debug("[DEBUG] Injected mixin method {} to {}", copy, targetClass);
++injected;
}
else if (mixinMethod.findAnnotation(REPLACE) != null)
{
Annotation replaceAnnotation = mixinMethod.findAnnotation(REPLACE);
String replacedName = replaceAnnotation.getValueString();
ClassFile deobClass = inject.toDeob(targetClass.getName());
Method deobMethod = findDeobMatching(deobClass, mixinMethod, replacedName);
if (deobMethod == null)
{
throw new InjectException("Failed to find the deob method " + replacedName + " for mixin " + mixinClass);
}
if (mixinMethod.isStatic() != deobMethod.isStatic())
{
throw new InjectException("Mixin method " + mixinMethod + " should be "
+ (deobMethod.isStatic() ? "static" : "non-static"));
}
String obReplacedName = InjectUtil.getObfuscatedName(deobMethod);
Signature obMethodSignature = deobMethod.getObfuscatedSignature();
// Find the vanilla class where the method to copy is in
ClassFile obCf = inject.toVanilla(deobMethod.getClassFile());
Method obMethod = obCf.findMethod(obReplacedName, obMethodSignature);
assert obMethod != null : "obfuscated method " + obReplacedName + obMethodSignature + " does not exist";
if (mixinMethod.getDescriptor().size() > obMethod.getDescriptor().size())
{
throw new InjectException("Mixin methods cannot have more parameters than their corresponding ob method");
}
Type returnType = mixinMethod.getDescriptor().getReturnValue();
Type deobReturnType = InjectUtil.apiToDeob(inject, returnType);
if (!returnType.equals(deobReturnType))
{
ClassFile deobReturnTypeClassFile = inject.getDeobfuscated()
.findClass(deobReturnType.getInternalName());
if (deobReturnTypeClassFile != null)
{
ClassFile obReturnTypeClass = inject.toVanilla(deobReturnTypeClassFile);
Instructions instructions = mixinMethod.getCode().getInstructions();
ListIterator<Instruction> listIter = instructions.listIterator();
while (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, mixinMethod.getCode());
boolean hasGarbageValue = mixinMethod.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(mixinClass, targetClass, obMethod, copiedMethods);
log.debug("[DEBUG] Replaced method {} with mixin method {}", obMethod, mixinMethod);
replaced++;
}
}
}
}
private void moveCode(Method targetMethod, Code sourceCode)
{
Code newCode = new Code(targetMethod);
newCode.setMaxStack(sourceCode.getMaxStack());
newCode.getInstructions().getInstructions().addAll(sourceCode.getInstructions().getInstructions());
// Update instructions for each instruction
for (Instruction i : newCode.getInstructions())
{
i.setInstructions(newCode.getInstructions());
}
newCode.getExceptions().getExceptions().addAll(sourceCode.getExceptions().getExceptions());
for (net.runelite.asm.attributes.code.Exception e : newCode.getExceptions().getExceptions())
{
e.setExceptions(newCode.getExceptions());
}
targetMethod.setCode(newCode);
}
private void setOwnersToTargetClass(ClassFile mixinCf, ClassFile cf, Method method, Map<net.runelite.asm.pool.Method, CopiedMethod> copiedMethods)
{
ListIterator<Instruction> iterator = method.getCode().getInstructions().listIterator();
while (iterator.hasNext())
{
Instruction i = iterator.next();
if (i instanceof ANewArray)
{
Type type = ((ANewArray) i).getType_();
ClassFile deobTypeClass = inject.toDeob(type.getInternalName());
if (deobTypeClass != null)
{
Type newType = new Type("L" + inject.toVanilla(deobTypeClass).getName() + ";");
((ANewArray) i).setType(newType);
log.debug("[DEBUG] Replaced {} type {} with type {}", i, type, newType);
}
}
else if (i instanceof InvokeInstruction)
{
InvokeInstruction ii = (InvokeInstruction) i;
CopiedMethod copiedMethod = copiedMethods.get(ii.getMethod());
if (copiedMethod != null)
{
ii.setMethod(copiedMethod.copy.getPoolMethod());
// Pass through garbage value if the method has one
if (copiedMethod.garbage != null)
{
iterator.previous();
iterator.add(new LDC(method.getCode().getInstructions(), copiedMethod.garbage));
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;
ShadowField shadowField = shadowFields.get(fi.getField());
if (shadowField != null)
{
Field shadowed = shadowField.targetField;
if (shadowField.obfuscatedGetter != null)
{
if (i instanceof SetFieldInstruction)
{
iterator.previous();
InjectUtil.injectObfuscatedSetter(shadowField.obfuscatedGetter, i.getInstructions(), iterator::add);
iterator.next();
}
else if (i instanceof GetFieldInstruction)
{
InjectUtil.injectObfuscatedGetter(shadowField.obfuscatedGetter, i.getInstructions(), iterator::add);
}
}
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 verify(ClassFile mixinCf, Instruction i)
{
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 InjectException("Access to non static member field of mixin");
}
Field field = fi.getMyField();
if (field != null && !field.isPublic())
{
throw new InjectException("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))
{
throw new InjectException("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 InjectException("Injected bytecode must be Java 6 compatible");
}
}
private Method findDeobMatching(ClassFile deobClass, Method mixinMethod, String deobName)
{
List<Method> matching = new ArrayList<>();
for (Method method : deobClass.getMethods())
{
if (!deobName.equals(method.getName()))
{
continue;
}
if (InjectUtil.apiToDeobSigEquals(inject, method.getDescriptor(), mixinMethod.getDescriptor()))
{
matching.add(method);
}
}
if (matching.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 InjectException("There are several matching methods when there should only be one");
}
else if (matching.size() == 1)
{
return matching.get(0);
}
return inject.getDeobfuscated().findStaticMethod(deobName);
}
private void shiftLocalIndices(Instructions instructions, int startIdx)
{
for (Instruction i : instructions)
{
if (i instanceof LVTInstruction)
{
LVTInstruction lvti = (LVTInstruction) i;
if (lvti.getVariableIndex() >= startIdx)
{
lvti.setVariableIndex(lvti.getVariableIndex() + 1);
}
}
}
}
@SuppressWarnings("unchecked")
private List<ClassFile> getMixins(Annotated from)
{
final Annotation mixin = from.findAnnotation(MIXIN);
if (mixin != null)
{
return List.of(InjectUtil.getVanillaClassFromAnnotationString(inject, mixin));
}
final Annotation mixins = from.findAnnotation(MIXINS);
if (mixins != null)
{
return ((List<Annotation>) mixins.getValue()).stream()
.map(mix -> InjectUtil.getVanillaClassFromAnnotationString(inject, mix))
.collect(Collectors.toUnmodifiableList());
}
throw new IllegalArgumentException("No MIXIN or MIXINS found on " + from.toString());
}
@Value
private static class CopiedMethod
{
@Nonnull
Method copy;
@Nullable Integer garbage;
}
@Value
private static class ShadowField
{
@Nonnull
Field targetField;
@Nullable Number obfuscatedGetter;
}
private static Provider<ClassFile> mixinProvider(ClassFile mixin)
{
return new Provider<>()
{
byte[] bytes = null;
@Override
public ClassFile get()
{
if (bytes != null)
{
return JarUtil.loadClass(bytes);
}
bytes = JarUtil.writeClass(mixin.getGroup(), mixin);
return mixin;
}
};
}
}

View File

@@ -0,0 +1,303 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.rsapi.InjectGetter;
import com.openosrs.injector.injectors.rsapi.InjectInvoke;
import com.openosrs.injector.injectors.rsapi.InjectSetter;
import com.openosrs.injector.rsapi.RSApiClass;
import com.openosrs.injector.rsapi.RSApiMethod;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
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.Annotated;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import static com.openosrs.injector.rsapi.RSApi.API_BASE;
public class RSApiInjector extends AbstractInjector
{
private final Map<Field, List<RSApiMethod>> retryFields = new HashMap<>();
private int get = 0, set = 0, voke = 0;
public RSApiInjector(InjectData inject)
{
super(inject);
}
public void inject()
{
for (final ClassFile deobClass : inject.getDeobfuscated())
{
final RSApiClass implementingClass = inject.getRsApi().findClass(API_BASE + deobClass.getName());
injectFields(deobClass, implementingClass);
injectMethods(deobClass, implementingClass);
}
retryFailures();
log.info("[INFO] Injected {} getters, {} setters, and {} invokers", get, set, voke);
}
private void injectFields(ClassFile deobClass, RSApiClass implementingClass)
{
for (Field deobField : deobClass.getFields())
{
final List<RSApiMethod> matching = findImportsFor(deobField, deobField.isStatic(), implementingClass);
if (matching == null)
{
continue;
}
final Type deobType = deobField.getType();
// We're dealing with a field here, so only getter/setter methods match
ListIterator<RSApiMethod> it = matching.listIterator();
while (it.hasNext())
{
RSApiMethod apiMethod = it.next();
if (apiMethod.isInjected())
{
it.remove();
continue;
}
// Check if there's a field with the same exported name on the api target class itself,
// as that should come before random static fields.
if (deobField.isStatic())
{
ClassFile deobApiTarget = InjectUtil.deobFromApiMethod(inject, apiMethod);
if (deobApiTarget != deobClass &&
deobApiTarget.findField(deobField.getName()) != null)
{
it.remove();
continue;
}
}
final Signature sig = apiMethod.getSignature();
if (sig.isVoid())
{
if (sig.size() == 1)
{
Type type = InjectUtil.apiToDeob(inject, sig.getTypeOfArg(0));
if (deobType.equals(type))
{
continue;
}
}
}
else if (sig.size() == 0)
{
Type type = InjectUtil.apiToDeob(inject, sig.getReturnValue());
if (deobType.equals(type))
{
continue;
}
}
it.remove();
}
if (matching.size() == 0)
{
continue;
}
else if (matching.size() > 2)
{
retryFields.put(deobField, new ArrayList<>(matching));
continue;
}
final Field vanillaField = inject.toVanilla(deobField);
final Number getter = DeobAnnotations.getObfuscatedGetter(deobField);
if (deobField.isStatic() != vanillaField.isStatic()) // Can this even happen
{
throw new InjectException("Something went horribly wrong, and this should honestly never happen, but you never know. Btw it's the static-ness");
}
inject(matching, deobField, vanillaField, getter);
}
}
private void injectMethods(ClassFile deobClass, RSApiClass implementingClass)
{
for (Method deobMethod : deobClass.getMethods())
{
final List<RSApiMethod> matching = findImportsFor(deobMethod, deobMethod.isStatic(), implementingClass);
if (matching == null)
{
continue;
}
final Signature deobSig = deobMethod.getDescriptor();
ListIterator<RSApiMethod> it = matching.listIterator();
while (it.hasNext())
{
final RSApiMethod apiMethod = it.next();
// Check if there's a method with the same exported name on the api target class itself,
// as that should come before random static methods.
if (deobMethod.isStatic())
{
ClassFile deobApiTarget = InjectUtil.deobFromApiMethod(inject, apiMethod);
if (deobApiTarget != deobClass &&
deobApiTarget.findMethod(deobMethod.getName()) != null)
{
it.remove();
continue;
}
}
final Signature apiSig = apiMethod.getSignature();
if (apiMethod.isInjected()
|| !InjectUtil.apiToDeobSigEquals(inject, deobSig, apiSig))
{
it.remove();
}
}
if (matching.size() == 1)
{
final RSApiMethod apiMethod = matching.get(0);
final ClassFile targetClass = InjectUtil.vanillaFromApiMethod(inject, apiMethod);
final Method vanillaMethod = inject.toVanilla(deobMethod);
final String garbage = DeobAnnotations.getDecoder(deobMethod);
log.debug("[DEBUG] Injecting invoker {} for {} into {}", apiMethod.getMethod(), vanillaMethod.getPoolMethod(), targetClass.getPoolClass());
InjectInvoke.inject(targetClass, apiMethod, vanillaMethod, garbage);
++voke;
apiMethod.setInjected(true);
}
else if (matching.size() != 0)
{
throw new InjectException("Multiple api imports matching method " + deobMethod.getPoolMethod());
}
}
}
private void retryFailures()
{
for (Map.Entry<Field, List<RSApiMethod>> entry : retryFields.entrySet())
{
final List<RSApiMethod> matched = entry.getValue();
final Field deobField = entry.getKey();
matched.removeIf(RSApiMethod::isInjected);
if (matched.size() > 2)
{
throw new InjectException("More than 2 imported api methods for field " + deobField.getPoolField());
}
final Field vanillaField = inject.toVanilla(deobField);
final Number getter = DeobAnnotations.getObfuscatedGetter(deobField);
inject(matched, deobField, vanillaField, getter);
}
}
private List<RSApiMethod> findImportsFor(Annotated object, boolean statik, RSApiClass implemented)
{
final String exportedName = InjectUtil.getExportedName(object);
if (exportedName == null)
{
return null;
}
final List<RSApiMethod> matching = new ArrayList<>();
if (statik)
{
for (RSApiClass api : inject.getRsApi())
{
api.fetchImported(matching, exportedName);
}
}
else if (implemented != null)
{
implemented.fetchImported(matching, exportedName);
}
return matching;
}
private void inject(List<RSApiMethod> matched, Field field, Field targetField, Number getter)
{
for (RSApiMethod apiMethod : matched)
{
final ClassFile targetClass = InjectUtil.vanillaFromApiMethod(inject, apiMethod);
apiMethod.setInjected(true);
if (apiMethod.getSignature().isVoid())
{
++set;
log.debug("[DEBUG] Injecting setter {} for {} into {}", apiMethod.getMethod(), field.getPoolField(), targetClass.getPoolClass());
InjectSetter.inject(
targetClass,
apiMethod,
targetField,
getter
);
}
else
{
++get;
log.debug("[DEBUG] Injecting getter {} for {} into {}", apiMethod.getMethod(), field.getPoolField(), targetClass.getPoolClass());
InjectGetter.inject(
targetClass,
apiMethod,
targetField,
getter
);
}
}
}
}

View File

@@ -0,0 +1,281 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* Copyright (c) 2020, ThatGamerBlue <https://github.com/ThatGamerBlue>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import net.runelite.asm.Method;
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.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.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.BiPush;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IAnd;
import net.runelite.asm.attributes.code.instructions.IfACmpEq;
import net.runelite.asm.attributes.code.instructions.IfACmpNe;
import net.runelite.asm.attributes.code.instructions.IfICmpNe;
import net.runelite.asm.attributes.code.instructions.IfNe;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.Return;
import net.runelite.asm.pool.Field;
public class AddPlayerToMenu extends AbstractInjector
{
public AddPlayerToMenu(InjectData inject)
{
super(inject);
}
public void inject()
{
final Method addPlayerOptions = InjectUtil.findMethod(inject, "addPlayerToMenu");
final net.runelite.asm.pool.Method shouldHideAttackOptionFor =
inject.getVanilla().findClass("client").findMethod("shouldHideAttackOptionFor").getPoolMethod();
final net.runelite.asm.pool.Method shouldDrawMethod =
inject.getVanilla().findStaticMethod("shouldDraw").getPoolMethod();
try
{
injectSameTileFix(addPlayerOptions, shouldDrawMethod);
injectHideAttack(addPlayerOptions, shouldHideAttackOptionFor);
injectHideCast(addPlayerOptions, shouldHideAttackOptionFor);
}
catch (InjectException | AssertionError e)
{
log.warn("[WARN] HidePlayerAttacks failed, but as this doesn't mess up anything other than that functionality, we're carrying on", e);
}
}
private void injectSameTileFix(Method addPlayerOptions, net.runelite.asm.pool.Method shouldDrawMethod)
{
// ALOAD 0
// ICONST_0
// INVOKESTATIC Scene.shouldDraw
// IFNE CONTINUE_LABEL if returned true then jump to continue
// RETURN
// CONTINUE_LABEL
// REST OF METHOD GOES HERE
Instructions insns = addPlayerOptions.getCode().getInstructions();
Label CONTINUE_LABEL = new Label(insns);
List<Instruction> prependList = new ArrayList<>()
{{
add(new ALoad(insns, 0));
add(new LDC(insns, 0));
add(new InvokeStatic(insns, shouldDrawMethod));
add(new IfNe(insns, CONTINUE_LABEL));
add(new Return(insns, InstructionType.RETURN));
add(CONTINUE_LABEL);
}};
int i = 0;
for (Instruction instruction : prependList)
{
insns.addInstruction(i++, instruction);
}
}
private void injectHideAttack(Method addPlayerOptions, net.runelite.asm.pool.Method shouldHideAttackOptionFor)
{
final Field AttackOption_hidden =
InjectUtil.findField(inject, "AttackOption_hidden", "AttackOption").getPoolField();
final Field attackOption = InjectUtil.findField(inject, "playerAttackOption", "Client").getPoolField();
// GETSTATIC GETSTATIC
// GETSTATIC GETSTATIC
// IFACMPEQ -> label continue IFACMPNE -> label whatever lets carry on
// MORE OBFUSCATION
int injectIdx = -1;
Instruction labelIns = null;
Label label = null;
Instructions ins = addPlayerOptions.getCode().getInstructions();
Iterator<Instruction> iterator = ins.getInstructions().iterator();
while (iterator.hasNext())
{
Instruction i = iterator.next();
if (!(i instanceof GetStatic))
{
continue;
}
Field field = ((GetStatic) i).getField();
if (!field.equals(AttackOption_hidden) && !field.equals(attackOption))
{
continue;
}
i = iterator.next();
if (!(i instanceof GetStatic))
{
continue;
}
field = ((GetStatic) i).getField();
if (!field.equals(AttackOption_hidden) && !field.equals(attackOption))
{
continue;
}
i = iterator.next();
if (!(i instanceof ComparisonInstruction && i instanceof JumpingInstruction))
{
log.info("[INFO] You're not supposed to see this lol");
continue;
}
if (i instanceof IfACmpEq)
{
injectIdx = ins.getInstructions().indexOf(i) + 1;
label = ((IfACmpEq) i).getJumps().get(0);
}
else if (i instanceof IfACmpNe)
{
injectIdx = ins.getInstructions().indexOf(((IfACmpNe) i).getJumps().get(0)) + 1;
// We're gonna have to inject a extra label
labelIns = iterator.next();
}
break;
}
if (injectIdx <= 0 || label == null && labelIns == null)
{
throw new InjectException("HidePlayerAttacks failed");
}
// Load the player
ALoad i1 = new ALoad(ins, 0);
// Get the boolean
InvokeStatic i2 = new InvokeStatic(ins, shouldHideAttackOptionFor);
ins.addInstruction(injectIdx, i1);
ins.addInstruction(injectIdx + 1, i2);
if (label == null)
{
label = ins.createLabelFor(labelIns);
ins.rebuildLabels();
injectIdx = ins.getInstructions().indexOf(i2) + 1;
}
// Compare n such
IfNe i3 = new IfNe(ins, label);
ins.addInstruction(injectIdx, i3);
}
private void injectHideCast(Method addPlayerOptions, net.runelite.asm.pool.Method shouldHideAttackOptionFor)
{
// LABEL before
// BIPUSH 8
// LDC (garbage)
// GETSTATIC selectedSpellFlags
// IMUL
// BIPUSH 8
// IAND
// IF_ICMPNE -> skip adding option
//
// <--- Inject call here
// <--- Inject comparison here (duh)
//
// add option n such
final Field flags = InjectUtil.findField(inject, "selectedSpellFlags", "Client").getPoolField();
Instructions ins = addPlayerOptions.getCode().getInstructions();
ListIterator<Instruction> iterator = ins.getInstructions().listIterator();
boolean b1, b2, iAnd, getstatic;
b1 = b2 = iAnd = getstatic = false;
while (iterator.hasNext())
{
Instruction i = iterator.next();
if (i instanceof Label)
{
b1 = b2 = iAnd = getstatic = false;
continue;
}
if ((i instanceof BiPush) && (byte) ((BiPush) i).getConstant() == 8)
{
if (!b1)
{
b1 = true;
}
else if (!b2)
{
b2 = true;
}
else
{
throw new InjectException("3 bipushes? fucking mental, Hide spells failed btw");
}
continue;
}
if (i instanceof IAnd)
{
iAnd = true;
continue;
}
if (i instanceof GetStatic && ((GetStatic) i).getField().equals(flags))
{
getstatic = true;
continue;
}
if (!(i instanceof JumpingInstruction))
{
if (b1 && b2 && iAnd && getstatic)
{
throw new InjectException("@ me in discord if this shit is broken lol, hide spells failed btw");
}
continue;
}
if (!(b1 && b2 && iAnd && getstatic))
{
continue;
}
Label target;
if (i instanceof IfICmpNe)
{
target = ((IfICmpNe) i).getJumps().get(0);
}
else
{
throw new InjectException("@ me in discord if this shit is broken lol, hide spells failed btw");
}
// Load the player
ALoad i1 = new ALoad(ins, 0);
// Get the boolean
InvokeStatic i2 = new InvokeStatic(ins, shouldHideAttackOptionFor);
// Compare n such
IfNe i3 = new IfNe(ins, target);
iterator.add(i1);
iterator.add(i2);
iterator.add(i3);
return;
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
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.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
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.asm.signature.Signature;
import static com.openosrs.injector.injection.InjectData.HOOKS;
public class ClearColorBuffer extends AbstractInjector
{
private static final net.runelite.asm.pool.Method CLEARBUFFER = new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(HOOKS),
"clearColorBuffer",
new Signature("(IIIII)V")
);
public ClearColorBuffer(InjectData inject)
{
super(inject);
}
public void inject()
{
/*
* This class stops the client from basically painting everything black before the scene is drawn
*/
final Execution exec = new Execution(inject.getVanilla());
final net.runelite.asm.pool.Method fillRectPool = InjectUtil.findMethod(inject, "Rasterizer2D_fillRectangle", "Rasterizer2D", null).getPoolMethod();
final Method drawEntities = InjectUtil.findMethod(inject, "drawEntities"); // XXX: should prob be called drawViewport?
exec.addMethod(drawEntities);
exec.noInvoke = true;
final AtomicReference<MethodContext> pcontext = new AtomicReference<>(null);
exec.addMethodContextVisitor(pcontext::set);
exec.run();
final MethodContext methodContext = pcontext.get();
for (InstructionContext ic : methodContext.getInstructionContexts())
{
final Instruction instr = ic.getInstruction();
if (!(instr instanceof InvokeStatic))
{
continue;
}
if (fillRectPool.equals(((InvokeStatic) instr).getMethod()))
{
List<StackContext> pops = ic.getPops();
// Last pop is constant value 0, before that are vars in order
assert pops.size() == 5 : "If this fails cause of this add in 1 for obfuscation, I don't think that happens here though";
int i = 0;
Instruction pushed = pops.get(i++).getPushed().getInstruction();
assert (pushed instanceof PushConstantInstruction) && ((PushConstantInstruction) pushed).getConstant().equals(0);
for (int varI = 3; i < 5; i++, varI--)
{
pushed = pops.get(i).getPushed().getInstruction();
assert (pushed instanceof ILoad) && ((ILoad) pushed).getVariableIndex() == varI;
}
Instructions ins = instr.getInstructions();
ins.replace(instr, new InvokeStatic(ins, CLEARBUFFER));
log.debug("[DEBUG] Injected drawRectangle at {}", methodContext.getMethod().getPoolMethod());
}
}
}
}

View File

@@ -0,0 +1,302 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import static com.openosrs.injector.injection.InjectData.CALLBACKS;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.HashSet;
import java.util.ListIterator;
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.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.GetField;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.InvokeInterface;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.signature.Signature;
public class DrawAfterWidgets extends AbstractInjector
{
public DrawAfterWidgets(InjectData inject)
{
super(inject);
}
public void inject()
{
/*
* 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(rootInterface != -1) {
* ClientPreferences.method1809(rootInterface);
* }
* 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(rootInterface != -1) {
* rootWidgetCount = 0;
* Interpreter.drawWidgets(rootInterface, 0, 0, SoundCache.canvasWidth, Huffman.canvasHeight, 0, 0, -1);
* }
* <-- here
* Rasterizer2D.Rasterizer2D_resetClip();
* ______________________________________________________
*/
boolean injected = false;
Field client = getVanillaStaticFieldFromDeob("client");
Field callbacks = getObfuscatedField("callbacks");
Method noClip = InjectUtil.findMethod(inject, "Rasterizer2D_resetClip", "Rasterizer2D", null); // !!!!!
if (noClip == null)
{
throw new InjectException("Mapped method \"Rasterizer2D_resetClip\" could not be found.");
}
net.runelite.asm.pool.Method poolNoClip = noClip.getPoolMethod();
for (ClassFile c : inject.getVanilla())
{
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.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)"
// log.debug("[DEBUG] Could not find the label for jumping to the " + noClip + " call in " + m);
continue;
}
Set<Label> labelsToInjectAfter = new HashSet<>();
ListIterator<Instruction> jumpIterator = instructions.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)
{
InvokeInterface invoke = new InvokeInterface(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(CALLBACKS),
"drawAfterWidgets",
new Signature("()V")
)
);
instructions.addInstruction(instructions.getInstructions().indexOf(l) + 1, invoke);
instructions.addInstruction(instructions.getInstructions().indexOf(l) + 1, new GetField(instructions, callbacks.getPoolField()));
instructions.addInstruction(instructions.getInstructions().indexOf(l) + 1, new GetStatic(instructions, client.getPoolField()));
log.debug("[DEBUG] injectDrawAfterWidgets injected a call after " + l);
injected = true;
}
}
}
if (!injected)
{
throw new InjectException("injectDrawAfterWidgets failed to inject!");
}
}
public Field getVanillaStaticFieldFromDeob(String s)
{
for (ClassFile c : inject.getDeobfuscated())
{
for (Field f : c.getFields())
{
if (f.isStatic())
{
if (f.getName().equals(s))
{
return inject.toVanilla(f);
}
}
}
}
return null;
}
public Field getObfuscatedField(String s)
{
for (ClassFile c : inject.getVanilla())
{
for (Field f : c.getFields())
{
if (!f.isStatic())
{
if (f.getName().equals(s))
{
return f;
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
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.instructions.GetStatic;
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.execution.MethodContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Field;
import net.runelite.asm.signature.Signature;
import static com.openosrs.injector.injection.InjectData.HOOKS;
public class DrawMenu extends AbstractInjector
{
private static final net.runelite.asm.pool.Method DRAWMENU = new net.runelite.asm.pool.Method(
new Class(HOOKS),
"drawMenu",
new Signature("()Z")
);
public DrawMenu(InjectData inject)
{
super(inject);
}
public void inject()
{
/*
* The drawMenu method can be inlined, so we need this raw injector to find where to inject.
*
* Originally I wanted to make sure we don't skip the method where client gets told not to draw
* the widgets behind the menu. This would be useless though, as the client redraws the widgets
* no matter what. It would also be such a insignificant performance boost it doesn't seem worth
* the extra headache to me.
*
* --- what the code looks like completely uninlined ---
* if (!isMenuOpen) {
* if (viewportX != -1) {
* drawTopLeftText(viewportX, viewportY);
* }
* } else {
* drawMenu();
* }
*
* if (gameDrawingMode == 3) {
* ...
* --------
*/
final Method drawLoggedIn = InjectUtil.findMethod(inject, "drawLoggedIn", "Client", null, true, false);
final Field gameDrawMode = InjectUtil.findField(inject, "gameDrawingMode", "Client").getPoolField();
final Field isMenuOpen = InjectUtil.findField(inject, "isMenuOpen", "Client").getPoolField();
final Execution execution = new Execution(inject.getVanilla());
execution.noInvoke = true;
execution.addMethod(drawLoggedIn);
AtomicReference<MethodContext> mcRef = new AtomicReference<>(null);
execution.addMethodContextVisitor(mcRef::set);
execution.run();
Instruction injectInvokeAfter = null;
Label labelToJumpTo = null;
MethodContext mc = mcRef.get();
for (InstructionContext ic : mc.getInstructionContexts())
{
Instruction instruction = ic.getInstruction();
if (!(instruction instanceof GetStatic))
{
continue;
}
if (((GetStatic) instruction).getField().equals(isMenuOpen))
{
InstructionContext isMenuOpenPopper = ic.getPushes().get(0).getPopped().get(0);
Instruction isMenuOpenPopI = isMenuOpenPopper.getInstruction();
// Unless there's a isMenuOpen in drawLoggedIn I missed (or something new got inlined (and I missed that)) this should never happen
assert isMenuOpenPopI instanceof IfEq || isMenuOpenPopI instanceof IfNe : "isMenuOpen was popped by a " + isMenuOpenPopI + "?";
// If the popper is a IfNe the label it's pointing to is the drawMenu one and topLeft is directly after it
// else it's the other way around, obviously
if (isMenuOpenPopI instanceof IfNe)
{
injectInvokeAfter = ((IfNe) isMenuOpenPopI).getTo();
}
else
{
injectInvokeAfter = isMenuOpenPopI;
}
}
else if (((GetStatic) instruction).getField().equals(gameDrawMode))
{
List<Instruction> instrL = instruction.getInstructions().getInstructions();
for (int i = instrL.indexOf(instruction); !(instruction instanceof Label); i--)
{
instruction = instrL.get(i);
}
labelToJumpTo = (Label) instruction;
}
if (injectInvokeAfter != null && labelToJumpTo != null)
{
break;
}
}
if (injectInvokeAfter == null || labelToJumpTo == null)
{
throw new InjectException("Couldn't find the right location for DrawMenu to inject");
}
final Instructions instrs = mc.getMethod().getCode().getInstructions();
int idx = instrs.getInstructions().indexOf(injectInvokeAfter);
instrs.addInstruction(++idx, new InvokeStatic(instrs, DRAWMENU));
instrs.addInstruction(++idx, new IfNe(instrs, labelToJumpTo));
log.info("[INFO] DrawMenu injected a method call at index {} in method {}. With a comparison jumping to {}", idx, drawLoggedIn, labelToJumpTo);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.ListIterator;
import net.runelite.asm.Method;
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.BiPush;
public class Occluder extends AbstractInjector
{
private static final byte OLDVALUE = 25;
private static final byte NEWVALUE = 90;
public Occluder(InjectData inject)
{
super(inject);
}
public void inject()
{
/*
* This class the max view distance length, higher than this is useless though
*/
final Method occlude = inject.toVanilla(
inject.getDeobfuscated()
.findClass("Scene")
.findMethod("occlude")
);
int replaced = 0;
final Code code = occlude.getCode();
final Instructions ins = code.getInstructions();
final ListIterator<Instruction> it = ins.listIterator();
while (it.hasNext())
{
Instruction i = it.next();
if (!(i instanceof BiPush))
{
continue;
}
boolean shouldChange = (byte) ((BiPush) i).getConstant() == OLDVALUE;
if (!shouldChange)
{
continue;
}
replaced++;
Instruction biPush = new BiPush(ins, NEWVALUE);
it.set(biPush);
}
if (replaced != 10)
{
throw new InjectException("Only found " + replaced + " 25's to replace in occlude instead of expected 10");
}
}
}

View File

@@ -0,0 +1,276 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors.raw;
import com.google.common.collect.Lists;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
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.instruction.types.GetFieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
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.IAdd;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.IOr;
import net.runelite.asm.attributes.code.instructions.IShR;
import net.runelite.asm.attributes.code.instructions.ISub;
import net.runelite.asm.attributes.code.instructions.IUShR;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.SiPush;
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.asm.execution.VariableContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
public class RasterizerAlpha extends AbstractInjector
{
private static final net.runelite.asm.pool.Method DRAWALPHA = new net.runelite.asm.pool.Method(
new Class("client"),
"drawAlpha",
new Signature("([IIII)V")
);
private static final int ALPHA = 0xff000000;
public RasterizerAlpha(InjectData inject)
{
super(inject);
}
/*
* This class exists to allow transparency in overlays
*/
public void inject()
{
final Field r2dPx = InjectUtil.findField(inject, "Rasterizer2D_pixels", "Rasterizer2D");
final Method draw = InjectUtil.findMethod(inject, "drawLoggedIn", "Client");
final ClassFile rasterizer2D = r2dPx.getClassFile();
final Execution ex = new Execution(rasterizer2D.getGroup());
ex.staticStep = false;
ex.step = false;
ex.addMethod(draw);
int[] counts = new int[2];
ex.addMethodContextVisitor((MethodContext mc) ->
{
Instructions instrs = getInstrs(mc);
if (instrs == null)
{
return;
}
int count = 0;
int orCount = 0;
outer:
for (InstructionContext ic : mc.getInstructionContexts())
{
Instruction instruction = ic.getInstruction();
if (!(instruction instanceof IAStore))
{
continue;
}
// Field field = astore.getMyField(ic);
// doesn't track into methods so doing it here
StackContext array = ic.getPops().get(2);
if (!isSameField(r2dPx, array))
{
continue;
}
// This is the colour that's being set
StackContext colour = ic.getPops().get(0);
// resolve gets the original value pusher
InstructionContext colPusher = colour.getPushed().resolve(colour);
Instruction colPushI = colPusher.getInstruction();
// If it's not a >> or a >>> or a + it's not alpha
if (colPushI instanceof IShR ||
colPushI instanceof IUShR ||
colPushI instanceof IAdd)
{
// So we know we may be dealing with alpha here, now we need the alpha value
// earlier on in the method there's been a 256 - XXX, where xxx is alpha.
// if that SiPush 256 doesn't exist, we should just | 0xff000000 instead
for (InstructionContext ins : mc.getInstructionContexts())
{
if (!(ins.getInstruction() instanceof SiPush))
{
continue;
}
SiPush pci = (SiPush) ins.getInstruction();
if ((short) pci.getConstant() != (short) 256)
{
continue;
}
InstructionContext isub = ins.getPushes().get(0).getPopped().get(0);
if (!(isub.getInstruction() instanceof ISub))
{
continue;
}
StackContext alphaPop = isub.getPops().get(0);
InstructionContext alphaPusher = alphaPop.getPushed();
InstructionContext isubResult = isub.getPushes().get(0).getPopped().get(0);
boolean shouldSubtractLocal = false;
if (pushesToSameVar(isubResult, alphaPusher))
{
shouldSubtractLocal = true;
alphaPusher = isubResult;
/*alphaPusher = resolveFieldThroughInvokes(alphaPop);
if (alphaPusher == null)
throw new RuntimeException("Alpha var is overwritten and we don't know what pushed it"); // cheeky unchecked*/
}
int storeIdx = instrs.getInstructions().indexOf(instruction);
Instruction alphaPushI = alphaPusher.getInstruction();
if (alphaPushI instanceof GetStatic)
{
instrs.addInstruction(storeIdx++, new LDC(instrs, 255));
instrs.addInstruction(storeIdx++, new GetStatic(instrs, ((GetStatic) alphaPushI).getField()));
instrs.addInstruction(storeIdx++, new ISub(instrs, InstructionType.ISUB));
}
else if (alphaPushI instanceof LVTInstruction)
{
if (shouldSubtractLocal)
{
instrs.addInstruction(storeIdx++, new LDC(instrs, 255));
instrs.addInstruction(storeIdx++, new ILoad(instrs, ((LVTInstruction) alphaPushI).getVariableIndex()));
instrs.addInstruction(storeIdx++, new ISub(instrs, InstructionType.ISUB));
}
else
{
instrs.addInstruction(storeIdx++, new ILoad(instrs, ((LVTInstruction) alphaPushI).getVariableIndex()));
}
}
instrs.getInstructions().set(storeIdx, new InvokeStatic(instrs, DRAWALPHA));
++count;
continue outer;
}
}
// If we're copying from the same field we don't have to apply extra alpha again
if (colPushI instanceof IALoad
&& isSameField(r2dPx, colPusher.getPops().get(1)))
{
continue;
}
// If the value is 0, it's supposed to be transparent, not black
if (colPushI instanceof PushConstantInstruction
&& ((PushConstantInstruction) colPushI).getConstant().equals(0))
{
continue;
}
// rasterPx[idx] = color | 0xff000000 (the | 0xff000000 is what's added)
int storeIdx = instrs.getInstructions().indexOf(instruction);
instrs.addInstruction(storeIdx++, new LDC(instrs, ALPHA));
instrs.addInstruction(storeIdx, new IOr(instrs, InstructionType.IOR));
++orCount;
}
if (orCount != 0)
{
counts[0] += orCount;
log.info("[INFO] Added {} OR's into {}", orCount, mc.getMethod());
}
if (count != 0)
{
counts[1] += count;
log.info("[INFO] Injected {} DrawAlpha invokes into {}", count, mc.getMethod());
}
});
ex.run();
log.info("[INFO] Injected {} DrawAlpha invokes and {} ors", counts[1], counts[0]);
}
private static boolean pushesToSameVar(InstructionContext cA, InstructionContext cB)
{
Instruction iA = cA.getInstruction(), iB = cB.getInstruction();
if (iA instanceof LVTInstruction && iB instanceof LVTInstruction)
{
int a = ((LVTInstruction) iA).getVariableIndex();
int b = ((LVTInstruction) iB).getVariableIndex();
return a == b;
}
return false;
}
private static Instructions getInstrs(MethodContext mc)
{
Code c = mc.getMethod().getCode();
if (c == null)
{
return null;
}
return c.getInstructions();
}
private static InstructionContext resolveFieldThroughInvokes(StackContext stackContext)
{
InstructionContext pusher = stackContext.getPushed().resolve(stackContext);
if (pusher.getInstruction() instanceof GetFieldInstruction)
{
return pusher;
}
// No field I wanna trace, rn at least
if (!(pusher.getInstruction() instanceof LVTInstruction))
{
return null;
}
int vidx = ((LVTInstruction) pusher.getInstruction()).getVariableIndex();
VariableContext vc = pusher.getVariables().get(vidx);
stackContext = Lists.reverse(vc.getInstructionWhichStored().getPops()).get(vidx);
return resolveFieldThroughInvokes(stackContext);
}
private static boolean isSameField(Field f, StackContext array)
{
InstructionContext ic = resolveFieldThroughInvokes(array);
if (ic == null)
{
return false;
}
return ((GetFieldInstruction) ic.getInstruction()).getMyField() == f;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.ListIterator;
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.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import static com.openosrs.injector.injection.InjectData.HOOKS;
public class RenderDraw extends AbstractInjector
{
private static final net.runelite.asm.pool.Method RENDERDRAW = new net.runelite.asm.pool.Method(
new Class(HOOKS),
"renderDraw",
new Signature("(Lnet/runelite/api/Renderable;IIIIIIIIJ)V")
);
private static final int EXPECTED = 21;
public RenderDraw(InjectData inject)
{
super(inject);
}
@Override
public void inject()
{
int replaced = 0;
/*
* This class replaces entity draw invocation instructions
* with the renderDraw method on drawcallbacks
*/
final net.runelite.asm.pool.Method draw = InjectUtil.findMethod(inject, "draw", "Renderable", null, true, false).getPoolMethod();
final Method drawTile = InjectUtil.findMethod(inject, "drawTile", "Scene", null, true, false);
Instructions ins = drawTile.getCode().getInstructions();
for (ListIterator<Instruction> iterator = ins.listIterator(); iterator.hasNext(); )
{
Instruction i = iterator.next();
if (i instanceof InvokeVirtual)
{
if (((InvokeVirtual) i).getMethod().equals(draw))
{
iterator.set(new InvokeStatic(ins, RENDERDRAW));
log.debug("[DEBUG] Replaced method call at {}", i);
++replaced;
}
}
}
if (replaced != EXPECTED)
{
throw new InjectException("Didn't replace the expected amount of method calls");
}
}
}

View File

@@ -0,0 +1,304 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
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.ClassGroup;
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;
public class ScriptVM extends AbstractInjector
{
public ScriptVM(InjectData inject)
{
super(inject);
}
public void inject()
{
final ClassGroup vanilla = inject.getVanilla();
/*
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
*/
final ClassFile deobScript = inject.getDeobfuscated().findClass("Script");
final String scriptObName = InjectUtil.getObfuscatedName(deobScript);
final Field scriptInstructions = InjectUtil.findField(inject, "opcodes", "Script");
final Field scriptStatePC = InjectUtil.findField(inject, "pc", "ScriptFrame");
final ClassFile vanillaClient = vanilla.findClass("client");
// Next 4 should be injected by mixins, so don't need fail fast
final Method runScript = vanillaClient.findStaticMethod("copy$runScript");
final Method vmExecuteOpcode = vanillaClient.findStaticMethod("vmExecuteOpcode");
final Method setCurrentScript = vanillaClient.findStaticMethod("setCurrentScript");
final Field currentScriptPCField = vanillaClient.findField("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 InjectException("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 InjectException("Unable to find any Script AStores in runScript"));
log.debug("[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 InvokeStatic(instrs, setCurrentScript.getPoolMethod()));
instrIter.next();
}
}
}
}
// Add PutStatics to all PC IStores and IIncs
{
if (pcLocalVar == null)
{
throw new InjectException("Unable to find ILoad for invokedFromPc IStore");
}
log.debug("[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("[DEBUG] Found instruction array index {}", instructionArrayLocalVar);
if (currentOpcodeStore == null)
{
throw new InjectException("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));
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors.rsapi;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.rsapi.RSApiMethod;
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.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.signature.Signature;
public class InjectGetter
{
public static void inject(ClassFile targetClass, RSApiMethod apiMethod, Field field, Number getter)
{
if (targetClass.findMethod(apiMethod.getName(), apiMethod.getSignature()) != null)
{
throw new InjectException("Duplicate getter method " + apiMethod.getMethod().toString());
}
final String name = apiMethod.getName();
final Signature sig = apiMethod.getSignature();
final Method method = new Method(targetClass, name, sig);
method.setPublic();
final Code code = new Code(method);
method.setCode(code);
final Instructions instructions = code.getInstructions();
final List<Instruction> ins = instructions.getInstructions();
if (field.isStatic())
{
ins.add(new GetStatic(instructions, field.getPoolField()));
}
else
{
ins.add(new ALoad(instructions, 0));
ins.add(new GetField(instructions, field.getPoolField()));
}
if (getter != null)
{
InjectUtil.injectObfuscatedGetter(getter, instructions, ins::add);
}
ins.add(InjectUtil.createReturnForType(instructions, field.getType()));
targetClass.addMethod(method);
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors.rsapi;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.rsapi.RSApiMethod;
import java.util.List;
import net.runelite.asm.ClassFile;
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.BiPush;
import net.runelite.asm.attributes.code.instructions.CheckCast;
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.SiPush;
import net.runelite.asm.signature.Signature;
public class InjectInvoke
{
public static void inject(ClassFile targetClass, RSApiMethod apiMethod, Method vanillaMethod, String garbage)
{
if (targetClass.findMethod(apiMethod.getName(), apiMethod.getSignature()) != null)
{
throw new InjectException("Duplicate invoker method " + apiMethod.getMethod().toString());
}
final Method method = new Method(targetClass, apiMethod.getName(), apiMethod.getSignature());
method.setPublic();
final Code code = new Code(method);
method.setCode(code);
final Instructions instructions = code.getInstructions();
final List<Instruction> ins = instructions.getInstructions();
int varIdx = 0;
if (!vanillaMethod.isStatic())
{
ins.add(new ALoad(instructions, varIdx));
}
++varIdx;
final Signature apiSig = apiMethod.getSignature();
final Signature vanSig = vanillaMethod.getDescriptor();
for (int i = 0; i < apiSig.size(); i++)
{
final Type type = apiSig.getTypeOfArg(i);
final Instruction loadInstruction = InjectUtil.createLoadForTypeIndex(instructions, type, varIdx);
ins.add(loadInstruction);
final Type obType = vanSig.getTypeOfArg(i);
if (!type.equals(obType))
{
final CheckCast checkCast = new CheckCast(instructions);
checkCast.setType(obType);
ins.add(checkCast);
}
varIdx += type.getSize();
}
if (apiSig.size() != vanSig.size())
{
if (garbage == null)
{
garbage = "0";
}
switch (vanSig.getTypeOfArg(vanSig.size() - 1).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 (vanillaMethod.isStatic())
{
ins.add(new InvokeStatic(instructions, vanillaMethod.getPoolMethod()));
}
else
{
ins.add(new InvokeVirtual(instructions, vanillaMethod.getPoolMethod()));
}
ins.add(InjectUtil.createReturnForType(instructions, vanSig.getReturnValue()));
targetClass.addMethod(method);
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*
* 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 com.openosrs.injector.injectors.rsapi;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.rsapi.RSApiMethod;
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.PutField;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import net.runelite.asm.attributes.code.instructions.VReturn;
import net.runelite.asm.signature.Signature;
public class InjectSetter
{
public static void inject(ClassFile targetClass, RSApiMethod apiMethod, Field field, Number getter)
{
if (targetClass.findMethod(apiMethod.getName(), apiMethod.getSignature()) != null)
{
throw new InjectException("Duplicate setter method " + apiMethod.getMethod().toString());
}
final String name = apiMethod.getName();
final Signature sig = apiMethod.getSignature();
final Method method = new Method(targetClass, name, sig);
method.setPublic();
final Code code = new Code(method);
method.setCode(code);
final Instructions instructions = code.getInstructions();
final List<Instruction> ins = instructions.getInstructions();
// load this
if (!field.isStatic())
{
ins.add(new ALoad(instructions, 0));
}
// load argument
final Type argumentType = sig.getTypeOfArg(0);
ins.add(InjectUtil.createLoadForTypeIndex(instructions, argumentType, 1));
// cast argument to field type
final Type fieldType = field.getType();
if (!argumentType.equals(fieldType))
{
CheckCast checkCast = new CheckCast(instructions);
checkCast.setType(fieldType);
ins.add(checkCast);
}
if (getter != null)
{
InjectUtil.injectObfuscatedSetter(getter, instructions, ins::add);
}
if (field.isStatic())
{
ins.add(new PutStatic(instructions, field));
}
else
{
ins.add(new PutField(instructions, field));
}
ins.add(new VReturn(instructions));
targetClass.addMethod(method);
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.rsapi;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.openosrs.injector.InjectException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.runelite.asm.Type;
import net.runelite.asm.pool.Class;
import org.gradle.api.file.FileTree;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.ClassReader;
@NoArgsConstructor
public class RSApi implements Iterable<RSApiClass>
{
public static final String API_BASE = "net/runelite/rs/api/RS";
public static final String RL_API_BASE = "net/runelite/api";
public static final Type CONSTRUCT = new Type("Lnet/runelite/mapping/Construct;");
public static final Type IMPORT = new Type("Lnet/runelite/mapping/Import;");
private final List<RSApiClass> classes = new ArrayList<>();
@Getter
private final List<RSApiMethod> constructs = new ArrayList<>();
private ImmutableMap<String, RSApiClass> map;
public RSApi(File[] classes)
{
for (File file : classes)
{
if (!file.getName().startsWith("RS"))
{
continue;
}
try (InputStream is = new FileInputStream(file))
{
final ClassReader reader = new ClassReader(is);
final RSApiClass apiClass = new RSApiClass();
reader.accept(apiClass, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
this.classes.add(apiClass);
}
catch (IOException e)
{
throw new InjectException(e);
}
}
init();
}
@VisibleForTesting
private void init()
{
final ImmutableMap.Builder<String, RSApiClass> builder = ImmutableMap.builder();
for (RSApiClass clazz : this)
{
builder.put(clazz.getName(), clazz);
}
this.map = builder.build();
for (RSApiClass clazz : this)
{
final List<RSApiClass> intfs = clazz.getApiInterfaces();
for (Class intfPool : clazz.getInterfaces())
{
final RSApiClass intfApi = map.get(intfPool.getName());
if (intfApi != null)
{
intfs.add(intfApi);
}
}
// Collect all @Constructs, and build @Import maps
clazz.init(constructs);
}
}
public int size()
{
return classes.size();
}
public RSApiClass findClass(String name)
{
return map.get(name);
}
public boolean hasClass(String name)
{
return findClass(name) != null;
}
public RSApiClass withInterface(Class interf)
{
RSApiClass clazz = findClass(interf.getName());
if (clazz != null)
{
return clazz;
}
for (RSApiClass apiC : this)
{
if (apiC.getInterfaces().contains(interf))
{
return apiC;
}
}
return null;
}
@NotNull
public Iterator<RSApiClass> iterator()
{
return classes.iterator();
}
@VisibleForTesting
public List<RSApiClass> getClasses()
{
return classes;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.rsapi;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import net.runelite.asm.Annotation;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import static com.openosrs.injector.rsapi.RSApi.CONSTRUCT;
import static com.openosrs.injector.rsapi.RSApi.IMPORT;
@Getter
@Setter
public class RSApiClass extends ClassVisitor implements Iterable<RSApiMethod>
{
private Class clazz;
private final List<Class> interfaces = new ArrayList<>();
private final List<RSApiMethod> methods = new ArrayList<>();
private final List<RSApiClass> apiInterfaces = new ArrayList<>();
private final Map<String, List<RSApiMethod>> imports = new HashMap<>();
RSApiClass()
{
super(Opcodes.ASM5);
}
void init(List<RSApiMethod> constructList)
{
for (RSApiMethod method : this)
{
if (method.isSynthetic())
{
continue;
}
if (method.findAnnotation(CONSTRUCT) != null)
{
constructList.add(method);
continue;
}
final Annotation imported = method.findAnnotation(IMPORT);
if (imported != null)
{
imports.computeIfAbsent(
imported.getValueString(),
(str) -> new ArrayList<>()
).add(method);
}
}
}
RSApiMethod addMethod(String name, Signature sig, int access)
{
final RSApiMethod method = new RSApiMethod(new Method(clazz, name, sig), access);
methods.add(method);
return method;
}
public String getName()
{
return clazz.getName();
}
public void fetchImported(List<RSApiMethod> to, String str)
{
List<RSApiMethod> imported = imports.get(str);
if (imported == null)
{
return;
}
to.addAll(imported);
}
@NotNull
public Iterator<RSApiMethod> iterator()
{
return this.methods.iterator();
}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
clazz = new Class(name);
for (String s : interfaces)
{
this.interfaces.add(new Class(s));
}
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
{
return addMethod(name, new Signature(desc), access);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.rsapi;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import net.runelite.asm.Annotation;
import net.runelite.asm.Named;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotated;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@Getter
@Setter
public class RSApiMethod extends MethodVisitor implements Annotated, Named
{
private final Method method;
private final int accessFlags;
private final Map<Type, Annotation> annotations = new HashMap<>();
private boolean injected;
RSApiMethod(Method method, int accesFlags)
{
super(Opcodes.ASM5);
this.method = method;
this.accessFlags = accesFlags;
}
public Class getClazz()
{
return method.getClazz();
}
public String getName()
{
return method.getName();
}
public Signature getSignature()
{
return method.getType();
}
public boolean isSynthetic()
{
return (accessFlags & Opcodes.ACC_SYNTHETIC) != 0;
}
public boolean isDefault()
{
return (accessFlags & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC)) == 1;
}
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible)
{
final var annotation = new Annotation(new Type(descriptor), visible);
this.addAnnotation(annotation);
return annotation;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.transformers;
import com.google.common.base.Stopwatch;
import com.openosrs.injector.injection.InjectData;
import lombok.RequiredArgsConstructor;
import net.runelite.asm.Named;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
@RequiredArgsConstructor
public abstract class InjectTransformer implements Named
{
protected final InjectData inject;
protected final Logger log = Logging.getLogger(this.getClass());
private Stopwatch stopwatch;
public final void transform()
{
stopwatch = Stopwatch.createStarted();
transformImpl();
}
abstract void transformImpl();
public final String getCompletionMsg()
{
return "finished in " + stopwatch.toString();
}
@Override
public final String getName()
{
return this.getClass().getSimpleName();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2020, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.transformers;
import com.openosrs.injector.injection.InjectData;
import net.runelite.asm.ClassFile;
/**
* This class changes the java source file debug information to the rs-client file name
*/
public class SourceChanger extends InjectTransformer
{
private int n = 0;
public SourceChanger(InjectData inject)
{
super(inject);
}
@Override
void transformImpl()
{
inject.forEachPair(this::rename);
log.info("[INFO] Changed source file debug information for {} classes", n);
}
private void rename(ClassFile rsclient, ClassFile vanilla)
{
++n;
final String newSrc = rsclient.getSource();
log.debug("[DEBUG] Changing src from {} to {}", vanilla.getSource(), newSrc);
vanilla.setSource(newSrc);
}
}