Files
ROSS/README.md
T
ThomasandClaude Sonnet 5 0884cab294 Make webtools host port configurable via WEBTOOLS_PORT
Port 80 on the host isn't always free (e.g. another service already bound
to it), so let .env override it like the game server ports already do.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 17:08:39 +02:00

130 lines
7.9 KiB
Markdown

# ROSS
A [ROSE Online](https://en.wikipedia.org/wiki/Rose_Online) (KTRose, pre-evo era) private server emulator, ported from its
original Windows/MSVC codebase to build and run natively on Linux, packaged as a Docker Compose stack.
Three C++ servers (login, character, world) talk to a MariaDB database and to each other over plain sockets, exactly like
the original 2007-era server did — just with the Windows-only bits (MSVC CRT calls, `clock()` semantics, path handling)
shimmed out so it builds and runs correctly under Linux/glibc/POSIX threads.
## Quick start
Needs Docker + the Compose plugin (`docker compose version` should work) on the host. Nothing else — the C++ servers,
their build toolchain, and the database all live inside the containers.
```sh
git clone <this repo> ross && cd ross
cp .env.example .env # edit PUBLIC_HOST at minimum if clients connect from another machine
docker compose up -d --build
```
`docker compose up -d --build` builds the `rose-server` image (compiles all three C++ servers inside a throwaway
builder stage, see `Dockerfile`), then starts every service in `docker-compose.yml` in the background. First start
takes a minute or two (compiling + MariaDB initializing and loading `database sql files/`); `docker compose logs -f`
to watch it happen.
That brings up:
| Service | Container | Port | What it is |
|---|---|---|---|
| `db` | `ross-db-1` | — (internal) | MariaDB, schema auto-loaded from `database sql files/` on first start |
| `loginserver` | `ross-loginserver-1` | `29000` | Account login |
| `charserver` | `ross-charserver-1` | `29100` | Character select/create/delete |
| `worldserver` | `ross-worldserver-1` | `29200` | The actual game world |
| `webtools` | `ross-webtools-1` | `80` (`WEBTOOLS_PORT`) | Static GM toolbox (see below) + reverse-proxies `/admin/` |
| `webadmin` | `ross-webadmin-1` | — (behind webtools) | Account/character/server admin panel |
Point a KTRose pre-evo client at `PUBLIC_HOST:29000` and log in — `LOGIN_CREATE_ACCOUNT=1` (the default) auto-creates an
account on first login with that username/password.
A default GM account is seeded into a fresh database by `database sql files/gm_account.sql`:
- username `gmadmin`, password `changeme`, `accesslevel 900` — logs into both the game (as a GM) and the web admin
panel below. **Change this password** (via the web admin panel, or `docker compose exec db mariadb ...`) on anything
other than a throwaway local instance — it's a public default sitting in git.
### `docker-compose.yml`, briefly
- `x-app` at the top is a YAML anchor: a template block (image, restart policy, the full list of `environment:`
variables read from `.env`) that `loginserver`, `charserver`, and `worldserver` each pull in with `<<: *app` so the
three definitions don't repeat themselves. They differ only in `command:` (which server to run) and which port they
publish.
- `db` is stock MariaDB; anything in `database sql files/` gets executed once, alphabetically, the first time the
container starts with an empty data volume (mounted read-only at `/docker-entrypoint-initdb.d`, MariaDB's standard
init-script hook). If you change schema files after the first start, they won't re-run automatically — either
`docker compose down -v` to wipe the `db-data` volume and reinitialize, or apply the change by hand.
- `webtools` (nginx) is what clients' browsers hit; it serves the static GM toolbox directly and reverse-proxies
`/admin/*` to the `webadmin` container, which isn't published to the host itself.
- `.env` (copied from `.env.example`) is where every port, server name, and shared inter-server password gets set —
`docker-compose.yml` itself should not need editing for a normal deploy.
Useful commands once it's up:
```sh
docker compose logs -f worldserver # tail one server's logs
docker compose restart worldserver # restart just one service (or use the webadmin Server tab)
docker compose down # stop everything, keep the DB volume
docker compose down -v # stop everything AND wipe the database
```
## Web tools — `http://<host>/`
A static, no-build-step GM toolbox, generated from the same STB/DB data the server itself reads:
- **GM Commands** — every `/command` `gmcmds.cpp` understands, with syntax, access-level, and a copy button.
- **Item Configurator** — browse all ~9,950 items, configure refine/count/etc., copy the exact `/item` or `/give2` command.
- **Maps** — every map with spawn/NPC activity, named from `LIST_ZONE.STB` (row index = map id, same as the server's own
`CWorldServer::LoadZoneData`).
- **NPCs** — every placed NPC (`list_npcs`) with coordinates; click to copy a `/tele <map> <x> <y>`.
- **Monsters** — all 1,193 named monster types from `LIST_NPC.STB`; click to copy a `/mon <montype> <count> <ai>`.
## Web admin — `http://<host>/admin/`
Logs in with an existing **game account** — only accounts with `accesslevel >= ADMIN_MIN_ACCESSLEVEL` (default `900`) get
in. From there:
- Browse/edit/delete accounts and characters, reset stats, grant GM/DEV, reset a stuck "online" flag.
- **Server** tab: start/stop/restart each of the three game servers, live-tail their logs (SSE), broadcast a message to
everyone online, or kick a character — all straight from the browser.
The server-control features talk to the Docker API (`/var/run/docker.sock`, mounted read-only into `webadmin`), so this
panel effectively has host-level privileges. It's fine behind GM-only login on a machine you control; don't put it
directly on the public internet without a reverse proxy that adds another auth layer.
## Building without Docker
```sh
apt-get install cmake g++ make libmariadb-dev pkg-config
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
```
Produces `build/loginserver`, `build/charserver`, `build/worldserver`. Each expects a `.conf` file as its only argument
(see `docker/conf/*.conf.tmpl` for the format, or `Binary/*.conf` for a filled-in example) and reads game data relative
to its working directory — run it from a directory containing `3DData/` (see below) and `clanmark/`.
## Repo layout
- `Src/` — the three servers' C++ source, plus `Src/Common/` (shared networking/DB/crypto code all three link against).
- `database sql files/` — MariaDB schema, loaded automatically into a fresh `db` container.
- `Binary/3DData/` — the subset of the original game's data files the **server** actually reads at runtime: item/skill/
quest/zone definitions (`STB/`), quest scripts (`QUESTDATA/`), monster AI scripts (`AI/`). This is *not* the client's
3D models/textures/sound — the server never touches those, so they aren't in this repo.
- `webtools/`, `webadmin/` — the two web UIs described above.
- `docker/` — Dockerfile support files: config templates, entrypoint script, the webtools nginx config.
- `tools/rose_login_test.cpp` — a standalone protocol-level login smoke test, linking against the server's own
`rosecrypt.cpp` so it exercises the real crypto/handshake instead of a reimplementation.
## Notes for anyone touching the server code
This codebase was written entirely against Windows/MSVC assumptions that don't hold on Linux. The two that will bite you
if you're not aware of them:
- **`clock()`**: POSIX `clock()` returns *CPU time summed across every thread in the process*, not wall-clock time, and
ticks at `CLOCKS_PER_SEC == 1,000,000` instead of Windows' `1,000`. `Src/Common/sockets.h` `#define`s `clock()` to a
`CLOCK_MONOTONIC`-backed wall-clock millisecond counter for the whole codebase — don't remove that shim, and don't
call `clock()` expecting Windows semantics anywhere it might not apply.
- **DB string widths**: this server was built against MySQL 5.0, which silently truncated over-length strings instead
of rejecting them. MariaDB's default strict mode does not — if you add a new DB-backed field that stores a
server-generated string (timestamps, etc.), size the column generously.