forked from openrs2/openrs2
The client scrambles static fields and methods - moving each to a random class where it does not logically belong. This transformer moves all static methods to a new set of empty classes. The intention is that the human refactoring the deobfuscated client can move the static methods around with an IDE to classes where it makes sense. In theory we could use heuristics to move some methods around - e.g. ideas I have include: * If the method takes no arguments, move it to the same class as its return type. * If it's a void method that takes one argument, move it to the same class as its single argument. * Group related methods together based on where they are called. However, some of the heuristics are probably fairly complicated to implement. Furthermore, as they are heuristics, they will make mistakes. In the naive approach, the Static* classes generated by this transformer effectively act as a queue of methods that a human has yet to move to an appropriate class. If the Static* classes are ever emptied after refactoring, then we will know that all static methods have been moved to an appropriate class. We wouldn't be able to guarantee this if some methods were moved to Class* classes with heuristics. The heuristics would also be fairly complicated to implement. Therefore, the transformer does not use heuristics. Field scrambling support will be included in a future commit.master
parent
4468766a9d
commit
ce74765269
@ -0,0 +1,99 @@ |
||||
package dev.openrs2.deob.transform |
||||
|
||||
import dev.openrs2.asm.ClassVersionUtils |
||||
import dev.openrs2.asm.MemberRef |
||||
import dev.openrs2.asm.classpath.ClassPath |
||||
import dev.openrs2.asm.classpath.Library |
||||
import dev.openrs2.asm.transform.Transformer |
||||
import dev.openrs2.deob.remap.TypedRemapper |
||||
import org.objectweb.asm.Opcodes |
||||
import org.objectweb.asm.tree.ClassNode |
||||
import org.objectweb.asm.tree.FieldInsnNode |
||||
import org.objectweb.asm.tree.MethodInsnNode |
||||
import org.objectweb.asm.tree.MethodNode |
||||
|
||||
class StaticScramblingTransformer : Transformer() { |
||||
private val fields = mutableMapOf<MemberRef, String>() |
||||
private val methods = mutableMapOf<MemberRef, String>() |
||||
private var nextStaticClass: ClassNode? = null |
||||
private val staticClasses = mutableListOf<ClassNode>() |
||||
|
||||
private fun nextClass(): ClassNode { |
||||
var clazz = nextStaticClass |
||||
if (clazz != null && (clazz.fields.size + clazz.methods.size) < MAX_FIELDS_AND_METHODS) { |
||||
return clazz |
||||
} |
||||
|
||||
clazz = ClassNode() |
||||
clazz.version = Opcodes.V1_1 |
||||
clazz.access = Opcodes.ACC_PUBLIC or Opcodes.ACC_SUPER |
||||
clazz.name = "Static${staticClasses.size + 1}" |
||||
clazz.superName = "java/lang/Object" |
||||
clazz.interfaces = mutableListOf() |
||||
clazz.innerClasses = mutableListOf() |
||||
clazz.fields = mutableListOf() |
||||
clazz.methods = mutableListOf() |
||||
|
||||
staticClasses += clazz |
||||
nextStaticClass = clazz |
||||
|
||||
return clazz |
||||
} |
||||
|
||||
override fun preTransform(classPath: ClassPath) { |
||||
fields.clear() |
||||
methods.clear() |
||||
nextStaticClass = null |
||||
staticClasses.clear() |
||||
|
||||
for (library in classPath.libraries) { |
||||
// TODO(gpe): improve detection of the client library |
||||
if ("client" !in library) { |
||||
continue |
||||
} |
||||
|
||||
for (clazz in library) { |
||||
// TODO(gpe): exclude the JSObject class |
||||
if (clazz.name in TypedRemapper.EXCLUDED_CLASSES) { |
||||
continue |
||||
} |
||||
|
||||
clazz.methods.removeIf { method -> |
||||
if (method.access and Opcodes.ACC_STATIC == 0) { |
||||
return@removeIf false |
||||
} else if (method.access and Opcodes.ACC_NATIVE != 0) { |
||||
return@removeIf false |
||||
} else if (method.name in TypedRemapper.EXCLUDED_METHODS) { |
||||
return@removeIf false |
||||
} |
||||
|
||||
val staticClass = nextClass() |
||||
staticClass.methods.add(method) |
||||
staticClass.version = ClassVersionUtils.maxVersion(staticClass.version, clazz.version) |
||||
|
||||
methods[MemberRef(clazz, method)] = staticClass.name |
||||
return@removeIf true |
||||
} |
||||
} |
||||
|
||||
for (clazz in staticClasses) { |
||||
library.add(clazz) |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { |
||||
for (insn in method.instructions) { |
||||
when (insn) { |
||||
is FieldInsnNode -> insn.owner = fields.getOrDefault(MemberRef(insn), insn.owner) |
||||
is MethodInsnNode -> insn.owner = methods.getOrDefault(MemberRef(insn), insn.owner) |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
companion object { |
||||
private const val MAX_FIELDS_AND_METHODS = 500 |
||||
} |
||||
} |
Loading…
Reference in new issue