Skip to content

Manual Client Mapping

This document will cover how to map each field from Kraken's API fully manually in case the automated updater partially or fully fails in the future. All examples used in this document are from the 238 revision of the client. This guide won't cover how to retrieve the client JAR file, decompiling tools, or any other tools that are required. This guide will only cover how to search through the client classes and map the fields manually.

Some tools that are indispensable for this process are:

  • IntelliJ IDEA's built-in decompiler (right click and add the jar as a library)
  • JStudio
  • Some self-written ASM-based bytecode analysis classes for searching through classes for specific methods and fields.

One quick note that may make your life easier, if someone else posts the Vitalite mappings (or mappings in Vitalite) format then you can look at ClientPacket class in the client and check the static constructor:

java
static {
    jb.ak = new jb(0, 7);
    jb.ag = new jb(1, 4);
    jb.az = new jb(2, 1);
    jb.av = new jb(3, 11);
    jb.ae = new jb(4, 9);
    jb.ah = new jb(5, 4);
    jb.aw = new jb(6, 22);
    jb.ay = new jb(7, -1);
    jb.as = new jb(8, 10);
    // ...
}

The first value passed into these functions is the ID, which can correspond directly to Vitalite's ID. For example, in Vitalite's packets.json:

java
  {
    "args": [
      "5",
      "worldY",
      "worldX",
      "ctrl"
    ],
    "name": "OP_WALK",
    "reads": [
      "readByte",
      "readShortAdd",
      "readShort",
      "readByteSub"
    ],
    "packet": {
      "length": -1,
      "id": 54
    },
    "writes": [
      "writeByte",
      "writeShortAdd",
      "writeShort",
      "writeByteSub"
    ]
  },

The ID is 54 which corresponds to the OP_WALK packet and this can be matched (purely as an example) to: jb.eo = new jb(54, 10); which can instantly tell you that eo is the OP_WALK packet without you having to do much.

Reflection Hooks

Reflection hooks are necessary to map first since they point to classes which are regularly used by packet sending functionality.

DoAction

It's best to use some sort of Bytecode scanning tool to find this class and method, but the easiest way to identify it is to search for the string: "Menu click op {} targ {} action {} id {} p0 {} p1 {} world {}". The class and method name that contains this string will be doAction.

The garbage value will always be the last parameter passed into the doAction method. Check and see how it is used in the method to determine the actual value. It may be checked against a constant value like:

java
if(var1 < -3478475) {
  throw new IllegalStateException();    
}

Thus, the garbage value can be any value > -3,478,475.

Mouse Hook DLL

To find the mouse hook open the client class and search for llimc. The method containing llimc = 0; is the method you are looking for. The class name will be client.

Client Log Field Name

Go into the doAction method. The method that logs the string "Menu click op {} targ {} action {} id {} p0 {} p1 {} world {}" will be the client log field name.

java
client.hy.trace("Menu click op {} targ {} action {} id {} p0 {} p1 {} world {}", v43);

In the example above it will be hy.

AddNode, IsaacCipher & ClientPacket

