Skip to content

Resource files and views ​

Resources ship client files (HTML, scripts, styles, images, fonts) that the client downloads over HTTP and checks against a manifest received in the game session, and open views: pages built from those files that receive JSON snapshots from the resource and send named actions back. The design is in ADR 0049.

Resource client files ​

A resource declares the files of its views in resource.toml as the list [client].files. A path is segments of [a-z0-9_.-] joined by /, 1..95 bytes, with no empty, . or .. segment; the extension is one of html, js, css, json, png, jpg, webp, svg, woff2, ttf. A resource has up to 64 files, each up to 4 MiB and all together up to 16 MiB; the resource name is [a-z0-9_-], 1..31 bytes. A violation refuses the resource's start. The server reads and hashes the files when the resource starts; every start of a resource gets a generation, a counter shared by the whole server process, and a reload gets a new one.

HTTP. The server serves the files over HTTP/1.1 on TCP port client_files_port from ServerHello (0x02) (server configuration; by default the same number as the game port), at the game server's address. A server that cannot listen on that TCP port does not start.

GET /<resource>/<path> returns exactly the bytes whose hash the manifest declares: 200 with Content-Type by extension, Content-Length, Accept-Ranges: bytes, Cache-Control: no-store, X-Content-Type-Options: nosniff, Connection: close. One request per connection. An unknown resource or path and a target with a query get 404, a method other than GET gets 405, and a request that cannot be parsed (including a version other than HTTP/1.1 or HTTP/1.0) gets 400.

extensionContent-Type
htmltext/html; charset=utf-8
jstext/javascript; charset=utf-8
csstext/css; charset=utf-8
jsonapplication/json
pngimage/png
jpgimage/jpeg
webpimage/webp
svgimage/svg+xml
woff2font/woff2
ttffont/ttf
  • Range is accepted only as a single header of the form bytes=N- with N below the file size: the answer is 206 with Content-Range: bytes N-<size-1>/<size> and the bytes from N. Any other Range (beyond the size, with an end of range, several ranges, garbage, a repeated header) gets 416 with Content-Range: bytes */<size>.
  • Limits: the request head is at most 4 KiB and must arrive within 10 s; a peer that does not read 16 KiB of the response for 10 s is cut off; the whole response must finish within 10 s plus its length at 8 KiB/s. At most 64 connections in total and 4 per address (an IPv6 address counts as its /64). A connection over a limit gets 503 with Retry-After: 2 after its head is read; past 256 such refusals at once, a connection is closed without an answer.

Downloads. The client trusts only the manifest from the game session: it checks the size and SHA-256 of every file before using it and treats the file's source as untrusted. The client:

  • takes the download endpoint only from an accepted ServerHello: the IP is the game session's address, the port is client_files_port; it downloads nothing before that or after the session ends, and a new connection replaces the endpoint entirely;
  • caches files by hash; downloads up to 2 files at once, first from the resource start with fewer downloads left; one hash is one download;
  • streams a file into <hash>.part with an incremental SHA-256 and renames it to <hash> only after the hash matches; an interrupted download resumes with Range: bytes=<received>-, including in a later session;
  • accepts 200 only with a Content-Length (when present) equal to the manifest size, and 206 only in answer to its own Range with a Content-Range from its offset to the manifest size; a byte past the size breaks the download;
  • times out on lack of progress (connect 5 s, receive 15 s), not on the total response time;
  • retries a transient failure (no connection, a broken connection, a timeout, any status other than 200, 206, 503) with exponential backoff of 1..30 s plus jitter, and a 503 after its Retry-After (up to 60 s) plus jitter of up to the same amount; meanwhile the resource start stays Pending;
  • marks the resource start Failed with reason download after 8 consecutive failures without a new byte, after 10 min of 503 without progress, or on a full disk or a failed cache write; with reason integrity on a size or hash mismatch of a file downloaded from the start;
  • aborts a download at once (by closing the HTTP request) at the end of the session, on a new endpoint, on a new generation and on the removal of the resource.

Packets ​

ResourceFiles (0x96) ​

The manifest of client files of one resource start, in parts. Server → Client, reliable event lane, critical. A new connection receives one for every running resource with client files right after ServerHello (0x02); everyone else receives it when the resource starts. A new generation of the same name replaces the manifest.

[u8  packet_id = 0x96]
[u32 event_id]
[u8  name[32]]         // resource name, NUL-terminated
[u32 generation]
[u8  total]            // 1..64 files in the manifest
[u8  first]            // index of the first file in this part
[u8  count]            // 1..7, first + count <= total
[u8  entries[924]]     // count entries of 132 bytes, the rest zeros

Total: 968 bytes. Entry: [u8 path[96]] NUL-terminated, [u32 size] up to 4 MiB, [u8 sha256[32]]. Parts arrive in order of first; the manifest is complete once all total files have arrived. The decoder rejects a name or path that breaks the rules of Resource client files, a size above the limit, count and first outside total, and a nonzero tail.

Example: resource_files.bin.

ResourceRemove (0x97) ​

The start generation of resource name has stopped: its manifest and an open view of this resource are dropped. Server → Client, reliable event lane, critical, to everyone.

[u8  packet_id = 0x97]
[u32 event_id]
[u8  name[32]]
[u32 generation]

Total: 41 bytes.

