The current RuneScape 2 protocol
the runescape protocol is split up into raw data (bare) or what many call frames (header).
Every revision of the RuneScape 2 protocol comes with a new array holding lengths for every frame used. examples:
RS2 #317:
Code:
public static final int[] packetSizes = {
0, 0, 0, 0, 6, 0, 0, 0, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, -2, 4, 3, 0, 0, 0,
0, 0, 0, 0, 5, 0, 0, 6, 0, 0,
9, 0, 0, -2, 0, 0, 0, 0, 0, 0,
-2, 1, 0, 0, 2, -2, 0, 0, 0, 0,
6, 3, 2, 4, 2, 4, 0, 0, 0, 4,
0, -2, 0, 0, 7, 2, 0, 6, 0, 0,
0, 0, 0, 0, 0, 0, 0, 2, 0, 1,
0, 2, 0, 0, -1, 4, 1, 0, 0, 0,
1, 0, 0, 0, 2, 0, 0, 15, 0, 0,
0, 4, 4, 0, 0, 0, -2, 0, 0, 0,
0, 0, 0, 0, 6, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0, 0, 14, 0, 0,
0, 4, 0, 0, 0, 0, 3, 0, 0, 0,
4, 0, 0, 0, 2, 0, 6, 0, 0, 0,
0, 3, 0, 0, 5, 0, 10, 6, 0, 0,
0, 0, 0, 0, 0, 2, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, -1, 0, 0, 0,
4, 0, 0, 0, 0, 0, 3, 0, 2, 0,
0, 0, 0, 0, -2, 7, 0, 0, 2, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
8, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, -2, 0, 0, 0, 0, 6, 0, 4, 3,
0, 0, 0, -1, 6, 0, 0
};
RS2 #508:
Code:
static int[] packetSizes = { 0, 3, 0, 0, 0, 0, 6, 0, 2, 0, 5, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 5, 0, 0, 0, 15, 4, 0, 0, 0, 0, 10, 0, 2, 0, 0, 4, 6, 4, 0, 0, 0, 0, -1, 0, 10, 7, 0, 0, 0, 0, 24, 0, 3, 0, 5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 7, 0, 0, 0, 0, 0, 1, 3, 4, 0, 0, 0, 0, 0, 0, 0, 6, 4, 0, 15, 0, 0, 1, 0, -2, 8, 5, 8, -1, 0, 0, -2, 0, 0, 0, 0, 0, 8, 0, -1, 0, 0, -2, 0, 20, 0, -2, 10, 0, -2, 0, -1, 0, 2, 0,
0, -1, 0, 0, -2, 4, -1, 0, 8, 0, 0, 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, -2, 1, 4, -2, 8, 0, -1, 2, -1, -2, 0, 0, 0, 0, 6, 0, 3, 0, 0, 0, 2, -1, 0, 6, 0, 7, 2, 0, 0, 0, 0, 3, 0, 0, 0, -1, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, -2, 6, -1, 4, 0, 0, -2, 12, 0, 0, 1, 9, 0, -1, 0, 0, 5, 0, -1, 0, 0, 0, 0, 3, -2, 0, 0, 0, 0, 6, 4, 5, 6, 2, 0, 5, -1, 0, 0, -2 };
The table of lengths is indexed by a frame's opcode, type, or id. These tell the client what type of frame the current is. The opcode of a frame is the first field in a frame's header, and is an unsigned 1-byte integer.
As you see, some of the frame sizes in these lists are negative, and it's impossible for a frame to be negatively sized.. these frames are variable-sized. With variable sized frames, an extra field is put into the frame's header which denotes the frame's payload's size.
There are two types of variable-sized frames: short and byte. Each are depicted in the packet lengths lists by a -2 and -1, respectively.
Their names come from the type of datatype and size they are written as in the header; a short variable-sized frame has the payload size written as an unsigned 2-byte short integer, while the byte variable-sized frame has the payload size written as an unsigned 1-byte integer.
Code:
Code:
if (Class49.packetOpcode == -1)
{
avail--;
PlayerDefinition.connection.read(1, 0, Class68_Sub13_Sub8.incomingVector.data);
Class68_Sub13_Sub8.incomingVector.position = 0;
Class49.packetOpcode = Class68_Sub13_Sub8.incomingVector.getPacketOpcode();
Class106.packetSize = Class9.packetSizes[Class49.packetOpcode];
}
if (Class106.packetSize == -1)
{
if (avail <= 0)
return false;
PlayerDefinition.connection.read(1, 0, Class68_Sub13_Sub8.incomingVector.data);
Class106.packetSize = 0xff & Class68_Sub13_Sub8.incomingVector.data[0];
avail--;
}
if (Class106.packetSize == -2)
{
if (avail > 1)
{
avail -= 2;
PlayerDefinition.connection.read(2, 0, Class68_Sub13_Sub8.incomingVector.data);
Class68_Sub13_Sub8.incomingVector.position = 0;
Class106.packetSize = Class68_Sub13_Sub8.incomingVector.readUShort();
} else
return false;
}
Frame structures:
A statically sized frame has the structure:
Code:
{
ubyte opcode;
byte payload[size];
}
where size is equivalent to the packet's size in the packet lengths list.
A short variable-sized frame:
Code:
{
ubyte opcode;
ushort size;
byte payload[size];
}
And lastly, a byte variable-sized frame:
Code:
{
ubyte opcode;
ubyte size;
byte payload[size];
}
After the client has successfully read a frame, it then checks against it's opcode to discover what type of data lies in the payload, and then begins to read.
JaGeX employs several different types of datatypes in their Buffer, Stream, Packet, etc. class.
They also employ several types of encryption. Among them include two methods: RSA and ISAAC ciphering.
RSA:
JaGeX only uses RSA upon the login block, which contains the ISAAC cipher seed, username, and password, among other things. This helps stop people from packet sniffing to steal passwords, as well as to kill proxy bots (like AutoRune).
Code:
RS2 #508:
Code:
int[] seed = new int[4];
seed[1] = (int) (9.9999999E7 * Math.random());
seed[0] = (int) (9.9999999E7 * Math.random());
seed[2] = (int) (Class68_Sub13_Sub11.serverSessionKey >> 32);
((ByteVector) CRCFileReader.outputVector).position = 0;
seed[3] = (int) Class68_Sub13_Sub11.serverSessionKey;
CRCFileReader.outputVector.writeByte(10);
CRCFileReader.outputVector.writeInteger(seed[0]);
CRCFileReader.outputVector.writeInteger(seed[1]);
CRCFileReader.outputVector.writeInteger(seed[2]);
CRCFileReader.outputVector.writeInteger(seed[3]);
CRCFileReader.outputVector.writeLong(Class68_Sub28_Sub2.username.toLong(10908));
CRCFileReader.outputVector.writeString(Class68_Sub28_Sub2.password);
CRCFileReader.outputVector.encodeRSABlock(Class68_Sub4.RSA_PUBLIC_KEY, Class68_Sub22.RSA_MODULUS);
RSC #204:
Code:
int seed[] = new int[4];
seed[0] = (int) (Math.random() * 99999999D);
seed[1] = (int) (Math.random() * 99999999D);
seed[2] = (int) (serverSessionKey >> 32);
seed[3] = (int) serverSessionKey;
EncodedBlock loginBlock = new EncodedBlock(new byte[500]);
loginBlock.offset = 0;
loginBlock.writeByte(10);
loginBlock.writeInt(seed[0]);
loginBlock.writeInt(seed[1]);
loginBlock.writeInt(seed[2]);
loginBlock.writeInt(seed[3]);
loginBlock.writeInt(getUserId());
loginBlock.writeString(username);
loginBlock.writeString(password);
loginBlock.encodeRSA(d, m);
RS2 #317:
Code:
int isaacRandomGenSeed[] = new int[4];
isaacRandomGenSeed[0] = (int) (Math.random() * 99999999D);
isaacRandomGenSeed[1] = (int) (Math.random() * 99999999D);
isaacRandomGenSeed[2] = (int) (serverSessionKey >> 32);
isaacRandomGenSeed[3] = (int) serverSessionKey;
outputStream.currentOffset = 0;
outputStream.addByte(10);
outputStream.addInt(isaacRandomGenSeed[0]);
outputStream.addInt(isaacRandomGenSeed[1]);
outputStream.addInt(isaacRandomGenSeed[2]);
outputStream.addInt(isaacRandomGenSeed[3]);
outputStream.addInt(signlink.uid);
outputStream.addString(username);
outputStream.addString(password);
outputStream.rsaEncodeBuffer();
ISAAC ciphering:
Once the login procedure occurs, the RuneScape client and server use the 4 integer seed from the login block to seed two ISAAC ciphers on both sides: one for ciphering, and one for deciphering. The ISAAC ciphers are used to mask and demask each frame's opcode. This was most likely created to stop AutoRune-like bots that worked by injecting data into the intercepted stream.
Code:
RS2 #317:
Code:
if (packetOpcode == -1) {
socketStream.flushInputStream(inStream.buffer, 1);
packetOpcode = inStream.buffer[0] & 0xff;
if (encryption != null)
packetOpcode = packetOpcode - encryption.nextInt() & 0xff;
packetSize = SizeConstants.packetSizes[packetOpcode];
dataAvailable--;
}
Code:
public void addPacketId(int i) {
buffer[currentOffset++] = (byte) (i + encryption.nextInt());
}
RS2 #508:
Code:
if (Class49.packetOpcode == -1)
{
avail--;
PlayerDefinition.connection.read(1, 0, Class68_Sub13_Sub8.incomingVector.data);
Class68_Sub13_Sub8.incomingVector.position = 0;
Class49.packetOpcode = Class68_Sub13_Sub8.incomingVector.getPacketOpcode();
Class106.packetSize = Class9.packetSizes[Class49.packetOpcode];
}
Code:
final int getPacketOpcode()
{
return (data[position++] - randomNumberGenerator.next()) & 0xff;
}
Code:
final void writeOpcode(int opcode)
{
data[position++] = (byte) (opcode + randomNumberGenerator.next());
}
RSC #204:
Code:
opcode = connection.decodeOpcodeISAAC(opcode);
Code:
if (outgoingISAACGen != null) {
int packetId = buffer[packetStartOffset + 2] & 0xff;
buffer[packetStartOffset + 2] = (byte) (packetId + outgoingISAACGen.next());
}
This is by far not that indepth or anything of that sort, just a small introduction to show how the RuneScape protocol worked.
If you have anything to add that is valid, feel free to ask.