introduction

My first experience with MMORPG’s came when I was a child of eight or so. Back then, a site called Miniclip had a few references to RuneScape so I gave it a shot. It was wonderful. There was something so enticing about the wild west of MMO’s at that time, especially RuneScape, given its crowded bazaars and prowling hackers.

I bled forty days of play time into the game and another 150 into World of Warcraft as a young adult. It’s no surprise that some of my strongest projects in my early explorations of software development revolved around games, like WoWkemon.

As any kid, I dreamed about what it would be like if the game could play itself. It’s a bit silly, really: the whole fun of video games is managing resource constraints and getting better at a set of tasks to optimize some kind of success function. One important input is your own time, which is part of why they’re so entertaining. Despite that, there was still a certain cachet I attributed to my friends and peers who had crazy skills or items that I just didn’t. If anything, the modern obsession with microtransactions is just a capitalistic expression of that desire to get ahead without working for it inside the game.

I digress. This desire led me to start developing a bot for RuneScape back in 2013 or so, even though I had long since stopped playing the game in my leisure time. At that point it was an interest spawned from intellectual curiosity.

Given that I’m writing about this some years later, it should be plain that my efforts failed. It turned out that making a bot required more cross-disciplinary knowledge than I had at the time, even if the weeks that I spent trying to get it to work were educational. But now I work in robotics, somehow, helping make self-driving trucks at Embark. Going back and taking a stab again with my freshly photocopied college degree and five years of experience seemed like a good time, if only to brush up on my robot planning skills.

the first attempt

The bot’s name was Jane, that much was obvious. I’d been a big fan of the Ender’s Game series of novels in which a charismatic AI known as Jane helps out the main characters from time to time. So I had a name, which is almost as good as having a functional codebase.

For those unfamiliar with how RuneScape bots work, it comes down to the following process:

  1. Download the official Java game client .jar file.
  2. Unpack it and attempt to deobfuscate the code.
  3. Look for patterns that indicate the functionality of a given method, class, or variable.
  4. Inject instructions into a class or method to read or write data.
  5. Draw the rest of the owl.

The code here, which I wrote during my initial attempt back in 2013, does most of 1-4. The whole idea is that you understand enough of the patterns of the official game client such that you can implement a useful API for all of your botmaking dreams.

For the most part this just comes down to time; the client is actually rather simple and there is plenty of information out there regarding how it functions, if not in documentation then at least in code. The other frustration arises when the game client changes; your “hooks”, as these points of injection are called, may break, hindering the functionality of your bot until you can adjust to the new patterns. This means that any code you write, at least below the level of abstraction of the API, has an expiration date about as long as a gallon of milk.

The tools for accomplishing 1-4 are actually pretty simple, too. For looking at nice, decompiled output of the official client I recommend jd-gui and for manipulating JVM bytecode take a look at ASM. Here’s an example of injecting a getter for a particular class:

// Makes a new public method called getDefinition, which returns a custom interface
// that the higher-level API can use.
MethodNode getter = new MethodNode(Opcodes.ACC_PUBLIC, "getDefinition", "()Lcom/sqweebloid/interfaces/NPCDefinition;", null, null);

// Load a reference from a local variable.
getter.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
// Get a field of the object pointed to by that reference.
getter.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, fn.name, fn.desc));
// Return it.
getter.instructions.add(new InsnNode(Opcodes.ARETURN));

For those of you familiar with the general process of injecting instructions into executables, this shouldn’t come as a big surprise. In fact, it’s quite a bit easier in the JVM than say, x86, because the level of abstraction is a lot higher. This page has a good listing of instructions and the Wikipedia page was a fantastic resource back when I worked on this.

All I did back during my first attempt was get this rudimentary API up and running with a little bit of clicking. It was fun, but the amount of work that would be required in order to get it up and running was more than a high school student wanted to spend on a game they didn’t play anymore. That brings us to Jane’s rebirth now in 2018.

take two

I still don’t play RuneScape or really any videogames. Whenever I sit down and try, it’s only a matter of time before I realize that most games are either a) building up some obscure corner of muscle memory reaction time; or b) systems optimization. There’s something to be said about great narratives, which I’ve certainly found in games like the Bioshock series, but those are just exceptions to the rule. However, nostalgia is a hell of a drug. So I sat down to make another Old School RuneScape bot.

Though some of the analyzers I wrote in part one somehow still worked, it turned out that there’s now a strong open-source effort to publish and maintain the hooks the analyzers looked for. This meant that I didn’t need to do the leg work of digging through deobfuscated code. Boo-yah. All I needed to do was come up with a way of providing human-like input and get all of my information about game state from the open-source API’s.

Movement

It turns out that the hardest part of the problem wasn’t building the API, it was trying to make the bot act like a human. I guess that academically I could have figured that out before starting, but I ended up spending hundreds of hours just trying to make the bot do things in a humanlike way. But let’s skip over that for now.

It turned out that just about every task in RuneScape could be reduced to the following:

  1. Find a game object, NPC, or interface element.
  2. Click it.
  3. Return to step 1.

Duh. But what this meant is that much of the interaction with the game world could be abstracted into composed tasks of clicking on something and waiting for the state to change. Unlike games like World of Warcraft, RuneScape seems almost uniquely intended for bots due to its simplicity.

This may seem obvious, but it meant that all I had to do was a) come up with a sound API for clicking, typing, and waiting; and b) make it easy to interface with any clickable game entity. I ended up with something akin to a DSL:

Loadout gathering = new Loadout();
gathering.hasFreeSpots(1);

machine.state(State.FINDING)
  .base()
  .enter(() -> {
      ensure(gathering);
      object(OAK).interact();
      sleep().some();
      })
.to(State.CUTTING).when(() -> isCutting());

machine.state(State.CUTTING)
  .enter(() -> {
      sleep().most();
      })
.to(State.FINDING).when(() -> !isCutting());

There’s some other stuff I should go into here. The Loadout class is a way of ensuring that the player’s inventory matches a given set of predicates, such as having five logs and a hatchet in their inventory. If one of the conditions isn’t true, the ensure(Loadout loadout) method will return to the bank and make it so the inventory matches up to what it should. This worked even if the character was a 10 minute walk from the bank because of the way I set up movement.

This meant that I could reduce gathering skills like mining, woodcutting, and fishing to just a few lines of code. Abstraction is cool as hell.

I’m skipping over a lot of details that I’ll just cover in bullet points here:

  • RuneScape uses a region system with globally unique coordinates. This made it easy to set up a graph of map-wide nodes that the bot could run depth-first search on and get paths to other parts of the world.
  • I used Ansible to provision cloud servers that would occasionally log in and run the bot(s) for a few hours.
  • A big challenge to overcome was only clicking on entities that the player could actually path to. The OSRS client has the entire “region” in memory including all blocking objects, so I just implemented A* search to find good paths to things.
  • I was working on generating humanlike mouse movement with an RNN, but lost interest. It seems like it would be worthwhile for this sort of thing.

conclusion

Banning

I was never in it for the gold, it was just fun to play around with bots for a while. I ended up having half a dozen or so accounts, only one of which got banned. I learned a lot about making robots smarter, even if RuneScape isn’t stochastic and I didn’t have to deal with perception.