inititial messy commit

This commit is contained in:
Lucwousin
2019-10-28 17:44:44 +01:00
commit a1ae8ac6ae
44 changed files with 5368 additions and 0 deletions

59
build.gradle.kts Normal file
View File

@@ -0,0 +1,59 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("java-gradle-plugin")
kotlin("jvm") version "1.3.50"
}
group = "com.openosrs"
version = "1.5.37-SNAPSHOT"
repositories {
mavenCentral()
mavenLocal()
maven {
url = uri("https://repo.runelite.net")
url = uri("https://raw.githubusercontent.com/open-osrs/hosting/master")
}
}
dependencies {
annotationProcessor("org.projectlombok:lombok:1.18.10")
implementation(kotlin("stdlib-jdk8"))
implementation("com.openosrs:deobfuscator:${project.version}") {
exclude("org.slf4j", "slf4j-simple")
}
implementation("com.google.guava:guava:28.1-jre")
implementation("org.ow2.asm:asm:7.2")
implementation("org.projectlombok:lombok:1.18.10")
testImplementation("junit:junit:4.12")
// testRuntimeOnly("com.openosrs.rs:rs-client:${project.version}")
testCompileOnly("com.openosrs.rs:runescape-api:${project.version}")
// testRuntimeOnly("com.openosrs:mixins:${project.version}")
// testRuntimeOnly("net.runelite.rs:vanilla")
}
gradlePlugin {
plugins {
create("injectorPlugin") {
id = "com.openosrs.injector"
implementationClass = "com.openosrs.injector.InjectPlugin"
}
}
}
configure<JavaPluginConvention> {
sourceCompatibility = JavaVersion.VERSION_1_8
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
gradlew vendored Normal file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle.kts Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name = "injector-plugin"

View File

@@ -0,0 +1,469 @@
package com.openosrs.injector;
import com.openosrs.injector.injection.InjectData;
import static com.openosrs.injector.rsapi.RSApi.*;
import static com.openosrs.injector.rsapi.RSApi.API_BASE;
import com.openosrs.injector.rsapi.RSApiClass;
import com.openosrs.injector.rsapi.RSApiMethod;
import java.util.stream.Collectors;
import net.runelite.asm.Annotated;
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.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.DLoad;
import net.runelite.asm.attributes.code.instructions.FLoad;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.LLoad;
import net.runelite.asm.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;
public class 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
*/
public static Method findStaticMethod(InjectData data, String name) throws Injexception
{
return findStaticMethod(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
*/
public static Method findStaticMethod(InjectData data, String name, String classHint, Signature sig) throws Injexception
{
final ClassGroup deob = data.getDeobfuscated();
Method method = null;
if (classHint != null)
{
ClassFile clazz = deob.findClass(classHint);
if (clazz == null)
{
throw new Injexception("Hint class " + classHint + " doesn't exist");
}
if (sig == null)
{
method = clazz.findStaticMethod(name);
}
else
{
method = clazz.findStaticMethod(name, sig);
}
}
for (ClassFile clazz : deob)
{
if (method != null)
{
break;
}
if (sig == null)
{
method = clazz.findStaticMethod(name);
}
else
{
method = clazz.findStaticMethod(name, sig);
}
}
if (method == null)
{
throw new Injexception("Static method " + name + " doesn't exist");
}
return data.toVanilla(method);
}
/**
* Finds a static method in deob and converts it to ob
*
* @param data InjectData instance
* @param pool Pool method of the method you want
*
* @return The obfuscated version of the found method
*/
public static Method findStaticMethod(InjectData data, net.runelite.asm.pool.Method pool) throws Injexception
{
return findStaticMethod(data, pool.getName(), pool.getClazz().getName(), pool.getType());
}
/**
* Fail-fast implementation of ClassGroup.findStaticMethod
*/
public static Method findStaticMethod(ClassGroup group, String name) throws Injexception
{
Method m = group.findStaticMethod(name);
if (m == null)
{
throw new Injexception(String.format("Method %s couldn't be found", name));
}
return m;
}
/**
* Fail-fast implementation of ClassGroup.findStaticMethod
*/
public static Method findStaticMethod(ClassGroup group, String name, Signature type) throws Injexception
{
Method m = group.findStaticMethod(name, type);
if (m == null)
{
throw new Injexception(String.format("Method %s couldn't be found", name + type.toString()));
}
return m;
}
/**
* Fail-fast implementation of ClassFile.findMethodDeep
*/
public static Method findMethodDeep(ClassFile clazz, String name) throws Injexception
{
Method m = clazz.findMethodDeep(name);
if (m == null)
{
throw new Injexception(String.format("Method %s couldn't be found", name));
}
return m;
}
/**
* Fail-fast implementation of ClassFile.findMethodDeep
*/
public static Method findMethodDeep(ClassFile clazz, String name, Signature type) throws Injexception
{
Method m = clazz.findMethodDeep(name, type);
if (m == null)
{
throw new Injexception(String.format("Method %s couldn't be found", name + type.toString()));
}
return m;
}
/**
* Fail-fast implementation of ClassGroup.findStaticField
*
* well...
*/
public static Field findStaticField(ClassGroup group, String name) throws Injexception
{
for (ClassFile clazz : group)
{
Field f = clazz.findField(name);
if (f != null)
{
return f;
}
}
throw new Injexception("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
*/
public static Field findStaticField(InjectData data, String name, String classHint, Type type) throws Injexception
{
final ClassGroup deob = data.getDeobfuscated();
Field field = null;
if (classHint != null)
{
ClassFile clazz = deob.findClass(classHint);
if (clazz == null)
{
throw new Injexception("Hint class " + classHint + " doesn't exist");
}
if (type == null)
{
field = clazz.findField(name);
}
else
{
field = clazz.findField(name, type);
}
}
for (ClassFile clazz : deob)
{
if (field != null)
{
break;
}
if (type == null)
{
field = clazz.findField(name);
}
else
{
field = clazz.findField(name, type);
}
}
if (field == null)
{
throw new Injexception(String.format("Static field %s doesn't exist", (type != null ? type + " " : "") + name));
}
return data.toVanilla(field);
}
/**
* Finds a static field in deob and converts it to ob
*
* @param data InjectData instance
* @param pool Pool field of the field you want
*
* @return The obfuscated version of the found field
*/
public static Field findStaticField(InjectData data, net.runelite.asm.pool.Field pool) throws Injexception
{
return findStaticField(data, pool.getName(), pool.getClazz().getName(), pool.getType());
}
/**
* Fail-fast implementation of ClassGroup.findFieldDeep
*/
public static Field findFieldDeep(ClassFile clazz, String name) throws Injexception
{
do
{
Field f = clazz.findField(name);
if (f != null)
{
return f;
}
}
while ((clazz = clazz.getParent()) != null);
throw new Injexception("Couldn't find field " + name);
}
public static Field findField(InjectData data, String name, String hintClass) throws Injexception
{
final ClassGroup deob = data.getDeobfuscated();
return data.toVanilla(findField(deob, name, hintClass));
}
public static Field findField(ClassGroup group, String name, String hintClass) throws Injexception
{
Field field;
if (hintClass != null)
{
ClassFile clazz = group.findClass(hintClass);
if (clazz == null)
{
throw new Injexception("Hint class " + hintClass + " doesn't exist");
}
field = clazz.findField(name);
if (field != null)
{
return field;
}
}
for (ClassFile clazz : group)
{
field = clazz.findField(name);
if (field != null)
{
return field;
}
}
throw new Injexception("Field " + name + " doesn't exist");
}
public static ClassFile fromApiMethod(InjectData data, RSApiMethod apiMethod)
{
return data.toVanilla(data.toDeob(apiMethod.getClazz().getName()));
}
public 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();
}
public 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, new Type(highestKnown.getName()));
}
return api;
}
public 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());
}
public static boolean apiToDeobSigEquals(InjectData data, Signature deobSig, Signature apiSig)
{
return deobSig.equals(apiToDeob(data, apiSig));
}
/**
* Gets the obfuscated name from something's annotations.
*
* If the annotation doesn't exist return the current name instead.
*/
public static <T extends Annotated & Named> String getObfuscatedName(T from)
{
Annotation name = from.getAnnotations().find(DeobAnnotations.OBFUSCATED_NAME);
return name == null ? from.getName() : name.getElement().getString();
}
/**
* Gets the value of the @Export annotation on the object.
*/
public static String getExportedName(Annotated from)
{
Annotation export = from.getAnnotations().find(DeobAnnotations.EXPORT);
return export == null ? null : export.getElement().getString();
}
/**
* Creates the correct load instruction for the variable with Type type and var index index.
*/
public 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
*/
public 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");
}
}
}

View File