Within the doAction method search for a block like this: if (local17 == 46) { there are a lot of them, and you can choose any (its simply comparing menu actions to determine which packet to build like a giant switch statement).

Within the block you will find something like this:

java
local32 = gi.ak(jb.by, client.aq.av, -3);
local32.ay.bc(!!fi.au(client.ej, 82, 10), 791378474);
local32.ay.eb(arg3, -16);
client.aq.az(local32, -2024100322);
  • av from client.aq.av is the IsaacCipher instance.
  • az is the AddNode method from client.aq.az(...)
  • client is the AddNode class
  • -2024100322 will be the addNodeGarbageValue
  • jb from jb.by will be the client packet class name.

PacketWriter

Continuing from the doAction method example above the PacketWriter field name will be aq from client.aq.av. Deobfuscated this reads: client.PacketWriter.IsaacCipher.

The packet writer class name can be found by searching for the field name in the client class. Search for aq; within the client class and you will find something like:

java
public static final df aq;

df is the packet writer class name.

Class Containing PacketBufferNode

Contining from the doAction method example find the line: gi.ak(jb.by, ...). This is the method that constructs the packet buffer node.

  • gi will be the classContainingPacketBufferNodeName
  • ak will be the packetBufferNodeFactoryMethodName

Buffer & Extended Buffer

The simplest way to find the PacketBuffer class name is to use a Bytecode analysis tool to scan for the class implementing net/runelite/api/PacketBuffer. This will be the PacketBuffer class name (in our examples it will be xj).

To find the extended buffer class name, it will simply be the class that xj extends. In our examples it is xi.

Buffer Writes & Multipliers

Go into the extended buffer class xi and search for a method write that occurred in the doAction packet write block. For example:

java
local32 = gi.ak(jb.by, client.aq.av, -3);
local32.ay.bc(!!fi.au(client.ej, 82, 10), 791378474);
local32.ay.eb(arg3, -16);
client.aq.az(local32, -2024100322);

The following methods are both buffer writes:

  • bc()
  • eb()

You will see something like this:

java
public void eb(int arg0, byte arg1) {
    try {
        try {
            this.au = this.au + 228932457;
            this.al[i7] = (byte) arg0;
            this.au = this.au + 228932457;
            this.al[i16] = (byte) (arg0 >> 8);
            return;
        }
        catch (RuntimeException r20) {
            throw wk.ag(r20, "xi.eb(" + ')');
        }
    }
    catch (RuntimeException r20) {
        throw wk.ag(r20, "xi.eb(" + ')');
    }
}
  • au is the buffer offset field
  • al is the buffer array field.
  • 228932457 is the buffer offset multiplier

You may also come across some methods that look like this:

java
public int mq() {
    this.au = this.au + 686797371;
    int local1 = (this.al[-661977895 * this.au - 3] & 255) + ((this.al[-661977895 * this.au - 1] & 255) << 16) + ((this.al[this.au * -661977895 - 2] & 255) << 8);
    if (local1 > 8388607) {
        local1 = local1 - 16777216;
    }
    return local1;
}

-661977895 will be the index multiplier.

Mouse Handler

The mouse handler class will be the class that implement the java/awt/event/MouseListener interface (tj in this example). To find the last pressed field, go back to search for llimc in the client class to find the mouse click handler. You will see something like: long local2 = tj.af * 3767455460529623151L - -6682804461438542089L * client.gx;

This line provides five values:

  • tj = mouseHandlerlastPressedClass
  • af = mouseHandlerLastPressedField
  • 3767455460529623151L = mouseHandlerMultiplier
  • -6682804461438542089L = clientMillisMultiplier
  • gx = clientMillisField

PacketBufferNode Class & Field

In the doAction method find a block that sends any packet and look for a line like:

java
jm local31_1 = gi.ak(jb.bi, client.aq.av, -74);

This constructs the packet buffer node and thus jm is the packetBufferNodeClassName. Go into the jm class and search for the buffer class name (xj in this example). You will find a field like: public xj ay;. ay is the packetBufferFieldName.

If you go into the classContainingPacketBufferNodeName class and search for the packetBufferNodeFactoryMethodName i.e. gi.ak() from the example above you can find the packetBufferNodeGarbageValue.

We see a bunch of these within that method, and thus the garbage value is zero or anything less than 0.

java
if (arg2 >= 1) {
    throw new IllegalStateException();
}

Packets

The packets that require mapping are:

  • EVENT_MOUSE_CLICK
  • MOVE_GAMECLICK
  • RESUME_COUNTDIALOG
  • RESUME_STRINGDIALOG
  • RESUME_OBJDIALOG

EVENT_MOUSE_CLICK

Open the client class and search for llimc. You will find a block of code which looks like:

java
local6 = (int) local2;
local7 = gi.ak(jb.ds, client.aq.av, -57);
local7.ay.ed(local4, 2044774468);
xi.cu(local7.ay, local5, 356393318);
client.llimc = 0;
local7.ay.bc((byte) client.llimc, 768504370);
local7.ay.bw((local6 << 1) + (650181903 * tj.ay == 2), 1309338010);
client.aq.az(local7, -2131500547);

The obfuscated name will be the ds from the jb.ds parameter being passed to the ak method. The method names of the four parameters will be the four methods being called i.e:

  • ed
  • cu
  • bc
  • bw

To find the shifts go into the extendedBufferClassName (xi) and search for the methods that are used. Here is an example of the ed() method:

java
public void ed(int arg0, int arg1) {
    try {
        try {
            this.au = this.au + 228932457;
            this.al[i7] = (byte) (arg0 >> 8);
            this.au = this.au + 228932457;
            this.al[i18] = (byte) (arg0 + 128);
            return;
        }
        catch (RuntimeException r22) {
            throw wk.ag(r22, "xi.ed(" + ')');
        }
    }
    catch (RuntimeException r22) {
        throw wk.ag(r22, "xi.ed(" + ')');
    }
}

The buffer writes for this parameter in the packet are:

  • RIGHT_SHIFT 8
  • ADD 128

and can be seen by the following lines:

  • this.al[i7] = (byte) (arg0 >> 8);
  • this.al[i18] = (byte) (arg0 + 128);

This may be a good place to leverage AI to assist with identifying these if you provide the methods.

MOVE_GAMECLICK

This is a tough one to find. I really rely on the updater to map this packet specifically.

RESUME_COUNTDIALOG, RESUME_STRINGDIALOG, RESUME_OBJDIALOG

This is another challenging one to find. You will need to search through the client jar's classes for a class with a field of type: Calendar. It will also have something like this in the constructor:

java
v1[0] = "Jan";
v1[1] = "Feb";
v1[2] = "Mar";
v1[3] = "Apr";
v1[4] = "May";
v1[5] = "Jun";
v1[6] = "Jul";
v1[7] = "Aug";
v1[8] = "Sep";
v1[9] = "Oct";
v1[10] = "Nov";

Within this class search for the following integer values 3104,3105, 3106, or 3115. Those will map directly (CS2 Opcodes) to the following packets:

  • RESUME_COUNT (3104)
  • RESUME_STRING (3106)
  • RESUME_OBJ (3115)

Within each if/else block checking these integer values you will find the packet being sent. For example here is the 3115 block for the RESUME_OBJDIALOG packet:

java
} else if (var0 == 3115) {
    int var13 = au[(gz.ax -= -1684678759) * -776631127];
    jm var23 = gi.ak(jb.ex, client.aq.av, (byte)-77);
    var23.ay.bw(var13, 270381955);
    client.aq.az(var23, -2132508855);
    return 1;
}

