# Rust — the Oxide/uMod ecosystem reference Reference material for the **upcoming `module-rust`**: a mirror of the uMod/Oxide documentation — the Rust game API *and* the game-independent plugin framework around it — captured here so the module can be designed and built against it without a round trip to umod.org on every question. Everything below was **scraped verbatim from uMod on 2026-09-15**. ## The mirror | Doc | What it holds | |---|---| | [`HOOKS.md`](HOOKS.md) | **What Rust will tell you.** All **477 hooks** in 20 categories — description, return contract, tags, every C# overload. The 34 *universal* hooks are marked. | | [`OXIDE_API.md`](OXIDE_API.md) | **How a plugin is built.** The 19 developer pages — plugin structure, hooks, commands, `IPlayer`, permissions, config, data files, database, localization, timers, web requests, dependencies, CI, review. | | [`DEFINITIONS.md`](DEFINITIONS.md) | **What things are called.** 678 items (short name, id, display name) and 2,590 workshop skin ids across 104 items. | | [`OPERATING.md`](OPERATING.md) | **How it gets run.** The 6 operator pages — installing Oxide on a server, then installing, configuring and permissioning plugins. | | [`agent/`](agent/README.md) | The same facts in **machine shape** — TSV and JSONL, ~46% of the tokens. Generated in the same pass, so it cannot drift. | > **This is a mirror, not a specification we own.** uMod is upstream and wins any disagreement; the > point of copying it is availability and grep-ability, not authority. Nothing here may be cited as a > Runic Gateway contract — our contracts are [`MODULE_API.md`](../../website/MODULE_API.md) and > [`docs/link/`](../../link/). --- ## 1. Why this exists The website core is game-agnostic; a **module** is what makes it a site for one particular game ([`MODULE_SYSTEM.md`](../../website/MODULE_SYSTEM.md)). `module-uo` was the first. A Rust module is the second, and it was already designed once on paper — [`../rust-dryrun.md`](../rust-dryrun.md) is that dry run, written deliberately *without* implementing it, to find out whether the module contract generalises past Ultima Online. The dry run's central structural fact is the thing this reference serves: > A ServUO shard is C# **source** the operator compiles into their own server, so our bridge plugin > can be anything we want. **A Rust server is a binary nobody outside Facepunch patches.** The only > way in is a mod — specifically an **Oxide plugin**, since Oxide/uMod is what modded Rust servers > run — hooking the game's own events. Two consequences, and they are the two halves of this directory: 1. **We can only emit what Oxide already hands us.** [`HOOKS.md`](HOOKS.md) is the hard ceiling on what a Rust module can ever know about a live server. If a fact is not reachable from one of those 477 hooks (or from a game type one of them hands you), the bridge cannot report it. That makes it the input to the Rust sidecar's event catalogue — the analogue of [`docs/link/PLAN.md`](../../link/PLAN.md) §5 on the UO side. 2. **We are a guest in someone else's plugin framework.** Our plugin is compiled, loaded, permissioned and configured by Oxide, on Oxide's terms. [`OXIDE_API.md`](OXIDE_API.md) is that rulebook, and [`OPERATING.md`](OPERATING.md) is what the server owner has to do — which is the surface our deployment story has to sit on, the way [`installer/INSTALL.md`](../../installer/INSTALL.md) sits on top of ServUO. --- ## 2. How an Oxide hook actually binds The one thing to understand before reading [`HOOKS.md`](HOOKS.md), because it is not in the table itself: **Oxide binds hooks by name and arity, by reflection, at runtime.** There is no interface to implement and no compile-time check. A method whose name is misspelled, or whose parameter types do not match, is simply **never called** — silently, with no warning at load. This is the single most common way a Rust plugin "does nothing". The consequence for us: **every hook name a module relies on is an untyped string constant against a moving upstream.** Facepunch renames and removes hooks on wipes. A Rust sidecar should log which of its expected hooks have actually fired at least once, so a hook that quietly stopped existing is visible rather than mysterious. ### The return contract [`HOOKS.md`](HOOKS.md) reproduces uMod's return line for every hook and it is the load-bearing part: | uMod's wording | Code in [`agent/hooks.tsv`](agent/hooks.tsv) | What it means | |---|---|---| | *No return behavior* | `none` | A **notification**. Declare `void`. Nothing you return is read. | | *Returning a non-null value overrides default behavior* | `nonnull` | A **veto with a payload**. Declare `object`; return `null` to let the game proceed, anything else to cancel it. | | *Returning true or false overrides default behavior* | `bool` | A **veto**. Declare `object`; `null` abstains — returning `false` is *not* the same as abstaining. | | *Returning a string will kick…* | `data` | The value is **consumed as data**, not merely as a veto. | Two cautions that apply across the whole table: - Never return non-`null` from a hook documented as *no return behavior*; on some hooks Oxide will still read it as a veto. - When several plugins hook the same veto, the **first non-`null` return wins** and the rest are not consulted. Abstain with `null` unless you mean to decide. A bridge plugin should be a **reader**, and must return `null` from every veto hook it listens on — see §5. Three hooks (`OnEntityTakeDamage`, `OnExcavatorSuppliesRequested`, `OnPhoneCallStarted`) state no return behaviour upstream at all; both the markdown and the TSV mark those *(not stated upstream)* / `?` rather than guessing. --- ## 3. What is in the 477 | Category | Hooks | What it covers | |---|---:|---| | Server | 17 | Process lifecycle, tick/frame, save and restart, RCON and console commands | | Player | 138 | Connect, chat, command, craft, loot, build, die, respawn | | Entity | 140 | The generic `BaseEntity` graph — spawn, kill, damage, mount, loot, flags | | Item | 43 | Item stacks and containers | | Vehicle | 22 | Boats, cars, horses, helicopters, trains, submarines | | Vending | 16 | Vending machines, the shop/trade flow, marketplace and drone deliveries | | Weapon | 16 | Firing, reloading, throwing, melee, projectiles and traps | | Structure | 15 | Placement, upgrade, demolish, stability, doors and locks | | Resource | 12 | Gathering and node dispensers | | Team | 12 | Rust's built-in team/party system | | Phone | 12 | In-game telephones | | Permission | 8 | uMod's own permission/group system (Covalence) | | Clan | 7 | The first-party clan system, distinct from Team | | Fishing · Plugin · TechTree | 4 each | | | Sign | 3 | | | Electronic | 2 | | | Terrain · World | 1 each | | **Player and Entity are 58% of the surface between them.** [`HOOKS.md`](HOOKS.md) opens with a full alphabetical index (name → category → universal → return contract), which is the fastest way in when you already know the hook's name. ### Universal vs. Rust-specific **34 of the 477 are not Rust's.** They are uMod's own *universal* (Covalence) hooks — `Init`, `Loaded`, `Unload`, `OnUserConnected`, the whole `OnGroup*`/`OnUserPermission*` family — which uMod raises identically on every game it supports. They appear on the Rust page because they fire on Rust too. Verified against in the same capture: all 34 are in the Rust set, and the Rust page adds none of its own, so the overlap is exact. The distinction is architectural, not trivia: **a universal hook is the portable part of the surface.** Code written against one would carry to any other uMod-supported game; code written against a Rust-specific hook would not. They are marked in `HOOKS.md` and flagged `1` in [`agent/hooks.tsv`](agent/hooks.tsv). Two notes on the data, both faithful to upstream: - `OnShopCompleteTrade` appears **twice** — two separate records. Both are kept; the second carries the anchor `#onshopcompletetrade-1`. - Twelve hooks document **more than one overload** (e.g. `CanUpdateSign` takes either a `Signage` or a `PhotoFrame`). Every code block is reproduced. --- ## 4. Reading the machine copy [`agent/`](agent/README.md) holds the same facts as TSV and JSONL, at ~46% of the tokens — a real 3.2× saving on the hooks, 2.1× on the API prose, and only ~1.25× on the item and skin tables, which were already dense. Reach for it when you want to *find* something; read the markdown when you want the reasoning, which is deliberately not in there. [`agent/README.md`](agent/README.md) has the column definitions and an honest account of where the saving is and is not. --- ## 5. Where this meets our architecture The bridge invariants the platform already holds ([`ARCHITECTURE.md`](../../website/ARCHITECTURE.md), [`docs/link/PLAN.md`](../../link/PLAN.md)) are game-independent, and a Rust module inherits all of them. Restated against Oxide: - **The game server is never network-reachable.** The Rust sidecar listens; the Oxide plugin dials out to it, exactly as `BridgeLink.cs` does on the UO side. - **A wedged or absent sidecar must never stall the server.** Oxide hooks run on Rust's main thread. An emit must be an enqueue onto a bounded drop-oldest queue that returns immediately — never a socket write, never a blocking call, and never anything expensive inside `OnTick`/`OnFrame`. Oxide's own `timer` and `NextFrame` helpers ([`OXIDE_API.md` § Timers](OXIDE_API.md#timers)) are the tools for deferring work off a hot hook. - **The bridge plugin decides nothing.** It returns `null` from every veto hook it subscribes to. Access control and audience scoping live on the website ([`SHARD_VISIBILITY.md`](../../website/SHARD_VISIBILITY.md)), not in the game plugin — the same rule that makes the uo-link sidecar a dumb forwarder. - **Sensitive events never reach the public stream.** Rust raises plenty that must not: `CanUserLogin` and `OnUserApproved` carry IP addresses, `OnPlayerReported` carries player reports. These belong on the admin channel only. Reading order for someone picking the module up: [`../rust-dryrun.md`](../rust-dryrun.md) for the design, [`MODULE_API.md`](../../website/MODULE_API.md) for the contract it must satisfy, [`OXIDE_API.md`](OXIDE_API.md) for the framework we are a guest in, then [`HOOKS.md`](HOOKS.md) for what the game can actually tell us. --- ## 6. Provenance and refreshing | | | |---|---| | Captured | **2026-09-15** | | Hooks | · | | Definitions | | | Developer API | — 19 pages | | Operator docs | — 6 pages | | Content | 477 hooks (34 universal) · 678 items · 2,590 skins · 25 prose pages · 150 code examples | **Rust wipes monthly and the hook list moves with it.** Re-capture before any significant work on the module, and update the date in every file — they all carry it. ### How to capture it Three different routes, because uMod serves these three ways. Cloudflare gates the HTML pages but **not** the JSON endpoints: **1. Hooks — a plain `curl`, no browser needed.** The page renders client-side from an endpoint that is not gated. One request returns everything (`per_page` is 9999, so there is no paging): ```bash curl -A "Mozilla/5.0" -H "Accept: application/json" \ "https://umod.org/documentation/hooks/rust.json" -o all_rust_hooks.json curl -A "Mozilla/5.0" -H "Accept: application/json" \ "https://umod.org/documentation/hooks/universal.json" -o all_universal_hooks.json ``` Each record carries `name`, `subcategory`, `tags`, `description` (HTML `
    `) and `example` (a markdown-fenced C# block). A `?subcategory=Server` query narrows it. **2. Definitions — a real browser.** No JSON endpoint, and the HTML *is* gated. Load `/documentation/games/rust/definitions`, then read the `Rust Items` table and the per-item `Rust Skins` tables out of the rendered DOM. **3. The prose pages — a real browser, throttled.** Also gated. Fetch each page same-origin from an already-loaded uMod tab and take `document.querySelector('.documentation')`, dropping the `.table-of-contents` child. **Pause ~1s between pages** — uMod returns `429 Too Many Requests` on a tight loop, and a 429 is easy to mistake for a real page because it still returns 200-shaped HTML with a title. The pages mirrored here, in the order they appear: - `api/` — overview, getting-started, hooks, commands, player, permissions, configuration, data-files, database, localization, timers, web-requests, dependencies, integration, preprocessor-directives, security, style-guide, continuous-integration, approval-guide - operator — getting-started, plugins/getting-started, plugins/installation, plugins/configuration, plugins/data-files, plugins/permissions ### Deliberately not mirrored - **The other games** uMod supports (Hurtworld, 7 Days to Die, Reign of Kings, The Forest). Not our ecosystem. `api/preprocessor-directives` lists their compile symbols if that ever changes. - **Community pages** — contributing, community guidelines, reporting issues, getting help. Process, not API. - **The `/documentation/umod/*` paths**, which are aliases of the same pages.