@@ -0,0 +1,104 @@
package com.openosrs.injector;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injection.InjectTaskHandler;
import com.openosrs.injector.injectors.InjectConstruct;
import com.openosrs.injector.injectors.Injector;
import com.openosrs.injector.injectors.InterfaceInjector;
import com.openosrs.injector.injectors.MixinInjector;
import com.openosrs.injector.injectors.RSApiInjector;
import com.openosrs.injector.injectors.raw.ClearColorBuffer;
import com.openosrs.injector.injectors.raw.DrawAfterWidgets;
import com.openosrs.injector.injectors.raw.HidePlayerAttacks;
import com.openosrs.injector.injectors.raw.Occluder;
import com.openosrs.injector.injectors.raw.RasterizerHook;
import com.openosrs.injector.injectors.raw.RenderDraw;
import com.openosrs.injector.injectors.raw.ScriptVM;
import com.openosrs.injector.rsapi.RSApi;
import java.io.File;
import java.io.IOException;
import net.runelite.deob.util.JarUtil;
import org.gradle.api.file.FileTree;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
public class Injection extends InjectData implements InjectTaskHandler
{
private static final Logger log = Logging.getLogger(Injection.class);
public Injection(File vanilla, FileTree rsclient, FileTree rsapi, FileTree mixins) throws Injexception, IOException
{
super(
JarUtil.loadJar(vanilla),
JarUtil.loadClasses(rsclient.getFiles()),
JarUtil.loadClasses(mixins.getFiles()),
new RSApi(rsapi)
);
}
public void inject() throws Injexception
{
log.debug("Starting injection");
inject(new InterfaceInjector(this));
inject(new RasterizerHook(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 HidePlayerAttacks(this));
new RSApiValidator(this).validate();
}
public void save(File outputJar) throws IOException
{
log.info("Saving jar to {}", outputJar.toString());
JarUtil.saveJar(this.getVanilla(), outputJar);
}
private void inject(Injector injector) throws Injexception
{
final String name = injector.getName();
log.info("Starting {}", name);
injector.start();
injector.inject();
log.lifecycle("{} {}", name, injector.getCompletionMsg());
if (!injector.validate())
{
throw new Injexception(String.format("%s failed validation", name));
}
}
public void runChildInjector(Injector injector) throws Injexception
{
inject(injector);
}
}

View File

@@ -0,0 +1,19 @@
package com.openosrs.injector;
public class Injexception extends Exception
{
public Injexception(String message)
{
super(message);
}
public Injexception(Throwable cause)
{
super(cause);
}
public Injexception(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,37 @@
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 org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.internal.impldep.aQute.libg.cryptography.RSA;
public class RSApiValidator
{
private static final Logger log = Logging.getLogger(RSApiValidator.class);
private final InjectData inject;
public RSApiValidator(InjectData inject)
{
this.inject = inject;
}
public boolean validate() throws Injexception
{
RSApi rsApi = inject.getRsApi();
for (RSApiClass apiClass : rsApi)
{
for (RSApiMethod apiMethod : apiClass)
{
if (!apiMethod.isInjected())
if (!apiMethod.isSynthetic())
if (inject.toVanilla(inject.toDeob(apiClass.getName())).findMethod(apiMethod.getName(), apiMethod.getSignature()) == null)
log.warn("{} not injected", apiMethod.getMethod());
}
}
return false;
}
}

View File

@@ -0,0 +1,188 @@
package com.openosrs.injector.injection;
import com.google.common.collect.ImmutableMap;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injectors.Injector;
import com.openosrs.injector.rsapi.RSApi;
import static com.openosrs.injector.rsapi.RSApi.*;
import com.openosrs.injector.rsapi.RSApiClass;
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.pool.Class;
import net.runelite.asm.signature.Signature;
/**
* Abstract class meant as the interface of {@link com.openosrs.injector.Injection injection} for injectors
*/
public abstract class InjectData
{
public static final String HOOKS = "net/runelite/client/callback/Hooks";
@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) throws Injexception;
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.
*
* 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());
}
}
public Type deobfuscatedTypeToApiType(Type type)
{
if (type.isPrimitive())
{
return type;
}
ClassFile cf = deobfuscated.findClass(type.getInternalName());
if (cf == null)
{
return type; // not my type
}
RSApiClass apiClass = rsApi.findClass(API_BASE + cf.getName());
Class rlApiType = null;
for (Class intf : apiClass.getInterfaces())
{
if (intf.getName().startsWith(RL_API_BASE))
{
rlApiType = intf;
}
}
final Class finalType = rlApiType == null ? apiClass.getClazz() : rlApiType;
return Type.getType("L" + finalType.getName() + ";", type.getDimensions());
}
}

View File

@@ -0,0 +1,21 @@
package com.openosrs.injector.injection;
import com.openosrs.injector.Injexception;
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() throws Injexception;
/**
* Call this to save the injected jar to outputJar
*/
void save(File outputJar) throws IOException;
}

View File

@@ -0,0 +1,28 @@
package com.openosrs.injector.injectors;
import com.google.common.base.Stopwatch;
import com.openosrs.injector.injection.InjectData;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
public abstract class AbstractInjector implements Injector
{
protected final InjectData inject;
protected final Logger log = Logging.getLogger(this.getClass());
private Stopwatch stopwatch;
protected AbstractInjector(InjectData inject)
{
this.inject = inject;
}
public void start()
{
stopwatch = Stopwatch.createStarted();
}
public final String getCompletionMsg()
{
return "finished in " + stopwatch.toString();
}
}

View File

