Actions

OpenRS2 Deobfuscator

From RuneWiki

Install Java 11 & kotlinc

git clone https://git.openrs2.org/openrs2/openrs2.git

2004 - 2006 RS2 Client

This is by no means a comprehensive guide, just some steps I took when deobfuscating 225. Some transformers had to be disabled to get it working properly, expect to play around with the transformers for any other version. The deobfuscator code was designed for 2009-2010 RS2 and has had some minor work to partially support 2004 (specifically 225).

NOTE: If any mistakes are made the nonfree folder needs to be cleared out before re-running the build. You can leave lib alone. Check for share/deob/map/client.yaml if you re-run gradle build after deob, it will process whatever the last output was.


1. Create nonfree/lib/ directory, place obfuscated runescape.jar in nonfree/lib/.

2. Delete profile mappings from share/deob/map/.

3. Add signlink entries to share/deob/profile.yaml. See below.

4. Edit deob-bytecode's BytecodeDeobfuscator.kt. Change client to load runescape.jar using JarLibraryReader, remove gl, loader, unpack, signlink, and reduce remapping code to only the client remapper. See below.

5. Remove everything other than client from deob-util's Module.kt. See below.

6. Remove GlRegistry, GlRegistryProvider, and GlTransformer from deob-ast's AstDeobfuscatorModule.kt (expect some linting errors to clean up from unused imports). See below.

7. (Recommended) Remove PatcherTransformer and all Unused_____Transformers from deob-bytecode's BytecodeDeobfuscatorModule.kt. See below.

8. Build and run:

gradlew build

java -jar all/build/libs/openrs2.jar deob


You should have some new java files in nonfree/client/src/main/java now. Compile them with deob-annotations in your javac classpath.

I've encountered one minor error when compiling, either find the problematic transformer or just edit the code to address it. For this specific one I just removed the referenced line:

Static15.java:1452: error: unreachable statement
                        return;

profile.yaml

---
excluded_classes:
  - "*!client"
  - "*!sign/signlink"
excluded_methods:
  - "*!**.<clinit> *"
  - "*!**.<init> *"
  - "*!**.main *"
  - "*!**.quit *"
  - "*!sign/signlink.* *"
excluded_fields:
  - "*!sign/signlink.* *"
entry_points:
  - "*!**.<clinit> *"
  - "*!**.main *"
  - "*!client.<init> *"
  - "*!sign/signlink.* *"
scrambled_libraries:
  - client
max_obfuscated_name_len: 2

BytecodeDeobfuscator.kt

Removed: all but client processing code

package org.openrs2.deob.bytecode

import com.github.michaelbull.logging.InlineLogger
import org.openrs2.asm.classpath.ClassPath
import org.openrs2.asm.classpath.Library
import org.openrs2.asm.io.JarLibraryReader
import org.openrs2.asm.io.JarLibraryWriter
import org.openrs2.asm.transform.Transformer
import org.openrs2.deob.bytecode.remap.ClassNamePrefixRemapper
import org.openrs2.deob.bytecode.remap.StripClassNamePrefixRemapper
import java.nio.file.Files
import java.nio.file.Path
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
public class BytecodeDeobfuscator @Inject constructor(
    @DeobfuscatorQualifier private val transformers: Set<Transformer>
) {
    public fun run(input: Path, output: Path) {
        // read input jars/packs
        logger.info { "Reading input jars" }
        val client = Library.read("client", input.resolve("runescape.jar"), JarLibraryReader)

        val clientRemapper = ClassNamePrefixRemapper(client)

        client.remap(clientRemapper)

        // bundle libraries together into a common classpath
        val runtime = ClassLoader.getPlatformClassLoader()
        val classPath = ClassPath(
            runtime,
            dependencies = emptyList(),
            libraries = listOf(client)
        )

        // deobfuscate
        logger.info { "Transforming client" }
        for (transformer in transformers) {
            logger.info { "Running transformer ${transformer.javaClass.simpleName}" }
            transformer.transform(classPath)
        }

        // strip class name prefixes
        client.remap(StripClassNamePrefixRemapper)

        // write output jars
        logger.info { "Writing output jars" }

        Files.createDirectories(output)

        client.write(output.resolve("client.jar"), JarLibraryWriter, classPath)
    }

    private companion object {
        private val logger = InlineLogger()
    }
}

Module.kt

Removed: all but client

package org.openrs2.deob.util

import java.nio.file.Path

