-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
With asm and kotlin-reflect added, we can analyze and even modify the client patch at runtime. A simple example has been added that will print out the instructions for client.changeWorld on startup. This is how we will get our mappings etc so best to get this launcher / client update out of the way now.
- Loading branch information
Showing
5 changed files
with
216 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package ext.java | ||
|
||
import java.io.ByteArrayOutputStream | ||
import java.util.jar.JarEntry | ||
import java.util.jar.JarFile | ||
|
||
object JarFileExt { | ||
fun JarFile.getBytes(entry: JarEntry): ByteArray { | ||
getInputStream(entry).use { inputStream -> | ||
ByteArrayOutputStream().use { byteArrayOutputStream -> | ||
val buffer = ByteArray(4096) | ||
var bytesRead: Int | ||
while (inputStream.read(buffer).also { bytesRead = it } != -1) { | ||
byteArrayOutputStream.write(buffer, 0, bytesRead) | ||
} | ||
return byteArrayOutputStream.toByteArray() | ||
} | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
runelite-client/src/main/java/hotlite/patch/ExamplePrintInstructionClassVisitor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package hotlite.patch | ||
|
||
import org.objectweb.asm.ClassVisitor | ||
import org.objectweb.asm.ClassWriter | ||
import org.objectweb.asm.MethodVisitor | ||
|
||
//TODO: methodDesc is inconsistent atm | ||
class ExamplePrintInstructionClassVisitor(api: Int, private val methodName: String, private val methodDesc: String, classWriter: ClassWriter) : ClassVisitor(api, classWriter) { | ||
override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, exceptions: Array<out String>?): MethodVisitor { | ||
if (methodName == name) { | ||
val originalMethodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) | ||
return PrintingMethodVisitor(api, originalMethodVisitor) | ||
} | ||
return super.visitMethod(access, name, descriptor, signature, exceptions) | ||
} | ||
} |
82 changes: 82 additions & 0 deletions
82
runelite-client/src/main/java/hotlite/patch/PatchManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package hotlite.patch | ||
|
||
import ext.java.JarFileExt.getBytes | ||
import org.objectweb.asm.ClassReader | ||
import org.objectweb.asm.ClassWriter | ||
import org.objectweb.asm.Opcodes | ||
import java.io.IOException | ||
import java.util.jar.JarFile | ||
import kotlin.reflect.KClass | ||
import kotlin.reflect.KFunction | ||
import kotlin.reflect.full.memberFunctions | ||
import kotlin.reflect.jvm.javaMethod | ||
|
||
|
||
object PatchManager { | ||
lateinit var PATCHED_JAR: String | ||
lateinit var CLASSLOADER: ClassLoader | ||
val classNames = HashSet<String>() | ||
val classes = HashMap<KClass<*>, ByteArray>() | ||
|
||
fun init(pathToJar: String, classLoader: ClassLoader) { | ||
PATCHED_JAR = pathToJar | ||
CLASSLOADER = classLoader | ||
|
||
loadPatch(PATCHED_JAR, CLASSLOADER) | ||
|
||
println("Loaded ${classes.size} patch classes") | ||
|
||
printChangeWorldInstructions() | ||
} | ||
|
||
fun loadPatch(pathToPatch: String, classLoader: ClassLoader) { | ||
try { | ||
val jarFile = JarFile(pathToPatch) | ||
val entries = jarFile.entries() | ||
|
||
while (entries.hasMoreElements()) { | ||
val entry = entries.nextElement() | ||
if (entry.name.endsWith(".class")) { | ||
val className = entry.name.replace('/', '.').substring(0, entry.name.length - 6) | ||
classNames.add(className) | ||
classes[classLoader.loadClass(className).kotlin] = | ||
jarFile.getBytes(entry) | ||
} | ||
} | ||
} catch (e: IOException) { | ||
e.printStackTrace() | ||
} | ||
} | ||
|
||
fun printChangeWorldInstructions() { | ||
for (c in classes.keys) { | ||
for (m in c.memberFunctions) { | ||
if (m.name == "changeWorld") { | ||
m.javaMethod?.let { | ||
try { | ||
val classBytes = classes[c] | ||
val reader = ClassReader(classBytes) | ||
val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS) | ||
val visitor = ExamplePrintInstructionClassVisitor(Opcodes.ASM9, m.name, getMethodDescriptor(m), classWriter) | ||
reader.accept(visitor, 0) | ||
} catch (e: IOException) { | ||
e.printStackTrace() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
//FIXME | ||
fun getMethodDescriptor(kFunction: KFunction<*>): String { | ||
val javaMethod = kFunction.javaMethod ?: error("Java method not found for the provided Kotlin function") | ||
val methodDescriptor = javaMethod.toGenericString() | ||
|
||
// If you specifically want just the method descriptor, you can extract it from the generic string | ||
val descriptorStart = methodDescriptor.indexOf('(') | ||
val descriptorEnd = methodDescriptor.indexOf(')') + 1 | ||
return methodDescriptor.substring(descriptorStart, descriptorEnd) | ||
} | ||
} | ||
|
91 changes: 91 additions & 0 deletions
91
runelite-client/src/main/java/hotlite/patch/PrintingMethodVisitor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package hotlite.patch | ||
|
||
import org.objectweb.asm.Label | ||
import org.objectweb.asm.MethodVisitor | ||
import org.objectweb.asm.util.Printer | ||
|
||
class PrintingMethodVisitor(api: Int, mv: MethodVisitor?) : MethodVisitor(api, mv) { | ||
override fun visitCode() { | ||
println("Method Instructions:") | ||
super.visitCode() | ||
} | ||
|
||
override fun visitInsn(opcode: Int) { | ||
println(getOpcodeName(opcode)) | ||
super.visitInsn(opcode) | ||
} | ||
|
||
override fun visitIntInsn(opcode: Int, operand: Int) { | ||
println(getOpcodeName(opcode) + " " + operand) | ||
super.visitIntInsn(opcode, operand) | ||
} | ||
|
||
override fun visitVarInsn(opcode: Int, `var`: Int) { | ||
println(getOpcodeName(opcode) + " " + `var`) | ||
super.visitVarInsn(opcode, `var`) | ||
} | ||
|
||
override fun visitTypeInsn(opcode: Int, type: String) { | ||
println(getOpcodeName(opcode) + " " + type) | ||
super.visitTypeInsn(opcode, type) | ||
} | ||
|
||
override fun visitFieldInsn(opcode: Int, owner: String, name: String, descriptor: String) { | ||
println(getOpcodeName(opcode) + " " + owner + " " + name + " " + descriptor) | ||
super.visitFieldInsn(opcode, owner, name, descriptor) | ||
} | ||
|
||
override fun visitMethodInsn(opcode: Int, owner: String, name: String, descriptor: String, isInterface: Boolean) { | ||
println(getOpcodeName(opcode) + " " + owner + " " + name + " " + descriptor + " " + isInterface) | ||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) | ||
} | ||
|
||
override fun visitJumpInsn(opcode: Int, label: Label) { | ||
println(getOpcodeName(opcode) + " " + label) | ||
super.visitJumpInsn(opcode, label) | ||
} | ||
|
||
override fun visitLabel(label: Label) { | ||
println("Label: $label") | ||
super.visitLabel(label) | ||
} | ||
|
||
override fun visitLdcInsn(value: Any) { | ||
println("LDC: $value") | ||
super.visitLdcInsn(value) | ||
} | ||
|
||
override fun visitIincInsn(`var`: Int, increment: Int) { | ||
println("IINC: $`var` $increment") | ||
super.visitIincInsn(`var`, increment) | ||
} | ||
|
||
override fun visitTableSwitchInsn(min: Int, max: Int, dflt: Label, vararg labels: Label) { | ||
println("TABLESWITCH: " + min + " " + max + " " + dflt + " " + labels.contentToString()) | ||
super.visitTableSwitchInsn(min, max, dflt, *labels) | ||
} | ||
|
||
override fun visitLookupSwitchInsn(dflt: Label, keys: IntArray, labels: Array<Label>) { | ||
println("LOOKUPSWITCH: " + dflt + " " + keys.contentToString() + " " + labels.contentToString()) | ||
super.visitLookupSwitchInsn(dflt, keys, labels) | ||
} | ||
|
||
override fun visitMultiANewArrayInsn(descriptor: String, dims: Int) { | ||
println("MULTIANEWARRAY: $descriptor $dims") | ||
super.visitMultiANewArrayInsn(descriptor, dims) | ||
} | ||
|
||
override fun visitMaxs(maxStack: Int, maxLocals: Int) { | ||
println("Max Stack: $maxStack, Max Locals: $maxLocals") | ||
super.visitMaxs(maxStack, maxLocals) | ||
} | ||
|
||
override fun visitEnd() { | ||
println("End of Method") | ||
super.visitEnd() | ||
} | ||
|
||
private fun getOpcodeName(opcode: Int): String { | ||
return Printer.OPCODES[opcode] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters