Building a RuneScape Bot
How I reverse-engineered RuneScape's obfuscated client and built a bot framework that survived weekly updates.
RuneScape is a massively multiplayer online game. Browser-based Java client, millions of players at its peak. The entire progression system is built on repetition: mine the same rock, cut the same tree, fish the same spot. Each action ticks up a skill level number. To max out a skill you might do the same click 50,000 times. That's not hyperbole. That's the design.
What a bot does
A bot is a program that plays the game for you while you do something else. It reads the game state: where is your character, what's nearby, what's in the inventory. Then it sends inputs back: click this spot, press this key. The game sees normal mouse clicks. It doesn't know it's talking to a program instead of a person.
This isn't hacking in the destructive sense. No exploiting vulnerabilities. No corrupting data. Just automation.
A framework bot injects into the game and exposes an API: player position, inventory contents, click targets. A script uses that API to automate one task — chop these trees, bank when full, repeat. The simplest bots skipped the framework entirely: one script, one task, hardcoded. The sophisticated ones were full platforms other people could write scripts for, with a UI, a developer API, and a script store. That's what I built. It was called Liquid Automation.
The problem: obfuscation
The first time I loaded RuneScape's jar file in a decompiler, I couldn't read it. Every class was named ab. Every field was named a, b, c. Every method too.
Obfuscation means taking normal code (int playerX, class Player) and renaming everything to gibberish (int b, class af). The program runs identically. But if you try to read it, it's meaningless. Jagex did this on purpose, every week, specifically to break bots.
It worked. Most bots died on patch day and stayed dead until someone manually traced through the new obfuscation. I wanted to build something that wouldn't die.
The Updater
The naive approach was to maintain a map: "the player's X coordinate is stored in field b of class af." That worked until Thursday, when the update dropped and af was now bc and b was now q. You'd update the map by hand, ship a fix, and be back on the same treadmill the following week.
The better approach: stop caring about names. Describe what a field is in terms that survive obfuscation. The player's X coordinate is always an int. It's always in a class with a certain number of fields. The class always extends the same parent. Those structural properties don't change just because the names do.
I built a tool called the Updater. Each week it analyzed the new client and produced a Hooks.html file: a map of every variable the bot needed, identified by structure rather than name. Most weeks it ran unattended. The bot didn't break.
There was a community called rs-hacking.com where people shared findings about the game's bytecode across versions. That's where the patterns came from initially. Looking at many versions of the same game, the structure was stable even when the names weren't.
Getting the data out
Knowing where the data lived didn't mean I could read it. RuneScape's fields were private. There was no getX() method Jagex left around for bots to call.
Two solutions. One: bytecode injection. I used ObjectWeb ASM to modify the game's bytecode at load time, before it ran. Getter methods were injected directly into the classes. By the time the JVM executed the game code, it had extra methods in it that I'd added. The game didn't know they were there. My bot called them normally.
Two: reflection. Java's reflection API lets you examine and modify a running program's internals at runtime, including private fields. For cases where injection was awkward, reflection worked as a fallback.
Drawing on the game
Bots don't need to see the game to play it. But users do, and I wanted a UI layer: character IDs drawn on NPCs, object IDs on items, a panel showing what the bot was currently doing.
The problem: drawing on the game's canvas directly would require frame-perfect timing to avoid overwriting what the game was rendering.
I injected a callback into the game's canvas class. When the game called paint(), my callback fired. I also used reflection to swap the game's canvas variable to my own canvas subclass, which let me draw a transparent overlay above the game without touching its rendering path.

The script network
When the platform was stable, other people wanted to write scripts for it. The obvious path: distribute them as .jar files dropped into a folder. That's how every other bot framework worked.
The problems: users have to find the script, download it, figure out where to put it. When a developer updates it, users have to do it again. Nothing stops a malicious developer from shipping something harmful in a script file.
I built a Script Delivery Network instead. Developers uploaded scripts to a server. Users opened the bot, picked a script from a list, clicked Add, and it installed. Updates happened automatically. Scripts were reviewed before being published.

It was the same distribution model Jagex used for the game itself. They pushed updates to a jar file on their servers. I was doing the same thing one layer above, for user scripts.
What actually ran
At peak I had six instances of the bot running on one machine simultaneously.

Each was a separate JVM instance with its own game client, its own state, its own script. The FPS controller kept them from burning CPU. Bots don't need to render at 60fps. At 2fps each, six of them barely registered.