@ -2,35 +2,33 @@ package dev.openrs2.deob.transform
import com.github.michaelbull.logging.InlineLogger
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.ClassVersionUtils
import dev.openrs2.asm.ClassVersionUtils
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.getExpression
import dev.openrs2.asm.sequential
import dev.openrs2.asm.transform.Transformer
import dev.openrs2.asm.transform.Transformer
import dev.openrs2.deob.remap.TypedRemapper
import dev.openrs2.deob.remap.TypedRemapper
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.JumpInsnNode
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 kotlin.math.max
import kotlin.math.max
class StaticScramblingTransformer : Transformer ( ) {
class StaticScramblingTransformer : Transformer ( ) {
private data class Field ( val node : FieldNode , val initializer : InsnList , val version : Int , val maxStack : Int ) {
private class FieldSet ( val owner : ClassNode , val fields : List < FieldNode > , val clinit : MethodNode ? ) {
val dependencies = initializer . asSequence ( )
val dependencies = clinit ?. instructions
. filterIsInstance < FieldInsnNode > ( )
? .filterIsInstance < FieldInsnNode > ( )
. filter { it . opcode == Opcodes . GETSTATIC }
? .filter { it . opcode == Opcodes . GETSTATIC && it . owner != owner . name }
. map ( :: MemberRef )
? .map ( :: MemberRef )
. to Set( )
?. toSet ( ) ?: empty Set( )
}
}
private val fields = mutableMapOf < MemberRef , Field > ( )
private val fieldSet s = mutableMapOf < MemberRef , FieldSet > ( )
private val fieldClasses = mutableMapOf < MemberRef , String > ( )
private val fieldClasses = mutableMapOf < MemberRef , String > ( )
private val methodClasses = mutableMapOf < MemberRef , String > ( )
private val methodClasses = mutableMapOf < MemberRef , String > ( )
private var nextStaticClass : ClassNode ? = null
private var nextStaticClass : ClassNode ? = null
@ -70,94 +68,61 @@ class StaticScramblingTransformer : Transformer() {
return Pair ( clazz , clinit )
return Pair ( clazz , clinit )
}
}
private fun MethodNode . extractEntryExitBlocks ( ) : List < AbstractInsnNode > {
private fun spliceFields ( ) {
/ *
val done = mutableSetOf < FieldSet > ( )
* Most ( or all ? ) of the < clinit > methods have " simple " initializers
for ( fieldSet in fieldSets . values ) {
* that we ' re capable of moving in the first and last basic blocks of
spliceFields ( done , fieldSet )
* the method . The last basic block is always at the end of the code
* and ends in a RETURN . This allows us to avoid worrying about making
* a full basic block control flow graph here .
* /
val entry = instructions . takeWhile { it . sequential }
val last = instructions . lastOrNull ( )
if ( last == null || last . opcode != Opcodes . RETURN ) {
return entry
}
}
val exit = instructions . toList ( )
. dropLast ( 1 )
. takeLastWhile { it . sequential }
return entry . plus ( exit )
}
}
private fun MethodNode . extractInitializers ( owner : String ) : Pair < Map < MemberDesc , InsnList > , Set < MemberDesc > > {
private fun spliceFields ( done : MutableSet < FieldSet > , fieldSet : FieldSet ) {
val entryExitBlocks = extractEntryExitBlocks ( )
if ( ! done . add ( fieldSet ) ) {
return
}
val simpleInitializers = mutableMapOf < MemberDesc , InsnList > ( )
for ( dependency in fieldSet . dependencies ) {
val complexInitializers = instructions . asSequence ( )
val dependencyFieldSet = fieldSets [ dependency ] ?: continue
. filter { ! entryExitBlocks . contains ( it ) }
spliceFields ( done , dependencyFieldSet )
. filterIsInstance < FieldInsnNode > ( )
}
. filter { it . opcode == Opcodes . GETSTATIC && it . owner == owner && it . name !in TypedRemapper . EXCLUDED _FIELDS }
. map ( :: MemberDesc )
. toSet ( )
val putstatics = entryExitBlocks
val ( staticClass , staticClinit ) = nextClass ( )
. filterIsInstance < FieldInsnNode > ( )
staticClass . fields . addAll ( fieldSet . fields )
. filter { it . opcode == Opcodes . PUTSTATIC && it . owner == owner && it . name !in TypedRemapper . EXCLUDED _FIELDS }
staticClass . version = ClassVersionUtils . maxVersion ( staticClass . version , fieldSet . owner . version )
for ( putstatic in putstatics ) {
if ( fieldSet . clinit != null ) {
val desc = MemberDesc ( putstatic )
// remove tail RETURN
if ( simpleInitializers . containsKey ( desc ) || complexInitializers . contains ( desc ) ) {
val insns = fieldSet . clinit . instructions
continue
val last = insns . lastOrNull ( )
if ( last != null && last . opcode == Opcodes . RETURN ) {
insns . remove ( last )
}
}
// TODO(gpe): use a filter here (pure with no *LOADs?)
// replace any remaining RETURNs with a GOTO to the end of the method
val expr = getExpression ( putstatic ) ?: continue
val end = LabelNode ( )
insns . add ( end )
val initializer = InsnList ( )
for ( insn in insns ) {
for ( insn in expr ) {
i f ( insn . opcode == Opcodes . RETURN ) {
instructions . remove ( insn )
insns . set ( insn , JumpInsnNode ( Opcodes . GOTO , end ) )
initializer . add ( insn )
}
}
}
instructions . remove ( putstatic )
initializer . add ( putstatic )
simpleInitializers [ desc ] = initializer
// append just before the end of the static <clinit> RETURN
}
staticClinit . instructions . insertBefore ( staticClinit . instructions . last , insns )
return Pair ( simpleInitializers , complexInitializers )
}
private fun spliceInitializers ( ) {
staticClinit . tryCatchBlocks . addAll ( fieldSet . clinit . tryCatchBlocks )
val done = mutableSetOf < MemberRef > ( )
staticClinit . maxStack = max ( staticClinit . maxStack , fieldSet . clinit . maxStack )
for ( ( ref , field ) in fields ) {
staticClinit . maxLocals = max ( staticClinit . maxLocals , fieldSet . clinit . maxLocals )
spliceInitializers ( done , ref , field )
}
}
private fun spliceInitializers ( done : MutableSet < MemberRef > , ref : MemberRef , field : Field ) {
if ( ! done . add ( ref ) ) {
return
}
}
for ( dependency in field . dependencie s) {
for ( field in fieldSet . fields ) {
val dependencyField = fields [ dependency ] ?: continue
val ref = MemberRef ( fieldSet . owner , field )
spliceInitializers ( done , dependency , dependencyField )
fieldClasses [ ref ] = staticClass . name
}
}
val ( clazz , clinit ) = nextClass ( )
clazz . fields . add ( field . node )
clazz . version = ClassVersionUtils . maxVersion ( clazz . version , field . version )
clinit . instructions . insertBefore ( clinit . instructions . last , field . initializer )
clinit . maxStack = max ( clinit . maxStack , field . maxStack )
fieldClasses [ ref ] = clazz . name
}
}
override fun preTransform ( classPath : ClassPath ) {
override fun preTransform ( classPath : ClassPath ) {
fields . clear ( )
fieldSets . clear ( )
fieldClasses . clear ( )
fieldClasses . clear ( )
methodClasses . clear ( )
methodClasses . clear ( )
nextStaticClass = null
nextStaticClass = null
@ -171,33 +136,22 @@ class StaticScramblingTransformer : Transformer() {
for ( clazz in library ) {
for ( clazz in library ) {
// TODO(gpe): exclude the JSObject class
// TODO(gpe): exclude the JSObject class
if ( clazz . name in TypedRemapper . EXCLUDED _CLASSES ) {
if ( clazz . name == " jagex3/jagmisc/jagmisc " ) {
continue
continue
}
}
val clinit = clazz . methods . find { it . name == " <clinit> " }
val fields = clazz . fields . filter { it . access and Opcodes . ACC _STATIC != 0 }
val ( simpleInitializers , complexInitializers ) = clinit ?. extractInitializers ( clazz . name )
clazz . fields . removeAll ( fields )
?: Pair ( emptyMap ( ) , emptySet ( ) )
clazz . fields . removeIf { field ->
if ( field . access and Opcodes . ACC _STATIC == 0 ) {
return @removeIf false
} else if ( field . name in TypedRemapper . EXCLUDED _METHODS ) {
return @removeIf false
}
val desc = MemberDesc ( field )
if ( complexInitializers . contains ( desc ) ) {
return @removeIf false
}
val initializer = simpleInitializers [ desc ] ?: InsnList ( )
val clinit = clazz . methods . find { it . name == " <clinit> " }
val maxStack = clinit ?. maxStack ?: 0
if ( clinit != null ) {
clazz . methods . remove ( clinit )
}
val fieldSet = FieldSet ( clazz , fields , clinit )
for ( field in fields ) {
val ref = MemberRef ( clazz , field )
val ref = MemberRef ( clazz , field )
fields [ ref ] = Field ( field , initializer , clazz . version , maxStack )
fieldSets [ ref ] = fieldSet
return @removeIf true
}
}
clazz . methods . removeIf { method ->
clazz . methods . removeIf { method ->
@ -218,7 +172,7 @@ class StaticScramblingTransformer : Transformer() {
}
}
}
}
spliceInitializer s ( )
spliceField s ( )
for ( clazz in staticClasses ) {
for ( clazz in staticClasses ) {
library . add ( clazz )
library . add ( clazz )