@@ -0,0 +1,118 @@
package com.openosrs.injector.injectors;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.rsapi.RSApiMethod;
import static com.openosrs.injector.rsapi.RSApi.CONSTRUCT;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.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 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() throws Injexception
{
for (RSApiMethod apiMethod : inject.getRsApi().getConstructs())
{
Annotation construct = apiMethod.getAnnotations().find(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("Injected {} constructors", injected);
}
private void injectConstruct(ClassFile targetClass, Method apiMethod) throws Injexception
{
log.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 Injexception("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,430 @@
package com.openosrs.injector.injectors;
import com.google.common.collect.Lists;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
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.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.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.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
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.signature.Signature;
import net.runelite.deob.DeobAnnotations;
public class InjectHook extends AbstractInjector
{
private static final String HOOK_METHOD_SIGNATURE = "(I)V";
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() throws Injexception
{
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectMethods(entry.getKey(), entry.getValue());
}
injectHooks();
log.info("Injected {} field hooks.", injectedHooks);
}
private void injectMethods(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses) throws Injexception
{
final ClassFile mixinClass = mixinProvider.get();
for (ClassFile targetClass : targetClasses)
{
for (Method mixinMethod : mixinClass.getMethods())
{
final Annotation fieldHook = mixinMethod.getAnnotations().find(FIELDHOOK);
if (fieldHook == null)
{
continue;
}
final String hookName = fieldHook.getElement().getString();
final boolean before = fieldHook.getElements().size() == 2 && fieldHook.getElements().get(1).getValue().equals(true);
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(mixinClass.getName(), hookName, 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;
}
String hookName = hookInfo.fieldName;
assert hookName != null;
log.trace("Found injection location for hook {} at instruction {}", hookName, 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 (Injexception ex)
{
throw new RuntimeException(ex);
}
});
// these look like:
// getfield
// iload_0
// iconst_0
// iastore
e.addExecutionVisitor((InstructionContext ic) ->
{
Instruction i = ic.getInstruction();
Instructions ins = i.getInstructions();
Code code = ins.getCode();
Method method = code.getMethod();
if (method.getName().equals(CLINIT))
{
return;
}
if (!(i instanceof ArrayStore))
{
return;
}
if (!doneIh.add(i))
{
return;
}
ArrayStore as = (ArrayStore) i;
Field fieldBeingSet = as.getMyField(ic);
if (fieldBeingSet == null)
{
return;
}
HookInfo hookInfo = hooked.get(fieldBeingSet);
if (hookInfo == null)
{
return;
}
String hookName = hookInfo.fieldName;
StackContext value = ic.getPops().get(0);
StackContext index = ic.getPops().get(1);
StackContext arrayReference = ic.getPops().get(2);
InstructionContext arrayReferencePushed = arrayReference.getPushed();
StackContext objectStackContext = null;
if (arrayReferencePushed.getInstruction().getType() == InstructionType.GETFIELD)
{
objectStackContext = arrayReferencePushed.getPops().get(0);
}
// inject hook after 'i'
log.debug("Found array injection location for hook {} at instruction {}", hookName, i);
++injectedHooks;
int idx = ins.getInstructions().indexOf(i);
assert idx != -1;
try
{
if (hookInfo.before)
{
injectCallbackBefore(ins, idx, hookInfo, index, objectStackContext, value);
}
else
{
injectCallback(ins, idx + 1, hookInfo, index, objectStackContext);
}
}
catch (Injexception ex)
{
throw new RuntimeException(ex);
}
});
e.run();
}
private void injectCallbackBefore(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext object, StackContext value) throws Injexception
{
Signature signature = hookInfo.method.getDescriptor();
Type methodArgumentType = signature.getTypeOfArg(0);
if (!hookInfo.method.isStatic())
{
if (object == null)
{
throw new Injexception("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));
}
if (!value.type.equals(methodArgumentType))
{
CheckCast checkCast = new CheckCast(ins);
checkCast.setType(methodArgumentType);
ins.getInstructions().add(idx++, checkCast);
}
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
InvokeVirtual invoke = new InvokeVirtual(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
signature
)
);
ins.getInstructions().add(idx++, invoke);
}
else
{
ins.getInstructions().add(idx++, new Dup(ins)); // dup value
if (!value.type.equals(methodArgumentType))
{
CheckCast checkCast = new CheckCast(ins);
checkCast.setType(methodArgumentType);
ins.getInstructions().add(idx++, checkCast);
}
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
InvokeStatic invoke = new InvokeStatic(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
signature
)
);
ins.getInstructions().add(idx++, invoke);
}
}
private int recursivelyPush(Instructions ins, int idx, StackContext sctx)
{
InstructionContext ctx = sctx.getPushed();
if (ctx.getInstruction() instanceof DupInstruction)
{
DupInstruction dupInstruction = (DupInstruction) ctx.getInstruction();
sctx = dupInstruction.getOriginal(sctx);
ctx = sctx.getPushed();
}
for (StackContext s : Lists.reverse(ctx.getPops()))
{
idx = recursivelyPush(ins, idx, s);
}
ins.getInstructions().add(idx++, ctx.getInstruction().clone());
return idx;
}
private void injectCallback(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext objectPusher) throws Injexception
{
if (!hookInfo.method.isStatic())
{
if (objectPusher == null)
{
throw new Injexception("Null object pusher");
}
idx = recursivelyPush(ins, idx, objectPusher);
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
else
{
ins.getInstructions().add(idx++, new LDC(ins, -1));
}
InvokeVirtual invoke = new InvokeVirtual(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
new Signature(HOOK_METHOD_SIGNATURE)
)
);
ins.getInstructions().add(idx++, invoke);
}
else
{
if (index != null)
{
idx = recursivelyPush(ins, idx, index);
}
else
{
ins.getInstructions().add(idx++, new LDC(ins, -1));
}
InvokeStatic invoke = new InvokeStatic(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(hookInfo.clazz),
hookInfo.method.getName(),
new Signature(HOOK_METHOD_SIGNATURE)
)
);
ins.getInstructions().add(idx++, invoke);
}
}
public String getName()
{
return super.getName();
}
@AllArgsConstructor
static class HookInfo
{
String fieldName;
String clazz;
Method method;
boolean before;
Number getter;
}
}

View File

@@ -0,0 +1,256 @@
package com.openosrs.injector.injectors;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.rsapi.RSApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.inject.Provider;
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.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.signature.Signature;
public class InjectHookMethod extends AbstractInjector
{
private static final String HOOKS = "net/runelite/client/callback/Hooks";
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() throws Injexception
{
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectMethods(entry.getKey(), entry.getValue());
}
log.info("Injected {} method hooks", injected);
}
private void injectMethods(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses) throws Injexception
{
final ClassFile mixinClass = mixinProvider.get();
for (ClassFile targetClass : targetClasses)
{
for (Method mixinMethod : mixinClass.getMethods())
{
final Annotation methodHook = mixinMethod.getAnnotations().find(METHODHOOK);
if (methodHook == null)
{
continue;
}
final String hookName = methodHook.getElement().getString();
final boolean end = methodHook.getElements().size() == 2 && methodHook.getElements().get(1).getValue().equals(true);
final ClassFile deobTarget = inject.toDeob(targetClass.getName());
final Method deobMethod;
if (mixinMethod.isStatic())
{
deobMethod = InjectUtil.findStaticMethod(deobTarget.getGroup(), hookName);
}
else
{
deobMethod = InjectUtil.findMethodDeep(deobTarget, hookName);
}
assert mixinMethod.isStatic() == deobMethod.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";
inject(mixinMethod, deobMethod, hookName, end);
++injected;
}
}
}
private void inject(Method mixinMethod, Method targetMethod, String name, boolean end) throws Injexception
{
// Method is hooked
// Find equivalent method in vanilla, and insert callback at the beginning
ClassFile deobClass = targetMethod.getClassFile();
String obfuscatedMethodName = InjectUtil.getObfuscatedName(targetMethod);
String obfuscatedClassName = InjectUtil.getObfuscatedName(deobClass);
assert obfuscatedClassName != null : "hook on method in class with no obfuscated name";
assert obfuscatedMethodName != null : "hook on method with no obfuscated name";
Signature obfuscatedSignature = targetMethod.getObfuscatedSignature();
ClassFile vanillaClass = inject.toVanilla(deobClass);
Method vanillaMethod = vanillaClass.findMethod(obfuscatedMethodName, obfuscatedSignature);
assert targetMethod.isStatic() == vanillaMethod.isStatic();
// Insert instructions at beginning of method
injectHookMethod(mixinMethod, name, end, targetMethod, vanillaMethod, false);
}
private void injectHookMethod(Method hookMethod, String hookName, boolean end, Method deobMethod, Method vanillaMethod, boolean useHooks) throws Injexception
{
Code code = vanillaMethod.getCode();
if (code == null)
{
log.warn(vanillaMethod + " code is null");
return;
}
Instructions instructions = code.getInstructions();
Signature.Builder builder = new Signature.Builder()
.setReturnType(Type.VOID); // Hooks always return void
for (Type type : deobMethod.getDescriptor().getArguments())
{
builder.addArgument(new Type("L" + RSApi.API_BASE + type.getInternalName() + ";"));
}
assert deobMethod.isStatic() == vanillaMethod.isStatic();
boolean modifiedSignature = false;
if (!deobMethod.isStatic() && useHooks)
{
// Add variable to signature
builder.addArgument(0, inject.deobfuscatedTypeToApiType(new Type(deobMethod.getClassFile().getName())));
modifiedSignature = true;
}
Signature signature = builder.build();
List<Integer> insertIndexes = findHookLocations(hookName, end, vanillaMethod);
insertIndexes.sort((a, b) -> Integer.compare(b, a));
for (int insertPos : insertIndexes)
{
if (!deobMethod.isStatic())
{
instructions.addInstruction(insertPos++, new ALoad(instructions, 0));
}
int signatureStart = modifiedSignature ? 1 : 0;
int index = deobMethod.isStatic() ? 0 : 1; // current variable index
for (int i = signatureStart; i < signature.size(); ++i)
{
Type type = signature.getTypeOfArg(i);
Instruction load = InjectUtil.createLoadForTypeIndex(instructions, type, index);
instructions.addInstruction(insertPos++, load);
index += type.getSize();
}
InvokeInstruction invoke;
// use old Hooks callback
if (useHooks)
{
// Invoke callback
invoke = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(HOOKS),
hookName,
signature
)
);
}
else
{
// Invoke methodhook
assert hookMethod != null;
if (vanillaMethod.isStatic())
{
invoke = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("client"), // Static methods are in client
hookMethod.getName(),
signature
)
);
}
else
{
// otherwise invoke member function
//instructions.addInstruction(insertPos++, new ALoad(instructions, 0));
invoke = new InvokeVirtual(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(vanillaMethod.getClassFile().getName()),
hookMethod.getName(),
hookMethod.getDescriptor()
)
);
}
}
instructions.addInstruction(insertPos++, (Instruction) invoke);
}
log.debug("Injected method hook {} in {} with {} args: {}",
hookName, vanillaMethod, signature.size(),
signature.getArguments());
}
private List<Integer> findHookLocations(String hookName, boolean end, Method vanillaMethod) throws Injexception
{
Instructions instructions = vanillaMethod.getCode().getInstructions();
if (end)
{
// find return
List<Instruction> returns = instructions.getInstructions().stream()
.filter(i -> i instanceof ReturnInstruction)
.collect(Collectors.toList());
List<Integer> indexes = new ArrayList<>();
for (Instruction ret : returns)
{
int idx = instructions.getInstructions().indexOf(ret);
assert idx != -1;
indexes.add(idx);
}
return indexes;
}
if (!vanillaMethod.getName().equals("<init>"))
{
return Arrays.asList(0);
}
// Find index after invokespecial
for (int i = 0; i < instructions.getInstructions().size(); ++i)
{
Instruction in = instructions.getInstructions().get(i);
if (in.getType() == InstructionType.INVOKESPECIAL)
{
return Arrays.asList(i + 1); // one after
}
}
throw new IllegalStateException("constructor with no invokespecial");
}
}

View File

