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>
Graham 5 years ago
parent f4314ca3cb
commit 5af43165ff
  1. 5
      asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt
  2. 55
      asm/src/main/java/dev/openrs2/asm/filter/Glob.kt
  3. 9
      asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt
  4. 24
      asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt
  5. 11
      asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt
  6. 9
      asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt
  7. 9
      asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt
  8. 9
      deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt
  9. 3
      deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt
  10. 45
      deob/src/main/java/dev/openrs2/deob/Profile.kt
  11. 47
      deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt
  12. 5
      deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt
  13. 86
      deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt
  14. 41
      deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt
  15. 6
      deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt
  16. 7
      deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt
  17. 12
      deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt
  18. 14
      deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt
  19. 31
      deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt

@ -0,0 +1,5 @@
package dev.openrs2.asm.filter
interface ClassFilter {
fun matches(name: String): Boolean
}

@ -0,0 +1,55 @@
package dev.openrs2.asm.filter
object Glob {
fun compile(pattern: String): Regex {
return compile(pattern, className = false)
}
fun compileClass(pattern: String): Regex {
return compile(pattern, className = true)
}
private fun compile(pattern: String, className: Boolean): Regex {
val regex = StringBuilder()
var star = false
var escape = false
for (ch in pattern) {
check(!star || !escape)
if (star) {
star = false
if (ch == '*') {
regex.append(".*")
continue
}
regex.append("[^/]*")
} else if (escape) {
regex.append(Regex.escape(ch.toString()))
continue
}
when (ch) {
'*' -> if (className) {
star = true
} else {
regex.append(".*")
}
'\\' -> escape = true
else -> regex.append(Regex.escape(ch.toString()))
}
}
if (star) {
regex.append(".*")
}
require(!escape) {
"Unterminated escape sequence"
}
return Regex(regex.toString())
}
}

@ -0,0 +1,9 @@
package dev.openrs2.asm.filter
class GlobClassFilter(vararg patterns: String) : ClassFilter {
private val patterns = patterns.map(Glob::compileClass).toList()
override fun matches(name: String): Boolean {
return patterns.any { it.matches(name) }
}
}

@ -0,0 +1,24 @@
package dev.openrs2.asm.filter
import dev.openrs2.asm.MemberRef
class GlobMemberFilter(vararg patterns: String) : MemberFilter {
private data class CompiledPattern(val owner: Regex, val name: Regex, val desc: Regex)
private val patterns = patterns.map(::compile).toList()
override fun matches(owner: String, name: String, desc: String): Boolean {
return patterns.any { it.owner.matches(owner) && it.name.matches(name) && it.desc.matches(desc) }
}
companion object {
private fun compile(pattern: String): CompiledPattern {
val ref = MemberRef.fromString(pattern)
return CompiledPattern(
Glob.compileClass(ref.owner),
Glob.compile(ref.name),
Glob.compile(ref.desc)
)
}
}
}

@ -0,0 +1,11 @@
package dev.openrs2.asm.filter
import dev.openrs2.asm.MemberRef
interface MemberFilter {
fun matches(owner: String, name: String, desc: String): Boolean
fun matches(member: MemberRef): Boolean {
return matches(member.owner, member.name, member.desc)
}
}

@ -0,0 +1,9 @@
package dev.openrs2.asm.filter
class UnionClassFilter(vararg filters: ClassFilter) : ClassFilter {
private val filters = filters.toList()
override fun matches(name: String): Boolean {
return filters.any { it.matches(name) }
}
}

@ -0,0 +1,9 @@
package dev.openrs2.asm.filter
class UnionMemberFilter(vararg filters: MemberFilter) : MemberFilter {
private val filters = filters.toList()
override fun matches(owner: String, name: String, desc: String): Boolean {
return filters.any { it.matches(owner, name, desc) }
}
}

