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

7.9 KiB

ROSS

A 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.

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:

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

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 #defines 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.