-
Notifications
You must be signed in to change notification settings - Fork 11
2. Reverse Engineering OSRS c Client: Simulating Menu Actions
20250419-1402-51.7861779.mp4
In this post, I'll walk through how I reverse engineered the native Old School RuneScape (OSRS) client to simulate in-game menu actions like "Talk-to" using Frida. It started with a string search, turned into low-level function analysis, and ended with full control over in-game interactions.
Everything began with the simple idea: search for the string "Walk here" in Ghidra. Since all menu options in OSRS like "Talk-to", "Examine", etc., are built around strings, it's a good entry point.
To do this:
- Open the binary in Ghidra.
- Open the Defined Strings window (Window → Defined Strings).
- Filter or search for "Walk here".
- Once found, double-click the address (1409f9308 in my case).
You'll see something like this in the disassembly:
1409f9308 57 61 6c 6b 20 68 65 72 65 ds "Walk here"
To the right, you'll notice XREFs (cross-references) – places in the code that use this string.
XREF[4,6]:
FUN_140009d40
FUN_140099db0
After taking a look at each of the functions I quickly noticed that FUN_140099db0 had a lot of paramaters and was a big function. This was in line with the 216 leaked debug build. (https://rune-server.org/threads/the-great-nxt-osrs-dump-thread.706428/)
This tells us that the string is referenced inside FUN_140099db0. That's our function of interest.
Now that we know FUN_140099db0 handles menu actions, we attach to it using Frida:
Interceptor.attach(ptr("0x140099db0"), {
onEnter(args) {
for (let i = 0; i <= 10; i++) {
console.log(`param ${i} ${args[i]}`);
}
}
});
After hooking, I clicked on the Ranged combat tutor NPC in-game and got the following output:
param 0 0x1479828c730
param 1 0x1f
param 2 0x25
param 3 0x9
param 4 0x13e
param 5 0xffffffff
param 6 0x2262fff450
param 7 0x147bbef03e0
param 8 0x305
param 9 0x20c
param 10 0x2262fff440
Here's where the fun begins: figuring out what each parameter means.
I started clicking the same NPC from different angles and noting which values changed:
- coordinates changed.
- Screen coordinates changed.
- The menu index stayed consistent.
I also clicked different NPCs, different actions ("Talk-to", "Claim"), and compared the logs. I slowly matched parameters by trial and error, then cross-referenced the DoAction function in Ghidra to see how those arguments were used.
To help with this, I also asked AI to analyze the function based on the values I saw.
Here's what each argument turned out to be:
| Frida param | Meaning |
|---|---|
| 0 | this pointer (Client instance) |
| 1 | Scene X tile where the entity is located |
| 2 | Scene Y tile |
| 3 | Menu index (slot for "Talk-to" = 9) |
| 4 | NPC spawn index (e.g. 318 for the tutor) |
| 5 | MinimenuSubject enum (always -1 for basic actions) |
| 6 | Pointer to the action string ("Talk-to", "Claim", etc.) |
| 7 | Pointer to a world coordinate struct (CoordType) |
| 8 | Screen X position of the mouse click |
| 9 | Screen Y position |
| 10 | Pointer to shared_ptr of the target |
After getting all parameters mapped, I wrote a Frida script to invoke the function. At first, it didn't work — the handler was called, but nothing happened in-game.
Turned out: I had swapped the enum and the string pointer. Classic.
Here's the fixed version that worked:
const DoAction = new NativeFunction(ptr("0x140099db0"), 'void', [
'pointer', // this
'int', // sceneX
'int', // sceneY
'int', // menuIndex
'int', // npc Index
'int', // subject (always -1)
'pointer', // pointer to action string
'pointer', // worldCoordPtr
'int', // screenX
'int', // screenY
'pointer' // worldview
]);
DoAction(
ptr("0x1479828c730"), // Client this
50, 55, // Scene X, Y
9, // Menu index ("Talk-to")
318, // NPC index
-1, // MinimenuSubject
ptr("0x2262fff450"), // Pointer to "Talk-to"
ptr("0x147bbef03e0"), // CoordType pointer
773, 524, // Screen X, Y
ptr("0x2262fff440") // worldView
);
Boom — it worked. My character started walking to the NPC and talking just like a normal in-game click.
What started with a basic string search for "Walk here" turned into a full reverse engineering process:
- Searching for references in Ghidra
- Hooking the native function with Frida
- Logging and analyzing parameters through repeated interaction
- Decompiling and verifying with AI
- Finally, successfully invoking it
With this in place, you can now simulate any in-game menu action like "Talk-to", "Claim", "Examine", and more.