Open-source multiplayer game server compatible with the RuneScape client https://www.openrs2.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openrs2/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt

203 lines
6.7 KiB

package dev.openrs2.deob.transform
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.hasCode
import dev.openrs2.asm.removeArgument
import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.ArgRef
Replace TypedRemapper constants with a more flexible system The new system will make it easier to port the deobfuscator to different revisions. There are two main changes: - The addition of a Profile class, which contains a list of excluded classes, methods and fields, and the maximum obfuscated name length. It is passed to Transformers that require it with dependency injection. - New ClassFilter and MemberFilter infrastructure. The MemberFilter class adds support for filtering fields and methods based on the owner and descriptor, in addition to the name. This makes the filters more precise than the previous system. It also supports globs, which makes it easy to filter whole groups of classes, fields and methods in one go. The Profile class uses a ClassFilter and MemberFilters to represent the list of excluded classes, methods and fields. A separate benefit is the addition of a separate entry points filter to the Profile class. Prior to this commit, many Transformers re-used the excluded method filter to find entry points, which is less precise (many of the excluded methods in 550 are not entry points). Support for filtering methods by owner and descriptor in addition to name allows the DEFAULT_PUBLIC_CTOR_CLASSES Set in VisibilityTransformer to be combined with the entry points filter. In the future it might be desirable to split the excluded method set into three separate sets: - One to represent methods that can't be renamed. - One to represent methods whose signature can't be changed. - One to represent methods that can't be removed. Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
import dev.openrs2.deob.Profile
import dev.openrs2.deob.analysis.ConstSourceInterpreter
import dev.openrs2.deob.analysis.ConstSourceValue
import dev.openrs2.deob.remap.TypedRemapper
import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.VarInsnNode
import org.objectweb.asm.tree.analysis.Analyzer
Replace TypedRemapper constants with a more flexible system The new system will make it easier to port the deobfuscator to different revisions. There are two main changes: - The addition of a Profile class, which contains a list of excluded classes, methods and fields, and the maximum obfuscated name length. It is passed to Transformers that require it with dependency injection. - New ClassFilter and MemberFilter infrastructure. The MemberFilter class adds support for filtering fields and methods based on the owner and descriptor, in addition to the name. This makes the filters more precise than the previous system. It also supports globs, which makes it easy to filter whole groups of classes, fields and methods in one go. The Profile class uses a ClassFilter and MemberFilters to represent the list of excluded classes, methods and fields. A separate benefit is the addition of a separate entry points filter to the Profile class. Prior to this commit, many Transformers re-used the excluded method filter to find entry points, which is less precise (many of the excluded methods in 550 are not entry points). Support for filtering methods by owner and descriptor in addition to name allows the DEFAULT_PUBLIC_CTOR_CLASSES Set in VisibilityTransformer to be combined with the entry points filter. In the future it might be desirable to split the excluded method set into three separate sets: - One to represent methods that can't be renamed. - One to represent methods whose signature can't be changed. - One to represent methods that can't be removed. Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
Replace TypedRemapper constants with a more flexible system The new system will make it easier to port the deobfuscator to different revisions. There are two main changes: - The addition of a Profile class, which contains a list of excluded classes, methods and fields, and the maximum obfuscated name length. It is passed to Transformers that require it with dependency injection. - New ClassFilter and MemberFilter infrastructure. The MemberFilter class adds support for filtering fields and methods based on the owner and descriptor, in addition to the name. This makes the filters more precise than the previous system. It also supports globs, which makes it easy to filter whole groups of classes, fields and methods in one go. The Profile class uses a ClassFilter and MemberFilters to represent the list of excluded classes, methods and fields. A separate benefit is the addition of a separate entry points filter to the Profile class. Prior to this commit, many Transformers re-used the excluded method filter to find entry points, which is less precise (many of the excluded methods in 550 are not entry points). Support for filtering methods by owner and descriptor in addition to name allows the DEFAULT_PUBLIC_CTOR_CLASSES Set in VisibilityTransformer to be combined with the entry points filter. In the future it might be desirable to split the excluded method set into three separate sets: - One to represent methods that can't be renamed. - One to represent methods whose signature can't be changed. - One to represent methods that can't be removed. Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
class UnusedArgTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private val retainedArgs = mutableSetOf<ArgRef>()
private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private var deletedArgs = 0
override fun preTransform(classPath: ClassPath) {
retainedArgs.clear()
inheritedMethodSets = classPath.createInheritedMethodSets()
deletedArgs = 0
for (library in classPath.libraries) {
for (clazz in library) {
for (method in clazz.methods) {
if (method.hasCode) {
populateRetainedArgs(classPath, clazz, method)
}
}
}
}
}
private fun populateRetainedArgs(classPath: ClassPath, clazz: ClassNode, method: MethodNode) {
val partition = inheritedMethodSets[MemberRef(clazz, method)]!!
val localToArgMap = createLocalToArgMap(method)
val analyzer = Analyzer(ConstSourceInterpreter())
val frames = analyzer.analyze(clazz.name, method)
frame@ for ((i, frame) in frames.withIndex()) {
if (frame == null) {
continue
}
val stackSize = frame.stackSize
when (val insn = method.instructions[i]) {
is VarInsnNode -> {
if (insn.opcode != Opcodes.ILOAD) {
continue@frame
}
val arg = localToArgMap[insn.`var`]
if (arg != null) {
retainedArgs.add(ArgRef(partition, arg))
}
}
is MethodInsnNode -> {
val invokePartition = inheritedMethodSets[MemberRef(insn)]
Replace TypedRemapper constants with a more flexible system The new system will make it easier to port the deobfuscator to different revisions. There are two main changes: - The addition of a Profile class, which contains a list of excluded classes, methods and fields, and the maximum obfuscated name length. It is passed to Transformers that require it with dependency injection. - New ClassFilter and MemberFilter infrastructure. The MemberFilter class adds support for filtering fields and methods based on the owner and descriptor, in addition to the name. This makes the filters more precise than the previous system. It also supports globs, which makes it easy to filter whole groups of classes, fields and methods in one go. The Profile class uses a ClassFilter and MemberFilters to represent the list of excluded classes, methods and fields. A separate benefit is the addition of a separate entry points filter to the Profile class. Prior to this commit, many Transformers re-used the excluded method filter to find entry points, which is less precise (many of the excluded methods in 550 are not entry points). Support for filtering methods by owner and descriptor in addition to name allows the DEFAULT_PUBLIC_CTOR_CLASSES Set in VisibilityTransformer to be combined with the entry points filter. In the future it might be desirable to split the excluded method set into three separate sets: - One to represent methods that can't be renamed. - One to represent methods whose signature can't be changed. - One to represent methods that can't be removed. Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
if (invokePartition == null) {
continue@frame
} else if (!TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, invokePartition)) {
continue@frame
}
val args = Type.getArgumentTypes(insn.desc).size
for (j in 0 until args) {
val source = frame.getStack(stackSize - args + j)
if (source !is ConstSourceValue.Single) {
retainedArgs.add(ArgRef(invokePartition, j))
}
}
}
}
}
}
override fun preTransformMethod(
classPath: ClassPath,
library: Library,
clazz: ClassNode,
method: MethodNode
): Boolean {
// delete unused int args from call sites
if (!method.hasCode) {
return false
}
val analyzer = Analyzer(ConstSourceInterpreter())
val frames = analyzer.analyze(clazz.name, method)
val deadInsns = mutableListOf<AbstractInsnNode>()
for ((i, frame) in frames.withIndex()) {
if (frame == null) {
continue
}
val stackSize = frame.stackSize
val insn = method.instructions[i]
if (insn !is MethodInsnNode) {
continue
}
val partition = inheritedMethodSets[MemberRef(insn)]
Replace TypedRemapper constants with a more flexible system The new system will make it easier to port the deobfuscator to different revisions. There are two main changes: - The addition of a Profile class, which contains a list of excluded classes, methods and fields, and the maximum obfuscated name length. It is passed to Transformers that require it with dependency injection. - New ClassFilter and MemberFilter infrastructure. The MemberFilter class adds support for filtering fields and methods based on the owner and descriptor, in addition to the name. This makes the filters more precise than the previous system. It also supports globs, which makes it easy to filter whole groups of classes, fields and methods in one go. The Profile class uses a ClassFilter and MemberFilters to represent the list of excluded classes, methods and fields. A separate benefit is the addition of a separate entry points filter to the Profile class. Prior to this commit, many Transformers re-used the excluded method filter to find entry points, which is less precise (many of the excluded methods in 550 are not entry points). Support for filtering methods by owner and descriptor in addition to name allows the DEFAULT_PUBLIC_CTOR_CLASSES Set in VisibilityTransformer to be combined with the entry points filter. In the future it might be desirable to split the excluded method set into three separate sets: - One to represent methods that can't be renamed. - One to represent methods whose signature can't be changed. - One to represent methods that can't be removed. Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
if (partition == null || !TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, partition)) {
continue
}
val type = Type.getType(insn.desc)
val argTypes = type.argumentTypes
val newArgTypes = mutableListOf<Type>()
for ((j, argType) in argTypes.withIndex()) {
if (argType.sort in INT_SORTS && ArgRef(partition, j) !in retainedArgs) {
val value = frame.getStack(stackSize - argTypes.size + j) as ConstSourceValue.Single
deadInsns.add(value.source)
} else {
newArgTypes.add(argType)
}
}
insn.desc = Type.getMethodDescriptor(type.returnType, *newArgTypes.toTypedArray())
}
deadInsns.forEach(method.instructions::remove)
return false
}
override fun postTransformMethod(
classPath: ClassPath,
library: Library,
clazz: ClassNode,
method: MethodNode
): Boolean {
// delete unused int args from the method itself
val partition = inheritedMethodSets[MemberRef(clazz, method)]!!
Replace TypedRemapper constants with a more flexible system The new system will make it easier to port the deobfuscator to different revisions. There are two main changes: - The addition of a Profile class, which contains a list of excluded classes, methods and fields, and the maximum obfuscated name length. It is passed to Transformers that require it with dependency injection. - New ClassFilter and MemberFilter infrastructure. The MemberFilter class adds support for filtering fields and methods based on the owner and descriptor, in addition to the name. This makes the filters more precise than the previous system. It also supports globs, which makes it easy to filter whole groups of classes, fields and methods in one go. The Profile class uses a ClassFilter and MemberFilters to represent the list of excluded classes, methods and fields. A separate benefit is the addition of a separate entry points filter to the Profile class. Prior to this commit, many Transformers re-used the excluded method filter to find entry points, which is less precise (many of the excluded methods in 550 are not entry points). Support for filtering methods by owner and descriptor in addition to name allows the DEFAULT_PUBLIC_CTOR_CLASSES Set in VisibilityTransformer to be combined with the entry points filter. In the future it might be desirable to split the excluded method set into three separate sets: - One to represent methods that can't be renamed. - One to represent methods whose signature can't be changed. - One to represent methods that can't be removed. Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
if (!TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, partition)) {
return false
}
val argTypes = Type.getType(method.desc).argumentTypes
for ((i, argType) in argTypes.withIndex().reversed()) {
if (argType.sort in INT_SORTS && ArgRef(partition, i) !in retainedArgs) {
method.removeArgument(i)
deletedArgs++
}
}
return false
}
override fun postTransform(classPath: ClassPath) {
logger.info { "Removed $deletedArgs unused arguments" }
}
private companion object {
private val logger = InlineLogger()
private val INT_SORTS = setOf(
Type.BOOLEAN,
Type.BYTE,
Type.SHORT,
Type.INT,
Type.CHAR
)
private fun createLocalToArgMap(method: MethodNode): Map<Int, Int> {
val argTypes = Type.getType(method.desc).argumentTypes
val map = mutableMapOf<Int, Int>()
var localIndex = 0
if (method.access and Opcodes.ACC_STATIC == 0) {
localIndex++
}
for ((argIndex, t) in argTypes.withIndex()) {
map[localIndex] = argIndex
localIndex += t.size
}
return map
}
}
}