United Offensive network protocol ================================= .. meta:: :description: Technical specification of the Call of Duty: United Offensive 1.51 protocol 22 UDP transport, connection handshake, netchan framing, compression, reliable commands, snapshots, delta records, downloads, and server discovery. :keywords: call of duty, united offensive, cod uo, network protocol, protocol 22, udp, netchan, snapshot, delta compression, server browser Status and scope ---------------- This document specifies the multiplayer wire protocol used by Call of Duty: United Offensive 1.51, identified on the wire as protocol ``22``. It describes the stock client/server protocol, including the legacy Activision master and authorization exchanges. The protocol is carried primarily over IPv4 UDP. The specification was reconstructed from the maintained CoD:UO client and dedicated-server recovery, whose packet constructors, parsers, constants, and descriptor tables are checked against the original Windows and Linux machine code. Unless a section is explicitly labelled as an implementation note, the field order and widths below are wire-level requirements. This is not a security protocol. The in-band XOR transform is reversible obfuscation and synchronization, not encryption or authentication. A robust implementation must treat every length, index, count, and string as untrusted network input. .. toctree:: :hidden: network_protocol_fields Protocol profile ---------------- ============================ =================================== Property Value ============================ =================================== Game Call of Duty: United Offensive 1.51 Advertised protocol ``22`` Default game port UDP ``28960`` Legacy authorization port UDP ``20600`` Legacy master-server port UDP ``20610`` Maximum logical message 32,768 bytes Netchan fragment payload 1,300 bytes Netchan datagram buffer 1,400 bytes Reliable-command window 64 commands in each direction Maximum user commands/packet 32 Snapshot history 32 frames Entity number domain 10 bits; 1,024 values Client number domain 6 bits; 64 values Configstring domain 2,048 indexes In-band download window 8 blocks of at most 2,048 bytes ============================ =================================== Layer model ----------- Every received UDP datagram enters one of two paths according to its first four bytes: .. code-block:: text UDP datagram | +-- first dword == 0xffffffff | connectionless command or response | (text, except the compressed tail of `connect` and binary master lists) | `-- any other first dword netchan sequence and optional fragment header -> fragment reassembly -> direction-specific XOR transform -> fixed-Huffman body decompression -> client or server message command stream -> reliable commands, usercmds, gamestate, snapshots, deltas, or downloads The connectionless and sequenced formats deliberately share the same socket. The value ``0xffffffff`` is therefore reserved and cannot be a normal netchan sequence. Representation conventions -------------------------- Integer and float representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``byte``, ``short``, and ``long`` mean 8-, 16-, and 32-bit quantities in this document. Multi-byte integers in game, netchan, and download messages are little-endian unless a field explicitly says otherwise. Master-list ports are the important exception: they are two bytes in network order. Signedness is a parser-level interpretation. A 16-bit field read with the stock ``ReadShort`` primitive is sign-extended to 32 bits. Raw bit fields are assembled into the low bits of a 32-bit word. Floats transmitted in the generic and player-state delta codecs are IEEE-754 binary32 bit patterns when their compact integer form cannot be used. Angles using the special ``angle16`` mode are encoded as: .. code-block:: text encoded = trunc(angle_degrees * 65536 / 360) & 0xffff decoded = int16(encoded) * 360 / 65536 The original Windows and Linux x87 encoders differ only for pathological conversion-boundary inputs. Ordinary finite gameplay angles are compatible. Bit and byte cursors ~~~~~~~~~~~~~~~~~~~~ Bit fields are written least-significant bit first. Bit zero occupies the least-significant bit of its physical byte. The message codec maintains a byte cursor and a bit cursor. On the first bit of a new bit byte, that byte is allocated at the byte cursor and the byte cursor advances. Byte, short, long, string, and raw-data operations always use the byte cursor. If such an operation occurs while a bit byte is only partly filled, later bit operations resume that earlier bit byte until it is full, then jump to the current byte cursor. The reader mirrors this behavior. Consequently, an implementation should reproduce the message primitives rather than assume every field is concatenated by one conventional bitstream writer. Strings ~~~~~~~ In-band strings are NUL-terminated byte sequences: * ordinary strings have a 1,024-byte storage limit, including the terminator; * big strings have an 8,192-byte storage limit; * outgoing text is cleaned by the engine character sanitizer; * line reads stop at newline or NUL; * connectionless text normally has no transmitted NUL and may end at the UDP datagram boundary; * the stock RCON client is an exception and includes its final NUL. Information strings use the id-style form ``\key\value\key\value``. They are text, not a map type on the wire. Keys and values cannot contain the separator backslash. Connectionless packets ---------------------- General format ~~~~~~~~~~~~~~ ====== ======== ======================================== Offset Size Meaning ====== ======== ======================================== 0 4 Marker ``ff ff ff ff`` 4 variable Command name and arguments, usually text ====== ======== ======================================== Command matching is case-insensitive for the normal dispatcher. The first line is tokenized as a console-style command. Several responses then consume additional newline-delimited lines directly from the message. Compressed ``connect`` request ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The client constructs this logical text without a terminating NUL: .. code-block:: text connect "\protocol\22\qport\\challenge\\...userinfo..." Only the prefix through the space after ``connect`` remains plain text. The rest is adaptive-Huffman compressed: .. list-table:: Compressed connect datagram :header-rows: 1 :widths: 12 14 74 * - Offset - Size - Meaning * - 0 - 4 - Connectionless marker * - 4 - 8 - ASCII ``connect`` followed by one space * - 12 - 2 - Uncompressed tail length, big-endian * - 14 - variable - Adaptive-Huffman stream for the opening quote, userinfo, and closing quote The adaptive tree begins with one not-yet-transmitted (NYT) leaf. An unseen symbol is represented by the NYT code followed by the literal symbol as eight bits, most-significant bit first in symbol value. Encoder and decoder update the adaptive tree after every symbol. The two length bytes occupy the first 16 bit positions of the compressed tail. This adaptive format is used for ``connect`` only. It is not the fixed, pretrained Huffman transform used for sequenced message bodies. After the last adaptive code bit, stock calculates the compressed-tail byte count as ``(finalBitOffset + 8) >> 3``; ``finalBitOffset`` already includes the 16-bit length header. This is ordinary round-up for a partial final byte, but retains one extra byte when the code ends exactly on a byte boundary. The decoder stops after producing the declared number of bytes and ignores that trailing storage. A deterministic implementation should clear all padding and the optional trailing byte. Public server commands ~~~~~~~~~~~~~~~~~~~~~~ The following commands are accepted by a game server: .. list-table:: Connectionless requests handled by a game server :header-rows: 1 :widths: 20 16 28 36 * - Request - Sender - Response - Notes * - ``getinfo `` - Browser/client - ``infoResponse\n`` - Compact server-browser metadata. The challenge is copied into the response information string. * - ``getstatus [challenge]`` - Browser/client/master - ``statusResponse\n\n`` - Full server information followed by zero or more player lines. * - ``getchallenge`` - Connecting client - ``challengeResponse [onlyVisibleClients]`` - Allocates or reuses a challenge for the complete source address, including UDP port. * - ``connect `` - Challenged client - ``connectResponse`` or ``error\n`` - The tail is adaptive-Huffman compressed as described above. * - ``rcon `` - Administrator - One or more ``print\n`` packets - Globally throttled to one accepted attempt per 500 ms. Output is split into chunks of at most 1,300 text bytes. * - ``disconnect`` - Peer - None - Silently ignored by the connectionless dispatcher. * - ``ipAuthorize ...`` - Legacy authorization service - Challenge, CD-key request, or error sent to the client - Accepted only from the configured authorization host address. * - ``pb_*`` / ``PB_*`` - PunkBuster component - Component-defined - Optional retired subsystem, consumed before ordinary dispatch. ``infoResponse`` information keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The server always emits ``challenge``, ``protocol``, ``hostname``, ``mapname``, ``gametype``, ``hw``, ``pb``, and ``mod``. It conditionally emits the following keys when applicable: ``clients``, ``sv_maxclients``, ``pure``, ``minPing``, ``maxPing``, ``game``, ``sv_allowAnonymous``, ``pswrd``, ``ff``, ``kc``, ``timeoutsAllowed``, ``jps``, and ``tnk``. ``statusResponse`` format ~~~~~~~~~~~~~~~~~~~~~~~~~ The response is: .. code-block:: text ffffffff statusResponse\n \key\value\key\value...\n ""\n ""\n ... The information line is built from server-info cvars and additionally carries the request ``challenge``, password state ``pswrd``, modification state ``mod``, and optionally ``con_disabled``. Only clients in server state ``CONNECTED`` or later appear in the player list. Client-side connectionless responses ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The stock client recognizes: .. list-table:: Connectionless commands handled by a client :header-rows: 1 :widths: 24 42 34 * - Command - Payload - Purpose * - ``challengeResponse`` - Signed challenge and optional visibility-policy integer - Advances ``CONNECTING`` to ``CHALLENGING``. * - ``connectResponse`` - No payload - Creates the client netchan and advances to ``CONNECTED``. * - ``infoResponse`` - Newline followed by one information string - Completes a browser/ping request or adds a LAN discovery result. * - ``statusResponse`` - Settings line followed by player lines - Completes a server-status request. * - ``disconnect`` - No payload - Drops only when sent by the connected endpoint and no sequenced packet has arrived for at least three seconds. * - ``echo`` - One token - Sends that token back as a connectionless packet. * - ``print`` - Newline followed by a big string - Displays server/RCON output. * - ``error`` - Newline followed by a big string - Drops the matching pending or active connection. * - ``motd`` - Information string containing the outstanding challenge and ``motd`` - Legacy message-of-the-day response. * - ``getserversResponse`` - Binary address list - Populates the global server browser. * - ``needcdkey`` - No required payload - Requests another legacy client authorization attempt. * - ``loadingnewmap`` - Newline, map name, newline, game type - Prepares a connected client for a server map change. * - ``updateResponse`` - Availability, download URL, and version tokens - Legacy Windows auto-update response. * - ``keyAuthorize`` - Legacy authorization data - Recognized and ignored by the recovered client dispatcher. Connection handshake -------------------- Normal remote connection ~~~~~~~~~~~~~~~~~~~~~~~~ The connection is established with the following exchange. Arrows describe UDP datagrams, not a stream: .. code-block:: text client server | | | OOB: getchallenge | |------------------------------------------>| | | optional legacy | | authorization exchange | OOB: challengeResponse C [visibility] | |<------------------------------------------| | | | OOB: connect "\protocol\22 | | \qport\Q\challenge\C\..." | |------------------------------------------>| | | | OOB: connectResponse | |<------------------------------------------| | | | sequenced client packet | |------------------------------------------>| | | | sequenced gamestate | |<------------------------------------------| | | | sequenced move / moveNoDelta | |------------------------------------------>| | | client enters ACTIVE | sequenced snapshot | |<------------------------------------------| The client retransmits the current ``getchallenge`` or ``connect`` request every 3,000 ms. The server requires protocol ``22`` and, for non-local clients, a matching challenge issued to the same IP address and UDP port. Reconnect attempts matching the base address plus qport or translated UDP port are subject to the server's reconnect interval. The ``connect`` userinfo must contain at least: * ``protocol=22``; * the client's 16-bit ``qport`` value rendered as decimal text; * the issued ``challenge``; * ordinary userinfo such as name, rate, snapshots/second, password, and download capability. The server clamps the advertised ``rate`` to 1,000..90,000 bytes/second and ``snaps`` to 1..30. Absence of these keys selects 5,000 bytes/second and a 50-ms snapshot interval in the stock server. Connection states ~~~~~~~~~~~~~~~~~ The client progresses through ``DISCONNECTED`` (0), ``CONNECTING`` (1), ``CHALLENGING`` (2), ``CONNECTED`` (3), ``LOADING`` (4), ``PRIMED`` (5), and ``ACTIVE`` (6). ``CINEMATIC`` (7) and ``LOGO`` (8) are local presentation states rather than network handshake stages. The server tracks ``FREE`` (0), ``ZOMBIE`` (1), ``CONNECTED`` (2), ``PRIMED`` (3), and ``ACTIVE`` (4). It enters ``PRIMED`` when sending a gamestate and enters ``ACTIVE`` when the first valid user-command bundle arrives. Netchan transport ----------------- Unfragmented headers ~~~~~~~~~~~~~~~~~~~~ Client-to-server datagram: ====== ==== ============= ============================== Offset Size Type Meaning ====== ==== ============= ============================== 0 4 ``uint32 LE`` Sequence; bit 31 clear 4 2 ``uint16 LE`` qport 6 n bytes Encoded logical client message ====== ==== ============= ============================== Server-to-client datagram: ====== ==== ============= ============================== Offset Size Type Meaning ====== ==== ============= ============================== 0 4 ``uint32 LE`` Sequence; bit 31 clear 4 n bytes Encoded logical server message ====== ==== ============= ============================== Sequences start at 1 independently in each direction. A receiver accepts only a sequence greater than its last accepted sequence. Older and duplicate datagrams are discarded. The gap between accepted sequences is recorded as a drop count, but there is no packet-level retransmission acknowledgement in the header. qport and translated ports ~~~~~~~~~~~~~~~~~~~~~~~~~~ Only client-to-server datagrams carry qport. The server identifies a channel by source base address and qport, deliberately ignoring the UDP source port at first. If the port changed, the channel's remote port is updated. This permits continued operation when a NAT remaps the client's UDP source port. Fragmented headers ~~~~~~~~~~~~~~~~~~ Fragmentation is indicated by sequence bit 31. The low 31 bits remain the message sequence. Client-to-server fragment: ====== ==== ============= ======================= Offset Size Type Meaning ====== ==== ============= ======================= 0 4 ``uint32 LE`` ``sequence | 0x80000000`` 4 2 ``uint16 LE`` qport 6 2 ``int16 LE`` Fragment start offset 8 2 ``int16 LE`` Fragment payload length 10 n bytes Fragment payload ====== ==== ============= ======================= Server-to-client fragment omits qport, so ``fragmentStart`` is at offset 4, ``fragmentLength`` at 6, and data at 8. Fragmentation and reassembly rules ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * A logical message of 1,300 bytes or more is fragmented. Shorter messages are sent as one datagram. * Every fragment of one logical message uses the same sequence. * Fragment starts are 0, 1,300, 2,600, and so on. * A receiver accepts a fragment only when its start equals the number of bytes already accumulated for that sequence. There is no out-of-order fragment queue. * A fragment of exactly 1,300 bytes means more fragments follow. * A fragment shorter than 1,300 bytes terminates the message. * Therefore, a message whose size is an exact multiple of 1,300 requires a final zero-length fragment. * Reassembled content may not exceed 32,768 bytes. * The qport and fragment fields are transport-only. The delivered reassembled message contains the four-byte sequence followed immediately by the logical payload. Fragment loss prevents that logical message from completing. Higher-level reliable commands and gamestate recovery are repeated in later logical messages; netchan itself does not resend a missing fragment. Sequence-space caveat ~~~~~~~~~~~~~~~~~~~~~ Stock uses the sign/high bit of a signed, unbounded sequence as the fragment flag. After sequence ``0x7fffffff`` the two roles collide. Implementations seeking exact stock interoperability should reconnect or otherwise avoid crossing that boundary rather than silently assigning bit 31 a different meaning. Sequenced payload pipeline -------------------------- Sending applies these operations in order: 1. Construct the direction-specific clear logical header and uncompressed command body. 2. Fixed-Huffman compress only the body. 3. XOR-transform only the compressed body. 4. Add the netchan header and, when required, fragment the transformed logical message. Receiving performs the exact reverse after netchan reassembly. Clear logical headers ~~~~~~~~~~~~~~~~~~~~~ .. list-table:: Uncompressed and unobfuscated logical prefixes :header-rows: 1 :widths: 22 14 26 38 * - Direction - Size - Fields - Purpose * - Client to server - 9 bytes - ``serverId:u8``, ``messageAcknowledge:i32``, ``reliableAcknowledge:i32`` - Selects the server generation, acknowledges the latest server netchan message, and acknowledges server reliable commands. * - Server to client - 4 bytes - ``lastClientCommand:i32`` - Acknowledges the latest executed client reliable command. The fixed-Huffman stream begins immediately after these clear prefixes. XOR transform ~~~~~~~~~~~~~ The same byte loop encodes and decodes because XOR is symmetric: .. code-block:: c uint8_t key = initial_key; size_t command_index = 0; for (size_t i = 0; i < body_length; ++i) { if (command[command_index] == '\0') command_index = 0; key ^= (uint8_t)command[command_index] << (i & 1); ++command_index; body[i] ^= key; } All operations reduce to eight bits. An empty command string contributes zero on every iteration. .. list-table:: XOR synchronization inputs :header-rows: 1 :widths: 24 36 40 * - Direction - Initial key - Repeating command string * - Client to server - low byte of ``serverId ^ challenge ^ messageAcknowledge`` - Latest server command named by ``reliableAcknowledge`` * - Server to client - low byte of ``netchanSequence ^ challenge`` - Latest client command named by the clear ``lastClientCommand`` acknowledgement Because command text participates in the key stream, a peer that loses reliable-command synchronization cannot decode subsequent bodies correctly. Fixed-Huffman body compression ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sequenced bodies use one fixed binary Huffman tree over all 256 byte symbols. The tree is built at startup by initializing an adaptive Huffman state and adding symbol 0 the number of times specified by seed entry 0, then symbol 1, and so on through 255. Compression and decompression thereafter traverse that pretrained tree without updating it. Codes are emitted root-to-leaf into the low-to-high bit positions of the output. The compressed stream has no uncompressed-size or exact-bit-count header. Its UDP/logical byte extent bounds decoding, and the command stream's ``EOF`` selector provides the semantic terminator. Unused high bits in the last compressed byte are zero. See :doc:`network_protocol_fields` for the complete 256-entry seed. Client-to-server messages ------------------------- Clear header ~~~~~~~~~~~~ ====== ==== ============ ====================================================== Offset Size Type Meaning ====== ==== ============ ====================================================== 0 1 ``uint8`` Current server id 1 4 ``int32 LE`` Latest received server netchan sequence 5 4 ``int32 LE`` Latest received server-command sequence 9 n bytes Fixed-Huffman compressed, XOR-transformed command body ====== ==== ============ ====================================================== The server compares the low/high server-id domains to detect restarts and dropped gamestates. If the client's generation is stale and its acknowledgement has passed the previous gamestate, the server resends a full gamestate. Client command selectors ~~~~~~~~~~~~~~~~~~~~~~~~ The decompressed body is controlled by two-bit selectors: ===== ================= =========================================== Value Name Payload ===== ================= =========================================== 0 ``move`` User-command count and keyed delta usercmds 1 ``moveNoDelta`` Same payload; requests a full snapshot 2 ``clientCommand`` Reliable sequence and NUL string 3 ``EOF`` No payload ===== ================= =========================================== The emitted grammar is: .. code-block:: text clientCommand* (move | moveNoDelta)? EOF The server consumes all leading reliable commands, then at most one move bundle. A ``move`` makes the snapshot named by ``messageAcknowledge`` the requested delta base. ``moveNoDelta`` sets the delta base to none. Reliable client command record ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: Reliable client command fields :header-rows: 1 :widths: 18 22 60 * - Field - Type - Meaning * - selector - 2 bits - ``2`` * - sequence - ``int32 LE`` - Monotonic reliable-command number * - text - NUL string - Console/game command, at most 1,023 bytes The client repeats every command after the server's latest acknowledged ``lastClientCommand``. The server ignores duplicates, requires newly observed commands to be exactly consecutive, and drops the client on a gap. Built-in engine commands include ``userinfo``, ``disconnect``, ``cp``, ``vdr``, ``download``, ``nextdl``, ``stopdl``, ``donedl``, ``retransdl``, and ``wwwdl``; other commands are offered to the game module. User-command bundle ~~~~~~~~~~~~~~~~~~~ After selector 0 or 1, one byte gives the command count. Valid values are 1..32. Commands are ordered oldest to newest. The first delta base is a default command derived from the latest player state; each later command is based on the command immediately before it. The per-bundle key is: .. code-block:: text key = messageAcknowledge ^ gamestateChecksumFeed ^ HashKey(last acknowledged server command, 32) The stock ``HashKey`` operation and the XOR masks in the usercmd codec are part of the wire format. The usercmd record contains ``commandTime``, two button bytes, a 7-bit weapon, three 16-bit angle words held in 32-bit members, and signed forward/right/up movement bytes. ``commandTime`` is represented first: a one bit followed by an unsigned byte means ``baseTime + byte`` for deltas below 256; a zero bit is followed by the absolute 32-bit time. Two keyed control bits then select one of three forms: .. list-table:: Delta usercmd forms :header-rows: 1 :widths: 20 22 58 * - First control - Second control - Meaning * - 0 - not present - All fields except the already-decoded command time equal the base. * - 1 - 0 - Compact form: fire bit, pitch, yaw, and horizontal movement may change. Other fields equal the base. * - 1 - 1 - Full form: compact fields plus roll, upper six button bits, secondary buttons, vertical movement, and weapon may change. Control values and changed field values are XOR-masked with the low requested number of bits from the key. Delta-coded fields carry a one-bit changed flag; changed bytes/shorts or bit fields are XORed with the current key. The codec XORs ``commandTime`` into the key before the compact form's field payload and before the full form's roll-and-later payload, matching the stock operation order. Movement is quantized before comparison: * values greater than 10 become ``+127``; * values less than -10 become ``-127``; * values from -10 through 10 become zero; * forward/right use four flags in four bits; up uses two flags in two bits. Server-to-client messages ------------------------- Clear header ~~~~~~~~~~~~ ====== ==== ============ ================================================================ Offset Size Type Meaning ====== ==== ============ ================================================================ 0 4 ``int32 LE`` Latest executed client-command sequence 4 n bytes Fixed-Huffman compressed, XOR-transformed service-command stream ====== ==== ============ ================================================================ Service opcodes ~~~~~~~~~~~~~~~ Service commands begin on byte boundaries in the decompressed body: .. list-table:: Server service opcodes :header-rows: 1 :widths: 10 28 62 * - Value - Name - Context and payload * - 0 - ``svc_bad`` - Invalid; never emitted * - 1 - ``svc_nop`` - No payload * - 2 - ``svc_gamestate`` - Full configstrings and entity baselines * - 3 - ``svc_configstring`` - Legal only inside ``svc_gamestate`` * - 4 - ``svc_baseline`` - Legal only inside ``svc_gamestate`` * - 5 - ``svc_serverCommand`` - Reliable server command * - 6 - ``svc_download`` - In-band block or HTTP redirect * - 7 - ``svc_snapshot`` - Delta-compressed world snapshot * - 8 - ``svc_EOF`` - End of command stream One logical message can contain multiple server commands and download blocks before its snapshot and final ``svc_EOF``. Reliable server command ~~~~~~~~~~~~~~~~~~~~~~~ ``svc_serverCommand`` carries a 32-bit sequence followed by a NUL-terminated string. The server repeats unacknowledged commands. The client ignores a sequence not newer than its current value, otherwise stores the text in a 64-entry ring and publishes that sequence in the next client clear header. The server internally distinguishes reliable and ignorable commands. Both use the same wire record and acknowledgement sequence; ignorable commands may be coalesced or discarded before transmission when the backlog grows. This policy must not be confused with UDP-level reliability. Gamestate ~~~~~~~~~ A gamestate logical body is: .. code-block:: text int32 lastClientCommand # clear logical prefix [svc_serverCommand records] # pending commands byte svc_gamestate # 2 int32 serverCommandSequence ( byte svc_configstring # 3 int16 configstringIndex bigString value | byte svc_baseline # 4 deltaEntity from an all-zero baseline )* byte svc_EOF # closes embedded gamestate int32 clientNum int32 gamestateChecksumFeed byte svc_EOF # closes outer message Only non-empty configstrings and baselines whose entity number is nonzero are sent. Configstring indexes are 0..2,047. The checksum feed becomes part of the usercmd key and pure-file validation. The embedded gamestate parser accepts only ``svc_configstring``, ``svc_baseline``, and its terminating ``svc_EOF``. The final outer EOF is consumed by the ordinary server-message parser. Snapshot header ~~~~~~~~~~~~~~~ ``svc_snapshot`` is followed by: ================= ============ =========================================================================== Field Type Meaning ================= ============ =========================================================================== ``serverTime`` ``int32 LE`` Server simulation time in ms ``deltaDistance`` ``uint8`` 0 for full snapshot, otherwise current message sequence minus base sequence ``snapFlags`` ``uint8`` Snapshot state flags player state bit/byte mix Delta from the base player state or all-zero state entity stream bit/byte mix Sorted entity delta records, then entity number 1023 client stream bit/byte mix Sorted client records, then a zero bit ================= ============ =========================================================================== Snapshot flag bits are: === ==================== ========================================= Bit Name Meaning === ==================== ========================================= 0 rate delayed Server rate scheduling delayed this frame 1 not active Snapshot cannot activate the client 2 server count/restart Toggles across a server restart === ==================== ========================================= The client accepts a delta only when the referenced 32-frame history slot still names the expected message and its entity/client parse-ring content has not aged out. It still consumes an unusable delta from the stream but discards the completed snapshot. The server uses at most 28 frames of delta distance (``32 - 4``) and falls back to a full snapshot when the frame or entity ring is too old. Snapshot entity and client merge streams ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Old and new entity/client lists are sorted by number and merged. This permits unchanged records to be omitted, new records to delta from a baseline, and removed records to carry only their number and removal bit. Entity records use a 10-bit number. Value 1023 terminates the stream and is also the engine's ``ENTITYNUM_NONE`` sentinel. Each non-terminal entity then carries a one-bit normal/vehicle table selector, a removal bit, and, when not removed, a changed bit. A changed entity carries an 8-bit ``lastChanged`` count and field deltas through that descriptor index. Client records begin with a one-bit present marker. Zero terminates the client stream. One is followed by a 6-bit client number, removal bit, changed bit, and optional 8-bit ``lastChanged`` plus field deltas. The exact generic delta grammar, field modes, and ordered tables are in :doc:`network_protocol_fields`. Player-state delta ~~~~~~~~~~~~~~~~~~ Player state begins with an 8-bit ``lastChanged`` count for the ordered 114-field main table. Fields from zero through ``lastChanged - 1`` each carry a changed bit and, when changed, their mode-specific value. Later main fields are copied from the base. The main table is followed by these auxiliary groups in fixed order: 1. one presence bit, a 6-bit changed mask, and changed stats; 2. one overall ammo-presence bit, then four optional 16-entry ammo groups; 3. four independently optional 16-entry clip groups; 4. one objective-array presence bit, then all 16 objective states and field deltas; 5. one HUD presence bit, then archival and current HUD arrays. Stats 0, 1, 2, and 4 use signed shorts; stat 3 uses six bits; stat 5 uses one byte. Ammo and clip values use shorts. Each ammo/clip group uses a 16-bit changed mask. Each objective starts with a three-bit state. If any of its six descriptor fields differs, a presence bit is one and every descriptor receives its own changed bit. HUD arrays begin with a six-bit active element count (0..63). Each active element has a five-bit final descriptor index followed by deltas from descriptor zero through that index. Delta field modes ~~~~~~~~~~~~~~~~~ For a generic entity/client/objective/HUD field, a zero changed bit copies the base. A one changed bit selects a new value: .. list-table:: Generic delta descriptor modes :header-rows: 1 :widths: 18 82 * - Descriptor ``bits`` - Encoding after the changed bit * - ``0`` - Float mode. One bit selects zero versus nonzero. A nonzero float uses another bit: zero means an exact integer in -4096..4095 encoded as ``value + 4096`` across 13 bits; one means a raw 32-bit float. * - ``-100`` - Angle16 mode. One bit selects zero versus a 16-bit encoded angle. * - any other value - One bit selects zero versus nonzero. A nonzero value transmits ``abs(bits)`` low bits. Negative values other than -100 do not add a sign bit; the magnitude is the physical bit width. The player-state main table uses a closely related but distinct codec: after the changed bit it omits the generic zero/nonzero bit for ordinary integers and floats. Float mode directly selects compact integer versus raw float; angle16 retains its zero/value selector. Downloads --------- Download control commands ~~~~~~~~~~~~~~~~~~~~~~~~~ The client initiates and acknowledges downloads through reliable client commands: .. list-table:: Client download commands :header-rows: 1 :widths: 28 72 * - Command - Meaning * - ``download `` - Close any previous transfer and request a server-relative file. * - ``nextdl `` - Acknowledge the next expected in-band block. * - ``retransdl `` - Ask the server to restart transmission from the current acknowledged block. * - ``stopdl`` - Abort and close the in-band transfer. * - ``donedl`` - Request a fresh gamestate after the download set completes. * - ``wwwdl ack`` - Accept an HTTP redirect. * - ``wwwdl done`` - Report redirect completion. * - ``wwwdl fail`` - Report redirect failure and request in-band fallback/recovery. * - ``wwwdl chkfail`` - Report a redirected file checksum failure. * - ``wwwdl bbl8r`` - Accept a redirect that requires disconnecting from the game server. In-band block record ~~~~~~~~~~~~~~~~~~~~ Each block is one ``svc_download`` record: ============ ============ ============================== Field Type Meaning ============ ============ ============================== opcode ``uint8`` 6 block number ``int16 LE`` Ordered block number total size ``int32 LE`` Present only for block 0 block length ``int16 LE`` 0..2,048 data raw bytes Present when length is nonzero ============ ============ ============================== Blocks are numbered from zero. The server buffers at most eight unacknowledged blocks. A zero-length block is an explicit end-of-file block and is itself acknowledged with ``nextdl``. If all buffered blocks were transmitted and no acknowledgement arrives for more than 1,000 ms, transmission restarts from the oldest unacknowledged block. Failure record ~~~~~~~~~~~~~~ Block number 0 followed by total size -1 denotes failure. A NUL-terminated error string follows the size; there is no block-length field in this form. HTTP redirect record ~~~~~~~~~~~~~~~~~~~~ Block number -1 selects a redirect: ============ ============ ===================================== Field Type Meaning ============ ============ ===================================== opcode ``uint8`` 6 block number ``int16 LE`` -1 URL NUL string Redirect target file size ``int32 LE`` Expected bytes flags ``int32 LE`` Bit 0 disconnect; bit 1 open URL/quit ============ ============ ===================================== Server discovery and legacy services ------------------------------------ LAN discovery ~~~~~~~~~~~~~ The client broadcasts ``getinfo xxx`` twice to each UDP port 28960 through 28963. Responding servers return the ordinary ``infoResponse``. An unsolicited valid protocol-22 response is admitted to the local-server list only while a LAN scan is active. Global master query ~~~~~~~~~~~~~~~~~~~ The client sends the legacy master at UDP 20610: .. code-block:: text ffffffff getservers [keyword ...] For CoD:UO the protocol argument is normally ``22``. A restricted/demo client adds the keyword ``demo``. The master response begins with connectionless text whose first token starts with ``getserversResponse``. The client then scans the complete datagram for records of this form: ====== ==== ============= ========================= Offset Size Type Meaning ====== ==== ============= ========================= 0 1 byte ASCII backslash ``0x5c`` 1 4 bytes IPv4 address 5 2 ``uint16 BE`` UDP port in network order ====== ==== ============= ========================= The next byte must be another backslash. ``\EOT`` terminates the list. A single response packet contributes at most 256 parsed addresses. Master heartbeat ~~~~~~~~~~~~~~~~ A public dedicated server resolves ``coduomaster.activision.com:20610`` and sends ``heartbeat \n`` at most every 180,000 ms. It sends a full ``statusResponse`` to the master at most every 600,000 ms and sends ``gameCompleteStatus`` after a completed game. Shutdown forces a ``heartbeat flatline\n``. Legacy authorization ~~~~~~~~~~~~~~~~~~~~ For non-LAN clients, the stock server may ask ``coduoauthorize.activision.com:20600``: .. code-block:: text getIpAuthorize The service replies with ``ipAuthorize ``. Results include ``accept``, ``deny``, and ``demo``. The server relays a challenge, ``needcdkey``, or an error to the client. If authorization does not answer within 5,000 ms, the server permits the challenge path to continue under the stock timeout rules. The original Activision master, authorization, MOTD, and auto-update services are historical dependencies and should not be assumed to remain available. Their formats are documented for compatibility and replacement services. Timing, rate, and recovery behavior ----------------------------------- .. list-table:: Important stock timing and policy values :header-rows: 1 :widths: 34 20 46 * - Mechanism - Default/limit - Behavior * - Handshake resend - 3,000 ms - Retransmit current challenge/connect request. * - Client timeout cvar - 200 seconds - Six consecutive expired checks are required before drop. * - Server timeout cvar - 240 seconds - Six consecutive maintenance scans are required before drop. * - Server zombie time - 2 seconds - Retains a dropped slot briefly before returning it to ``FREE``. * - Snapshot request rate - ``snaps`` 1..30 - Server interval is integer ``1000 / snaps`` ms; default 50 ms. * - Client packet rate - at least 15/s; stock platform maximum 100 or 125 - LAN and loopback bypass this pacing gate. * - Client packet duplication - 0..5 old packets - Expands the usercmd bundle to repeat commands since an older sent packet; it does not duplicate the UDP datagram itself. * - Server rate accounting - message capped at 1,500 bytes plus 48 bytes overhead - Determines next snapshot time from client/server rate limits. * - Connecting client snapshot floor - 1,000 ms - Applied when not active and not downloading. * - Reliable command capacity - 64 each direction - Falling outside the acknowledged ring loses synchronization and causes reset/drop behavior. Error handling and interoperability requirements ------------------------------------------------ A compatible parser should enforce at least the following invariants: * reject datagrams too short for their selected header; * never accept a stale netchan sequence; * require fragment starts to be contiguous and bound the reassembled message to 32,768 bytes; * validate fixed-Huffman input/output extents before parsing commands; * require client reliable commands to be consecutive; * bound usercmd count to 1..32; * bound configstrings to 0..2,047, entities to 0..1,023, and clients to 0..63; * reject descriptor counts outside their exact table extents; * validate every variable download length before copying or writing; * treat all connectionless text and info-string values as untrusted data, not format strings or filesystem paths; * retain EOF-driven command parsing even if padding causes a Huffman decoder to produce an additional trailing byte. Stock protocol and hardened implementations can remain wire-compatible. Bounds checks, safe formatting, and rejection of impossible indexes do not require a protocol version change. Conversely, enlarging a transmitted index domain, changing a descriptor order, using a different Huffman tree, or changing the XOR synchronization inputs is a protocol break and requires a new advertised protocol. Implementation checklist ------------------------ An independent protocol-22 implementation should be tested in this order: 1. Connectionless marker, text parsing, ``getinfo``, and ``getstatus``. 2. Adaptive-Huffman ``connect`` tail and challenge validation. 3. Directional netchan headers, qport matching, sequence rejection, and exact 1,300-byte fragmentation including zero-length terminators. 4. Fixed-Huffman tree construction from the exact seed and round-trip body compression. 5. Directional XOR transforms with empty and non-empty reliable-command keys. 6. Clear logical acknowledgements and reliable command rings. 7. Gamestate configstrings and entity baselines. 8. Usercmd keying and all three delta forms. 9. Full and delta snapshots using the exact ordered wire tables. 10. In-band block download, retransmission, EOF, failure, and redirect forms. 11. LAN discovery and binary master-list parsing. Golden tests should compare complete datagrams, not merely reconstructed states. This catches bit order, cursor interleaving, compression padding, XOR start offsets, and descriptor-order mistakes that a semantic round trip can hide. Wire-schema appendix -------------------- The exact fixed-Huffman seed and delta descriptor tables are maintained in :doc:`network_protocol_fields`.