OMaNGOS — Oxidized Massive Network Game Object Server #
A faithful, idiomatic Rust rewrite of CMaNGOS Classic (game version 1.12.1), wire-compatible with the retail 1.12.1 client. The C++ reference server and the community database are the behavioural oracle.
This README is the from-zero setup guide. For the contributor contract (coding standards, the
canonical checks, the porting workflow) see AGENTS.md; for writing your own
server plugins see plugins/README.md.
Warning
This project was initially generated entirely by Claude (Anthropic's AI) over multiple days. It has not yet had a thorough human review. Bugs are present and quality varies from one subsystem to the next — treat everything as unverified until a human has reviewed it. Do not run it in production, expose it to untrusted networks, or trust it with real credentials. See Systems pending human review below for the audit checklist.
Where to go #
| I want to… | Go to |
|---|---|
| run a server | What you need → Setup, end to end. Steps 1–4 are shared; step 5 is where you pick Docker or native. |
| operate one | Configuration, Administration (the mangos-admin CLI/TUI), and docs/metrics.md. |
| write code | Start here (contributing code) — then just doc, and read world-core's crate doc. |
| write a plugin | plugins/README.md. |
| port from CMaNGOS | docs/porting/MAPPING.md and the /port-upstream skill. |
Acknowledgements #
This project stands entirely on the shoulders of CMaNGOS and the
years of work its developers and the wider MaNGOS community have poured into it. OMaNGOS is a Rust
rewrite of CMaNGOS Classic: the C++ server is our behavioural oracle, its source is the reference
we port from line by line, and the cmangos/classic-db
community database supplies the world content. Without CMaNGOS — and the MaNGOS and ScriptDev2/Dev
projects it descends from — this project simply would not exist.
Our deepest thanks to the CMaNGOS developers and contributors. Their work is licensed under the
GPL-2.0, and every ported item in this tree carries an upstream: provenance tag back to the C++
symbol it came from (see docs/porting/MAPPING.md).
What's in this project #
An at-a-glance map of what the repo contains. Directory-level detail is in Repository layout below.
Servers — bin/
realmd— authentication/logon server: SRP6 handshake + realm list for the 1.12.1 client.vanilla-world— the 1.12.1 world/game server (validates the realmd session key, serves gameplay).mangos-admin— maintainer admin client (CLI + interactive TUI) over the gRPC control plane: accounts, realms, GM commands, plugin management.portal— player-facing web portal (Leptos SSR + WASM, Axum): registration, SRP6 auth, password management, server status, tickets/bug reports.
Tools — tools/
extractor— pulls DBC/cameras/maps/nav out of the retail 1.12.1 client's MPQ archives (pure-Rustwow_mpq).dbtool— offline DB tooling: schema transpile,classic-dbimport, GM account/realm bootstrap, and one-way player migration from an existing CMaNGOS realm.docgen— generates the GM-command reference + example configs from the code.plugingen— generates the WASM plugin contract (WIT + SDK + docs) fromcrates/plugin-idl.omangos-mcp— MCP server exposing the read-onlyWorldInspectService(maps/entities/bot brains) as LLM tools.bot-inspector— egui app that live-renders a running player-bot's "brain" over the gRPC stream.
Plugin system — an optional WASM plugin layer, on by default (WORLD_PLUGINS__ENABLED=false disables it):
- Host (
crates/wasm-host) — awasmtimeComponent-Model host that loads sandboxed WASM components, dispatches game events into them, and enforces the sandbox (per-map isolation, fuel limits, an admin-gatedraw-sqlcapability, and an install/ownership ledger for plugin-created content). - SDK (
plugins/sdk+sdk-macro) — the guest authoring layer (theScripttrait,#[plugin]macro, gossip builder, KV state); the host/guest contract is generated fromcrates/plugin-idl. - Example plugins (
plugins/, all ported from Eluna Lua scripts) —ruleset(per-character Normal/Boosted/Hardcore/Self-Found rules; it absorbed the former standaloneboostplugin),vendor(free consumables/reagents/world-buffs NPC),enchanter(permanent gear enchants), andexamples/hello(the minimal template).
Vendored third-party — vendor/ — one all-Rust nav crate, forked and wired in via
[patch.crates-io]; it replaces part of the old C++ namigator so the workspace stays FFI-free
(ADR 0013):
rerecast— Recast navmesh generation, patched to skip a bad contour instead of aborting the whole mesh (fix pending upstream).
Runtime navmesh queries are a first-party Detour-style polygon-corridor pathfinder over the coarse
rerecast mesh (ADR 0014, crates/nav-query), which replaced the previously-vendored polyanya.
Client addon — client-addons/
BugReport— a one-click in-game bug reporter (1.12.1): it whispers a compact report to a sentinel name the server intercepts and auto-enriches with character/map/target/quest context.
What you need #
Nearly all of this is for the one-time data setup. A server that is already set up needs only
its binaries, Postgres, and the extracted data/ directory.
| What | Why | Needed for |
|---|---|---|
| Docker / Podman + compose | Postgres 18, plus a dev-only MariaDB the content import stages through | both tracks — on the native track, only if you would rather not install the databases yourself |
Rust — pinned by rust-toolchain.toml (1.96.0); rustup installs it on first build |
building the servers and the setup tools | both tracks |
The mariadb and psql client binaries |
dbtool import shells out to them, as the upstream installer does |
step 3 only |
| A 1.12.1 client | to extract DBC/map/nav data, and to log in and play. The server reads only the extracted artifacts, never the client | step 2, and playing |
A checkout of cmangos/classic-db |
the world content — creatures, quests, loot, … It is not vendored; you import it locally | step 3 only |
Most workflows are wrapped in the Justfile — install just
and run just to list recipes. The raw commands are shown alongside each step below.
just setup-tools # rustup targets + sqlx-cli, taplo, nextest, ast-grep, typos, cargo-deny
Setup, end to end #
The order is: databases → client data → world content → an account → run. Each step is idempotent, so you can re-run any of them. Steps 1–4 are the same either way; only step 5 differs between running in Docker and running natively.
1. Start the databases #
just db-up
# = docker compose up -d postgres mariadb
This brings up Postgres on 127.0.0.1:5432 (user/password/db all mangos) and a dev-only
MariaDB used only as a staging ground for the content import. Nothing else uses MariaDB at runtime.
The schemas are created and migrated automatically:
realm— accounts + realm list (auth). Applied byrealmd(anddbtool bootstrap) on startup.vanilla— world content and character data, each tracked separately. Character/plugin tables are applied by the world server on startup; world content is loaded in step 3.
Prefer your own Postgres? Nothing requires the container. Install Postgres 18, create a
mangosrole and database (CREATE ROLE mangos LOGIN PASSWORD 'mangos'; CREATE DATABASE mangos OWNER mangos;), and the built-in defaults find it. MariaDB is only needed while you run step 3.
2. Extract client data #
Point at a 1.12.1 client install (the directory that contains Data/). Artifacts land under data/.
just CLIENT=/path/to/WoW-1.12.1 extract
# = cargo run --release -p extractor -- all --client /path/to/WoW-1.12.1 --out data
This produces data/dbc (DBC tables), data/cameras (the ten cinematic fly-by camera models),
data/maps (terrain height), and data/nav (navigation meshes + line-of-sight geometry). The world
server reads data/ at startup and refuses to start if the DBC tables are missing; a missing
data/cameras only costs the server-side camera follow during cinematics, which nothing on screen
depends on. You can extract a subset with the dbc / cameras / maps / nav subcommands.
3. Import world content #
Load cmangos/classic-db into the Postgres vanilla schema. This consolidates the base dump plus
the ordered update files in MariaDB, then streams every table into Postgres through the value mapper.
just CLASSIC_DB=/path/to/classic-db import
# = cargo run -p dbtool -- import --classic-db /path/to/classic-db --schema vanilla
Needs the mariadb and psql client binaries on your PATH — the importer drives them directly.
4. Create a GM account and a realm #
So the client can authenticate and see a realm to join (security 3 = administrator):
just bootstrap MYACCOUNT mypassword
# = cargo run -p dbtool -- bootstrap --username MYACCOUNT --password mypassword
Defaults register a realm named MaNGOS pointing the client at the world server on
127.0.0.1:8085. Override with --realm-name, --realm-address, --realm-port if needed — use
the address players will connect to, not 127.0.0.1, if the server is not on their machine.
5. Run the servers — pick a track #
Both tracks run the same two daemons. They are log-only: there is no in-process console, and stdin is never read.
Track A — Docker (the whole stack in containers) #
docker-compose.yml also defines the servers themselves, not just the
databases:
MANGOS_DATA_DIR=./data docker compose up -d --build realmd vanilla portal
docker compose logs -f vanilla
realmd,vanillaandportalare built from source on first run — statically linked (musl) intoscratchimages, so it is a full release build the first time and cached after.MANGOS_DATA_DIRis where your extracteddata/lives; it is mounted read-only at/data, and defaults to./data. It must be readable by uid65534(the images run asnobody).- Drop
portalfrom the command if you do not want the player web portal. - Logs are JSON by default so they ship cleanly into Loki/Grafana. Set
LOG_FORMAT: ""on a service in the compose file for the human-readable text format. - Builds through compose report their version as
dev. Stamping the git revision needs a build arg compose does not pass, so build the image directly when the stamp matters (it is what ties a bug report back to the code that was running):docker build -f bin/vanilla-world/Dockerfile --build-arg GIT_SHA="$(git rev-parse --short=12 HEAD)" .
Track B — Native (cargo run) #
Start each in its own terminal:
just realmd # = cargo run --release -p realmd
just world # = cargo run --release -p vanilla-world
The world server reads data/ from its working directory and applies the character + plugin
migrations on startup; the world content imported in step 3 is read live. If you want to drive the
server with mangos-admin later, give it a realm id matching the realm.list row bootstrap
created (WORLD_REALM_ID=1) — it defaults to 0, which means "unset" and affects nothing else.
Ports either way #
| Port | Service | Notes |
|---|---|---|
3724 |
realmd — auth | what the client's realmlist points at |
8085 |
vanilla-world — game | the address registered in realm.list (step 4) |
8080 |
portal — player web portal | Docker track only unless you run it yourself |
5432 |
Postgres | |
3306 |
MariaDB | dev-only, for the import; not used at runtime |
50051 / 50052 |
realmd / world gRPC admin | unauthenticated; loopback-only by default |
6. Connect the client #
Point the client's realmlist.wtf at the auth server host (set realmlist 127.0.0.1), launch it,
and log in with the account from step 4. The realm MaNGOS appears in the list; selecting it
connects to the world server.
Configuration #
Both servers take an optional TOML config file and fall back to built-in defaults; every field is overridable by an environment variable. Precedence is defaults → TOML file → env.
cargo run -p vanilla-world -- --config world.toml
cargo run -p realmd -- --config realmd.toml
- World server env prefix
WORLD_(nested keys joined by__, e.g.WORLD_DATABASE__URL=postgres://user:pass@host/db,WORLD_PLUGINS__ENABLED=false). - realmd env prefix
REALMD_(e.g.REALMD_DATABASE__URL=postgres://user:pass@host/db).
Annotated example configs (the full option set, regenerated from the code by cargo run -p docgen)
live in docs/config/. Key world-server defaults: data_dir = "data", database
mangos/mangos@localhost:5432 on schema vanilla, gRPC admin on 127.0.0.1:50052.
Administration #
Runtime control is out-of-band over gRPC — the servers have no console. The
mangos-admin CLI/TUI talks to realmd (127.0.0.1:50051) for account + realm
management, and — once a realm is targeted — to that realm's world server for announce/kick/shutdown,
GM commands, and plugins. Run it with no subcommand for the interactive console.
just admin account create someuser somepass
just admin realm list # realms realmd knows about
just admin --realm 1 online-list # a world command — pick a realm
just admin --realm 1 gm tele Stormwind on MyChar # GM command (no leading dot), as a character
just admin --realm 1 plugins list
Realmd-scoped commands (account, realm, portal, ban-ip) talk to realmd directly; world
commands need a realm — pass --realm <id|name> or target one in the console (Ctrl-R). GM commands
take the target character after the command, as a trailing on <character> clause.
The gRPC endpoints bind loopback only and are unauthenticated. To reach a remote server the
client stands up its own in-process SSH tunnel — a realmd bastion chosen at launch (--realmd-ssh <host>) and a per-realm world bastion (stored per environment, selected with --context); never
bind a public address. Realm-targeted environments and their bastions are remembered in
~/.config/mangos-admin/config.toml.
Plugins #
The server has an optional WASM plugin layer (sandboxed guest components that subscribe to game
events). It is compiled in by default and on unless you set WORLD_PLUGINS__ENABLED=false. Bundled
plugins: ruleset, vendor, enchanter. To write or build one, see plugins/README.md.
just plugin-deploy vendor-plugin vendor_plugin # build + drop into the plugins dir
just plugin-install vendor_plugin # install into the running server + hot-reload
Development #
Run the full check suite green before any change is reviewable (all offline — no database needed):
just check
which runs cargo fmt --check, taplo fmt --check, cargo clippy --workspace --all-targets -D warnings (plus a second pass over world-core --no-default-features), cargo check, cargo nextest run, the doctests, ast-grep scan, typos, the docgen/plugingen --check passes, and
cargo deny check.
SQL is compile-time-checked via sqlx with the verified metadata committed under
.sqlx/, so builds run offline. After changing a query (or the schema it hits),
regenerate the cache against the live dev DB:
just sqlx-prepare # DATABASE_URL=postgres://mangos:mangos@127.0.0.1:5432/mangos cargo sqlx prepare ...
There is no CI — the local checks are the gate.
Start here (contributing code) #
The code is documented where it lives. Build the API docs and read world-core's front page — it
is the navigation map:
just doc # = cargo doc --workspace --no-deps --open
Four files answer "where do I look?":
| File | What it tells you |
|---|---|
crates/world-core/src/lib.rs |
The symptom → module table, the four kinds of module, and a worked bug hunt. Start here. |
crates/world-core/src/world.rs |
Index of the ~90 private world/ submodules (rustdoc cannot list them). |
bin/vanilla-world/src/server.rs |
Where every CMSG_* packet the client sends is dispatched. |
crates/playerbot/src/lib.rs |
Which of the four bot layers a misbehaving bot is misbehaving in. |
Then: AGENTS.md is the contributor contract (the non-negotiables and the check
suite), CONTRIBUTING.md the conventions a linter can't enforce, and
docs/porting/MAPPING.md runs the map the other way — CMaNGOS C++
area → Rust crate/module — for when you are porting from the reference server.
Systems pending human review #
Because the codebase was AI-generated, every subsystem below should be read and verified by a human before being relied on. They are grouped by review priority — the security- and memory-safety-relevant ones first, since those are where a defect is most costly.
Security & safety critical (review first) #
Core gameplay correctness #
Subsystems #
Data, tooling & infrastructure #
When a subsystem has been human-reviewed, check it off here (and note who/when) so the status stays honest.
Repository layout #
crates/— libraries:shared,auth,dbc,db,world-core(the gameplay graph),content(native ScriptDevAI port),wasm-host(the plugin runtime),plugin-idl,nav/nav-geom/nav-build/nav-query/terrain(all-Rust navigation),wire,golden.bin/— servers:realmd,vanilla-world,mangos-admin,portal(Leptos player web portal).tools/— dev tooling:extractor,dbtool(schema/import/bootstrap/migrate-players),docgen,plugingen,omangos-mcp(read-only inspection over MCP),bot-inspector(egui bot viewer).plugins/— the separatewasm32-wasip2guest workspace: the plugin SDK + the bundled plugins.vendor/— the patched all-Rustrerecastnav-generation fork (see above).client-addons/— client-side 1.12.1 addons (BugReport).docs/— design notes, ADRs, config examples, and the upstream-porting map (docs/porting/).docker-compose.yml— the dev Postgres + MariaDB (and optional realmd/world/portal service builds).