@@ -0,0 +1,37 @@
package com.openosrs.injector.injectors;
import com.openosrs.injector.Injexception;
public interface Injector
{
/**
* Where all the injection should be done
*/
void inject() throws Injexception;
/**
* Should return `true` if injection was succesful, `false` otherwise.
*/
default boolean validate()
{
return true;
}
/**
* 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,53 @@
package com.openosrs.injector.injectors;
import com.openosrs.injector.injection.InjectData;
import static com.openosrs.injector.rsapi.RSApi.API_BASE;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Interfaces;
import net.runelite.asm.pool.Class;
import net.runelite.deob.DeobAnnotations;
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("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.debug("Class {} implements nonexistent interface {}, skipping interface injection",
deobCf.getName(),
fullName
);
return;
}
final Interfaces interfaces = vanillaCf.getInterfaces();
interfaces.addInterface(new Class(fullName));
implemented++;
inject.addToDeob(fullName, deobCf);
}
}

View File

@@ -0,0 +1,689 @@
package com.openosrs.injector.injectors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
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 javax.inject.Provider;
import lombok.AllArgsConstructor;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.ANewArray;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.attributes.code.instructions.GetField;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.InvokeDynamic;
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.Pop;
import net.runelite.asm.attributes.code.instructions.PutField;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.util.JarUtil;
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 final Map<String, Field> injectedFields = new HashMap<>();
private final Map<net.runelite.asm.pool.Field, Field> shadowFields = new HashMap<>();
private int copied = 0, replaced = 0, injected = 0;
public MixinInjector(InjectData inject)
{
super(inject);
}
@Override
public void inject() throws Injexception
{
final Map<Provider<ClassFile>, List<ClassFile>> mixinTargets = initTargets();
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectFields(entry.getKey(), entry.getValue());
}
log.info("Injected {} fields", injectedFields.size());
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
findShadowFields(entry.getKey(), entry.getValue());
}
log.info("Shadowed {} fields", shadowFields.size());
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectMethods(entry.getKey(), entry.getValue());
}
log.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();
for (ClassFile mixinClass : inject.getMixins())
{
for (Annotation annotation : mixinClass.getAnnotations())
{
// If there's multiple mixins we gotta create new copies of the classfiles
// to make sure we aren't changing code in all classes at once
if (MIXIN.equals(annotation.getType()))
{
final String str = ((org.objectweb.asm.Type) annotation.getElement().getValue()).getInternalName();
builder.put(
new Provider<ClassFile>()
{
@Override
public ClassFile get()
{
return mixinClass;
}
},
ImmutableList.of(inject.toVanilla(inject.toDeob(str)))
);
}
else if (MIXINS.equals(annotation.getType()))
{
final Provider<ClassFile>mixinProvider = new Provider<ClassFile>()
{
byte[] bytes = null;
@Override
public ClassFile get()
{
if (bytes == null)
{
bytes = JarUtil.writeClass(mixinClass.getGroup(), mixinClass);
return mixinClass;
}
else
{
return JarUtil.loadClass(bytes);
}
}
};
final List<ClassFile> targetClasses = annotation
.getElements()
.stream()
.map(e -> ((org.objectweb.asm.Type) e.getValue()).getInternalName())
.map(inject::toDeob)
.map(inject::toVanilla)
.collect(ImmutableList.toImmutableList());
builder.put(mixinProvider, targetClasses);
}
}
}
return builder.build();
}
private void injectFields(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses) throws Injexception
{
final ClassFile mixinClass = mixinProvider.get();
for (final ClassFile targetClass : targetClasses)
{
for (Field field : mixinClass.getFields())
{
if (field.getAnnotations().find(INJECT) != null ||
ASSERTION_FIELD.equals(field.getName()) &&
targetClass.findField(ASSERTION_FIELD, Type.BOOLEAN) == null)
{
Field copy = new Field(targetClass, field.getName(), field.getType());
copy.setAccessFlags(field.getAccessFlags());
copy.setPublic();
copy.setValue(field.getValue());
for (Annotation annotation : field.getAnnotations())
{
if (!annotation.getType().toString().startsWith("Lnet/runelite/api/mixins"))
{
copy.getAnnotations().addAnnotation(annotation);
}
}
targetClass.addField(copy);
if (injectedFields.containsKey(field.getName()) && !ASSERTION_FIELD.equals(field.getName()))
{
throw new Injexception("Duplicate field: " + field.getName());
}
injectedFields.put(field.getName(), copy);
}
}
}
}
private void findShadowFields(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses) throws Injexception
{
final ClassFile mixinClass = mixinProvider.get();
for (final Field field : mixinClass.getFields())
{
Annotation shadow = field.getAnnotations().find(SHADOW);
if (shadow != null)
{
if (!field.isStatic())
{
throw new Injexception("Shadowed fields must be static");
}
String shadowed = shadow.getElement().getString();
Field targetField = injectedFields.get(shadowed);
if (targetField == null)
{
targetField = InjectUtil.findStaticField(inject, shadowed, null, InjectUtil.apiToDeob(inject, field.getType()));
}
shadowFields.put(field.getPoolField(), targetField);
}
}
}
private void injectMethods(Provider<ClassFile> mixinProvider, List<ClassFile> targetClasses) throws Injexception
{
final ClassFile mixinClass = mixinProvider.get();
for (ClassFile targetClass : targetClasses)
{
// 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.getAnnotations().find(COPY);
if (copyA == null)
{
continue;
}
String copiedName = copyA.getElement().getString();
// The method we're copying, deob
Method deobSourceMethod;
if (mixinMethod.isStatic())
{
deobSourceMethod = InjectUtil.findStaticMethod(inject.getDeobfuscated(), copiedName, mixinMethod.getDescriptor().rsApiToRsClient());
}
else
{
deobSourceMethod = InjectUtil.findMethodDeep(inject.toDeob(targetClass.getClassName()), copiedName, mixinMethod.getDescriptor().rsApiToRsClient());
}
if (mixinMethod.isStatic() != deobSourceMethod.isStatic())
{
throw new Injexception("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 Injexception("Mixin methods cannot have more parameters than their corresponding ob method");
}
Method copy = new Method(targetClass, "copy$" + copiedName, sourceMethod.getObfuscatedSignature());
moveCode(copy, sourceMethod.getCode());
copy.setAccessFlags(sourceMethod.getAccessFlags());
copy.setPublic();
copy.getExceptions().getExceptions().addAll(sourceMethod.getExceptions().getExceptions());
copy.getAnnotations().getAnnotations().addAll(sourceMethod.getAnnotations().getAnnotations());
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));
}
// 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.getAnnotations().find(INJECT) != null;
// You can't annotate clinit, so its always injected
if ((hasInject && isInit) || isClinit)
{
if (!"()V".equals(mixinMethod.getDescriptor().toString()))
{
throw new Injexception("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();
for (; listIter.hasNext(); )
{
Instruction instr = listIter.next();
if (instr instanceof InvokeSpecial)
{
InvokeSpecial invoke = (InvokeSpecial) instr;
assert invoke.getMethod().getName().equals("<init>");
listIter.remove();
int pops = invoke.getMethod().getType().getArguments().size() + 1;
for (int i = 0; i < pops; i++)
{
listIter.add(new Pop(instructions));
}
break;
}
}
}
setOwnersToTargetClass(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("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 Injexception("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("Injected mixin method {} to {}", copy, targetClass);
++injected;
}
else if (mixinMethod.getAnnotations().find(REPLACE) != null)
{
Annotation replaceAnnotation = mixinMethod.getAnnotations().find(REPLACE);
String replacedName = (String) replaceAnnotation.getElement().getValue();
ClassFile deobClass = inject.toDeob(targetClass.getName());
Method deobMethod = findDeobMatching(deobClass, mixinMethod, replacedName);
if (deobMethod == null)
{
throw new Injexception("Failed to find the deob method " + replacedName + " for mixin " + mixinClass);
}
if (mixinMethod.isStatic() != deobMethod.isStatic())
{
throw new Injexception("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 Injexception("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("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) throws Injexception
{
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) + ";");
((ANewArray) i).setType(newType);
log.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.obMethod.getPoolMethod());
// Pass through garbage value if the method has one
if (copiedMethod.hasGarbageValue)
{
int garbageIndex = copiedMethod.obMethod.isStatic()
? copiedMethod.obMethod.getDescriptor().size() - 1
: copiedMethod.obMethod.getDescriptor().size();
iterator.previous();
iterator.add(new ILoad(method.getCode().getInstructions(), garbageIndex));
iterator.next();
}
}
else if (ii.getMethod().getClazz().getName().equals(mixinCf.getName()))
{
ii.setMethod(new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(cf.getName()),
ii.getMethod().getName(),
ii.getMethod().getType()
));
}
}
else if (i instanceof FieldInstruction)
{
FieldInstruction fi = (FieldInstruction) i;
Field shadowed = shadowFields.get(fi.getField());
if (shadowed != null)
{
fi.setField(shadowed.getPoolField());
}
else if (fi.getField().getClazz().getName().equals(mixinCf.getName()))
{
fi.setField(new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(cf.getName()),
fi.getField().getName(),
fi.getField().getType()
));
}
}
else if (i instanceof PushConstantInstruction)
{
PushConstantInstruction pi = (PushConstantInstruction) i;
if (mixinCf.getPoolClass().equals(pi.getConstant()))
{
pi.setConstant(cf.getPoolClass());
}
}
verify(mixinCf, i);
}
}
private void verify(ClassFile mixinCf, Instruction i) throws Injexception
{
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 Injexception("Access to non static member field of mixin");
}
Field field = fi.getMyField();
if (field != null && !field.isPublic())
{
throw new Injexception("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 Injexception("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 Injexception("Injected bytecode must be Java 6 compatible");
}
}
private Method findDeobMatching(ClassFile deobClass, Method mixinMethod, String deobName) throws Injexception
{
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 Injexception("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);
}
}
}
}
@AllArgsConstructor
private static class CopiedMethod
{
private Method obMethod;
private boolean hasGarbageValue;
}
}

View File

@@ -0,0 +1,289 @@
package com.openosrs.injector.injectors;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
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 static com.openosrs.injector.rsapi.RSApi.*;
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.signature.Signature;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.deobfuscators.arithmetic.DMath;
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() throws Injexception
{
for (final ClassFile deobClass : inject.getDeobfuscated())
{
final RSApiClass implementingClass = inject.getRsApi().findClass(API_BASE + deobClass.getName());
injectFields(deobClass, implementingClass);
injectMethods(deobClass, implementingClass); // aka invokers
}
retryFailures();
log.info("Injected {} getters, {} setters, and {} invokers", get, set, voke);
}
private void injectFields(ClassFile deobClass, RSApiClass implementingClass) throws Injexception
{
for (Field deobField : deobClass.getFields())
{
final String exportedName = InjectUtil.getExportedName(deobField);
if (exportedName == null)
{
continue;
}
final List<RSApiMethod> matching = new ArrayList<>();
if (deobField.isStatic())
{
for (RSApiClass api : inject.getRsApi())
{
api.fetchImported(matching, exportedName);
}
}
else if (implementingClass != null)
{
implementingClass.fetchImported(matching, exportedName);
}
if (matching.size() == 0)
{
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;
}
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 Injexception("Something went horribly wrong, and this should honestly never happen, but you never know. Btw it's the static-ness");
}
for (RSApiMethod apiMethod : matching)
{
final ClassFile targetClass = InjectUtil.fromApiMethod(inject, apiMethod);
apiMethod.setInjected(true);
if (apiMethod.getSignature().isVoid())
{
++set;
log.debug("Injecting setter {} for {} into {}", apiMethod.getMethod(), vanillaField.getPoolField(), targetClass.getPoolClass());
InjectSetter.inject(
targetClass,
apiMethod,
vanillaField,
modInverseOrNull(getter)
);
}
else
{
++get;
log.debug("Injecting getter {} for {} into {}", apiMethod.getMethod(), vanillaField.getPoolField(), targetClass.getPoolClass());
InjectGetter.inject(
targetClass,
apiMethod,
vanillaField,
getter
);
}
}
}
}
private void injectMethods(ClassFile deobClass, RSApiClass implementingClass) throws Injexception
{
for (Method deobMethod : deobClass.getMethods())
{
final String exportedName = InjectUtil.getExportedName(deobMethod);
if (exportedName == null)
{
continue;
}
final List<RSApiMethod> matching = new ArrayList<>();
if (deobMethod.isStatic())
{
for (RSApiClass api : inject.getRsApi())
{
api.fetchImported(matching, exportedName);
}
}
else if (implementingClass != null)
{
implementingClass.fetchImported(matching, exportedName);
}
if (matching.size() == 0)
{
continue;
}
final Signature deobSig = deobMethod.getDescriptor();
ListIterator<RSApiMethod> it = matching.listIterator();
while (it.hasNext())
{
final RSApiMethod apiMethod = it.next();
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.fromApiMethod(inject, apiMethod);
final Method vanillaMethod = inject.toVanilla(deobMethod);
final String garbage = DeobAnnotations.getDecoder(deobMethod);
log.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 Injexception("Multiple api imports matching method " + deobMethod.getPoolMethod());
}
}
}
private void retryFailures() throws Injexception
{
for (Map.Entry<Field, List<RSApiMethod>> entry : retryFields.entrySet())
{
final List<RSApiMethod> matched = entry.getValue();
final Field field = entry.getKey();
matched.removeIf(RSApiMethod::isInjected);
if (matched.size() > 2)
{
throw new Injexception("More than 2 imported api methods for field " + field.getPoolField());
}
final Field vanillaField = inject.toVanilla(field);
final Number getter = DeobAnnotations.getObfuscatedGetter(field);
for (RSApiMethod apiMethod : matched)
{
final ClassFile targetClass = InjectUtil.fromApiMethod(inject, apiMethod);
apiMethod.setInjected(true);
if (apiMethod.getSignature().isVoid())
{
++set;
log.debug("Injecting setter {} for {} into {}", apiMethod.getMethod(), field.getPoolField(), targetClass.getPoolClass());
InjectSetter.inject(
targetClass,
apiMethod,
vanillaField,
modInverseOrNull(getter)
);
}
else
{
++get;
log.debug("Injecting getter {} for {} into {}", apiMethod.getMethod(), field.getPoolField(), targetClass.getPoolClass());
InjectGetter.inject(
targetClass,
apiMethod,
vanillaField,
getter
);
}
}
}
}
private static Number modInverseOrNull(Number getter)
{
if (getter == null)
{
return null;
}
// inverse getter to get the setter
return DMath.modInverse(getter);
}
}

View File

@@ -0,0 +1,85 @@
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injection.InjectData;
import static com.openosrs.injector.injection.InjectData.HOOKS;
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;
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() throws Injexception
{
/*
* 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.findStaticMethod(inject, "Rasterizer2D_fillRectangle", "Rasterizer2D", null).getPoolMethod();
final Method drawEntities = InjectUtil.findStaticMethod(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("Injected drawRectangle at {}", methodContext.getMethod().getPoolMethod());
}
}
}
}

View File

@@ -0,0 +1,230 @@
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injection.InjectData;
import static com.openosrs.injector.injection.InjectData.HOOKS;
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.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.signature.Signature;
public class DrawAfterWidgets extends AbstractInjector
{
public DrawAfterWidgets(InjectData inject)
{
super(inject);
}
public void inject() throws Injexception
{
/*
* 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.method1977(rootInterface, 0, 0, SoundCache.canvasWidth, Huffman.canvasHeight, 0, 0, -1);
* }
* < -- here appearantly
* Rasterizer2D.Rasterizer2D_resetClip();
* ______________________________________________________
*/
boolean injected = false;
Method noClip = InjectUtil.findStaticMethod(inject, "Rasterizer2D_resetClip", "Rasterizer2D", null); // !!!!!
if (noClip == null)
{
throw new Injexception("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("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)
{
InvokeStatic invoke = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(HOOKS),
"drawAfterWidgets",
new Signature("()V")
)
);
instructions.addInstruction(instructions.getInstructions().indexOf(l) + 1, invoke);
log.debug("injectDrawAfterWidgets injected a call after " + l);
injected = true;
}
}
}
if (!injected)
{
throw new Injexception("injectDrawAfterWidgets failed to inject!");
}
}
}

