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
class Deobfuscator @Inject constructor(
private val profile: Profile,
@DeobfuscatorQualifier private val transformers: Set<@JvmSuppressWildcards Transformer>
) {
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)
logger.info { "Prefixing loader and unpackclass class names" }
loader.remap(PrefixRemapper.create(loader, "loader_"))
glLoader.remap(PrefixRemapper.create(glLoader, "loader_"))
unpackClass.remap(PrefixRemapper.create(unpackClass, "unpackclass_"))
glUnpackClass.remap(PrefixRemapper.create(glUnpackClass, "unpackclass_"))
loader.remap(PrefixRemapper.create(loader, "loader_", profile.excludedClasses))
glLoader.remap(PrefixRemapper.create(glLoader, "loader_", profile.excludedClasses))
unpackClass.remap(PrefixRemapper.create(unpackClass, "unpackclass_", profile.excludedClasses))
glUnpackClass.remap(PrefixRemapper.create(glUnpackClass, "unpackclass_", profile.excludedClasses))
// bundle libraries together into a common classpath
val runtime = ClassLoader.getPlatformClassLoader()

@ -40,6 +40,9 @@ object DeobfuscatorModule : AbstractModule() {
install(BundlerModule)
install(DeobfuscatorMapModule)
bind(Profile::class.java)
.toInstance(Profile.BUILD_550)
val binder = Multibinder.newSetBinder(binder(), Transformer::class.java, DeobfuscatorQualifier::class.java)
binder.addBinding().to(OriginalPcSaveTransformer::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
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.filter.ClassFilter
import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.commons.SimpleRemapper
object PrefixRemapper {
fun create(library: Library, prefix: String): Remapper {
fun create(library: Library, prefix: String, excluded: ClassFilter): Remapper {
val mapping = mutableMapOf<String, String>()
for (clazz in library) {
if (clazz.name in TypedRemapper.EXCLUDED_CLASSES) {
if (excluded.matches(clazz.name)) {
mapping[clazz.name] = clazz.name
} else {
mapping[clazz.name] = prefix + clazz.name

@ -5,6 +5,9 @@ import dev.openrs2.asm.MemberDesc
import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassMetadata
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.indefiniteArticle
import org.objectweb.asm.Opcodes
@ -31,57 +34,38 @@ class TypedRemapper private constructor(
companion object {
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 const val MAX_OBFUSCATED_NAME_LEN = 2
fun create(classPath: ClassPath): TypedRemapper {
fun create(classPath: ClassPath, profile: Profile): TypedRemapper {
val inheritedFieldSets = classPath.createInheritedFieldSets()
val inheritedMethodSets = classPath.createInheritedMethodSets()
val classes = createClassMapping(classPath)
val fields = createFieldMapping(classPath, inheritedFieldSets, classes)
val methods = createMethodMapping(classPath, inheritedMethodSets)
val classes = createClassMapping(classPath, profile.excludedClasses)
val fields = createFieldMapping(classPath, profile.excludedFields, inheritedFieldSets, classes)
val methods = createMethodMapping(classPath, profile.excludedMethods, inheritedMethodSets)
verifyMapping(classes)
verifyMemberMapping(fields)
verifyMemberMapping(methods)
verifyMapping(classes, profile.maxObfuscatedNameLen)
verifyMemberMapping(fields, profile.maxObfuscatedNameLen)
verifyMemberMapping(methods, profile.maxObfuscatedNameLen)
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) {
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) {
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, "")
if (originalName.length > MAX_OBFUSCATED_NAME_LEN && originalName != mappedName) {
if (originalName.length > maxObfuscatedNameLen && originalName != 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)
}
private fun createClassMapping(classPath: ClassPath): Map<String, String> {
private fun createClassMapping(classPath: ClassPath, excludedClasses: ClassFilter): Map<String, String> {
val mapping = mutableMapOf<String, String>()
val prefixes = mutableMapOf<String, Int>()
for (clazz in classPath.libraryClasses) {
populateClassMapping(mapping, prefixes, clazz)
populateClassMapping(excludedClasses, mapping, prefixes, clazz)
}
return mapping
}
private fun populateClassMapping(
excludedClasses: ClassFilter,
mapping: MutableMap<String, String>,
prefixes: MutableMap<String, Int>,
clazz: ClassMetadata
): String {
val name = clazz.name
if (mapping.containsKey(name) || !isClassRenamable(clazz)) {
if (mapping.containsKey(name) || !isClassRenamable(clazz, excludedClasses)) {
return mapping.getOrDefault(name, name)
}
val mappedName = generateClassName(mapping, prefixes, clazz)
val mappedName = generateClassName(excludedClasses, mapping, prefixes, clazz)
mapping[name] = mappedName
return mappedName
}
private fun isClassRenamable(clazz: ClassMetadata): Boolean {
if (clazz.name in EXCLUDED_CLASSES || clazz.dependency) {
private fun isClassRenamable(clazz: ClassMetadata, excludedClasses: ClassFilter): Boolean {
if (excludedClasses.matches(clazz.name) || clazz.dependency) {
return false
}
@ -134,6 +119,7 @@ class TypedRemapper private constructor(
}
private fun generateClassName(
excludedClasses: ClassFilter,
mapping: MutableMap<String, String>,
prefixes: MutableMap<String, Int>,
clazz: ClassMetadata
@ -143,7 +129,7 @@ class TypedRemapper private constructor(
val superClass = clazz.superClass
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)
mappedName += generateName(prefixes, superName + "_Sub")
} else if (clazz.`interface`) {
@ -157,6 +143,7 @@ class TypedRemapper private constructor(
private fun createFieldMapping(
classPath: ClassPath,
excludedFields: MemberFilter,
disjointSet: DisjointSet<MemberRef>,
classMapping: Map<String, String>
): Map<MemberRef, String> {
@ -164,7 +151,7 @@ class TypedRemapper private constructor(
val prefixes = mutableMapOf<String, Int>()
for (partition in disjointSet) {
if (!isFieldRenamable(classPath, partition)) {
if (!isFieldRenamable(classPath, excludedFields, partition)) {
continue
}
@ -178,11 +165,15 @@ class TypedRemapper private constructor(
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) {
val clazz = classPath[field.owner]!!
if (field.name in EXCLUDED_FIELDS || clazz.dependency) {
if (excludedFields.matches(field) || clazz.dependency) {
return false
}
}
@ -221,13 +212,14 @@ class TypedRemapper private constructor(
private fun createMethodMapping(
classPath: ClassPath,
excludedMethods: MemberFilter,
disjointSet: DisjointSet<MemberRef>
): Map<MemberRef, String> {
val mapping = mutableMapOf<MemberRef, String>()
var id = 0
for (partition in disjointSet) {
if (!isMethodRenamable(classPath, partition)) {
if (!isMethodRenamable(classPath, excludedMethods, partition)) {
continue
}
@ -240,11 +232,15 @@ class TypedRemapper private constructor(
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) {
val clazz = classPath[method.owner]!!
if (method.name in EXCLUDED_METHODS || clazz.dependency) {
if (excludedMethods.matches(method) || clazz.dependency) {
return false
}

@ -6,6 +6,8 @@ import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.copy
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.intConstant
import dev.openrs2.asm.isPure
@ -15,12 +17,13 @@ import dev.openrs2.asm.stackMetadata
import dev.openrs2.asm.toAbstractInsnNode
import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.ArgRef
import dev.openrs2.deob.Profile
import dev.openrs2.deob.analysis.IntBranch
import dev.openrs2.deob.analysis.IntBranchResult.ALWAYS_TAKEN
import dev.openrs2.deob.analysis.IntBranchResult.NEVER_TAKEN
import dev.openrs2.deob.analysis.IntInterpreter
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.removeFirstOrNull
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.MethodNode
import org.objectweb.asm.tree.analysis.Analyzer
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ConstantArgTransformer : Transformer() {
class ConstantArgTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private val pendingMethods = LinkedHashSet<MemberRef>()
private val arglessMethods = mutableSetOf<DisjointSet.Partition<MemberRef>>()
private val argValues = mutableMapOf<ArgRef, IntValueSet>()
private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private lateinit var entryPoints: MemberFilter
private var branchesSimplified = 0
private var constantsInlined = 0
@ -59,6 +64,7 @@ class ConstantArgTransformer : Transformer() {
arglessMethods.clear()
argValues.clear()
inheritedMethodSets = classPath.createInheritedMethodSets()
entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
branchesSimplified = 0
constantsInlined = 0
@ -72,29 +78,24 @@ class ConstantArgTransformer : Transformer() {
private fun queueEntryPoints(classPath: ClassPath) {
for (partition in inheritedMethodSets) {
/*
* 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)) {
if (isEntryPoint(classPath, 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) {
// find ClassNode/MethodNode
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.transform.Transformer
import dev.openrs2.deob.Profile
import dev.openrs2.deob.remap.TypedRemapper
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RemapTransformer : Transformer() {
class RemapTransformer @Inject constructor(private val profile: Profile) : Transformer() {
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.Library
import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.remap.TypedRemapper
import dev.openrs2.deob.Profile
import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes
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.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.max
@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?) {
val dependencies = clinit?.instructions
?.filterIsInstance<FieldInsnNode>()
@ -165,7 +166,7 @@ class StaticScramblingTransformer : Transformer() {
return@removeIf false
} else if (method.access and Opcodes.ACC_NATIVE != 0) {
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
}

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

@ -6,23 +6,29 @@ import com.google.common.collect.Multimap
import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath
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.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.removeFirst
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class UnusedMethodTransformer : Transformer() {
class UnusedMethodTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private lateinit var excludedMethods: MemberFilter
private val methodReferences = HashMultimap.create<DisjointSet.Partition<MemberRef>, MemberRef>()
override fun preTransform(classPath: ClassPath) {
inheritedMethodSets = classPath.createInheritedMethodSets()
excludedMethods = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
methodReferences.clear()
}
@ -44,7 +50,9 @@ class UnusedMethodTransformer : Transformer() {
val methods = clazz.methods.iterator()
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
}

@ -3,12 +3,15 @@ package dev.openrs2.deob.transform
import com.github.michaelbull.logging.InlineLogger
import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap
import dev.openrs2.asm.ClassForNameUtils
import dev.openrs2.asm.MemberDesc
import dev.openrs2.asm.MemberRef
import dev.openrs2.asm.classpath.ClassPath
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.deob.Profile
import dev.openrs2.deob.filter.ReflectedConstructorFilter
import dev.openrs2.util.collect.DisjointSet
import org.objectweb.asm.Opcodes
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.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class VisibilityTransformer : Transformer() {
class VisibilityTransformer @Inject constructor(private val profile: Profile) : Transformer() {
private lateinit var inheritedFieldSets: DisjointSet<MemberRef>
private lateinit var inheritedMethodSets: DisjointSet<MemberRef>
private lateinit var entryPoints: MemberFilter
private val fieldReferences = 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) {
inheritedFieldSets = classPath.createInheritedFieldSets()
inheritedMethodSets = classPath.createInheritedMethodSets()
entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath))
fieldReferences.clear()
methodReferences.clear()
publicCtorClasses.clear()
publicCtorClasses.addAll(DEFAULT_PUBLIC_CTOR_CLASSES)
}
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) {
when (insn) {
is FieldInsnNode -> addReference(fieldReferences, inheritedFieldSets, MemberRef(insn), clazz.name)
@ -71,11 +67,8 @@ class VisibilityTransformer : Transformer() {
if (member.name == "<clinit>") {
// the visibility flags don't really matter - we use package-private to match javac
return 0
} else if (member.owner in publicCtorClasses && member.name == "<init>") {
// constructors invoked with reflection (including applets) must be public
return Opcodes.ACC_PUBLIC
} else if (member.name in PUBLIC_METHODS) {
// methods invoked with reflection must also be public
} else if (entryPoints.matches(member)) {
// entry points must be public
return Opcodes.ACC_PUBLIC
}
}
@ -106,8 +99,6 @@ class VisibilityTransformer : Transformer() {
}
override fun postTransform(classPath: ClassPath) {
logger.info { "Identified constructors invoked with reflection $publicCtorClasses" }
var classesChanged = 0
var fieldsChanged = 0
var methodsChanged = 0
@ -166,8 +157,6 @@ class VisibilityTransformer : Transformer() {
companion object {
private val logger = InlineLogger()
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(
references: Multimap<DisjointSet.Partition<MemberRef>, String>,

Loading…
Cancel
Save