diff --git a/asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt new file mode 100644 index 00000000..d2608600 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt @@ -0,0 +1,5 @@ +package dev.openrs2.asm.filter + +interface ClassFilter { + fun matches(name: String): Boolean +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/Glob.kt b/asm/src/main/java/dev/openrs2/asm/filter/Glob.kt new file mode 100644 index 00000000..076f3fce --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/Glob.kt @@ -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()) + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt new file mode 100644 index 00000000..3845f9b1 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt @@ -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) } + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt new file mode 100644 index 00000000..47f3eeeb --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt @@ -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) + ) + } + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt new file mode 100644 index 00000000..394a6c11 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt @@ -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) + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt new file mode 100644 index 00000000..17b8cd39 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt @@ -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) } + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt new file mode 100644 index 00000000..2194fa93 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt @@ -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) } + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt index 32a7554d..a3bb46f6 100644 --- a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt +++ b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt @@ -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() diff --git a/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt b/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt index 7f91852f..e3bfc002 100644 --- a/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt +++ b/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt @@ -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) diff --git a/deob/src/main/java/dev/openrs2/deob/Profile.kt b/deob/src/main/java/dev/openrs2/deob/Profile.kt new file mode 100644 index 00000000..2d74e5a3 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/Profile.kt @@ -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( + "**. *", + "**. *", + "**.main *", + "**.providesignlink *", + "**.quit *" + ), + excludedFields = GlobMemberFilter( + "**.cache *" + ), + entryPoints = GlobMemberFilter( + "**. *", + "**.main *", + "**.providesignlink *", + "client. *", + "loader. *", + "unpackclass. *" + ), + maxObfuscatedNameLen = 2 + ) + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt b/deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt new file mode 100644 index 00000000..f4f2ca47 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt @@ -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) : MemberFilter { + override fun matches(owner: String, name: String, desc: String): Boolean { + return classes.contains(owner) && name == "" + } + + companion object { + private val logger = InlineLogger() + + fun create(classPath: ClassPath): MemberFilter { + val classes = mutableSetOf() + + 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) { + if (!method.hasCode) { + return + } + + for (name in ClassForNameUtils.findClassNames(method)) { + val clazz = classPath[name] + if (clazz != null && !clazz.dependency) { + classes.add(name) + } + } + } + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt b/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt index c7c3e0f2..92ca5914 100644 --- a/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt +++ b/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt @@ -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() 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 diff --git a/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt b/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt index b6a70413..bcdc3816 100644 --- a/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt +++ b/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt @@ -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( - "", - "", - "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) { + private fun verifyMapping(mapping: Map, maxObfuscatedNameLen: Int) { for ((key, value) in mapping) { - verifyMapping(key, value) + verifyMapping(key, value, maxObfuscatedNameLen) } } - private fun verifyMemberMapping(mapping: Map) { + private fun verifyMemberMapping(mapping: Map, 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 { + private fun createClassMapping(classPath: ClassPath, excludedClasses: ClassFilter): Map { val mapping = mutableMapOf() val prefixes = mutableMapOf() for (clazz in classPath.libraryClasses) { - populateClassMapping(mapping, prefixes, clazz) + populateClassMapping(excludedClasses, mapping, prefixes, clazz) } return mapping } private fun populateClassMapping( + excludedClasses: ClassFilter, mapping: MutableMap, prefixes: MutableMap, 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, prefixes: MutableMap, 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, classMapping: Map ): Map { @@ -164,7 +151,7 @@ class TypedRemapper private constructor( val prefixes = mutableMapOf() 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): Boolean { + private fun isFieldRenamable( + classPath: ClassPath, + excludedFields: MemberFilter, + partition: DisjointSet.Partition + ): 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 ): Map { val mapping = mutableMapOf() 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): Boolean { + fun isMethodRenamable( + classPath: ClassPath, + excludedMethods: MemberFilter, + partition: DisjointSet.Partition + ): 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 } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt index c6e2b835..63e0077b 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt @@ -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() private val arglessMethods = mutableSetOf>() private val argValues = mutableMapOf() private lateinit var inheritedMethodSets: DisjointSet + 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 (called by the JVM). - * - * It isn't perfect - it counts every method as an entry - * point, but strictly speaking we only need to count - * 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): 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 diff --git a/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt index 8af2b09c..e8a768e1 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt @@ -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)) } } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt index f1cc073b..36ecdc08 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt @@ -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, val clinit: MethodNode?) { val dependencies = clinit?.instructions ?.filterIsInstance() @@ -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 } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt index 08f84d4d..9297e722 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt @@ -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() private lateinit var inheritedMethodSets: DisjointSet 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 } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt index 20a038d1..49362ae1 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt @@ -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 + private lateinit var excludedMethods: MemberFilter private val methodReferences = HashMultimap.create, 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 } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt index cb6fbb7b..332aacc8 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt @@ -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 private lateinit var inheritedMethodSets: DisjointSet + private lateinit var entryPoints: MemberFilter private val fieldReferences = HashMultimap.create, String>() private val methodReferences = HashMultimap.create, String>() - private val publicCtorClasses = mutableSetOf() 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 == "") { // the visibility flags don't really matter - we use package-private to match javac return 0 - } else if (member.owner in publicCtorClasses && member.name == "") { - // 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, String>,