View File

@@ -0,0 +1,195 @@
//package com.openosrs.injector.injectors.raw;
//import com.openosrs.injector.InjectUtil;
//import com.openosrs.injector.Injexception;
//import com.openosrs.injector.injection.InjectData;
//import static com.openosrs.injector.injection.InjectData.HOOKS;
//import com.openosrs.injector.injectors.AbstractInjector;
//import java.util.List;
//import java.util.ListIterator;
//import net.runelite.asm.ClassFile;
//import net.runelite.asm.ClassGroup;
//import net.runelite.asm.Method;
//import net.runelite.asm.Type;
//import net.runelite.asm.attributes.code.Instruction;
//import net.runelite.asm.attributes.code.Instructions;
//import net.runelite.asm.attributes.code.Label;
//import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
//import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
//import net.runelite.asm.attributes.code.instructions.GetStatic;
//import net.runelite.asm.attributes.code.instructions.IStore;
//import net.runelite.asm.attributes.code.instructions.IfEq;
//import net.runelite.asm.attributes.code.instructions.IfNe;
//import net.runelite.asm.attributes.code.instructions.InvokeStatic;
//import net.runelite.asm.execution.Execution;
//import net.runelite.asm.execution.InstructionContext;
//import net.runelite.asm.pool.Class;
//import net.runelite.asm.pool.Field;
//import net.runelite.asm.signature.Signature;
//public class DrawMenu extends AbstractInjector
//{
//private static final net.runelite.asm.pool.Method HOOK = new net.runelite.asm.pool.Method(
//new Class(HOOKS),
//"drawMenu",
//new Signature("()Z")
//);
//private static final int MENU_COLOR = 0x5d5447;
//public DrawMenu(InjectData inject)
//{
//super(inject);
//}
//public void inject() throws Injexception
//{
///*
//* Label Getstatic client.isMenuOpen
//* Ifne -> Label Drawmenu
//* Jump -> Label Drawtext
//*
//* Label drawtext
//* Ldc xxx
//* Getstatic client. something with viewport size?
//* Imul
//* Iconst_m1
//* Ifne -> Label after draw menu <- info we need
//* Getstatic / LDC (same getstatic and LDC before)
//* Getstatic / LDC
//*/
//final ClassFile deobClient = inject.getDeobfuscated().findClass("Client");
//final ClassGroup vanilla = inject.getVanilla();
//final Field isMenuOpen = inject.toVanilla(deobClient.findField("isMenuOpen")).getPoolField();
//final Method drawLoggedIn = inject.toVanilla(deobClient.findMethod("drawLoggedIn"));
//final Instructions inst = drawLoggedIn.getCode().getInstructions();
//boolean foundCol = false, foundEnd = false;
//int injIdx = -1;
//final ListIterator<Instruction> it = inst.listIterator();
//while (it.hasNext())
//{
//Instruction i = it.next();
//if (!foundCol &&
//(i instanceof PushConstantInstruction) &&
//((PushConstantInstruction) i).getConstant().equals(MENU_COLOR))
//{
//foundCol = true;
//injIdx = it.nextIndex();
//}
//else if (!foundEnd &&
//(i instanceof PushConstantInstruction) &&
//((PushConstantInstruction) i).getConstant().equals(0))
//{
//i = it.next();
//if (!(i instanceof IStore))
//{
//continue;
//}
//int varIdx = ((IStore) i).getVariableIndex();
//i = it.next();
//if (!(i instanceof JumpingInstruction))
//{
//continue;
//}
//List<Label> jumps = ((JumpingInstruction) i).getJumps();
//if (jumps.size() > 1)
//{
//continue;
//}
//
//Instruction afterLabel = inst.
//}
//}
//final Execution ex = new Execution(vanilla);
//// Static step makes static methods not count as methods
//ex.staticStep = true;
//ex.noExceptions = true;
//ex.addMethod(drawLoggedIn);
//final Instruction
//ex.addExecutionVisitor((InstructionContext ic) ->
//{
//Instruction i = ic.getInstruction();
//if (i instanceof PushConstantInstruction)
//{
//if (((PushConstantInstruction) i).getConstant().equals(MENU_COLOR))
//{
//injIdx
//}
//}
//});
//ex.run();
//final Instructions ins = drawLoggedIn.getCode().getInstructions();
//ListIterator<Instruction> it = ins.getInstructions().listIterator();
//int injectIndex = -1;
//Label after = null;
//boolean foundBefore = false;
//boolean foundAfter = false;
//while (it.hasNext())
//{
//Instruction i = it.next();
//if (!(i instanceof GetStatic) && !(i instanceof InvokeStatic))
//{
//continue;
//}
//if (!foundBefore && i instanceof GetStatic)
//{
//if (!((GetStatic) i).getField().equals(isMenuOpen))
//{
//continue;
//}
//i = it.next();
//if (!(i instanceof IfEq) && !(i instanceof IfNe))
//{
//continue;
//}
//if (i instanceof IfEq)
//{
//injectIndex = it.nextIndex();
//}
//else
//{
//injectIndex = ins.getInstructions().indexOf(((IfNe) i).getJumps().get(0)) + 1;
//}
//foundBefore = true;
//}
//else if (!foundAfter && i instanceof InvokeStatic
//&& ((InvokeStatic) i).getMethod().equals("topLeftText"))
//{
//i = it.next();
//assert i instanceof JumpingInstruction;
//after = ((JumpingInstruction) i).getJumps().get(0);
//foundAfter = true;
//}
//if (foundBefore && foundAfter)
//{
//break;
//}
//}
//if (!foundBefore || !foundAfter || injectIndex == -1)
//{
//}
//ins.addInstruction(injectIndex, new IfNe(ins, after));
//ins.addInstruction(injectIndex, new InvokeStatic(ins, HOOK));
//log.info("Injected drawmenu hook in {} at index {}", "", injectIndex);
//}
//}