Example: resource_remove.bin.

ViewOpen (0x98) ​

Opens the recipient's view: page entry from the client files of start generation of resource name. One view per connection; a new one replaces the open one. Server → Client, reliable event lane, critical; allowed in any connection state, including PreSpawn. The manifest of that start arrives earlier on the same lane.

[u8  packet_id = 0x98]
[u32 event_id]
[u32 view_id]          // shared counter, never repeats
[u8  name[32]]
[u32 generation]
[u8  entry[96]]        // an .html path from the manifest, NUL-terminated
[u8  input]            // 0 display only, 1 all input, 2 page rectangles
[u8  origins[256]]     // auth sites of the sign-in screen separated by \n, NUL-terminated; empty for any other view

Total: 398 bytes. The page opens only from files checked against the manifest. A view runs on an origin of its own resource; requests outside it and to the external network are blocked.

The exception is the sign-in screen (ADR 0055): a view opened to a connection in PreAuth carries up to 4 origins from [[auth.providers]]. An origin is https://<host>[:port] with a lowercase DNS name under a top-level domain (not an IP literal, not a single label, not .localhost), or http:// on 127.0.0.1 or localhost; a default port is not written; at most 63 bytes. The client lets the page make only non-navigation requests to them (the page's CSP connect-src allows them), and only while it waits for sign-in: origins of a view opened after AuthAccepted (0xA1) are refused, and after AuthAccepted new requests to them are canceled. The client accepts an http:// origin only when the server itself is on loopback. The decoder rejects a field with any other origin, an extra \n or more than 4 lines.

Input:

  • 1: mouse and keyboard belong to the view while it is open and visible.
  • 2: cursor mode. The mouse does not control the camera or weapons; over the rectangles the page declared it belongs to the view, outside them to nobody, and in an edit session (EditState) to the editor's tool. The keyboard belongs to the game, and to the view while an input field of the page has focus; Esc removes the focus.

An open pause menu hides the view and releases input. The client answers the load with ViewStatus.

Example: view_open.bin.

ViewClose (0x99) ​

Closes view view_id if it is open. Server → Client, reliable event lane, critical.

[u8  packet_id = 0x99]
[u32 event_id]
[u32 view_id]

Total: 9 bytes.

Example: view_close.bin.

ViewData (0x9A) ​

A part of the JSON snapshot of topic topic for the open view at revision revision. Server → Client, reliable event lane, droppable: the server queues either all parts of a snapshot or none.

[u8  packet_id = 0x9A]
[u32 event_id]
[u32 view_id]
[u32 revision]         // the view's revision after this snapshot
[u8  topic[32]]        // [a-z0-9_], 1..31 bytes, NUL-terminated
[u8  part]             // 0..parts-1, parts arrive in order
[u8  parts]            // 1..16
[u16 len]              // bytes of data, 0..1024
[u8  data[1024]]       // the rest zeros

Total: 1073 bytes. A snapshot is UTF-8 JSON of up to 16 KiB, with object keys sorted and an empty table as []. The client assembles the parts of one topic, view and revision in order; a part out of order resets the assembly. The page receives the latest complete snapshot of each topic, including those that arrived before it loaded. Only the resource raises the view's revision, and never lowers it.

Example: view_data.bin.

ViewStatus (0x9B) ​

The client's report on a view. Client → Server, reliable event lane.

[u8  packet_id = 0x9B]
[u64 token]
[u32 event_id]
[u32 view_id]
[u8  status]           // 0 page loaded; failure: 1 download, 2 integrity, 3 no browser in this client build, 4 page

Total: 18 bytes. The client sends a failure after it has already closed the view; the server closes it and tells the resource the reason. Dropped silently (but acknowledged): the view is not open on this connection, a repeated load, an unknown status, past the limit shared with ViewAction.

Example: view_status.bin.

ViewAction (0x9C) ​

An action of the open view's page. Client → Server, reliable event lane.

[u8  packet_id = 0x9C]
[u64 token]
[u32 event_id]
[u32 view_id]
[u32 revision]         // revision of the last snapshot the page received
[u8  action[32]]       // action name, NUL-terminated
[u8  count]            // 0..16 fields
[u8  fields[1024]]     // count fields back to back, the rest zeros

Total: 1078 bytes. A field is [u8 name_len] 1..31, the name [a-z0-9_], [u8 type], then the value: 0 boolean — [u8] 0 or 1; 1 number — [f64]; 2 string — [u8 len] and UTF-8.

Dropped silently (but acknowledged): the view is not open on this connection or its page has not loaded yet, the action is not in the resource's allowlist, revision is not the view's current revision, a field is undeclared or repeated, a type is wrong, a number is NaN, infinite, out of range or has a fractional part where an integer is declared, a string is longer than its limit, not UTF-8 or contains a control or format (Cf) character, a required field is missing, the fields do not match count or are followed by nonzero bytes, or the per-connection limit of 20 at once and 10 per second is exceeded (rejected actions and ViewStatus spend it as well). An accepted action goes to the resource that opened the view, with its fields by name.

Example: view_action.bin.

Documentation licensed under CC BY 4.0. DarqlineMP is not affiliated with Rockstar Games or Take-Two Interactive. Grand Theft Auto is a trademark of Take-Two Interactive Software, Inc.