Structure of a packet
This article covers G-Earth's packet log, packet expression format and the different packet data types.
Reading packet logs
A packet has 3 parts - the length, header, and data. The length declares the number of bytes that make up the header and data. The header value defines what type of message the packet represents, for example, whether a user is talking, shouting, or whispering, whether a furni was placed, moved, or removed, and so on. The data contains the information of the message, for example, the contents of the chat message, what furni was placed and where, which furni was removed, etc.
Take a look at the following packet from G-Earth's packet logger.
[Chat]
Incoming[1064] -> [0][0][0]$[4]([0][0][0][0][0][12]Hello, world[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]
{in:Chat}{i:0}{s:"Hello, world"}{i:0}{i:0}{i:0}{i:0}
In the packet logger output, the first line has the message name, Chat
.
The second line has the direction Incoming
and header value 1064
, followed by a text
representation of the entire packet, including its length, header and data. This is known as the
legacy packet format.
The third line represents the packet as an expression. G-Earth will attempt to guess the structure of the packet for us and output a packet expression such as this one.
Note
In xabbo, a Header contains a Direction and Value. The length of a packet defines the length of the data only, excluding the header.
On Shockwave, the length field is not present in the packet log. It starts with the 2-byte header (which itself is a B64), followed by the data.
Warning
The expression provided by G-Earth is not always 100% accurate as it is only a prediction of the
packet's structure based on an algorithm. The expression may represent the same data, but not
reflect the actual structure of the packet. For example, the integer 157801
could also be
interpreted as the string "hi"
- they both encode to the exact same bytes 00 02 68 69
(on Flash).
Legacy packet format
From the example packet log above, the legacy packet format is as follows:
[0][0][0]$[4]([0][0][0][0][0][12]Hello, world[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]
Each individual character represents a single byte, with the exception of the numbers enclosed in
square brackets such as [12]
which each represent the value of a single byte. These numbers are
used because a byte with that value does not represent a printable character. The square brackets
themselves must be encoded in the same way or it would be ambiguous as to whether it is a literal
square bracket or part of an encoded byte. Therefore, [
becomes [91]
and ]
becomes [93]
.
Braces {
}
must also be encoded to [123]
and [125]
respectively as they are part of the
packet expression syntax.
The legacy format shows the entire packet, of which we can see the three parts:
[0][0][0]$
decodes to the integer36
, representing the length of the header and data, and it is always 4 bytes in length. This means that 36 more bytes follow. (36 is the ASCII value of the$
character)[4](
is the header value, which decodes to1064
. It is always 2 bytes in length.- The rest of the text contains the packet data, in this case we have 34 bytes.
Packet expression
Packet expressions provide an easier way to read the data in a packet. G-Earth will attempt to guess
the structure of the packet and output its expression. The first part {in:Chat}
represents the
packet direction and message name. {i:0}
represents an integer with the value 0
. Since an
integer is 4 bytes long, it is represented as [0][0][0][0]
in the legacy packet format.
{s:"Hello, world"}
represents the string of characters, Hello, world
. Since strings are prefixed
by a short to indicate their length, and Hello, world
contains 12 characters, it is
represented as [0][12]Hello, world
in the legacy packet format.
Note
It is also possible to mix the legacy and packet expression formats. For example,
{i:1}{i:2}{i:3}
can be represented as {i:1}[0][0][0][2]{i:3}
.
Packet data types
Boolean
A boolean takes up a single byte and can have the value \(true\) or \(false\). It is represented as
{b:false}
or {b:true}
in the packet expression format.
On Flash, a boolean is written as a byte with the value 0
or 1
.
On Shockwave, it is written as a VL64 with the value 0
or 1
.
Representations
Byte
A byte value ranges from \(0\) to \(255\).
It is represented as a single character or a single number surrounded by square brackets in the
legacy packet format. For example, [2]
would be the value 2
, and A
would be the value 65
(the ASCII value of the character A
).
G-Earth uses the ISO-8859-1
or Latin1
character set to represent packet bytes in the legacy
packet format, so the value 255
would display as ÿ
.
Bytes are rarely used in the Flash client, and not used in the Shockwave client.
Representations
Value | Legacy | Expression | Hex |
---|---|---|---|
0 |
[0] |
{b:0} |
00 |
1 |
[1] |
{b:1} |
01 |
10 |
[10] |
{b:10} |
0a |
100 |
d |
{b:100} |
64 |
144 |
[144] |
{b:144} |
90 |
200 |
È |
{b:200} |
c8 |
255 |
ÿ |
{b:255} |
ff |
Minimum value | |||
0 |
[0] |
{b:0} |
00 |
Maximum value | |||
255 |
ÿ |
{b:255} |
ff |
Short
On Flash, a short is 2 bytes long and ranges from \(-32768\) to \(32767\).
On Shockwave, shorts are encoded to B64 which are also 2 bytes, however their encoding is completely different.
Representations
Value | Legacy | Expression | Hex |
---|---|---|---|
0 |
[0][0] |
{u:0} |
00 00 |
1 |
[0][1] |
{u:1} |
00 01 |
10 |
[0][10] |
{u:10} |
00 0a |
100 |
[0]d |
{u:100} |
00 64 |
200 |
[0]È |
{u:200} |
00 c8 |
255 |
[0]ÿ |
{u:255} |
00 ff |
256 |
[1][0] |
{u:256} |
01 00 |
4000 |
[15] 1 |
{u:4000} |
0f a0 |
4095 |
[15]ÿ |
{u:4095} |
0f ff |
4096 |
[16][0] |
{u:4096} |
10 00 |
-1 |
ÿÿ |
{u:65535} 2 |
ff ff |
Minimum value (signed) | |||
-32768 |
[128][0] |
{u:32768} 3 |
80 00 |
Maximum value (signed) | |||
32767 |
[127]ÿ |
{u:32767} |
7f ff |
Int
On Flash, an int is 4 bytes long and ranges from \(-2147483648\) to \(2147483647\).
On Shockwave, integers are encoded to VL64 which have a variable length, and a slightly different range.
Representations
Value | Legacy | Expression | Hex |
---|---|---|---|
0 |
[0][0][0][0] |
{i:0} |
00 00 00 00 |
1 |
[0][0][0][1] |
{i:1} |
00 00 00 01 |
2 |
[0][0][0][2] |
{i:2} |
00 00 00 02 |
3 |
[0][0][0][3] |
{i:3} |
00 00 00 03 |
4 |
[0][0][0][4] |
{i:4} |
00 00 00 04 |
255 |
[0][0][0]ÿ |
{i:255} |
00 00 00 ff |
256 |
[0][0][1][0] |
{i:256} |
00 00 01 00 |
16777215 |
[0]ÿÿÿ |
{i:16777215} |
00 ff ff ff |
49848964 |
[2]ø¢[132] |
{i:49848964} |
02 f8 a2 84 |
2147418112 4 |
[127]ÿ[0][0] |
{i:2147418112} |
7f ff 00 00 |
-1 |
ÿÿÿÿ |
{i:-1} |
ff ff ff ff |
-2 |
ÿÿÿþ |
{i:-2} |
ff ff ff fe |
-3 |
ÿÿÿý |
{i:-3} |
ff ff ff fd |
-4 |
ÿÿÿü |
{i:-4} |
ff ff ff fc |
Minimum value | |||
-2147483648 |
[128][0][0][0] |
{i:-2147483648} |
80 00 00 00 |
Maximum value | |||
2147483647 |
[127]ÿÿÿ |
{i:2147483647} |
7f ff ff ff |
Float
On the Unity client, a single-precision floating point number is 4 bytes long. Floats can represent
real numbers such as 3.14
and are often used for the Z or height coordinate of avatars and furni
in a room.
On Flash and Shockwave, Read<float>()
will read a string and parse it into a float, while
Write(float)
will format the float to a string and write it to the packet.
Long
A long is 8 bytes long and ranges from \(-9223372036854775808\) to \(9223372036854775807\).
This type is very rarely used in the Flash client. It was introduced in the Unity client which changed the numeric IDs of users, groups, furni etc. to this type which can hold a much larger range of values.
Representations
Value | Legacy | Expression | Hex |
---|---|---|---|
0 |
[0][0][0][0][0][0][0][0] |
{i:0} |
00 00 00 00 00 00 00 00 |
1 |
[0][0][0][0][0][0][0][1] |
{i:1} |
00 00 00 00 00 00 00 01 |
255 |
[0][0][0][0][0][0][0]ÿ |
{i:10} |
00 00 00 00 00 00 00 0a |
65535 |
[0][0][0][0][0][0]ÿÿ |
{i:32767} |
00 00 00 00 00 00 7f ff |
4294967295 |
[0][0][0][0]ÿÿÿÿ |
{i:2147418112} |
00 00 00 00 7f ff 00 00 |
-1 |
ÿÿÿÿÿÿÿÿ |
{i:-1} |
ff ff ff ff ff ff ff ff |
Minimum value | |||
-9223372036854775808 |
[128][0][0][0][0][0][0][0] |
{l:-9223372036854775808} |
80 00 00 00 00 00 00 00 |
Maximum value | |||
9223372036854775807 |
[127]ÿÿÿÿÿÿÿ |
{l:9223372036854775807} |
7f ff ff ff ff ff ff ff |
String
A string is a sequence of characters that make up text, such as "Hello, world"
.
On Unity, Flash, and in outgoing Shockwave packets, strings are prefixed with a short that indicates the length of the string in bytes. On Shockwave, this will be written as a B64.
For example, the string "hello"
is 5 bytes long, so a short
with the value 5
will be written
before the characters of the string. In the legacy packet format, this will be represented as
[0][5]hello
on modern clients, and @Ehello
on Shockwave, where @E
decodes to the value 5
.
Strings are handled differently in incoming Shockwave packets. They do not have a length prefix
and are terminated by the byte 0x02
or [2]
. This means that to read a string from an incoming
Shockwave packet, you must read each byte until you reach a byte with the value 2
. For example,
"hello"
will be represented as hello[2]
in the legacy packet format.
Shockwave often splits strings by the tab character \t
or [9]
, and lines by the carriage-return
character \r
or [13]
.
Representations
Value | Legacy | Expression |
---|---|---|
"" |
[0][0] |
{s:""} |
"hi" |
[0][2]hi |
{s:"hi"} |
"hello" |
[0][5]hello |
{s:"hello"} |
"hello world" |
[0][11]hello world |
{s:"hello world"} |
"hello\tworld" |
[0][11]hello[9]world |
{s:"hello\tworld"} |
"hello\rworld" |
[0][11]hello[13]world |
{s:"hello\rworld"} |
Shockwave data types
B64
A B64 represents a 12-bit unsigned fixed-length radix-64 encoded integer. Each character contains 6 bits of information, which can each represent \(2^6=64\) unique values (including \(0\), ranges from \(0\) to \(63\)).
A B64 is always 2 bytes long, which means that with 6 bits of information in each byte, the range of a B64 is from \(0\) to \(2^{12}-1=4095\).
Let's look at the number 70
which is AF
, or 01000001
01000110
in binary when encoded to B64.
The first 2 bits of each byte are always 01
:
A F
01000001 01000110
^^------ ^^------
Always 01
This means that the range of a single byte can be from 01000000
to 01111111
in binary, 0x40
to
0x7f
in hex, or from \(64\) to \(127\) in decimal. This entire range can be represented by a printable
ASCII character with the exception of the final value, which is represented by the legacy packet
format as [127]
. See the right half of
this ASCII table for a visual
representation.
The remaining 6 bits of each byte contribute to its value:
A F
01000001 01000110
--^^^^^^ --^^^^^^
Value
The bits from the first byte represent the most significant bits of the value:
6 bits from
2nd byte
vvvvvv
000001000110 <- Value
^^^^^^
6 bits from
1st byte
Where the 000001
from the 1st byte has the value of \(64\) and the 000110
from the 2nd byte has
the value of \(6\), which when added together equals \(70\).
See short for its packet representations.
VL64
A VL64 represents a signed variable-length radix-64 encoded integer. Like the B64, each character in a VL64 also contains 6 bits of information.
Let's look at the number 38
which is RI
, or 01010010
01001001
in binary when encoded to
VL64.
The first 2 bits of each byte are always 01
:
R I
01010010 01001001
^^------ ^^------
Always 01
The next 3 bits of the first byte indicate the byte length of the VL64, which allows it to have a variable length.
01010010 01001001
--^^^--- --------
Length in bytes = 2
Since we have 010
which equals 2, this VL64 is 2 bytes long.
The next bit indicates the sign, if it is 1
then the number represents a negative value.
01010010 01001001
-----^-- --------
Sign bit, 0 = positive
The remaining bits hold the value of the number. Since the first byte only has 2 bits left, the maximum value that a VL64 can represent with one byte is \(2^2-1=3\).
01010010 01001001
------^^ --^^^^^^
Value
The 6 bits from each consecutive byte are shifted onto the left of the value.
1st byte
01010010
||
vv
00100110 <- Value
^^^^^^
\\\\\\
01001001
2nd byte
We can see above that the value of the number is 00100110
in binary, or \(38\) in decimal.
Since there are 3 bits for the length, theoretically the maximum value using 7 bytes
(\(2+6*6=38\) bits) would be \(2^{38}-1=274877906943\), however, from my testing on the Origins server,
the VL64 is backed by a signed 32-bit integer with a maximum value of \(2147483647\) or 0x7fffffff
(the "sign" bit is unused), making the effective range from \(-2147483647\) to \(2147483647\).
See int for its packet representations.
Xabbo data types
Length
The Length
data type is often written before a collection of items to specify the number of items
in the collection, such as the list of users or furni in the room. It was added due to how Unity
uses a short in front of collections instead of an int. Therefore, it is written
as an int on Flash and Shockwave, and as a short on Unity.
If you read an array from a packet, a Length
is read to determine the number of items to read:
var values = e.Packet.Read<int[]>();
The above code will first read a Length
, followed by a number of integers specified by the
length, and then return the resulting array of integers.
Id
The Id
data type represents a unique ID of a user, group, furni, etc. and was added due to how
Unity changed these IDs from an int
to a long
. Therefore, an Id
is read as an int on
Flash and Shockwave, and as a long on a Unity session.
PacketContent
The PacketContent data type is specific to the Shockwave client, and was added due to how some packet structures on Shockwave use their entire data as a single string, which means there is no string length header, and no string terminator byte on incoming packets.
Often you will see a packet such as the following:
[ADDSTRIPITEM]
Outgoing[67] -> ACnew stuff 49848964
Where AC
is the header value 67
, and new stuff 49848964
is the packet data. We can see that
there is no short at the beginning to indicate the number of characters in the string
"new stuff 49848964"
, as there usually is with strings. This is because the entire data
of the packet is being used as a single string, so there is no need for a length header. Similarly,
on an incoming packet, you would not see the string terminated with a [2]
byte.
To read or write a PacketContent
, the packet's position must be at the start of the packet, after
which the position will be advanced to the end of the packet. Attempting to read or write a
PacketContent
when the position is not at \(0\) will throw an exception.
-
a0
encodes to the non-breaking space.↩ -
G-Earth represents shorts as unsigned 16-bit integers (ranging from
0
to65535
), while xabbo uses signed 16-bit integers which allow negative values (ranging from-32768
to32767
). However,-1
and65535
are represented exactly the same when encoded to 16 bits.↩ -
See 2. above. The value
32768
of an unsigned 16-bit integer and the negative value-32768
of a signed 16-bit integer encode to the exact same bytes:80 00
. This is known as two's complement.↩ -
This is the number at which Builder's Club furni IDs start at:
2147418112
or0x7fff0000
.↩