United Offensive network protocol¶
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.
Protocol profile¶
Property |
Value |
|---|---|
Game |
Call of Duty: United Offensive 1.51 |
Advertised protocol |
|
Default game port |
UDP |
Legacy authorization port |
UDP |
Legacy master-server port |
UDP |
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:
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:
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 |
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:
connect "\protocol\22\qport\<qport>\challenge\<challenge>\...userinfo..."
Only the prefix through the space after connect remains plain text. The
rest is adaptive-Huffman compressed:
Offset |
Size |
Meaning |
|---|---|---|
0 |
4 |
Connectionless marker |
4 |
8 |
ASCII |
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:
Request |
Sender |
Response |
Notes |
|---|---|---|---|
|
Browser/client |
|
Compact server-browser metadata. The challenge is copied into the response information string. |
|
Browser/client/master |
|
Full server information followed by zero or more player lines. |
|
Connecting client |
|
Allocates or reuses a challenge for the complete source address, including UDP port. |
|
Challenged client |
|
The tail is adaptive-Huffman compressed as described above. |
|
Administrator |
One or more |
Globally throttled to one accepted attempt per 500 ms. Output is split into chunks of at most 1,300 text bytes. |
|
Peer |
None |
Silently ignored by the connectionless dispatcher. |
|
Legacy authorization service |
Challenge, CD-key request, or error sent to the client |
Accepted only from the configured authorization host address. |
|
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:
ffffffff statusResponse\n
\key\value\key\value...\n
<score> <ping> "<name>"\n
<score> <ping> "<name>"\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:
Command |
Payload |
Purpose |
|---|---|---|
|
Signed challenge and optional visibility-policy integer |
Advances |
|
No payload |
Creates the client netchan and advances to |
|
Newline followed by one information string |
Completes a browser/ping request or adds a LAN discovery result. |
|
Settings line followed by player lines |
Completes a server-status request. |
|
No payload |
Drops only when sent by the connected endpoint and no sequenced packet has arrived for at least three seconds. |
|
One token |
Sends that token back as a connectionless packet. |
|
Newline followed by a big string |
Displays server/RCON output. |
|
Newline followed by a big string |
Drops the matching pending or active connection. |
|
Information string containing the outstanding challenge and |
Legacy message-of-the-day response. |
|
Binary address list |
Populates the global server browser. |
|
No required payload |
Requests another legacy client authorization attempt. |
|
Newline, map name, newline, game type |
Prepares a connected client for a server map change. |
|
Availability, download URL, and version tokens |
Legacy Windows auto-update response. |
|
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:
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
qportvalue 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 |
|
Sequence; bit 31 clear |
4 |
2 |
|
qport |
6 |
n |
bytes |
Encoded logical client message |
Server-to-client datagram:
Offset |
Size |
Type |
Meaning |
|---|---|---|---|
0 |
4 |
|
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 |
|
|
4 |
2 |
|
qport |
6 |
2 |
|
Fragment start offset |
8 |
2 |
|
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:
Construct the direction-specific clear logical header and uncompressed command body.
Fixed-Huffman compress only the body.
XOR-transform only the compressed body.
Add the netchan header and, when required, fragment the transformed logical message.
Receiving performs the exact reverse after netchan reassembly.
Clear logical headers¶
Direction |
Size |
Fields |
Purpose |
|---|---|---|---|
Client to server |
9 bytes |
|
Selects the server generation, acknowledges the latest server netchan message, and acknowledges server reliable commands. |
Server to client |
4 bytes |
|
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:
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.
Direction |
Initial key |
Repeating command string |
|---|---|---|
Client to server |
low byte of |
Latest server command named by |
Server to client |
low byte of |
Latest client command named by the clear
|
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 United Offensive protocol 22 wire-schema appendix for the
complete 256-entry seed.
Client-to-server messages¶
Clear header¶
Offset |
Size |
Type |
Meaning |
|---|---|---|---|
0 |
1 |
|
Current server id |
1 |
4 |
|
Latest received server netchan sequence |
5 |
4 |
|
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 |
|
User-command count and keyed delta usercmds |
1 |
|
Same payload; requests a full snapshot |
2 |
|
Reliable sequence and NUL string |
3 |
|
No payload |
The emitted grammar is:
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¶
Field |
Type |
Meaning |
|---|---|---|
selector |
2 bits |
|
sequence |
|
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:
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:
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 |
|
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:
Value |
Name |
Context and payload |
|---|---|---|
0 |
|
Invalid; never emitted |
1 |
|
No payload |
2 |
|
Full configstrings and entity baselines |
3 |
|
Legal only inside |
4 |
|
Legal only inside |
5 |
|
Reliable server command |
6 |
|
In-band block or HTTP redirect |
7 |
|
Delta-compressed world snapshot |
8 |
|
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:
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 |
|---|---|---|
|
|
Server simulation time in ms |
|
|
0 for full snapshot, otherwise current message sequence minus base sequence |
|
|
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 United Offensive protocol 22 wire-schema appendix.
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:
one presence bit, a 6-bit changed mask, and changed stats;
one overall ammo-presence bit, then four optional 16-entry ammo groups;
four independently optional 16-entry clip groups;
one objective-array presence bit, then all 16 objective states and field deltas;
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:
Descriptor |
Encoding after the changed bit |
|---|---|
|
Float mode. One bit selects zero versus nonzero. A nonzero float uses
another bit: zero means an exact integer in -4096..4095 encoded as
|
|
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
|
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:
Command |
Meaning |
|---|---|
|
Close any previous transfer and request a server-relative file. |
|
Acknowledge the next expected in-band block. |
|
Ask the server to restart transmission from the current acknowledged block. |
|
Abort and close the in-band transfer. |
|
Request a fresh gamestate after the download set completes. |
|
Accept an HTTP redirect. |
|
Report redirect completion. |
|
Report redirect failure and request in-band fallback/recovery. |
|
Report a redirected file checksum failure. |
|
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 |
|
6 |
block number |
|
Ordered block number |
total size |
|
Present only for block 0 |
block length |
|
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 |
|
6 |
block number |
|
-1 |
URL |
NUL string |
Redirect target |
file size |
|
Expected bytes |
flags |
|
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:
ffffffff getservers <protocol> [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 |
1 |
4 |
bytes |
IPv4 address |
5 |
2 |
|
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 <name>\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.
Timing, rate, and recovery behavior¶
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 |
Snapshot request rate |
|
Server interval is integer |
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:
Connectionless marker, text parsing,
getinfo, andgetstatus.Adaptive-Huffman
connecttail and challenge validation.Directional netchan headers, qport matching, sequence rejection, and exact 1,300-byte fragmentation including zero-length terminators.
Fixed-Huffman tree construction from the exact seed and round-trip body compression.
Directional XOR transforms with empty and non-empty reliable-command keys.
Clear logical acknowledgements and reliable command rings.
Gamestate configstrings and entity baselines.
Usercmd keying and all three delta forms.
Full and delta snapshots using the exact ordered wire tables.
In-band block download, retransmission, EOF, failure, and redirect forms.
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 United Offensive protocol 22 wire-schema appendix.