@ -15,6 +15,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class Deobfuscator @Inject constructor( class Deobfuscator @Inject constructor(
private val profile: Profile,
@DeobfuscatorQualifier private val transformers: Set<@JvmSuppressWildcards Transformer> @DeobfuscatorQualifier private val transformers: Set<@JvmSuppressWildcards Transformer>
) { ) {
fun run(input: Path, output: Path) { fun run(input: Path, output: Path) {
@ -56,10 +57,10 @@ class Deobfuscator @Inject constructor(
// prefix remaining loader/unpacker classes (to avoid conflicts when we rename in the same classpath as the client) // prefix remaining loader/unpacker classes (to avoid conflicts when we rename in the same classpath as the client)
logger.info { "Prefixing loader and unpackclass class names" } logger.info { "Prefixing loader and unpackclass class names" }
loader.remap(PrefixRemapper.create(loader, "loader_")) loader.remap(PrefixRemapper.create(loader, "loader_", profile.excludedClasses))
glLoader.remap(PrefixRemapper.create(glLoader, "loader_")) glLoader.remap(PrefixRemapper.create(glLoader, "loader_", profile.excludedClasses))
unpackClass.remap(PrefixRemapper.create(unpackClass, "unpackclass_")) unpackClass.remap(PrefixRemapper.create(unpackClass, "unpackclass_", profile.excludedClasses))
glUnpackClass.remap(PrefixRemapper.create(glUnpackClass, "unpackclass_")) glUnpackClass.remap(PrefixRemapper.create(glUnpackClass, "unpackclass_", profile.excludedClasses))
// bundle libraries together into a common classpath // bundle libraries together into a common classpath
val runtime = ClassLoader.getPlatformClassLoader() val runtime = ClassLoader.getPlatformClassLoader()

@ -40,6 +40,9 @@ object DeobfuscatorModule : AbstractModule() {
install(BundlerModule) install(BundlerModule)
install(DeobfuscatorMapModule) install(DeobfuscatorMapModule)
bind(Profile::class.java)
.toInstance(Profile.BUILD_550)
val binder = Multibinder.newSetBinder(binder(), Transformer::class.java, DeobfuscatorQualifier::class.java) val binder = Multibinder.newSetBinder(binder(), Transformer::class.java, DeobfuscatorQualifier::class.java)
binder.addBinding().to(OriginalPcSaveTransformer::class.java) binder.addBinding().to(OriginalPcSaveTransformer::class.java)
binder.addBinding().to(OriginalNameTransformer::class.java) binder.addBinding().to(OriginalNameTransformer::class.java)

@ -0,0 +1,45 @@
package dev.openrs2.deob
import dev.openrs2.asm.filter.ClassFilter
import dev.openrs2.asm.filter.GlobClassFilter
import dev.openrs2.asm.filter.GlobMemberFilter
import dev.openrs2.asm.filter.MemberFilter
class Profile(
val excludedClasses: ClassFilter,
val excludedMethods: MemberFilter,
val excludedFields: MemberFilter,
val entryPoints: MemberFilter,
val maxObfuscatedNameLen: Int
) {
companion object {
val BUILD_550 = Profile(
excludedClasses = GlobClassFilter(
"client",
"jagex3/jagmisc/jagmisc",
"loader",
"unpack",
"unpackclass"
),
excludedMethods = GlobMemberFilter(
"**.<clinit> *",
"**.<init> *",
"**.main *",
"**.providesignlink *",
"**.quit *"
),
excludedFields = GlobMemberFilter(
"**.cache *"
),
entryPoints = GlobMemberFilter(
"**.<clinit> *",
"**.main *",
"**.providesignlink *",
"client.<init> *",
"loader.<init> *",
"unpackclass.<init> *"
),
maxObfuscatedNameLen = 2
)
}
}

@ -0,0 +1,47 @@
package dev.openrs2.deob.filter
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.ClassForNameUtils
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.filter.MemberFilter
import dev.openrs2.asm.hasCode
import org.objectweb.asm.tree.MethodNode
class ReflectedConstructorFilter private constructor(private val classes: Set<String>) : MemberFilter {
override fun matches(owner: String, name: String, desc: String): Boolean {
return classes.contains(owner) && name == "<init>"
}
companion object {
private val logger = InlineLogger()
fun create(classPath: ClassPath): MemberFilter {
val classes = mutableSetOf<String>()
for (library in classPath.libraries) {
for (clazz in library) {
for (method in clazz.methods) {
processMethod(classPath, method, classes)
}
}
}
logger.info { "Identified constructors invoked with reflection $classes" }
return ReflectedConstructorFilter(classes)
}
private fun processMethod(classPath: ClassPath, method: MethodNode, classes: MutableSet<String>) {
if (!method.hasCode) {
return
}
for (name in ClassForNameUtils.findClassNames(method)) {
val clazz = classPath[name]
if (clazz != null && !clazz.dependency) {
classes.add(name)
}
}
}
}
}

@ -1,15 +1,16 @@
package dev.openrs2.deob.remap package dev.openrs2.deob.remap
import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.filter.ClassFilter
import org.objectweb.asm.commons.Remapper import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.commons.SimpleRemapper import org.objectweb.asm.commons.SimpleRemapper
object PrefixRemapper { object PrefixRemapper {
fun create(library: Library, prefix: String): Remapper { fun create(library: Library, prefix: String, excluded: ClassFilter): Remapper {
val mapping = mutableMapOf<String, String>() val mapping = mutableMapOf<String, String>()
for (clazz in library) { for (clazz in library) {
if (clazz.name in TypedRemapper.EXCLUDED_CLASSES) { if (excluded.matches(clazz.name)) {
mapping[clazz.name] = clazz.name mapping[clazz.name] = clazz.name
} else { } else {
mapping[clazz.name] = prefix + clazz.name mapping[clazz.name] = prefix + clazz.name

@ -5,6 +5,9 @@ import dev.openrs2.asm.MemberDesc
import dev.openrs2.asm.MemberRef import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassMetadata import dev.openrs2.asm.classpath.ClassMetadata
import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.filter.ClassFilter
import dev.openrs2.asm.filter.MemberFilter
import dev.openrs2.deob.Profile
import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.DisjointSet
import dev.openrs2.util.indefiniteArticle import dev.openrs2.util.indefiniteArticle
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
@ -31,57 +34,38 @@ class TypedRemapper private constructor(
companion object { companion object {
private val logger = InlineLogger() private val logger = InlineLogger()
val EXCLUDED_CLASSES = setOf(
"client",
"jagex3/jagmisc/jagmisc",
"loader",
"unpack",
"unpackclass"
)
val EXCLUDED_METHODS = setOf(
"<clinit>",
"<init>",
"main",
"providesignlink",
"quit"
)
val EXCLUDED_FIELDS = setOf(
"cache"
)
private val LIBRARY_PREFIX_REGEX = Regex("^(?:loader|unpackclass)_") private val LIBRARY_PREFIX_REGEX = Regex("^(?:loader|unpackclass)_")
private const val MAX_OBFUSCATED_NAME_LEN = 2
fun create(classPath: ClassPath): TypedRemapper { fun create(classPath: ClassPath, profile: Profile): TypedRemapper {
val inheritedFieldSets = classPath.createInheritedFieldSets() val inheritedFieldSets = classPath.createInheritedFieldSets()
val inheritedMethodSets = classPath.createInheritedMethodSets() val inheritedMethodSets = classPath.createInheritedMethodSets()
val classes = createClassMapping(classPath) val classes = createClassMapping(classPath, profile.excludedClasses)
val fields = createFieldMapping(classPath, inheritedFieldSets, classes) val fields = createFieldMapping(classPath, profile.excludedFields, inheritedFieldSets, classes)
val methods = createMethodMapping(classPath, inheritedMethodSets) val methods = createMethodMapping(classPath, profile.excludedMethods, inheritedMethodSets)
verifyMapping(classes) verifyMapping(classes, profile.maxObfuscatedNameLen)
verifyMemberMapping(fields) verifyMemberMapping(fields, profile.maxObfuscatedNameLen)
verifyMemberMapping(methods) verifyMemberMapping(methods, profile.maxObfuscatedNameLen)
return TypedRemapper(classes, fields, methods) return TypedRemapper(classes, fields, methods)
} }
private fun verifyMapping(mapping: Map<String, String>) { private fun verifyMapping(mapping: Map<String, String>, maxObfuscatedNameLen: Int) {
for ((key, value) in mapping) { for ((key, value) in mapping) {
verifyMapping(key, value) verifyMapping(key, value, maxObfuscatedNameLen)
} }
} }
private fun verifyMemberMapping(mapping: Map<MemberRef, String>) { private fun verifyMemberMapping(mapping: Map<MemberRef, String>, maxObfuscatedNameLen: Int) {
for ((key, value) in mapping) { for ((key, value) in mapping) {
verifyMapping(key.name, value) verifyMapping(key.name, value, maxObfuscatedNameLen)
} }
} }
private fun verifyMapping(name: String, mappedName: String) { private fun verifyMapping(name: String, mappedName: String, maxObfuscatedNameLen: Int) {
val originalName = name.replace(LIBRARY_PREFIX_REGEX, "") val originalName = name.replace(LIBRARY_PREFIX_REGEX, "")
if (originalName.length > MAX_OBFUSCATED_NAME_LEN && originalName != mappedName) { if (originalName.length > maxObfuscatedNameLen && originalName != mappedName) {
logger.warn { "Remapping probably unobfuscated name $originalName to $mappedName" } logger.warn { "Remapping probably unobfuscated name $originalName to $mappedName" }
} }
} }
@ -95,32 +79,33 @@ class TypedRemapper private constructor(
return prefix + separator + prefixes.merge(prefix, 1, Integer::sum) return prefix + separator + prefixes.merge(prefix, 1, Integer::sum)
} }
private fun createClassMapping(classPath: ClassPath): Map<String, String> { private fun createClassMapping(classPath: ClassPath, excludedClasses: ClassFilter): Map<String, String> {
val mapping = mutableMapOf<String, String>() val mapping = mutableMapOf<String, String>()
val prefixes = mutableMapOf<String, Int>() val prefixes = mutableMapOf<String, Int>()
for (clazz in classPath.libraryClasses) { for (clazz in classPath.libraryClasses) {
populateClassMapping(mapping, prefixes, clazz) populateClassMapping(excludedClasses, mapping, prefixes, clazz)
} }
return mapping return mapping
} }
private fun populateClassMapping( private fun populateClassMapping(
excludedClasses: ClassFilter,
mapping: MutableMap<String, String>, mapping: MutableMap<String, String>,
prefixes: MutableMap<String, Int>, prefixes: MutableMap<String, Int>,
clazz: ClassMetadata clazz: ClassMetadata
): String { ): String {
val name = clazz.name val name = clazz.name
if (mapping.containsKey(name) || !isClassRenamable(clazz)) { if (mapping.containsKey(name) || !isClassRenamable(clazz, excludedClasses)) {
return mapping.getOrDefault(name, name) return mapping.getOrDefault(name, name)
} }
val mappedName = generateClassName(mapping, prefixes, clazz) val mappedName = generateClassName(excludedClasses, mapping, prefixes, clazz)
mapping[name] = mappedName mapping[name] = mappedName
return mappedName return mappedName
} }
private fun isClassRenamable(clazz: ClassMetadata): Boolean { private fun isClassRenamable(clazz: ClassMetadata, excludedClasses: ClassFilter): Boolean {
if (clazz.name in EXCLUDED_CLASSES || clazz.dependency) { if (excludedClasses.matches(clazz.name) || clazz.dependency) {
return false return false
} }
@ -134,6 +119,7 @@ class TypedRemapper private constructor(
} }
private fun generateClassName( private fun generateClassName(
excludedClasses: ClassFilter,
mapping: MutableMap<String, String>, mapping: MutableMap<String, String>,
prefixes: MutableMap<String, Int>, prefixes: MutableMap<String, Int>,
clazz: ClassMetadata clazz: ClassMetadata
@ -143,7 +129,7 @@ class TypedRemapper private constructor(
val superClass = clazz.superClass val superClass = clazz.superClass
if (superClass != null && superClass.name != "java/lang/Object") { if (superClass != null && superClass.name != "java/lang/Object") {
var superName = populateClassMapping(mapping, prefixes, superClass) var superName = populateClassMapping(excludedClasses, mapping, prefixes, superClass)
superName = superName.substring(superName.lastIndexOf('/') + 1) superName = superName.substring(superName.lastIndexOf('/') + 1)
mappedName += generateName(prefixes, superName + "_Sub") mappedName += generateName(prefixes, superName + "_Sub")
} else if (clazz.`interface`) { } else if (clazz.`interface`) {
@ -157,6 +143,7 @@ class TypedRemapper private constructor(
private fun createFieldMapping( private fun createFieldMapping(
classPath: ClassPath, classPath: ClassPath,
excludedFields: MemberFilter,
disjointSet: DisjointSet<MemberRef>, disjointSet: DisjointSet<MemberRef>,
classMapping: Map<String, String> classMapping: Map<String, String>
): Map<MemberRef, String> { ): Map<MemberRef, String> {
@ -164,7 +151,7 @@ class TypedRemapper private constructor(
val prefixes = mutableMapOf<String, Int>() val prefixes = mutableMapOf<String, Int>()
for (partition in disjointSet) { for (partition in disjointSet) {
if (!isFieldRenamable(classPath, partition)) { if (!isFieldRenamable(classPath, excludedFields, partition)) {
continue continue
} }
@ -178,11 +165,15 @@ class TypedRemapper private constructor(
return mapping return mapping
} }
private fun isFieldRenamable(classPath: ClassPath, partition: DisjointSet.Partition<MemberRef>): Boolean { private fun isFieldRenamable(
classPath: ClassPath,
excludedFields: MemberFilter,
partition: DisjointSet.Partition<MemberRef>
): Boolean {
for (field in partition) { for (field in partition) {
val clazz = classPath[field.owner]!! val clazz = classPath[field.owner]!!
if (field.name in EXCLUDED_FIELDS || clazz.dependency) { if (excludedFields.matches(field) || clazz.dependency) {
return false return false
} }
} }
@ -221,13 +212,14 @@ class TypedRemapper private constructor(
private fun createMethodMapping( private fun createMethodMapping(
classPath: ClassPath, classPath: ClassPath,
excludedMethods: MemberFilter,
disjointSet: DisjointSet<MemberRef> disjointSet: DisjointSet<MemberRef>
): Map<MemberRef, String> { ): Map<MemberRef, String> {
val mapping = mutableMapOf<MemberRef, String>() val mapping = mutableMapOf<MemberRef, String>()
var id = 0 var id = 0
for (partition in disjointSet) { for (partition in disjointSet) {
if (!isMethodRenamable(classPath, partition)) { if (!isMethodRenamable(classPath, excludedMethods, partition)) {
continue continue
} }
@ -240,11 +232,15 @@ class TypedRemapper private constructor(
return mapping return mapping
} }
fun isMethodRenamable(classPath: ClassPath, partition: DisjointSet.Partition<MemberRef>): Boolean { fun isMethodRenamable(
classPath: ClassPath,
excludedMethods: MemberFilter,
partition: DisjointSet.Partition<MemberRef>
): Boolean {
for (method in partition) { for (method in partition) {
val clazz = classPath[method.owner]!! val clazz = classPath[method.owner]!!
if (method.name in EXCLUDED_METHODS || clazz.dependency) { if (excludedMethods.matches(method) || clazz.dependency) {
return false return false
} }

@ -6,6 +6,8 @@ import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.copy import dev.openrs2.asm.copy
import dev.openrs2.asm.deleteExpression import dev.openrs2.asm.deleteExpression
import dev.openrs2.asm.filter.MemberFilter
import dev.openrs2.asm.filter.UnionMemberFilter
import dev.openrs2.asm.hasCode import dev.openrs2.asm.hasCode
import dev.openrs2.asm.intConstant import dev.openrs2.asm.intConstant
import dev.openrs2.asm.isPure import dev.openrs2.asm.isPure
@ -15,12 +17,13 @@ import dev.openrs2.asm.stackMetadata
import dev.openrs2.asm.toAbstractInsnNode import dev.openrs2.asm.toAbstractInsnNode
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.ArgRef import dev.openrs2.deob.ArgRef
import dev.openrs2.deob.Profile
import dev.openrs2.deob.analysis.IntBranch import dev.openrs2.deob.analysis.IntBranch
import dev.openrs2.deob.analysis.IntBranchResult.ALWAYS_TAKEN import dev.openrs2.deob.analysis.IntBranchResult.ALWAYS_TAKEN
import dev.openrs2.deob.analysis.IntBranchResult.NEVER_TAKEN import dev.openrs2.deob.analysis.IntBranchResult.NEVER_TAKEN
import dev.openrs2.deob.analysis.IntInterpreter import dev.openrs2.deob.analysis.IntInterpreter
import dev.openrs2.deob.analysis.IntValueSet import dev.openrs2.deob.analysis.IntValueSet
import dev.openrs2.deob.remap.TypedRemapper import dev.openrs2.deob.filter.ReflectedConstructorFilter
import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.DisjointSet
import dev.openrs2.util.collect.removeFirstOrNull import dev.openrs2.util.collect.removeFirstOrNull
import org.objectweb.asm.Opcodes.GOTO import org.objectweb.asm.Opcodes.GOTO
@ -43,14 +46,16 @@ import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.analysis.Analyzer import org.objectweb.asm.tree.analysis.Analyzer
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class ConstantArgTransformer : Transformer() { class ConstantArgTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private val pendingMethods = LinkedHashSet<MemberRef>() private val pendingMethods = LinkedHashSet<MemberRef>()
private val arglessMethods = mutableSetOf<DisjointSet.Partition<MemberRef>>() private val arglessMethods = mutableSetOf<DisjointSet.Partition<MemberRef>>()
private val argValues = mutableMapOf<ArgRef, IntValueSet>() private val argValues = mutableMapOf<ArgRef, IntValueSet>()
private lateinit var inheritedMethodSets: DisjointSet<MemberRef> private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private lateinit var entryPoints: MemberFilter
private var branchesSimplified = 0 private var branchesSimplified = 0
private var constantsInlined = 0 private var constantsInlined = 0
@ -59,6 +64,7 @@ class ConstantArgTransformer : Transformer() {
arglessMethods.clear() arglessMethods.clear()
argValues.clear() argValues.clear()
inheritedMethodSets = classPath.createInheritedMethodSets() inheritedMethodSets = classPath.createInheritedMethodSets()
entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
branchesSimplified = 0 branchesSimplified = 0
constantsInlined = 0 constantsInlined = 0
@ -72,29 +78,24 @@ class ConstantArgTransformer : Transformer() {
private fun queueEntryPoints(classPath: ClassPath) { private fun queueEntryPoints(classPath: ClassPath) {
for (partition in inheritedMethodSets) { for (partition in inheritedMethodSets) {
/* if (isEntryPoint(classPath, partition)) {
* The set of non-renamable methods roughly matches up with the
* methods we want to consider as entry points. It includes methods
* which we override, which may be called by the standard library),
* the main() method (called by the JVM), providesignlink() (called
* with reflection) and <clinit> (called by the JVM).
*
* It isn't perfect - it counts every <init> method as an entry
* point, but strictly speaking we only need to count <init>
* methods invoked with reflection as entry points (like
* VisibilityTransformer). However, it makes no difference in this
* case, as the obfuscator does not add dummy constant arguments to
* constructors.
*
* It also counts native methods as an entry point. This isn't
* problematic as they don't have an InsnList, so we skip them.
*/
if (!TypedRemapper.isMethodRenamable(classPath, partition)) {
pendingMethods.addAll(partition) pendingMethods.addAll(partition)
} }
} }
} }
private fun isEntryPoint(classPath: ClassPath, partition: DisjointSet.Partition<MemberRef>): Boolean {
for (method in partition) {
val clazz = classPath[method.owner]!!
if (entryPoints.matches(method) || clazz.dependency) {
return true
}
}
return false
}
private fun analyzeMethod(classPath: ClassPath, ref: MemberRef) { private fun analyzeMethod(classPath: ClassPath, ref: MemberRef) {
// find ClassNode/MethodNode // find ClassNode/MethodNode
val owner = classPath.getClassNode(ref.owner) ?: return val owner = classPath.getClassNode(ref.owner) ?: return

@ -2,12 +2,14 @@ package dev.openrs2.deob.transform
import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.Profile
import dev.openrs2.deob.remap.TypedRemapper import dev.openrs2.deob.remap.TypedRemapper
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class RemapTransformer : Transformer() { class RemapTransformer @Inject constructor(private val profile: Profile) : Transformer() {
override fun preTransform(classPath: ClassPath) { override fun preTransform(classPath: ClassPath) {
classPath.remap(TypedRemapper.create(classPath)) classPath.remap(TypedRemapper.create(classPath, profile))
} }
} }

@ -6,7 +6,7 @@ import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.remap.TypedRemapper import dev.openrs2.deob.Profile
import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.ClassNode
@ -18,11 +18,12 @@ import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LabelNode import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.MethodNode
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.max import kotlin.math.max
@Singleton @Singleton
class StaticScramblingTransformer : Transformer() { class StaticScramblingTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private class FieldSet(val owner: ClassNode, val fields: List<FieldNode>, val clinit: MethodNode?) { private class FieldSet(val owner: ClassNode, val fields: List<FieldNode>, val clinit: MethodNode?) {
val dependencies = clinit?.instructions val dependencies = clinit?.instructions
?.filterIsInstance<FieldInsnNode>() ?.filterIsInstance<FieldInsnNode>()
@ -165,7 +166,7 @@ class StaticScramblingTransformer : Transformer() {
return@removeIf false return@removeIf false
} else if (method.access and Opcodes.ACC_NATIVE != 0) { } else if (method.access and Opcodes.ACC_NATIVE != 0) {
return@removeIf false return@removeIf false
} else if (method.name in TypedRemapper.EXCLUDED_METHODS) { } else if (profile.excludedMethods.matches(clazz.name, method.name, method.desc)) {
return@removeIf false return@removeIf false
} }

@ -8,6 +8,7 @@ import dev.openrs2.asm.hasCode
import dev.openrs2.asm.removeArgument import dev.openrs2.asm.removeArgument
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.ArgRef import dev.openrs2.deob.ArgRef
import dev.openrs2.deob.Profile
import dev.openrs2.deob.analysis.ConstSourceInterpreter import dev.openrs2.deob.analysis.ConstSourceInterpreter
import dev.openrs2.deob.analysis.ConstSourceValue import dev.openrs2.deob.analysis.ConstSourceValue
import dev.openrs2.deob.remap.TypedRemapper import dev.openrs2.deob.remap.TypedRemapper
@ -20,10 +21,11 @@ import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.VarInsnNode import org.objectweb.asm.tree.VarInsnNode
import org.objectweb.asm.tree.analysis.Analyzer import org.objectweb.asm.tree.analysis.Analyzer
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class UnusedArgTransformer : Transformer() { class UnusedArgTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private val retainedArgs = mutableSetOf<ArgRef>() private val retainedArgs = mutableSetOf<ArgRef>()
private lateinit var inheritedMethodSets: DisjointSet<MemberRef> private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private var deletedArgs = 0 private var deletedArgs = 0
@ -71,7 +73,9 @@ class UnusedArgTransformer : Transformer() {
} }
is MethodInsnNode -> { is MethodInsnNode -> {
val invokePartition = inheritedMethodSets[MemberRef(insn)] val invokePartition = inheritedMethodSets[MemberRef(insn)]
if (invokePartition == null || !TypedRemapper.isMethodRenamable(classPath, invokePartition)) { if (invokePartition == null) {
continue@frame
} else if (!TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, invokePartition)) {
continue@frame continue@frame
} }
@ -115,7 +119,7 @@ class UnusedArgTransformer : Transformer() {
} }
val partition = inheritedMethodSets[MemberRef(insn)] val partition = inheritedMethodSets[MemberRef(insn)]
if (partition == null || !TypedRemapper.isMethodRenamable(classPath, partition)) { if (partition == null || !TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, partition)) {
continue continue
} }
@ -148,7 +152,7 @@ class UnusedArgTransformer : Transformer() {
): Boolean { ): Boolean {
// delete unused int args from the method itself // delete unused int args from the method itself
val partition = inheritedMethodSets[MemberRef(clazz, method)]!! val partition = inheritedMethodSets[MemberRef(clazz, method)]!!
if (!TypedRemapper.isMethodRenamable(classPath, partition)) { if (!TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, partition)) {
return false return false
} }

@ -6,23 +6,29 @@ import com.google.common.collect.Multimap
import dev.openrs2.asm.MemberRef import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.filter.MemberFilter
import dev.openrs2.asm.filter.UnionMemberFilter
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.remap.TypedRemapper import dev.openrs2.deob.Profile
import dev.openrs2.deob.filter.ReflectedConstructorFilter
import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.DisjointSet
import dev.openrs2.util.collect.removeFirst import dev.openrs2.util.collect.removeFirst
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.MethodNode
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class UnusedMethodTransformer : Transformer() { class UnusedMethodTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private lateinit var inheritedMethodSets: DisjointSet<MemberRef> private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private lateinit var excludedMethods: MemberFilter
private val methodReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, MemberRef>() private val methodReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, MemberRef>()
override fun preTransform(classPath: ClassPath) { override fun preTransform(classPath: ClassPath) {
inheritedMethodSets = classPath.createInheritedMethodSets() inheritedMethodSets = classPath.createInheritedMethodSets()
excludedMethods = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
methodReferences.clear() methodReferences.clear()
} }
@ -44,7 +50,9 @@ class UnusedMethodTransformer : Transformer() {
val methods = clazz.methods.iterator() val methods = clazz.methods.iterator()
for (method in methods) { for (method in methods) {
if (method.access and Opcodes.ACC_NATIVE != 0 || method.name in TypedRemapper.EXCLUDED_METHODS) { if (method.access and Opcodes.ACC_NATIVE != 0) {
continue
} else if (excludedMethods.matches(clazz.name, method.name, method.desc)) {
continue continue
} }

@ -3,12 +3,15 @@ package dev.openrs2.deob.transform
import com.github.michaelbull.logging.InlineLogger import com.github.michaelbull.logging.InlineLogger
import com.google.common.collect.HashMultimap import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap import com.google.common.collect.Multimap
import dev.openrs2.asm.ClassForNameUtils
import dev.openrs2.asm.MemberDesc import dev.openrs2.asm.MemberDesc
import dev.openrs2.asm.MemberRef import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.filter.MemberFilter
import dev.openrs2.asm.filter.UnionMemberFilter
import dev.openrs2.asm.transform.Transformer import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.Profile
import dev.openrs2.deob.filter.ReflectedConstructorFilter
import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type import org.objectweb.asm.Type
@ -16,33 +19,26 @@ import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.MethodNode
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class VisibilityTransformer : Transformer() { class VisibilityTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private lateinit var inheritedFieldSets: DisjointSet<MemberRef> private lateinit var inheritedFieldSets: DisjointSet<MemberRef>
private lateinit var inheritedMethodSets: DisjointSet<MemberRef> private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private lateinit var entryPoints: MemberFilter
private val fieldReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, String>() private val fieldReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, String>()
private val methodReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, String>() private val methodReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, String>()
private val publicCtorClasses = mutableSetOf<String>()
override fun preTransform(classPath: ClassPath) { override fun preTransform(classPath: ClassPath) {
inheritedFieldSets = classPath.createInheritedFieldSets() inheritedFieldSets = classPath.createInheritedFieldSets()
inheritedMethodSets = classPath.createInheritedMethodSets() inheritedMethodSets = classPath.createInheritedMethodSets()
entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
fieldReferences.clear() fieldReferences.clear()
methodReferences.clear() methodReferences.clear()
publicCtorClasses.clear()
publicCtorClasses.addAll(DEFAULT_PUBLIC_CTOR_CLASSES)
} }
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean {
for (name in ClassForNameUtils.findClassNames(method)) {
val loadedClass = classPath[name]
if (loadedClass != null && !loadedClass.dependency) {
publicCtorClasses.add(name)
}
}
for (insn in method.instructions) { for (insn in method.instructions) {
when (insn) { when (insn) {
is FieldInsnNode -> addReference(fieldReferences, inheritedFieldSets, MemberRef(insn), clazz.name) is FieldInsnNode -> addReference(fieldReferences, inheritedFieldSets, MemberRef(insn), clazz.name)
@ -71,11 +67,8 @@ class VisibilityTransformer : Transformer() {
if (member.name == "<clinit>") { if (member.name == "<clinit>") {
// the visibility flags don't really matter - we use package-private to match javac // the visibility flags don't really matter - we use package-private to match javac
return 0 return 0
} else if (member.owner in publicCtorClasses && member.name == "<init>") { } else if (entryPoints.matches(member)) {
// constructors invoked with reflection (including applets) must be public // entry points must be public
return Opcodes.ACC_PUBLIC
} else if (member.name in PUBLIC_METHODS) {
// methods invoked with reflection must also be public
return Opcodes.ACC_PUBLIC return Opcodes.ACC_PUBLIC
} }
} }
@ -106,8 +99,6 @@ class VisibilityTransformer : Transformer() {
} }
override fun postTransform(classPath: ClassPath) { override fun postTransform(classPath: ClassPath) {
logger.info { "Identified constructors invoked with reflection $publicCtorClasses" }
var classesChanged = 0 var classesChanged = 0
var fieldsChanged = 0 var fieldsChanged = 0
var methodsChanged = 0 var methodsChanged = 0
@ -166,8 +157,6 @@ class VisibilityTransformer : Transformer() {
companion object { companion object {
private val logger = InlineLogger() private val logger = InlineLogger()
private const val VISIBILITY_FLAGS = Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE private const val VISIBILITY_FLAGS = Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE
private val DEFAULT_PUBLIC_CTOR_CLASSES = setOf("client", "loader", "unpackclass")
private val PUBLIC_METHODS = setOf("main", "providesignlink")
private fun addReference( private fun addReference(
references: Multimap<DisjointSet.Partition<MemberRef>, String>, references: Multimap<DisjointSet.Partition<MemberRef>, String>,

Loading…
Cancel
Save