This packet will be ex from the jb.ex parameter being passed to the ak method.

Note: It's easier to open this in Intellij's decompiler as JStudio is great but makes it hard to read this particular method.

Follow the same process to find the other dialog packets.

Login Hooks

The login hooks are required for Kraken plugins to be able to login to the game world automatically.

JX_* Fields

In the client class search for "JX_SESSION_ID" and find a block that looks like:

java
bo.lf = client.bl("JX_ACCESS_TOKEN");
mf.ld = client.bl("JX_REFRESH_TOKEN");
ay.lp = client.bl("JX_SESSION_ID");
cq.ll = client.bl("JX_CHARACTER_ID");
hz.aj(client.bl("JX_DISPLAY_NAME"), -124);

The session field name is lp and the class name is ay. This same process is repeated for the accountId being cq.ll

Note: The accountId in the packet mapping JSON file is the same as the character ID here. The refresh token and access token are not used.

To find the display name class and field hop into the hz.aj() method being called.

java
static void aj(String arg0, byte arg1) {
    try {
        try {
            bn.ct = rj.ae(arg0, -1699354081);
            return;
        }
        catch (RuntimeException r2) {
            throw wk.ag(r2, "hz.aj(" + ')');
        }
    }
    catch (RuntimeException r2) {
        throw wk.ag(r2, "hz.aj(" + ')');
    }
}

The display name class and field will be bn.ct seen in the method above.

Login Index

To find the login index go into the client class and search for the string: "Please enter your date of birth (DD/MM/YYYY)". You will find a line that looks like:

java
cl.ad("", "Please enter your date of birth (DD/MM/YYYY)", "", -37752315);
jz.ac(7, -88);

The login class name will be jz and the method name will be ac from this line: jz.ac(7, -88); The garbage value will be the last parameter passed into the method. In this example it's -88.

Account Check

The find the account check field and class names first search for: "RuneLite has been updated!", "Please restart your client." in the client class. You will be in the right method but have to scroll a bit to find this (probably ~45 lines or so).

java
if (!client.dm.ak(9)) {
    try {}
    catch (IOException i1673) {
        jp.ao(-2, 1021551372);
        ha.ih = bb.ij * -960016293;
        ha.ih = bo.id * 1683256539;
        client.in = client.in + 1874226503;
        ot.hw(cj.ak, 101);
        throw new IllegalStateException();
    }
    jp.ao(-2, 1021551372);
    return;
}

The account check field name will be the dm from the client.dm.ak(9) line. The account check class name should always be client.

Jagex & Legacy Fields

After you find the account check field name in the client class find the definition near the top of the client (where the field is defined). You will find a line that looks like: static aao dm;

aao will be the class names for both the jagex and legacy values. Step into the aao class and you will see the field definitions and static initializers that look like:

java
public static final aao ak;
public static final aao ag;
static final aao az;
static final aao av;
static final aao ae;
static final aao ah;
final int aw;
final int ay;

static {
    aao.ak = new aao(5, 0);
    aao.ag = new aao(3, 2);
    aao.az = new aao(0, 5);
    aao.av = new aao(4, 6);
    aao.ae = new aao(2, 7);
    aao.ah = new aao(1, 8);
}

The static final fields will be what we are looking for:

  • ak will be the legacy value
  • ag will be the jagex value

I don't know if the int values change each revision but this can be checked on revision 239. Simply, look for

  • ak = Legacy = 5, 0
  • ag = Jagex = 3, 2

and match them up next revision if the int values are the same.