# 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 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` | 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:///` 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 `. - **Monsters** — all 1,193 named monster types from `LIST_NPC.STB`; click to copy a `/mon `. ## Web admin — `http:///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.