public class Module(public val name: String, public val dependencies: Set<Module> = emptySet()) {
    public val jar: Path = Path.of("nonfree/var/cache/deob").resolve("$name.jar")
    public val sources: Path = Path.of("nonfree").resolve(name).resolve("src/main/java")
    public val transitiveDependencies: Set<Module> =
        dependencies.plus(dependencies.flatMap { it.transitiveDependencies })

    public companion object {
        private val CLIENT = Module("client")

        public val ALL: Set<Module> = setOf(CLIENT)
    }
}

AstDeobfuscatorModule.kt

Removed: GlTransformer, GlRegistry, GlRegistryProvider

Add/remove transformers as necessary as different versions may have conflicts.

package org.openrs2.deob.ast

import com.google.inject.AbstractModule
import com.google.inject.multibindings.Multibinder
import org.openrs2.deob.ast.transform.AddSubTransformer
import org.openrs2.deob.ast.transform.BinaryExprOrderTransformer
import org.openrs2.deob.ast.transform.BitMaskTransformer
import org.openrs2.deob.ast.transform.CharLiteralTransformer
import org.openrs2.deob.ast.transform.ComplementTransformer
import org.openrs2.deob.ast.transform.EncloseTransformer
import org.openrs2.deob.ast.transform.ForLoopConditionTransformer
import org.openrs2.deob.ast.transform.HexLiteralTransformer
import org.openrs2.deob.ast.transform.IfElseTransformer
import org.openrs2.deob.ast.transform.IncrementTransformer
import org.openrs2.deob.ast.transform.NegativeLiteralTransformer
import org.openrs2.deob.ast.transform.NewInstanceTransformer
import org.openrs2.deob.ast.transform.NotTransformer
import org.openrs2.deob.ast.transform.RedundantCastTransformer
import org.openrs2.deob.ast.transform.TernaryTransformer
import org.openrs2.deob.ast.transform.Transformer
import org.openrs2.deob.ast.transform.UnencloseTransformer
import org.openrs2.deob.ast.transform.ValueOfTransformer

public object AstDeobfuscatorModule : AbstractModule() {
    override fun configure() {
        // bind(GlRegistry::class.java)
        //     .toProvider(GlRegistryProvider::class.java)
        //     .`in`(Scopes.SINGLETON)

        val binder = Multibinder.newSetBinder(binder(), Transformer::class.java)
        binder.addBinding().to(UnencloseTransformer::class.java)
        binder.addBinding().to(NegativeLiteralTransformer::class.java)
        binder.addBinding().to(ComplementTransformer::class.java)
        binder.addBinding().to(NotTransformer::class.java)
        binder.addBinding().to(CharLiteralTransformer::class.java)
        binder.addBinding().to(IfElseTransformer::class.java)
        binder.addBinding().to(TernaryTransformer::class.java)
        binder.addBinding().to(BinaryExprOrderTransformer::class.java)
        binder.addBinding().to(AddSubTransformer::class.java)
        // binder.addBinding().to(IdentityTransformer::class.java)
        binder.addBinding().to(BitMaskTransformer::class.java)
        binder.addBinding().to(HexLiteralTransformer::class.java)
        binder.addBinding().to(ValueOfTransformer::class.java)
        binder.addBinding().to(NewInstanceTransformer::class.java)
        binder.addBinding().to(IncrementTransformer::class.java)
        binder.addBinding().to(ForLoopConditionTransformer::class.java)
        binder.addBinding().to(RedundantCastTransformer::class.java)
        // binder.addBinding().to(GlTransformer::class.java)
        binder.addBinding().to(EncloseTransformer::class.java)
    }
}

BytecodeDeobfuscatorModule.kt

Removed: UnusedLocalTransformer, UnusedMethodTransformer, UnusedArgTransformer, ConstantArgTransformer, PatcherTransformer

The ones I've outlined aren't necessary, I've just had the Unused* transformers and ConstantArg transformer remove code I wanted to keep. Patcher transformer is a good one, but has some changes specific to OpenRS2 / 2009 RS so you can be more selective in that module file.

Some client versions may require commenting out FinalFieldTransformer, possibly due to a deobfuscation edge case?

package org.openrs2.deob.bytecode

