Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BitProtocol

Overview

The game client communicates with the server over a persistent TCP connection using a custom binary protocol. All data is serialized with the BitProtocol codec - a bit-level encoding scheme that compresses integers by encoding only the bytes that are actually needed.

The implementation lives in internal/network/

Wire Format

Every message follows this structure:

┌─────────────────────────────────────────────────────────┐
│  length prefix  │        payload bytes          │ 0x00  │
│  (varint)       │     (BitStream encoding)      │ (EOF) │
└─────────────────────────────────────────────────────────┘

Length prefix

The length prefix is a variable-length big-endian integer where the MSB of each byte signals whether more bytes follow:

Each group of 7 bits contributes to the value, MSB = continuation flag:

[1xxxxxxx] [1xxxxxxx] [1xxxxxxx] [0xxxxxxx]
 more bytes more bytes more bytes last byte

Payload

The payload is a BitStream - a sequence of bits packed into bytes. The stream is read and written bit-by-bit; byte boundaries are only respected for raw string/byte-array data (aligned reads/writes).

After the last payload byte comes a null terminator 0x00.

BitStream Encoding

Objects (nullable)

Objects begin with a null flag bit:

[0] -> object is not null
[1] -> null / not present

Booleans

A single bit: 1 = true, 0 = false.

Compressed integers (int16, int32, int64)

Integers are compressed using a variable-width encoding that minimizes bit usage for small values.

The encoding starts with a compressed flag bit:

[1] -> compressed (fewer than max bytes needed)
      followed by a count of how many bytes are needed
      [0]   -> 0 bytes -> value fits in 4 bits
      [10]  -> 1 byte  -> value is 1 byte  (8 bits)
      [110] -> 2 bytes -> value is 2 bytes (16 bits)
      ...
[0] -> full width (all bytes written)

Floating-point numbers (float32, float64)

Written as raw not compressed IEEE 754 bytes.

Strings

Strings are encoded as:

  1. A compressed int32 for the UTF-8 byte length.
  2. The raw UTF-8 bytes, byte-aligned (any remaining bits in the current byte are skipped before writing).

Byte arrays

Same as strings: compressed length prefix followed by byte-aligned data.

Dates

Dates are encoded relative to a fixed epoch (0001-03-01). A null flag bit precedes the value:

[1] -> zero time (null)
[0] -> seconds since epoch, written as a raw 8-byte integer

Message Structure

Every message has a header followed by an optional body. The outer wrapper adds a null flag for the entire message object.

[0]          -> message is not null
[header]     -> GSF header fields
[0]          -> body is not null
[body fields]

GSF Header

FieldTypeConditionDescription
flagsint32alwaysBit flags
svcClassint32alwaysMessage service class
msgTypeint32alwaysMessage type for the given svcClass
requestIdint32if IsService()Correlates request/response pairs
logCorrelatorstringif IsRequest()Client-side correlation string
resultCodeint32if IsResponse()Result code (0 = success)
appCodeint32if IsResponse()Application-level error code
appStringstringif appCode != 0Error description string
appCodesarrayif appCode == 17Extended error code list

Flag semantics:

BitMeaning
flags & 2 == 0IsService - a request/response pair
flags & 1 != 0IsResponse (only when IsService)
flags & 2 != 0IsNotify - fire-and-forget notification
flags & 16 != 0IsDiscardable

Annotated Example

The very first message the client sends after connecting is a GetClientVersionInfo request. It is 22 bytes total:

hex:  15 20 c2 5c 04 6d 0c 0c 18 41 6d 61 7a 69 6e 67 57 6f 72 6c 64 00

Breakdown:

BytesValueMeaning
1521Length prefix: payload is 21 bytes
20 c2 5c 04 6d 0c 0c 18bit streamHeader + body
41 6d 61 7a 69 6e 67 57 6f 72 6c 64AmazingWorldString content (byte-aligned)
000Null terminator

Bit-by-bit payload decoding:

Bit  8:  [0]                -> message is not null
Bit  9:  [0]                -> header is not null
Bit 10:  [1][0]             -> flags compressed, 0 bytes -> 4-bit value
Bit 14:  [0000]             -> flags = 0
Bit 16:  [1][1][0]          -> svcClass compressed, 1 byte follows
Bit 19:  [00010010]         -> svcClass = 18 (USER_SERVER)
Bit 27:  [1][1][1][0]       -> msgType compressed, 2 bytes follow
Bit 31:  [0000001000110110] -> msgType = 566 (GET_CLIENT_VERSION_INFO)
Bit 47:  [1][0]             -> requestId compressed, 0 bytes -> 4-bit value
Bit 51:  [0001]             -> requestId = 1
Bit 53:  [1][0]             -> logCorrelator length compressed, 0 bytes -> 4-bit value
Bit 57:  [0000]             -> length = 0 (empty string)
Bit 59:  [0]                -> body is not null
Bit 60:  [1][1][0]          -> clientName length compressed, 1 byte follows
Bit 63:  [00001100]         -> length = 12 (bytes in "AmazingWorld")
Bit 71:  [0] align          -> skip to byte boundary
Bits 72–167:                -> "AmazingWorld" (12 bytes, 96 bits)