All projects

Reverse engineering

RuneScape Classic — renderer parity

Deobfuscated a 71-class J++-era game client into ~50k lines of readable Java, then proved it, the original JAR, and a from-scratch Go renderer draw byte-identical frames.

CompleteJun 2026 – Jun 2026freeqaz/strip-obf-exceptions

Java · deobfuscation · software rendering · Go · bytecode

The deobfuscated RuneScape Classic client rendering the 3D viewport inside the remote-control debug UI used for parity testing

A three-day sprint inside a collaborator’s AI-agent research project that needed one missing piece: a trustworthy, readable client for a 2001 MMO whose only client was closed-source and obfuscated. I deobfuscated the 71-class, Microsoft J++-built RuneScape Classic client into roughly 50,000 lines of readable Java — and then held the result to an unusual acceptance bar: three independently built engines (the deobfuscated Java client, the untouched original rev-235 JAR as an oracle, and a from-scratch Go port of the software 3D renderer) had to produce byte-identical framebuffers. Not “looks right” — zero pixel difference.

The hardest problem was that a 1990s obfuscator still defeats modern tooling. The J++ obfuscator injects bogus exception-table ranges that break the control-flow structurer in Vineflower, the best current Java decompiler: 238 methods across 41 of the 71 classes failed outright, on every upstream version. Rather than hand-patch 238 methods, I wrote a custom ASM bytecode pass, strip-obf-exceptions.jar, that strips the bogus ranges before decompilation. One pass cut the failures from 238 to 4, cross-validated against a second decompiler, with the root cause written up as a reproducible bug analysis. A separate lossless pass normalized 206 shift-obfuscation sites, and a method-by-method diff against a clean re-decompilation fixed roughly 236 logic bugs.

Parity itself demanded a counterintuitive discipline: the Go port deliberately preserves Java’s arithmetic quirks — int32 overflow wraparound, shift-versus-AND operator precedence, unsigned right shifts, signed-byte reads — because “fixing” any of them would have broken the proof. I built the verification harness first (a three-way structural and pixel diff producing heatmap PNGs, later hardened into a CI regression gate), then earned parity in staged milestones: terrain, scenery meshes, texture banks, and finally the full 2D sprite surface including animation frames, fixed-point clipping, and ground items.

The outcome: 87 commits in three days, a fully functioning deobfuscated client rendering the live 3D world, a parity-proven Go renderer, and a reusable tool for anyone decompiling exception-table-obfuscated JARs — a class of target upstream decompilers still cannot handle. It is the same byte-exact verification methodology I apply to native console decompilation, transplanted onto JVM bytecode.

RuneScape Classic — renderer parity | Free Wortley