import com.google.inject.AbstractModule
import com.google.inject.Scopes
import com.google.inject.multibindings.Multibinder
import org.openrs2.asm.transform.Transformer
import org.openrs2.deob.bytecode.transform.BitShiftTransformer
import org.openrs2.deob.bytecode.transform.BitwiseOpTransformer
import org.openrs2.deob.bytecode.transform.CanvasTransformer
import org.openrs2.deob.bytecode.transform.ClassLiteralTransformer
import org.openrs2.deob.bytecode.transform.CopyPropagationTransformer
import org.openrs2.deob.bytecode.transform.CounterTransformer
import org.openrs2.deob.bytecode.transform.EmptyClassTransformer
import org.openrs2.deob.bytecode.transform.ExceptionTracingTransformer
import org.openrs2.deob.bytecode.transform.FernflowerExceptionTransformer
import org.openrs2.deob.bytecode.transform.FieldOrderTransformer
import org.openrs2.deob.bytecode.transform.FinalClassTransformer
import org.openrs2.deob.bytecode.transform.FinalFieldTransformer
import org.openrs2.deob.bytecode.transform.FinalMethodTransformer
import org.openrs2.deob.bytecode.transform.InvokeSpecialTransformer
import org.openrs2.deob.bytecode.transform.MethodOrderTransformer
import org.openrs2.deob.bytecode.transform.MonitorTransformer
import org.openrs2.deob.bytecode.transform.OpaquePredicateTransformer
import org.openrs2.deob.bytecode.transform.OriginalNameTransformer
import org.openrs2.deob.bytecode.transform.OriginalPcRestoreTransformer
import org.openrs2.deob.bytecode.transform.OriginalPcSaveTransformer
import org.openrs2.deob.bytecode.transform.OverrideTransformer
import org.openrs2.deob.bytecode.transform.RedundantGotoTransformer
import org.openrs2.deob.bytecode.transform.RemapTransformer
import org.openrs2.deob.bytecode.transform.ResetTransformer
import org.openrs2.deob.bytecode.transform.VisibilityTransformer
import org.openrs2.deob.util.DeobfuscatorUtilModule
import org.openrs2.patcher.PatcherModule
import org.openrs2.patcher.transform.ResourceTransformer

public object BytecodeDeobfuscatorModule : AbstractModule() {
    override fun configure() {
        install(PatcherModule)
        install(DeobfuscatorUtilModule)

        bind(Profile::class.java)
            .toProvider(ProfileProvider::class.java)
            .`in`(Scopes.SINGLETON)

        val binder = Multibinder.newSetBinder(binder(), Transformer::class.java, DeobfuscatorQualifier::class.java)
        binder.addBinding().to(OriginalPcSaveTransformer::class.java)
        binder.addBinding().to(OriginalNameTransformer::class.java)
        binder.addBinding().to(ClassLiteralTransformer::class.java)
        binder.addBinding().to(InvokeSpecialTransformer::class.java)
        binder.addBinding().to(RemapTransformer::class.java)
        // binder.addBinding().to(PatcherTransformer::class.java)
        binder.addBinding().to(ResourceTransformer::class.java)
        binder.addBinding().to(OpaquePredicateTransformer::class.java)
        binder.addBinding().to(ExceptionTracingTransformer::class.java)
        binder.addBinding().to(MonitorTransformer::class.java)
        binder.addBinding().to(BitShiftTransformer::class.java)
        binder.addBinding().to(CanvasTransformer::class.java)
        binder.addBinding().to(FieldOrderTransformer::class.java)
        binder.addBinding().to(BitwiseOpTransformer::class.java)
        //binder.addBinding().to(ConstantArgTransformer::class.java)
        binder.addBinding().to(CopyPropagationTransformer::class.java)
        // binder.addBinding().to(UnusedLocalTransformer::class.java)
        // binder.addBinding().to(UnusedMethodTransformer::class.java)
        // binder.addBinding().to(UnusedArgTransformer::class.java)
        binder.addBinding().to(CounterTransformer::class.java)
        binder.addBinding().to(ResetTransformer::class.java)
        binder.addBinding().to(EmptyClassTransformer::class.java)
        binder.addBinding().to(MethodOrderTransformer::class.java)
        binder.addBinding().to(VisibilityTransformer::class.java)
        binder.addBinding().to(FinalClassTransformer::class.java)
        binder.addBinding().to(FinalMethodTransformer::class.java)
        binder.addBinding().to(FinalFieldTransformer::class.java)
        binder.addBinding().to(OverrideTransformer::class.java)
        binder.addBinding().to(RedundantGotoTransformer::class.java)
        binder.addBinding().to(OriginalPcRestoreTransformer::class.java)
        binder.addBinding().to(FernflowerExceptionTransformer::class.java)
    }
}