View File

@@ -0,0 +1,194 @@
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injection;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
import com.openosrs.injector.injectors.Injector;
import java.util.Iterator;
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.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.pool.Field;
public class HidePlayerAttacks extends AbstractInjector
{
public HidePlayerAttacks(InjectData inject)
{
super(inject);
}
public void inject() throws Injexception
{
final Method addPlayerOptions = InjectUtil.findStaticMethod(inject, "addPlayerToMenu");
final net.runelite.asm.pool.Method shouldHideAttackOptionFor = inject.getVanilla().findClass("client").findMethod("shouldHideAttackOptionFor").getPoolMethod();
injectHideAttack(addPlayerOptions, shouldHideAttackOptionFor);
injectHideCast(addPlayerOptions, shouldHideAttackOptionFor);
}
private void injectHideAttack(Method addPlayerOptions, net.runelite.asm.pool.Method shouldHideAttackOptionFor) throws Injexception
{
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("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 Injexception("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) throws Injexception
{
// 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
Instructions ins = addPlayerOptions.getCode().getInstructions();
log.info(String.valueOf(ins.getInstructions().size()));
ListIterator<Instruction> iterator = ins.getInstructions().listIterator();
while (iterator.hasNext())
{
Instruction i = iterator.next();
if (!(i instanceof BiPush) || (byte) ((BiPush) i).getConstant() != 8)
{
continue;
}
i = iterator.next();
while (!(i instanceof BiPush) || (byte) ((BiPush) i).getConstant() != 8)
{
i = iterator.next();
}
i = iterator.next();
if (!(i instanceof IAnd))
{
throw new Injexception("Yikes I didn't expect this");
}
i = iterator.next();
if (!(i instanceof IfICmpNe))
{
continue;
}
Label target = ((IfICmpNe) i).getJumps().get(0);
// 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,68 @@
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.Injexception;
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() throws Injexception
{
/*
* 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 Injexception("Only found " + replaced + " 25's to replace in occlude instead of expected 10");
}
}
}

View File

@@ -0,0 +1,383 @@
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.runelite.asm.Field;
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.LVTInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.ArrayStore;
import net.runelite.asm.attributes.code.instructions.GetField;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IALoad;
import net.runelite.asm.attributes.code.instructions.IAStore;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.IOr;
import net.runelite.asm.attributes.code.instructions.ISub;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
public class RasterizerHook extends AbstractInjector
{
// TODO: Should probably make this better
private static final int val = 0xff000000;
private static final Class R3D = new Class("Rasterizer3D");
private static final Class FONT = new Class("AbstractFont");
private static final Class R2D = new Class("Rasterizer2D");
private static final Class SPRITE = new Class("Sprite");
private net.runelite.asm.pool.Field R2D_PIXELS;
private static final Method r3d_vert = new Method(R3D, "Rasterizer3D_vertAlpha", new Signature("([IIIIIIII)V"));
private static final Method r3d_horiz = new Method(R3D, "Rasterizer3D_horizAlpha", new Signature("([IIIIII)V"));
private static final net.runelite.asm.pool.Field r3d_field = new net.runelite.asm.pool.Field(R3D, "Rasterizer3D_alpha", Type.INT);
private static final Method font_alpha = new Method(FONT, "AbstractFont_placeGlyphAlpha", new Signature("([I[BIIIIIIII)V"));
private static final Method circle_alpha = new Method(R2D, "Rasterizer2D_drawCircleAlpha", new Signature("(IIIII)V"));
private static final Method line_alpha = new Method(R2D, "Rasterizer2D_drawHorizontalLineAlpha", new Signature("(IIIII)V"));
private static final Method line_alpha2 = new Method(R2D, "Rasterizer2D_drawVerticalLineAlpha", new Signature("(IIIII)V"));
private static final Method fill_rect_alpha = new Method(R2D, "Rasterizer2D_fillRectangleAlpha", new Signature("(IIIIII)V"));
private static final Method sprite_alpha1 = new Method(SPRITE, "Sprite_drawTransparent", new Signature("([I[IIIIIIIII)V"));
private static final Method sprite_alpha2 = new Method(SPRITE, "Sprite_drawTransScaled", new Signature("([I[IIIIIIIIIIII)V"));
private static final String font = "AbstractFont_placeGlyph";
private static final String rast3D = "Rasterizer3D_iDontKnow";
private static final String rast3D2 = "Rasterizer3D_textureAlpha";
private static final String sprite = "Sprite_draw";
private static final String sprite2 = "Sprite_drawScaled";
private static final String sprite3 = "Sprite_drawTransOverlay";
private static final String sprite4 = "Sprite_drawTransBg";
private static final String indexedSprite = "IndexedSprite_something";
private static final String indexedSprite2 = "IndexedSprite_two";
private static final net.runelite.asm.pool.Method drawAlpha = new net.runelite.asm.pool.Method(
new Class("client"),
"drawAlpha",
new Signature("([IIII)V")
);
private int drawAlphaCount = 0;
private int orCount = 0;
public RasterizerHook(InjectData inject) throws Injexception
{
super(inject);
}
public void inject() throws Injexception
{
R2D_PIXELS = InjectUtil.findStaticField(inject, "Rasterizer2D_pixels", R2D.getName(), new Type("[I")).getPoolField();
injectDrawAlpha();
runVars();
run();
}
private void injectDrawAlpha() throws Injexception
{
final Field field = InjectUtil.findStaticField(inject, r3d_field);
runR3DAlpha(r3d_horiz, field, 15);
runR3DAlpha(r3d_vert, field, 12);
runFontAlpha(font_alpha, 1, 9);
runAlpha(circle_alpha, 2, 4);
runAlpha(line_alpha, 1, 4);
runAlpha(line_alpha2, 1, 4);
runAlpha(fill_rect_alpha, 1, 5);
runAlpha(sprite_alpha1, 1, 9, 0);
runAlpha(sprite_alpha2, 1, 12, 0);
}
private void runR3DAlpha(Method pool, net.runelite.asm.Field field, int req) throws Injexception
{
net.runelite.asm.Method method = InjectUtil.findStaticMethod(inject, pool);
Instructions ins = method.getCode().getInstructions();
int varIdx = 0; // This is obviously dumb but I cba making this better
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : method.findLVTInstructionsForVariable(varIdx))
{
indices.add(ins.getInstructions().indexOf(i));
}
if (indices.isEmpty())
{
throw new Injexception("Couldn't find hook location in " + pool.toString());
}
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
ins.getInstructions().add(codeIndex, new ISub(ins, InstructionType.ISUB));
ins.getInstructions().add(codeIndex, new GetStatic(ins, field.getPoolField()));
ins.getInstructions().add(codeIndex, new LDC(ins, 255));
added++;
drawAlphaCount++;
break;
}
}
}
if (added != req)
{
throw new Injexception("Not enough drawAlphas injected into " + pool.toString() + " (" + added + "/" + req + ")");
}
}
private void runFontAlpha(Method pool, int req, int extraArg) throws Injexception
{
net.runelite.asm.Method meth = InjectUtil.findStaticMethod(inject, pool);
Instructions ins = meth.getCode().getInstructions();
int varIdx = 0; // This is obviously dumb but I cba making this better
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : meth.findLVTInstructionsForVariable(varIdx))
{
indices.add(ins.getInstructions().indexOf(i));
}
if (indices.isEmpty())
{
throw new Injexception("Couldn't find hook location in " + pool.toString());
}
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
ins.getInstructions().add(codeIndex, new ISub(ins, InstructionType.ISUB));
ins.getInstructions().add(codeIndex, new ILoad(ins, extraArg));
ins.getInstructions().add(codeIndex, new LDC(ins, 255));
added++;
drawAlphaCount++;
break;
}
}
}
if (added != req)
{
throw new Injexception("Not enough drawAlphas injected into " + pool.toString() + " (" + added + "/" + req + ")");
}
}
private void runAlpha(Method pool, int req, int extraArg) throws Injexception
{
runAlpha(pool, req, extraArg, -1);
}
private void runAlpha(Method pool, int req, int extraArg, int varIndex) throws Injexception
{
net.runelite.asm.Method meth = InjectUtil.findStaticMethod(inject, pool);
Code code = meth.getCode();
Instructions ins = code.getInstructions();
int added = 0;
List<Integer> indices = new ArrayList<>();
for (Instruction i : ins.getInstructions())
{
if (!(i instanceof IALoad) && !(i instanceof GetField) && !(i instanceof ALoad))
{
continue;
}
if (i instanceof GetField)
{
if (((GetField) i).getField().equals(R2D_PIXELS))
{
indices.add(ins.getInstructions().indexOf(i));
}
}
else if ((i instanceof ALoad) && varIndex >= 0 && ((LVTInstruction) i).getVariableIndex() == varIndex)
{
indices.add(ins.getInstructions().indexOf(i));
}
else if (varIndex == -1)
{
indices.add(ins.getInstructions().indexOf(i));
}
}
if (indices.isEmpty())
{
throw new Injexception("Couldn't find hook location in " + pool.toString());
}
final int oldCount = drawAlphaCount;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
ins.getInstructions().set(codeIndex, new InvokeStatic(ins, drawAlpha));
if (extraArg != -1)
{
ins.getInstructions().add(codeIndex, new ILoad(ins, extraArg));
added++;
}
drawAlphaCount++;
break;
}
}
}
if (drawAlphaCount - oldCount > req)
{
throw new Injexception("Too many drawAlpha's were injected into " + pool.toString());
}
}
private void runVars() throws Injexception
{
runOnMethodWithVar(rast3D, 0, 36);
runOnMethodWithVar(rast3D2, 0, 36);
// 36 expected
runOnMethodWithVar(font, 0, 5);
// 5 expected
runOnMethodWithVar(sprite, 1, 5);
runOnMethodWithVar(sprite2, 0, 1);
runOnMethodWithVar(sprite3, 0, 1);
runOnMethodWithVar(sprite4, 0, 5);
// 12 expected
runOnMethodWithVar(indexedSprite, 0, 1);
runOnMethodWithVar(indexedSprite2, 0, 5);
// 6 expected
}
private void run() throws Injexception
{
final int startCount = orCount; // Cause you can't just count shit ty
Execution ex = new Execution(inject.getVanilla());
ex.populateInitialMethods();
Set<Instruction> done = new HashSet<>();
ex.addExecutionVisitor((InstructionContext ic) ->
{
Instruction i = ic.getInstruction();
Instructions ins = i.getInstructions();
Code code = ins.getCode();
net.runelite.asm.Method method = code.getMethod();
if (!(i instanceof IAStore))
{
return;
}
if (!done.add(i))
{
return;
}
ArrayStore as = (ArrayStore) i;
Field fieldBeingSet = as.getMyField(ic);
if (fieldBeingSet == null)
{
return;
}
if (!fieldBeingSet.getPoolField().equals(R2D_PIXELS))
{
return;
}
int index = ins.getInstructions().indexOf(i);
if (!(ins.getInstructions().get(index - 1) instanceof ILoad) && !ic.getPops().get(0).getValue().isUnknownOrNull())
{
if ((int) ic.getPops().get(0).getValue().getValue() == 0)
{
log.debug("Didn't add hook in method {}.{}. {} added, {} total, value 0", method.getClassFile().getClassName(), method.getName(), orCount - startCount, orCount);
return;
}
}
ins.getInstructions().add(index, new IOr(ins, InstructionType.IOR)); // Add instructions backwards
ins.getInstructions().add(index, new LDC(ins, val));
orCount++;
log.debug("Added hook in method {}.{}. {} added, {} total", method.getClassFile().getClassName(), method.getName(), orCount - startCount, orCount);
});
ex.run();
}
private void runOnMethodWithVar(String meth, int varIndex, int req) throws Injexception
{
net.runelite.asm.Method method = InjectUtil.findStaticMethod(inject, meth);
Instructions ins = method.getCode().getInstructions();
List<Integer> indices = new ArrayList<>();
for (Instruction i : method.findLVTInstructionsForVariable(varIndex))
{
int index = ins.getInstructions().indexOf(i);
assert index != -1;
assert ins.getInstructions().get(index + 1) instanceof ILoad; // Index in the array
indices.add(index);
}
int added = 0;
for (int i : indices)
{
for (int codeIndex = i + added; codeIndex < ins.getInstructions().size(); codeIndex++)
{
if (ins.getInstructions().get(codeIndex) instanceof IAStore)
{
added += 2;
orCount++;
ins.addInstruction(codeIndex, new IOr(ins, InstructionType.IOR)); // Add instructions backwards
ins.addInstruction(codeIndex, new LDC(ins, val));
break;
}
}
}
if (added / 2 != req)
{
throw new Injexception("Didn't inject the right amount of ors into " + meth);
}
log.info("Added {} instructions in {}. {} total", added / 2, meth, orCount);
}
public boolean validate()
{
return drawAlphaCount + 1 == 35 && orCount + 1 == 125;
}
}

View File

@@ -0,0 +1,69 @@
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.Injexception;
import com.openosrs.injector.injection.InjectData;
import static com.openosrs.injector.injection.InjectData.HOOKS;
import com.openosrs.injector.injectors.AbstractInjector;
import java.util.ListIterator;
import net.runelite.asm.ClassGroup;
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;
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/Entity;IIIIIIIIJ)V")
);
private static final int EXPECTED = 21;
public RenderDraw(InjectData inject)
{
super(inject);
}
@Override
public void inject() throws Injexception
{
int replaced = 0;
/*
* This class replaces entity draw invocation instructions
* with the renderDraw method on drawcallbacks
*/
final ClassGroup deob = inject.getDeobfuscated();
final net.runelite.asm.pool.Method draw = inject.toVanilla(deob
.findClass("Entity")
.findMethod("draw")
).getPoolMethod();
final Method drawTile = inject.toVanilla(deob
.findClass("Scene")
.findMethod("drawTile")
);
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("Replaced method call at {}", i);
++replaced;
}
}
}
if (replaced != EXPECTED)
throw new Injexception("Didn't replace the expected amount of method calls");
}
}

