---
mvp: yes
subsystem: client-networking
---

# Client Networking (39dll wrapper layer)

The BNO 5-8 client networks via **39dll**, a third-party Win32 sockets shim distributed as `39dll.dll` (the file lives in `legacy/open-source-release/39dll.dll`). The DLL exposes a small set of TCP/UDP primitives plus a binary read/write buffer API; the GML side uses `external_define` / `external_call` / `external_free` to import the symbols and invoke them.

Scripts **0014-0084** form the canonical 39dll wrapper layer. Each is a one-line `external_call` of one DLL function:

| Range | Role |
|-------|------|
| 0014-0015 | `dllinit` / `dllfree` — load + unload `39dll.dll` |
| 0016-0036 | TCP / UDP socket primitives — `tcpconnect`, `tcplisten`, `tcpaccept`, `udpconnect`, `closesocket`, etc. |
| 0038-0058 | Binary write-* (`writebyte`, `writeshort`, `writeint`, `writefloat`, `writechars`, `writestring`) |
| 0049-0058 | Binary read-* (`readbyte`, `readshort`, `readint`, `readfloat`, `readchars`, `readstring`) |
| 0061-0068 | Buffer position / size helpers (`getpos`, `setpos`, `clearbuffer`, etc.) |
| 0066-0072 | Buffer alloc + crypto (`createbuffer`, `freebuffer`, `md5string`, `bufferencrypt`) |
| 0073-0080 | File I/O via the DLL (`fileopen`, `fileread`, `filewrite`) — distinct from GM's `file_text_*` |
| 0081-0084 | Network utility (`getmacaddress`, `iptouint`, `uinttoip`, `netconnected`) |

These scripts are **mechanical bindings** — most are 1-3 lines each. The narrative substance is in the call patterns from higher-level code:

- **Script 0023 `sendmessage`** is the canonical send. It packages the current buffer (built by chained `writebyte`/`writeshort`/...) and ships it to the connected socket.
- **Script 0024 `receivemessage`** drains one message from the wire into the buffer.
- **Script 0025 `peekmessage`** is the non-destructive variant — used to check whether enough bytes are on the wire to parse a complete protocol frame.

These are the three call sites Phase 4 (server rebuild) cares about most: every protocol frame either gets `send`-ed or `receive`-d through them.

`extracted/client-5-8/scripts/0014-dllinit.gml` is the full DLL bind-list — 60+ `external_define` calls, one per DLL symbol. It runs once at game start (Object 0042-player or its Create-event spawner). Each `external_define(dll, name, calltype, returntype, argcount, argtype...)` produces a callable handle stored in a `global.<name>` variable.

## Pattern: thin wrapper into the wiki

Per Phase 1 D-19, this document is the **client-side narrative** for networking only. The wire-protocol bytes (opcodes, frame layout, field encodings) belong in **[decomp/wiki/08-39dll-networking.md](../../decomp/wiki/08-39dll-networking.md)**, which is the canonical reference for everyone (client + server + future tools).

Phase 3 (server documentation, plan SDOC-02) reverses the opcode table from `extracted/server-5-4/scripts/` + `objects/`, building on the same wrapper layer. Both extracted trees use 39dll; the wrapper scripts are essentially identical.

## Init / connect / disconnect lifecycle

