inititial messy commit
This commit is contained in:
59
build.gradle.kts
Normal file
59
build.gradle.kts
Normal 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
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
172
gradlew
vendored
Normal 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
84
gradlew.bat
vendored
Normal 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
2
settings.gradle.kts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
rootProject.name = "injector-plugin"
|
||||||
|
|
||||||
469
src/main/java/com/openosrs/injector/InjectUtil.java
Normal file
469
src/main/java/com/openosrs/injector/InjectUtil.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/main/java/com/openosrs/injector/Injection.java
Normal file
104
src/main/java/com/openosrs/injector/Injection.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/main/java/com/openosrs/injector/Injexception.java
Normal file
19
src/main/java/com/openosrs/injector/Injexception.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/main/java/com/openosrs/injector/RSApiValidator.java
Normal file
37
src/main/java/com/openosrs/injector/RSApiValidator.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
188
src/main/java/com/openosrs/injector/injection/InjectData.java
Normal file
188
src/main/java/com/openosrs/injector/injection/InjectData.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
430
src/main/java/com/openosrs/injector/injectors/InjectHook.java
Normal file
430
src/main/java/com/openosrs/injector/injectors/InjectHook.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/main/java/com/openosrs/injector/injectors/Injector.java
Normal file
37
src/main/java/com/openosrs/injector/injectors/Injector.java
Normal 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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
689
src/main/java/com/openosrs/injector/injectors/MixinInjector.java
Normal file
689
src/main/java/com/openosrs/injector/injectors/MixinInjector.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
289
src/main/java/com/openosrs/injector/injectors/RSApiInjector.java
Normal file
289
src/main/java/com/openosrs/injector/injectors/RSApiInjector.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
195
src/main/java/com/openosrs/injector/injectors/raw/DrawMenu.java
Normal file
195
src/main/java/com/openosrs/injector/injectors/raw/DrawMenu.java
Normal 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);
|
||||||
|
|
||||||
|
|
||||||
|
//}
|
||||||
|
//}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
274
src/main/java/com/openosrs/injector/injectors/raw/ScriptVM.java
Normal file
274
src/main/java/com/openosrs/injector/injectors/raw/ScriptVM.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
142
src/main/java/com/openosrs/injector/rsapi/RSApi.java
Normal file
142
src/main/java/com/openosrs/injector/rsapi/RSApi.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
85
src/main/java/com/openosrs/injector/rsapi/RSApiClass.java
Normal file
85
src/main/java/com/openosrs/injector/rsapi/RSApiClass.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/main/java/com/openosrs/injector/rsapi/RSApiMethod.java
Normal file
41
src/main/java/com/openosrs/injector/rsapi/RSApiMethod.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/main/kotlin/com/openosrs/injector/Inject.kt
Normal file
43
src/main/kotlin/com/openosrs/injector/Inject.kt
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/kotlin/com/openosrs/injector/InjectPlugin.kt
Normal file
20
src/main/kotlin/com/openosrs/injector/InjectPlugin.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/kotlin/com/openosrs/injector/Injextention.kt
Normal file
14
src/main/kotlin/com/openosrs/injector/Injextention.kt
Normal 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()
|
||||||
|
}
|
||||||
|
|
||||||
47
src/test/java/RSApiTest.java
Normal file
47
src/test/java/RSApiTest.java
Normal 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
26
src/test/java/TTest.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/test/java/net/runelite/rs/api/RSInterface.java
Normal file
5
src/test/java/net/runelite/rs/api/RSInterface.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package net.runelite.rs.api;
|
||||||
|
|
||||||
|
public interface RSInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
20
src/test/java/net/runelite/rs/api/RSTest.java
Normal file
20
src/test/java/net/runelite/rs/api/RSTest.java
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user