···11+package com.jagex.runescape.collection
22+33+/**
44+ * A [Node] subclass that participates in a second linked list layer used by
55+ * [Queue] and [Cache]. The separate [nextQueue]/[prevQueue] pointers here
66+ * allow a node to be in both a [HashTable] bucket chain (via [Node.next]/
77+ * [Node.previous]) and a [Queue] eviction chain simultaneously — which is
88+ * exactly how the LRU [Cache] works: hash table for O(1) lookup, queue for
99+ * eviction ordering.
1010+ *
1111+ * In the original Java client, these were also named `next`/`prev` and relied
1212+ * on field shadowing. Kotlin doesn't allow field shadowing, so they're renamed
1313+ * here to make the dual-link design explicit.
1414+ */
1515+open class CacheableNode : Node() {
1616+ @JvmField var nextQueue: CacheableNode? = null
1717+ @JvmField var prevQueue: CacheableNode? = null
1818+1919+ fun clear() {
2020+ if (prevQueue == null) return
2121+ prevQueue!!.nextQueue = nextQueue
2222+ nextQueue!!.prevQueue = prevQueue
2323+ nextQueue = null
2424+ prevQueue = null
2525+ }
2626+}
···11+package com.jagex.runescape.collection
22+33+/**
44+ * Open-addressing hash table with chained buckets via intrusive [Node] links.
55+ *
66+ * Each bucket is a circular doubly-linked list with a sentinel node. The hash
77+ * function is simply `id AND (size - 1)`, which means [size] must be a power
88+ * of two for correct distribution. This matches the original client behavior.
99+ */
1010+class HashTable(@JvmField val size: Int) {
1111+ @JvmField val cache: Array<Node> = Array(size) { Node().also { it.next = it; it.previous = it } }
1212+1313+ fun get(id: Long): Node? {
1414+ val bucket = cache[(id and (size - 1).toLong()).toInt()]
1515+ var node = bucket.next
1616+ while (node !== bucket) {
1717+ if (node!!.id == id) return node
1818+ node = node.next
1919+ }
2020+ return null
2121+ }
2222+2323+ fun put(node: Node, id: Long) {
2424+ if (node.previous != null) node.remove()
2525+ val bucket = cache[(id and (size - 1).toLong()).toInt()]
2626+ node.previous = bucket.previous
2727+ node.next = bucket
2828+ node.previous!!.next = node
2929+ node.next!!.previous = node
3030+ node.id = id
3131+ }
3232+}
···11+package com.jagex.runescape.collection
22+33+/**
44+ * Base node for intrusive linked data structures (hash tables, linked lists).
55+ *
66+ * "Intrusive" means the node pointers live directly on the data object rather
77+ * than in a wrapper. This avoids an extra allocation per element — the same
88+ * pattern the original client uses for every cacheable asset and queue entry.
99+ *
1010+ * [id] is the hash key used by [HashTable] for O(1) bucket lookup.
1111+ * [next]/[previous] form a circular doubly-linked list within each bucket.
1212+ */
1313+open class Node {
1414+ @JvmField var id: Long = 0
1515+ @JvmField var next: Node? = null
1616+ @JvmField var previous: Node? = null
1717+1818+ fun remove() {
1919+ if (previous != null) {
2020+ previous!!.next = next
2121+ next!!.previous = previous
2222+ next = null
2323+ previous = null
2424+ }
2525+ }
2626+}
···11-package com.jagex.runescape.config;
22-33-/**
44- * Created by Promises on 16/06/17.
55- */
66-public class Actions {
77- /**
88- * The action type for when the examine menu action has been selected.
99- */
1010- public static final int EXAMINE_ITEM = 1094;
1111-1212- /**
1313- * The action type for when a Widget that toggles a setting value has been interacted with.
1414- */
1515- public static final int TOGGLE_SETTING_WIDGET = 890;
1616-1717- /**
1818- * The action type to accept a challenge from a player.
1919- */
2020- public static final int ACCEPT_CHALLENGE = 695;
2121-2222- /**
2323- * The action type to accept a trade from a player.
2424- */
2525- public static final int ACCEPT_TRADE = 544;
2626-2727- /**
2828- * The action type to add a player to the friends list.
2929- */
3030- public static final int ADD_FRIEND = 762;
3131-3232- /**
3333- * The action type to add a player to the ignore list.
3434- */
3535- public static final int ADD_IGNORE = 574;
3636-3737- /**
3838- * The action type that indicates a usable widget has been interacted with (i.e. clicked).
3939- */
4040- public static final int USABLE_WIDGET = 70;
4141-4242- /**
4343- * The action type to remove a player from the ignore list.
4444- */
4545- public static final int REMOVE_IGNORE = 859;
4646-4747- /**
4848- * The action that sends the continue dialogue frame.
4949- */
5050- public static final int CLICK_TO_CONTINUE = 575;
5151-5252- /**
5353- * The action type that closes the open widgets.
5454- */
5555- public static final int CLOSE_WIDGETS = 639;
5656-5757- /**
5858- * The action type to remove a player from the friends list.
5959- */
6060- public static final int REMOVE_FRIEND = 775;
6161-6262- /**
6363- * The action type for when a Widget that resets a setting value has been interacted with.
6464- */
6565- public static final int RESET_SETTING_WIDGET = 518;
6666-6767- /**
6868- * The action type for sending a private message.
6969- */
7070- public static final int PRIVATE_MESSAGE = 984;
7171-}
···11+package com.jagex.runescape.config
22+33+/**
44+ * Menu action type constants. Each value is an opcode the client sends to the
55+ * server when the player selects a context menu option. The server uses these
66+ * to dispatch the correct handler (e.g. trade request, friend list add, etc.).
77+ */
88+object Actions {
99+ const val EXAMINE_ITEM = 1094
1010+ const val TOGGLE_SETTING_WIDGET = 890
1111+ const val ACCEPT_CHALLENGE = 695
1212+ const val ACCEPT_TRADE = 544
1313+ const val ADD_FRIEND = 762
1414+ const val ADD_IGNORE = 574
1515+ const val USABLE_WIDGET = 70
1616+ const val REMOVE_IGNORE = 859
1717+ const val CLICK_TO_CONTINUE = 575
1818+ const val CLOSE_WIDGETS = 639
1919+ const val REMOVE_FRIEND = 775
2020+ const val RESET_SETTING_WIDGET = 518
2121+ const val PRIVATE_MESSAGE = 984
2222+}
···11-package com.jagex.runescape.net;
22-33-public class ISAACCipher {
44-55-66- /**
77- * The golden ratio.
88- */
99- private static final int GOLDEN_RATIO = 0x9e3779b9;
1010-1111- /**
1212- * The log of the size of the result and memory arrays.
1313- */
1414- private static final int SIZEL = 8;
1515-1616- /**
1717- * The size of the result and memory arrays.
1818- */
1919- private static final int SIZE = 1 << ISAACCipher.SIZEL;
2020-2121- /**
2222- * A mask for pseudorandom lookup.
2323- */
2424- private static int MASK = (ISAACCipher.SIZE - 1) << 2;
2525-2626- /**
2727- * The count through the results in the results array.
2828- */
2929- private int count;
3030-3131- /**
3232- * The results given to the user.
3333- */
3434- private final int[] rsl;
3535-3636- /**
3737- * The internal state.
3838- */
3939- private final int[] mem;
4040-4141- /**
4242- * The accumulator.
4343- */
4444- private int a;
4545-4646- /**
4747- * The last result.
4848- */
4949- private int b;
5050-5151- /**
5252- * The counter.
5353- */
5454- private int c;
5555-5656- /**
5757- * Creates the random number generator without an initial seed.
5858- */
5959- public ISAACCipher() {
6060- mem = new int[ISAACCipher.SIZE];
6161- rsl = new int[ISAACCipher.SIZE];
6262- init(false);
6363- }
6464-6565- /**
6666- * Creates the random number generator with the specified seed.
6767- *
6868- * @param seed
6969- * The seed.
7070- */
7171- public ISAACCipher(int[] seed) {
7272- mem = new int[ISAACCipher.SIZE];
7373- rsl = new int[ISAACCipher.SIZE];
7474- for (int i = 0; i < seed.length; ++i) {
7575- rsl[i] = seed[i];
7676- }
7777- init(true);
7878- }
7979-8080- /**
8181- * Generates 256 results.
8282- */
8383- private void isaac() {
8484- int i, j, x, y;
8585-8686- b += ++c;
8787- for (i = 0, j = ISAACCipher.SIZE / 2; i < ISAACCipher.SIZE / 2;) {
8888- x = mem[i];
8989- a ^= a << 13;
9090- a += mem[j++];
9191- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
9292- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
9393-9494- x = mem[i];
9595- a ^= a >>> 6;
9696- a += mem[j++];
9797- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
9898- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
9999-100100- x = mem[i];
101101- a ^= a << 2;
102102- a += mem[j++];
103103- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
104104- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
105105-106106- x = mem[i];
107107- a ^= a >>> 16;
108108- a += mem[j++];
109109- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
110110- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
111111- }
112112-113113- for (j = 0; j < ISAACCipher.SIZE / 2;) {
114114- x = mem[i];
115115- a ^= a << 13;
116116- a += mem[j++];
117117- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
118118- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
119119-120120- x = mem[i];
121121- a ^= a >>> 6;
122122- a += mem[j++];
123123- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
124124- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
125125-126126- x = mem[i];
127127- a ^= a << 2;
128128- a += mem[j++];
129129- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
130130- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
131131-132132- x = mem[i];
133133- a ^= a >>> 16;
134134- a += mem[j++];
135135- mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b;
136136- rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x;
137137- }
138138- }
139139-140140- /**
141141- * Initialises this random number generator.
142142- *
143143- * @param flag
144144- * Set to {@code true} if a seed was passed to the constructor.
145145- */
146146- private void init(boolean flag) {
147147- int i;
148148- int a, b, c, d, e, f, g, h;
149149- a = b = c = d = e = f = g = h = ISAACCipher.GOLDEN_RATIO;
150150-151151- for (i = 0; i < 4; ++i) {
152152- a ^= b << 11;
153153- d += a;
154154- b += c;
155155- b ^= c >>> 2;
156156- e += b;
157157- c += d;
158158- c ^= d << 8;
159159- f += c;
160160- d += e;
161161- d ^= e >>> 16;
162162- g += d;
163163- e += f;
164164- e ^= f << 10;
165165- h += e;
166166- f += g;
167167- f ^= g >>> 4;
168168- a += f;
169169- g += h;
170170- g ^= h << 8;
171171- b += g;
172172- h += a;
173173- h ^= a >>> 9;
174174- c += h;
175175- a += b;
176176- }
177177-178178- for (i = 0; i < ISAACCipher.SIZE; i += 8) { /*
179179- * fill in mem[] with messy
180180- * stuff
181181- */
182182- if (flag) {
183183- a += rsl[i];
184184- b += rsl[i + 1];
185185- c += rsl[i + 2];
186186- d += rsl[i + 3];
187187- e += rsl[i + 4];
188188- f += rsl[i + 5];
189189- g += rsl[i + 6];
190190- h += rsl[i + 7];
191191- }
192192- a ^= b << 11;
193193- d += a;
194194- b += c;
195195- b ^= c >>> 2;
196196- e += b;
197197- c += d;
198198- c ^= d << 8;
199199- f += c;
200200- d += e;
201201- d ^= e >>> 16;
202202- g += d;
203203- e += f;
204204- e ^= f << 10;
205205- h += e;
206206- f += g;
207207- f ^= g >>> 4;
208208- a += f;
209209- g += h;
210210- g ^= h << 8;
211211- b += g;
212212- h += a;
213213- h ^= a >>> 9;
214214- c += h;
215215- a += b;
216216- mem[i] = a;
217217- mem[i + 1] = b;
218218- mem[i + 2] = c;
219219- mem[i + 3] = d;
220220- mem[i + 4] = e;
221221- mem[i + 5] = f;
222222- mem[i + 6] = g;
223223- mem[i + 7] = h;
224224- }
225225-226226- if (flag) { /* second pass makes all of seed affect all of mem */
227227- for (i = 0; i < ISAACCipher.SIZE; i += 8) {
228228- a += mem[i];
229229- b += mem[i + 1];
230230- c += mem[i + 2];
231231- d += mem[i + 3];
232232- e += mem[i + 4];
233233- f += mem[i + 5];
234234- g += mem[i + 6];
235235- h += mem[i + 7];
236236- a ^= b << 11;
237237- d += a;
238238- b += c;
239239- b ^= c >>> 2;
240240- e += b;
241241- c += d;
242242- c ^= d << 8;
243243- f += c;
244244- d += e;
245245- d ^= e >>> 16;
246246- g += d;
247247- e += f;
248248- e ^= f << 10;
249249- h += e;
250250- f += g;
251251- f ^= g >>> 4;
252252- a += f;
253253- g += h;
254254- g ^= h << 8;
255255- b += g;
256256- h += a;
257257- h ^= a >>> 9;
258258- c += h;
259259- a += b;
260260- mem[i] = a;
261261- mem[i + 1] = b;
262262- mem[i + 2] = c;
263263- mem[i + 3] = d;
264264- mem[i + 4] = e;
265265- mem[i + 5] = f;
266266- mem[i + 6] = g;
267267- mem[i + 7] = h;
268268- }
269269- }
270270-271271- isaac();
272272- count = ISAACCipher.SIZE;
273273- }
274274-275275- /**
276276- * Gets the next random value.
277277- *
278278- * @return The next random value.
279279- */
280280- public int nextInt() {
281281- if (0 == count--) {
282282- isaac();
283283- count = ISAACCipher.SIZE - 1;
284284- }
285285- return rsl[count];
286286- }
287287-288288-}
···11+package com.jagex.runescape.net
22+33+/**
44+ * ISAAC (Indirection, Shift, Accumulate, Add, and Count) stream cipher.
55+ * Generates a cryptographically secure pseudorandom sequence from a seed.
66+ * Used to obfuscate packet opcodes — each opcode is XOR'd with the next
77+ * value from the stream, preventing trivial packet sniffing.
88+ *
99+ * The algorithm produces 256 results per round via the [isaac] function,
1010+ * then serves them one at a time via [nextInt]. When the buffer is exhausted,
1111+ * another round is generated automatically.
1212+ */
1313+class ISAACCipher {
1414+1515+ private var count: Int = 0
1616+ private val rsl: IntArray
1717+ private val mem: IntArray
1818+ private var a: Int = 0
1919+ private var b: Int = 0
2020+ private var c: Int = 0
2121+2222+ constructor() {
2323+ mem = IntArray(SIZE)
2424+ rsl = IntArray(SIZE)
2525+ init(false)
2626+ }
2727+2828+ constructor(seed: IntArray) {
2929+ mem = IntArray(SIZE)
3030+ rsl = IntArray(SIZE)
3131+ for (i in seed.indices) {
3232+ rsl[i] = seed[i]
3333+ }
3434+ init(true)
3535+ }
3636+3737+ private fun isaac() {
3838+ b += ++c
3939+ var i = 0
4040+ var j = SIZE / 2
4141+ while (i < SIZE / 2) {
4242+ var x = mem[i]
4343+ a = a xor (a shl 13)
4444+ a += mem[j++]
4545+ var y = mem[(x and MASK) ushr 2] + a + b
4646+ mem[i] = y
4747+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
4848+ rsl[i++] = b
4949+5050+ x = mem[i]
5151+ a = a xor (a ushr 6)
5252+ a += mem[j++]
5353+ y = mem[(x and MASK) ushr 2] + a + b
5454+ mem[i] = y
5555+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
5656+ rsl[i++] = b
5757+5858+ x = mem[i]
5959+ a = a xor (a shl 2)
6060+ a += mem[j++]
6161+ y = mem[(x and MASK) ushr 2] + a + b
6262+ mem[i] = y
6363+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
6464+ rsl[i++] = b
6565+6666+ x = mem[i]
6767+ a = a xor (a ushr 16)
6868+ a += mem[j++]
6969+ y = mem[(x and MASK) ushr 2] + a + b
7070+ mem[i] = y
7171+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
7272+ rsl[i++] = b
7373+ }
7474+7575+ j = 0
7676+ while (j < SIZE / 2) {
7777+ var x = mem[i]
7878+ a = a xor (a shl 13)
7979+ a += mem[j++]
8080+ var y = mem[(x and MASK) ushr 2] + a + b
8181+ mem[i] = y
8282+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
8383+ rsl[i++] = b
8484+8585+ x = mem[i]
8686+ a = a xor (a ushr 6)
8787+ a += mem[j++]
8888+ y = mem[(x and MASK) ushr 2] + a + b
8989+ mem[i] = y
9090+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
9191+ rsl[i++] = b
9292+9393+ x = mem[i]
9494+ a = a xor (a shl 2)
9595+ a += mem[j++]
9696+ y = mem[(x and MASK) ushr 2] + a + b
9797+ mem[i] = y
9898+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
9999+ rsl[i++] = b
100100+101101+ x = mem[i]
102102+ a = a xor (a ushr 16)
103103+ a += mem[j++]
104104+ y = mem[(x and MASK) ushr 2] + a + b
105105+ mem[i] = y
106106+ b = mem[(y ushr SIZEL and MASK) ushr 2] + x
107107+ rsl[i++] = b
108108+ }
109109+ }
110110+111111+ private fun init(flag: Boolean) {
112112+ var a = GOLDEN_RATIO; var b = GOLDEN_RATIO; var c = GOLDEN_RATIO; var d = GOLDEN_RATIO
113113+ var e = GOLDEN_RATIO; var f = GOLDEN_RATIO; var g = GOLDEN_RATIO; var h = GOLDEN_RATIO
114114+115115+ for (i in 0 until 4) {
116116+ a = a xor (b shl 11); d += a; b += c
117117+ b = b xor (c ushr 2); e += b; c += d
118118+ c = c xor (d shl 8); f += c; d += e
119119+ d = d xor (e ushr 16); g += d; e += f
120120+ e = e xor (f shl 10); h += e; f += g
121121+ f = f xor (g ushr 4); a += f; g += h
122122+ g = g xor (h shl 8); b += g; h += a
123123+ h = h xor (a ushr 9); c += h; a += b
124124+ }
125125+126126+ var i = 0
127127+ while (i < SIZE) {
128128+ if (flag) {
129129+ a += rsl[i]; b += rsl[i + 1]; c += rsl[i + 2]; d += rsl[i + 3]
130130+ e += rsl[i + 4]; f += rsl[i + 5]; g += rsl[i + 6]; h += rsl[i + 7]
131131+ }
132132+ a = a xor (b shl 11); d += a; b += c
133133+ b = b xor (c ushr 2); e += b; c += d
134134+ c = c xor (d shl 8); f += c; d += e
135135+ d = d xor (e ushr 16); g += d; e += f
136136+ e = e xor (f shl 10); h += e; f += g
137137+ f = f xor (g ushr 4); a += f; g += h
138138+ g = g xor (h shl 8); b += g; h += a
139139+ h = h xor (a ushr 9); c += h; a += b
140140+ mem[i] = a; mem[i + 1] = b; mem[i + 2] = c; mem[i + 3] = d
141141+ mem[i + 4] = e; mem[i + 5] = f; mem[i + 6] = g; mem[i + 7] = h
142142+ i += 8
143143+ }
144144+145145+ if (flag) {
146146+ i = 0
147147+ while (i < SIZE) {
148148+ a += mem[i]; b += mem[i + 1]; c += mem[i + 2]; d += mem[i + 3]
149149+ e += mem[i + 4]; f += mem[i + 5]; g += mem[i + 6]; h += mem[i + 7]
150150+ a = a xor (b shl 11); d += a; b += c
151151+ b = b xor (c ushr 2); e += b; c += d
152152+ c = c xor (d shl 8); f += c; d += e
153153+ d = d xor (e ushr 16); g += d; e += f
154154+ e = e xor (f shl 10); h += e; f += g
155155+ f = f xor (g ushr 4); a += f; g += h
156156+ g = g xor (h shl 8); b += g; h += a
157157+ h = h xor (a ushr 9); c += h; a += b
158158+ mem[i] = a; mem[i + 1] = b; mem[i + 2] = c; mem[i + 3] = d
159159+ mem[i + 4] = e; mem[i + 5] = f; mem[i + 6] = g; mem[i + 7] = h
160160+ i += 8
161161+ }
162162+ }
163163+164164+ isaac()
165165+ count = SIZE
166166+ }
167167+168168+ fun nextInt(): Int {
169169+ if (0 == count--) {
170170+ isaac()
171171+ count = SIZE - 1
172172+ }
173173+ return rsl[count]
174174+ }
175175+176176+ companion object {
177177+ private const val GOLDEN_RATIO = 0x9e3779b9.toInt()
178178+ private const val SIZEL = 8
179179+ private const val SIZE = 1 shl SIZEL
180180+ private val MASK = (SIZE - 1) shl 2
181181+ }
182182+}
···11-package com.jagex.runescape.net.requester;
22-33-import com.jagex.runescape.collection.CacheableNode;
44-55-public class OnDemandNode extends CacheableNode {
66-77- public int type;
88- public int id;
99- public int cyclesSinceSend;
1010- public byte[] buffer;
1111- public boolean immediate = true;
1212-}
···11+package com.jagex.runescape.net.requester
22+33+import com.jagex.runescape.collection.CacheableNode
44+55+/**
66+ * Represents a single on-demand file request. Tracks the file type/id,
77+ * request priority, and holds the received data buffer. Lives in the
88+ * [CacheableNode] hierarchy so it can be stored in [com.jagex.runescape.collection.Queue].
99+ */
1010+class OnDemandNode : CacheableNode() {
1111+ @JvmField var type: Int = 0
1212+ @JvmField var fileId: Int = 0
1313+ @JvmField var cyclesSinceSend: Int = 0
1414+ @JvmField var buffer: ByteArray? = null
1515+ @JvmField var immediate: Boolean = true
1616+}