|
|
|
package dev.openrs2.deob
|
|
|
|
|
|
|
|
import com.github.michaelbull.logging.InlineLogger
|
|
|
|
import dev.openrs2.asm.classpath.ClassPath
|
|
|
|
import dev.openrs2.asm.classpath.Library
|
|
|
|
import dev.openrs2.asm.io.JarLibraryReader
|
|
|
|
import dev.openrs2.asm.io.JarLibraryWriter
|
|
|
|
import dev.openrs2.asm.io.Pack200LibraryReader
|
|
|
|
import dev.openrs2.bundler.Bundler
|
|
|
|
import dev.openrs2.bundler.transform.ResourceTransformer
|
|
|
|
import dev.openrs2.deob.SignedClassUtils.move
|
|
|
|
import dev.openrs2.deob.remap.PrefixRemapper.create
|
|
|
|
import dev.openrs2.deob.transform.BitShiftTransformer
|
|
|
|
import dev.openrs2.deob.transform.BitwiseOpTransformer
|
|
|
|
import dev.openrs2.deob.transform.CanvasTransformer
|
|
|
|
import dev.openrs2.deob.transform.ClassLiteralTransformer
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
import dev.openrs2.deob.transform.ConstantArgTransformer
|
|
|
|
import dev.openrs2.deob.transform.CounterTransformer
|
|
|
|
import dev.openrs2.deob.transform.EmptyClassTransformer
|
|
|
|
import dev.openrs2.deob.transform.ExceptionTracingTransformer
|
|
|
|
import dev.openrs2.deob.transform.FernflowerExceptionTransformer
|
|
|
|
import dev.openrs2.deob.transform.FieldOrderTransformer
|
|
|
|
import dev.openrs2.deob.transform.FinalTransformer
|
|
|
|
import dev.openrs2.deob.transform.InvokeSpecialTransformer
|
|
|
|
import dev.openrs2.deob.transform.MethodOrderTransformer
|
|
|
|
import dev.openrs2.deob.transform.MonitorTransformer
|
|
|
|
import dev.openrs2.deob.transform.OpaquePredicateTransformer
|
|
|
|
import dev.openrs2.deob.transform.OriginalNameTransformer
|
|
|
|
import dev.openrs2.deob.transform.OriginalPcRestoreTransformer
|
|
|
|
import dev.openrs2.deob.transform.OriginalPcSaveTransformer
|
|
|
|
import dev.openrs2.deob.transform.OverrideTransformer
|
|
|
|
import dev.openrs2.deob.transform.RedundantGotoTransformer
|
|
|
|
import dev.openrs2.deob.transform.RemapTransformer
|
|
|
|
import dev.openrs2.deob.transform.ResetTransformer
|
|
|
|
import dev.openrs2.deob.transform.StaticScramblingTransformer
|
|
|
|
import dev.openrs2.deob.transform.UnusedArgTransformer
|
|
|
|
import dev.openrs2.deob.transform.UnusedLocalTransformer
|
|
|
|
import dev.openrs2.deob.transform.UnusedMethodTransformer
|
|
|
|
import dev.openrs2.deob.transform.VisibilityTransformer
|
|
|
|
import dev.openrs2.util.io.DeterministicJarOutputStream
|
|
|
|
import java.nio.file.Files
|
|
|
|
import java.nio.file.Path
|
|
|
|
import java.util.jar.JarInputStream
|
|
|
|
|
|
|
|
class Deobfuscator(private val input: Path, private val output: Path) {
|
|
|
|
fun run() {
|
|
|
|
// read input jars/packs
|
|
|
|
logger.info { "Reading input jars" }
|
|
|
|
val unpackClass = readJar(input.resolve("unpackclass.pack"))
|
|
|
|
val glUnpackClass = Library(unpackClass)
|
|
|
|
val loader = readJar(input.resolve("loader.jar"))
|
|
|
|
val glLoader = readJar(input.resolve("loader_gl.jar"))
|
|
|
|
val gl = readPack(input.resolve("jaggl.pack200"))
|
|
|
|
val client = readJar(input.resolve("runescape.jar"))
|
|
|
|
val glClient = readPack(input.resolve("runescape_gl.pack200"))
|
|
|
|
|
|
|
|
// TODO(gpe): it'd be nice to have separate signlink.jar and
|
|
|
|
// signlink-unsigned.jar files so we don't (effectively) deobfuscate
|
|
|
|
// runescape.jar twice with different sets of names, but thinking about
|
|
|
|
// how this would work is tricky (as the naming must match)
|
|
|
|
val unsignedClient = Library(client)
|
|
|
|
|
|
|
|
// overwrite client's classes with signed classes from the loader
|
|
|
|
logger.info { "Moving signed classes from loader" }
|
|
|
|
val signLink = Library()
|
|
|
|
move(loader, client, signLink)
|
|
|
|
|
|
|
|
logger.info { "Moving signed classes from loader_gl" }
|
|
|
|
val glSignLink = Library()
|
|
|
|
move(glLoader, glClient, glSignLink)
|
|
|
|
|
|
|
|
// move unpack class out of the loader (so the unpacker and loader can both depend on it)
|
|
|
|
logger.info { "Moving unpack from loader to unpack" }
|
|
|
|
val unpack = Library()
|
|
|
|
unpack.add(loader.remove("unpack")!!)
|
|
|
|
|
|
|
|
logger.info { "Moving unpack from loader_gl to unpack_gl" }
|
|
|
|
val glUnpack = Library()
|
|
|
|
glUnpack.add(glLoader.remove("unpack")!!)
|
|
|
|
|
|
|
|
// 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(create(loader, "loader_"))
|
|
|
|
glLoader.remap(create(glLoader, "loader_"))
|
|
|
|
unpackClass.remap(create(unpackClass, "unpackclass_"))
|
|
|
|
glUnpackClass.remap(create(glUnpackClass, "unpackclass_"))
|
|
|
|
|
|
|
|
// bundle libraries together into a common classpath
|
|
|
|
val runtime = ClassLoader.getPlatformClassLoader()
|
|
|
|
val classPath = ClassPath(
|
|
|
|
runtime,
|
|
|
|
dependencies = emptyList(),
|
|
|
|
libraries = listOf(client, loader, signLink, unpack, unpackClass)
|
|
|
|
)
|
|
|
|
val glClassPath = ClassPath(
|
|
|
|
runtime,
|
|
|
|
dependencies = listOf(gl),
|
|
|
|
libraries = listOf(glClient, glLoader, glSignLink, glUnpack, glUnpackClass)
|
|
|
|
)
|
|
|
|
val unsignedClassPath = ClassPath(
|
|
|
|
runtime,
|
|
|
|
dependencies = emptyList(),
|
|
|
|
libraries = listOf(unsignedClient)
|
|
|
|
)
|
|
|
|
|
|
|
|
// deobfuscate
|
|
|
|
logger.info { "Transforming client" }
|
|
|
|
for (transformer in TRANSFORMERS) {
|
|
|
|
logger.info { "Running transformer ${transformer.javaClass.simpleName} " }
|
|
|
|
transformer.transform(classPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info { "Transforming client_gl" }
|
|
|
|
for (transformer in TRANSFORMERS) {
|
|
|
|
logger.info { "Running transformer ${transformer.javaClass.simpleName} " }
|
|
|
|
transformer.transform(glClassPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info { "Transforming client_unsigned" }
|
|
|
|
for (transformer in TRANSFORMERS) {
|
|
|
|
logger.info { "Running transformer ${transformer.javaClass.simpleName} " }
|
|
|
|
transformer.transform(unsignedClassPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// write output jars
|
|
|
|
logger.info { "Writing output jars" }
|
|
|
|
|
|
|
|
Files.createDirectories(output)
|
|
|
|
|
|
|
|
writeJar(classPath, client, output.resolve("runescape.jar"))
|
|
|
|
writeJar(classPath, loader, output.resolve("loader.jar"))
|
|
|
|
writeJar(classPath, signLink, output.resolve("signlink.jar"))
|
|
|
|
writeJar(classPath, unpack, output.resolve("unpack.jar"))
|
|
|
|
writeJar(classPath, unpackClass, output.resolve("unpackclass.jar"))
|
|
|
|
|
|
|
|
writeJar(glClassPath, gl, output.resolve("jaggl.jar"))
|
|
|
|
writeJar(glClassPath, glClient, output.resolve("runescape_gl.jar"))
|
|
|
|
writeJar(glClassPath, glLoader, output.resolve("loader_gl.jar"))
|
|
|
|
writeJar(glClassPath, glSignLink, output.resolve("signlink_gl.jar"))
|
|
|
|
writeJar(glClassPath, glUnpack, output.resolve("unpack_gl.jar"))
|
|
|
|
writeJar(glClassPath, glUnpackClass, output.resolve("unpackclass_gl.jar"))
|
|
|
|
|
|
|
|
writeJar(unsignedClassPath, unsignedClient, output.resolve("runescape_unsigned.jar"))
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun readJar(path: Path): Library {
|
|
|
|
logger.info { "Reading jar $path" }
|
|
|
|
|
|
|
|
return JarInputStream(Files.newInputStream(path)).use { input ->
|
|
|
|
JarLibraryReader(input).read()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun readPack(path: Path): Library {
|
|
|
|
logger.info { "Reading pack $path" }
|
|
|
|
|
|
|
|
return Files.newInputStream(path).use { input ->
|
|
|
|
Pack200LibraryReader(input).read()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun writeJar(classPath: ClassPath, library: Library, path: Path) {
|
|
|
|
logger.info { "Writing jar $path" }
|
|
|
|
|
|
|
|
DeterministicJarOutputStream(Files.newOutputStream(path)).use { output ->
|
|
|
|
JarLibraryWriter(output).write(classPath, library)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
private val logger = InlineLogger()
|
|
|
|
private val TRANSFORMERS = listOf(
|
|
|
|
OriginalPcSaveTransformer(),
|
|
|
|
OriginalNameTransformer(),
|
|
|
|
*Bundler.TRANSFORMERS.toTypedArray(),
|
|
|
|
ResourceTransformer(),
|
|
|
|
OpaquePredicateTransformer(),
|
|
|
|
ExceptionTracingTransformer(),
|
|
|
|
MonitorTransformer(),
|
|
|
|
BitShiftTransformer(),
|
|
|
|
CanvasTransformer(),
|
|
|
|
FieldOrderTransformer(),
|
|
|
|
BitwiseOpTransformer(),
|
|
|
|
RemapTransformer(),
|
Replace DummyArgTransformer with new ConstantArgTransformer
The new transformer uses a different approach to the old one. It starts
exploring the call graph from the entry points, recursively analysing
method calls. Methods are only re-analysed if their possible argument
values change, with the Unknown value being used if we can't identify a
single integer constant at a call site. This prevents us from recursing
infinitely if the client code does.
While this first pass does simplify branches in order to ignore dummy
method calls that are never evaluated at runtime, it operates on a copy
of the method (as we initially ignore more calls while the argument
value sets are smaller, ignoring fewer calls as they build up).
A separate second pass simplifies branches on the original method and
inlines singleton constants, paving the way for the UnusedArgTransformer
to actually remove the newly unused arguments.
This new approach has several benefits:
- It is much faster than the old approach, as we only re-analyse methods
as required by argument value changes, rather than re-analysing every
method during every pass.
- It doesn't require special cases for dealing with mutually recursive
dummy calls. The old approach hard-coded special cases for mutually
recursive calls involving groups of 1 and 2 methods. The code for this
wasn't clean. Furthermore, while it was just about good enough for the
HD client, the SD client contains a mutually recursive group of 3
methods. The new approach is capable of dealing with mutually
recursive groups of any size.
Finally, the new transformer has a much cleaner implementation.
Signed-off-by: Graham <gpe@openrs2.dev>
4 years ago
|
|
|
ConstantArgTransformer(),
|
|
|
|
UnusedLocalTransformer(),
|
|
|
|
UnusedMethodTransformer(),
|
|
|
|
UnusedArgTransformer(),
|
|
|
|
CounterTransformer(),
|
|
|
|
ResetTransformer(),
|
|
|
|
ClassLiteralTransformer(),
|
|
|
|
InvokeSpecialTransformer(),
|
|
|
|
StaticScramblingTransformer(),
|
|
|
|
EmptyClassTransformer(),
|
|
|
|
MethodOrderTransformer(),
|
|
|
|
VisibilityTransformer(),
|
|
|
|
FinalTransformer(),
|
|
|
|
OverrideTransformer(),
|
|
|
|
RedundantGotoTransformer(),
|
|
|
|
OriginalPcRestoreTransformer(),
|
|
|
|
FernflowerExceptionTransformer()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|