View File

@@ -0,0 +1,274 @@
package com.openosrs.injector.injectors.raw;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
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() throws Injexception
{
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");
// Next 4 should be injected by mixins, so don't need fail fast
final ClassFile vanillaClient = vanilla.findClass("client");
final Method runScript = vanillaClient.findStaticMethod("copy$runScript");
final Method vmExecuteOpcode = vanillaClient.findStaticMethod("vmExecuteOpcode");
final Field currentScriptField = vanillaClient.findField("currentScript");
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 Injexception("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 Injexception("Unable to find any Script AStores in runScript"));
log.debug("Found script index {}", outerSciptIdx);
ListIterator<Instruction> instrIter = instrs.getInstructions().listIterator();
while (instrIter.hasNext())
{
Instruction instr = instrIter.next();
if (instr instanceof AStore)
{
AStore il = (AStore) instr;
if (il.getVariableIndex() == outerSciptIdx)
{
instrIter.previous();
instrIter.add(new Dup(instrs));
instrIter.add(new PutStatic(instrs, currentScriptField));
instrIter.next();
}
}
}
}
// Add PutStatics to all PC IStores and IIncs
{
if (pcLocalVar == null)
{
throw new Injexception("Unable to find ILoad for invokedFromPc IStore");
}
log.debug("Found pc index {}", pcLocalVar);
ListIterator<Instruction> instrIter = instrs.getInstructions().listIterator();
while (instrIter.hasNext())
{
Instruction instr = instrIter.next();
if (instr instanceof IStore)
{
IStore il = (IStore) instr;
if (il.getVariableIndex() == pcLocalVar)
{
instrIter.previous();
instrIter.add(new Dup(instrs));
instrIter.add(new PutStatic(instrs, currentScriptPCField));
instrIter.next();
}
}
if (instr instanceof IInc)
{
IInc iinc = (IInc) instr;
if (iinc.getVariableIndex() == pcLocalVar)
{
instrIter.add(new ILoad(instrs, pcLocalVar));
instrIter.add(new PutStatic(instrs, currentScriptPCField));
}
}
}
}
// Inject call to vmExecuteOpcode
log.debug("Found instruction array index {}", instructionArrayLocalVar);
if (currentOpcodeStore == null)
{
throw new Injexception("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,67 @@
package com.openosrs.injector.injectors.rsapi;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
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.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.signature.Signature;
public class InjectGetter
{
public static void inject(ClassFile targetClass, RSApiMethod apiMethod, Field field, Number getter) throws Injexception
{
if (targetClass.findMethod(apiMethod.getName(), apiMethod.getSignature()) != null)
{
throw new Injexception("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 instanceof Integer)
{
ins.add(new LDC(instructions, getter));
ins.add(new IMul(instructions));
}
else if (getter instanceof Long)
{
ins.add(new LDC(instructions, getter));
ins.add(new LMul(instructions));
}
ins.add(InjectUtil.createReturnForType(instructions, field.getType()));
targetClass.addMethod(method);
}
}

View File

@@ -0,0 +1,112 @@
package com.openosrs.injector.injectors.rsapi;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
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) throws Injexception
{
if (targetClass.findMethod(apiMethod.getName(), apiMethod.getSignature()) != null)
{
throw new Injexception("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++));
}
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,88 @@
package com.openosrs.injector.injectors.rsapi;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.Injexception;
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.IMul;
import net.runelite.asm.attributes.code.instructions.LDC;
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 setter) throws Injexception
{
if (targetClass.findMethod(apiMethod.getName(), apiMethod.getSignature()) != null)
{
throw new Injexception("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 (setter instanceof Integer)
{
ins.add(new LDC(instructions, setter));
ins.add(new IMul(instructions));
}
else if (setter instanceof Long)
{
ins.add(new LDC(instructions, setter));
ins.add(new IMul(instructions));
}
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,142 @@
package com.openosrs.injector.rsapi;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.openosrs.injector.Injexception;
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(FileTree classes) throws Injexception
{
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(
new RSApiClassVisitor(apiClass),
ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
);
this.classes.add(apiClass);
}
catch (IOException e)
{
throw new Injexception(e);
}
}
init();
}
@VisibleForTesting
public 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,85 @@
package com.openosrs.injector.rsapi;
import static com.openosrs.injector.rsapi.RSApi.CONSTRUCT;
import static com.openosrs.injector.rsapi.RSApi.IMPORT;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import lombok.Data;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
import org.jetbrains.annotations.NotNull;
@Data
public class RSApiClass 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<>();
void init(List<RSApiMethod> constructList)
{
for (RSApiMethod method : this)
{
if (method.isSynthetic())
{
continue;
}
final Annotations annotations = method.getAnnotations();
if (annotations.find(CONSTRUCT) != null)
{
constructList.add(method);
continue;
}
final Annotation imported = annotations.find(IMPORT);
if (imported != null)
{
final String importStr = imported.getElement().getString();
imports.computeIfAbsent(
importStr,
(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();
}
}

View File

@@ -0,0 +1,34 @@
package com.openosrs.injector.rsapi;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class RSApiClassVisitor extends ClassVisitor
{
private final RSApiClass apiClass;
public RSApiClassVisitor(RSApiClass apiClass)
{
super(Opcodes.ASM5);
this.apiClass = apiClass;
}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
apiClass.setClazz(new Class(name));
for (String s : interfaces)
{
apiClass.getInterfaces().add(new Class(s));
}
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
{
final RSApiMethod method = apiClass.addMethod(name, new Signature(desc), access);
return new RSApiMethodVisitor(method);
}
}

View File

@@ -0,0 +1,41 @@
package com.openosrs.injector.rsapi;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import net.runelite.asm.Annotated;
import net.runelite.asm.Named;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
import org.objectweb.asm.Opcodes;
@Data
@RequiredArgsConstructor
public class RSApiMethod implements Annotated, Named
{
private final Method method;
private final int accessFlags;
private final Annotations annotations = new Annotations();
private boolean injected;
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;
}
}

View File

@@ -0,0 +1,42 @@
package com.openosrs.injector.rsapi;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.annotation.Element;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Opcodes;
public class RSApiMethodAnnotationVisitor extends AnnotationVisitor
{
private final RSApiMethod method;
private final Type type;
private final Annotation annotation;
public RSApiMethodAnnotationVisitor(RSApiMethod method, Type type)
{
super(Opcodes.ASM5);
this.method = method;
this.type = type;
annotation = new Annotation(method.getAnnotations());
annotation.setType(type);
}
@Override
public void visit(String name, Object value)
{
Element element = new Element(annotation);
element.setName(name);
element.setValue(value);
annotation.addElement(element);
}
@Override
public void visitEnd()
{
method.getAnnotations().addAnnotation(annotation);
}
}

View File

@@ -0,0 +1,24 @@
package com.openosrs.injector.rsapi;
import net.runelite.asm.Type;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class RSApiMethodVisitor extends MethodVisitor
{
private final RSApiMethod method;
RSApiMethodVisitor(RSApiMethod method)
{
super(Opcodes.ASM5);
this.method = method;
}
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible)
{
final Type type = new Type(descriptor);
return new RSApiMethodAnnotationVisitor(method, type);
}
}

View File

@@ -0,0 +1,43 @@
package com.openosrs.injector
import com.openosrs.injector.injection.InjectTaskHandler
import com.openosrs.injector.rsapi.RSApi
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
open class Inject: DefaultTask() {
@InputFile
val vanilla = project.objects.fileProperty()
@InputDirectory
val rsclient = project.objects.directoryProperty()
@InputDirectory
val mixins = project.objects.directoryProperty()
@InputDirectory
val rsapi = project.objects.directoryProperty()
@OutputFile
val output = project.objects.fileProperty().convention {
project.file("${project.buildDir}/libs/${project.name}-${project.version}")
}
@TaskAction
fun inject() {
val vanilla = this.vanilla.get().asFile
val rsclient = this.rsclient.asFileTree
val mixins = this.mixins.asFileTree
val rsapi = this.rsapi.asFileTree
val output = this.output.asFile
val injector: InjectTaskHandler = Injection(vanilla, rsclient, rsapi, mixins)
injector.inject()
injector.save(output.get())
}
}

View File

@@ -0,0 +1,20 @@
package com.openosrs.injector
import org.gradle.api.Plugin
import org.gradle.api.Project
class InjectPlugin: Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create("injector", Injextention::class.java, project)
project.tasks.register("inject", Inject::class.java) {
it.vanilla.set(extension.vanilla)
it.rsclient.set(extension.rsclient)
it.mixins.set(extension.mixins)
it.rsapi.set(extension.rsapi)
if (extension.output.isPresent) {
it.output.set(extension.output)
}
}
}
}

View File

@@ -0,0 +1,14 @@
package com.openosrs.injector
import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import javax.inject.Inject
open class Injextention(project: Project) {
val vanilla = project.objects.fileProperty()
val rsclient = project.objects.directoryProperty()
val mixins = project.objects.directoryProperty()
val rsapi = project.objects.directoryProperty()
val output = project.objects.fileProperty()
}

View File

@@ -0,0 +1,47 @@
import com.openosrs.injector.rsapi.RSApi;
import com.openosrs.injector.rsapi.RSApiClass;
import com.openosrs.injector.rsapi.RSApiClassVisitor;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
public class RSApiTest
{
private final RSApi api = new RSApi();
@Test
@Ignore
public void test() throws IOException
{
loadAndAdd("/net/runelite/rs/api/RSTest.class");
loadAndAdd("/net/runelite/rs/api/RSInterface.class");
api.init();
List<RSApiClass> classes = api.getClasses();
assert classes.size() == 2;
RSApiClass clazz = api.findClass("net/runelite/rs/api/RSTest");
assert clazz != null;
assert clazz.getMethods().size() == 4;
}
private void loadAndAdd(String path) throws IOException
{
List<RSApiClass> classes = api.getClasses();
try (InputStream is = RSApiTest.class.getResourceAsStream(path))
{
ClassReader reader = new ClassReader(is);
RSApiClass apiClass = new RSApiClass();
reader.accept(
new RSApiClassVisitor(apiClass),
ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
);
classes.add(apiClass);
}
}
}

26
src/test/java/TTest.java Normal file
View File

@@ -0,0 +1,26 @@
import com.openosrs.injector.InjectPlugin;
import com.openosrs.injector.Injection;
import com.openosrs.injector.injection.InjectTaskHandler;
import java.io.File;
import org.gradle.api.Project;
import org.gradle.api.file.FileTree;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.Test;
public class TTest
{
private static final File VAN = new File("C:\\Users\\Lucas\\.gradle\\caches\\modules-2\\files-2.1\\net.runelite.rs\\vanilla\\184\\1bdb54d90d696598a8ee5ff793155482970180a\\vanilla-184.jar");
private static final Project project = ProjectBuilder.builder().withProjectDir(new File("C:\\Users\\Lucas\\IdeaProjects\\runelite")).build();
private static final FileTree API = project.zipTree("/runescape-api/build/libs/runescape-api-1.5.37-SNAPSHOT.jar"),
DEOB = project.zipTree("/runescape-client/build/libs/rs-client-1.5.37-SNAPSHOT.jar"),
MIXINS = project.zipTree("/runelite-mixins/build/libs/mixins-1.5.37-SNAPSHOT.jar");
@Test
public void test() throws Exception
{
InjectTaskHandler inj = new Injection(VAN, DEOB, API, MIXINS);
inj.inject();
}
}

View File

@@ -0,0 +1,5 @@
package net.runelite.rs.api;
public interface RSInterface
{
}

View File

@@ -0,0 +1,20 @@
package net.runelite.rs.api;
import javax.inject.Inject;
import net.runelite.mapping.Import;
public interface RSTest extends RSInterface
{
@Import("test1")
void setTest1(String test1);
@Import("test1")
String getTest1();
@Import("test2")
@Inject
void invokeTest2(String var1, int var3, String var2);
@Import("test3")
void setTest3(String test1);
}