- **Init** — Script `0014 dllinit` is called once at game start. Registers ~60 DLL symbols.
- **Connect** — User clicks "Connect" in Main_Menu (object 0036-connect), which calls `tcpconnect(server_address, server_port)` (script 0016). On success, sets `global.connected = 1`.
- **Authenticate** — Once connected, the client sends a login packet (username + password — see CLAUDE.md hard rule #2 for the security migration plan); server responds with session state.
- **Game loop** — Every step, scripts `0094 begin_client_receive` and `0097 client_receive` drain incoming messages via `receivemessage` and dispatch by opcode. The `0023 sendmessage` calls are scattered throughout per-event handlers (movement, chat, request actions).
- **Disconnect** — Script `0034 sockexit` closes the socket and frees the buffer. `0015 dllfree` runs at game exit.
- **Reconnect** — Not gracefully implemented in the original. Connection drops require a full client restart.

## Key idioms

- **Manual buffer build before send.** A typical "send packet" looks like `writebyte(buf, OPCODE); writechars(buf, name, 16); writeint(buf, x); writeint(buf, y); sendmessage(socket, buf)`. The buffer is a globally-allocated handle.
- **Length-prefix vs sentinel terminator.** 39dll defaults to length-prefix framing (the DLL writes a 4-byte big-endian length before each `sendmessage` payload). The reverse-engineering wiki documents this in detail.
- **Polling, not async.** `client_receive` is called from the player's Step event every frame. There are no callbacks — the game loop pulls from the socket every 33 ms.
- **Single connection.** One socket per client. No multiplexing, no sub-channels.
- **Synchronous reads.** `peekmessage` checks readiness; `receivemessage` blocks for one full message (with the DLL doing the wait). Phase 4 server replaces this with WebSocket binary frames + an event-driven receive loop.

## Scripts referenced in this subsystem

<!-- AUTOGEN:scripts:start -->
| Script ID | Name | Lines | Used in objects |
|-----------|------|-------|------------------|
| 0014 | dllinit | 85 | — |
| 0015 | dllfree | 2 | — |
| 0016 | tcpconnect | 12 | — |
| 0017 | tcplisten | 12 | — |
| 0018 | tcpaccept | 8 | — |
| 0019 | tcpip | 4 | — |
| 0020 | setnagle | 7 | — |
| 0021 | tcpconnected | 6 | — |
| 0022 | udpconnect | 9 | — |
| 0023 | sendmessage | 16 | — |
| 0024 | receivemessage | 15 | — |
| 0025 | peekmessage | 16 | — |
| 0026 | setformat | 20 | — |
| 0027 | lastinIP | 10 | — |
| 0028 | lastinPort | 8 | — |
| 0029 | setsync | 7 | — |
| 0030 | closesocket | 6 | — |
| 0031 | socklasterror | 4 | — |
| 0032 | myhost | 3 | — |
| 0033 | compareip | 10 | — |
| 0034 | sockexit | 4 | — |
| 0035 | sockstart | 3 | — |
| 0036 | hostip | 7 | — |
| 0037 | getsockid | 6 | — |
| 0038 | writebyte | 8 | — |
| 0039 | writeshort | 9 | — |
| 0040 | writeushort | 9 | — |
| 0041 | writeint | 9 | — |
| 0042 | writeuint | 9 | — |
| 0043 | writefloat | 8 | — |
| 0044 | writedouble | 9 | — |
| 0045 | writechars | 8 | — |
| 0046 | writestring | 10 | — |
| 0047 | copybuffer | 8 | — |
| 0048 | copybuffer2 | 9 | — |
| 0049 | readbyte | 7 | — |
| 0050 | readshort | 7 | — |
| 0051 | readushort | 7 | — |
| 0052 | readint | 7 | — |
| 0053 | readuint | 7 | — |
| 0054 | readfloat | 7 | — |
| 0055 | readdouble | 7 | — |
| 0056 | readchars | 8 | — |
| 0057 | readstring | 7 | — |
| 0058 | readsep | 13 | — |
| 0059 | readbit | 8 | — |
| 0060 | buildbyte | 6 | — |
| 0061 | getpos | 8 | — |
| 0062 | clearbuffer | 6 | — |
| 0063 | buffsize | 7 | — |
| 0064 | setpos | 8 | — |
| 0065 | bytesleft | 7 | — |
| 0066 | createbuffer | 5 | — |
| 0067 | freebuffer | 6 | — |
| 0068 | bufferexists | 6 | — |
| 0069 | md5string | 6 | — |
| 0070 | md5buffer | 5 | — |
| 0071 | bufferencrypt | 6 | — |
| 0072 | bufferdecrypt | 6 | — |
| 0073 | fileopen | 8 | — |
| 0074 | fileclose | 6 | — |
| 0075 | filewrite | 8 | — |
| 0076 | fileread | 10 | — |
| 0077 | filepos | 6 | — |
| 0078 | filesetpos | 7 | — |
| 0079 | filesize | 6 | — |
| 0080 | adler32 | 9 | — |
| 0081 | getmacaddress | 6 | — |
| 0082 | iptouint | 9 | — |
| 0083 | uinttoip | 7 | — |
| 0084 | netconnected | 6 | — |
| 0092 | init_user | 15 | — |
| 0094 | begin_client_receive | 164 | — |
| 0097 | client_receive | 448 | — |
| 0098 | uninit_user | 14 | — |
| 0099 | orpos_meeting | 5 | — |
| 0366 | clear_socket | 6 | — |
<!-- AUTOGEN:scripts:end -->

## Objects referenced in this subsystem

<!-- AUTOGEN:objects:start -->
| Object ID | Name | Sprite | Mask | Events |
|-----------|------|--------|------|--------|
<!-- AUTOGEN:objects:end -->

## Engine functions used

<!-- AUTOGEN:gml-functions:start -->
| GML function | Call sites | Sample script | Wiki link |
|--------------|------------|---------------|-----------|
| `external_define` | 1 | 0014-dllinit | [wiki/07-gml-core-functions](../../decomp/wiki/07-gml-core-functions.md) |
| `external_call` | 68 | 0014-dllinit | [wiki/07-gml-core-functions](../../decomp/wiki/07-gml-core-functions.md) |
| `external_free` | 1 | 0015-dllfree | [wiki/07-gml-core-functions](../../decomp/wiki/07-gml-core-functions.md) |
<!-- AUTOGEN:gml-functions:end -->

## Rebuild guidance

For the Phase 6 client:

- **WebSocket binary frames replace 39dll.** Per `.planning/research/STACK.md`, transport is WSS + `@colyseus/schema` + `msgpackr`. No DLL load step.
- **No Win32 dependency**. The whole `external_define` / `external_call` mechanism is gone; modern code makes direct `WebSocket` calls.
- **Server-authoritative**. Per CLAUDE.md hard rule #1, the server owns truth. Client sends intent (`{type: 'walk', dir: 'east'}`); server emits state delta.
- **Reuse the opcode names**. Phase 3 (SDOC-02) reverses the canonical opcode list from server GML. Phase 6 client uses those names verbatim — naming match makes parity testing trivial.
- **`packages/protocol` is the shared types**. Wire types + codec live there; both client and server import.

## See also

- [decomp/wiki/08-39dll-networking.md](../../decomp/wiki/08-39dll-networking.md) — the canonical wire-protocol reference
- [scene-room-model.md](scene-room-model.md) — zone changes are server-driven via this wire
- Phase 3 plan SDOC-02 — opcode table reverse engineering (server-side)
- Phase 4 server rebuild plans — Colyseus authoritative server consuming the same protocol
