From a9153ed2bcfc41422361d51eab16bcd122c5bfcc Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 8 Aug 2026 11:57:50 +0200 Subject: [PATCH] add readme --- README.md | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f844e6 --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# Zomboid client-hosted → dedicated server + +Convert a locally **hosted** Project Zomboid multiplayer world into a **dedicated** server layout, then run it with Docker. + +The world save format is the same (B42 `Saves/Multiplayer/…`). Conversion is packaging + naming + `players.db` world rename + mod recovery — not a binary rewrite. + +## Layout + +Paths below are relative to the **repo root** (parent of this `converter/` folder): + +| Path | Role | +|------|------| +| `original/` | Source saves / zips (**do not edit the zips**) | +| `converter/` | This crate — Rust CLI (`zomboid-converter`) | +| `server/` | Docker dedicated server (`docker-compose.yml`, `.env`) | +| `server/server-data/` | Zomboid userdir (Saves, Server, db, Logs) | +| `server/server-files/` | Game install (Steam download target) | + +Typical source pair from a host: + +- `Saves/Multiplayer//` — authoritative world (**use this**) +- `Saves/Multiplayer/_player/` — client cache (**ignore**) + +Server settings live separately under `Zomboid/Server/.*` on the host machine. They are **not** inside the world folder. + +## Prerequisites + +- Rust toolchain (for the converter) +- Docker + Docker Compose +- Optional: local Steam workshop cache for app `108600` (to resolve `WorkshopItems`) + +## Converter + +```bash +cd converter +cargo build --release + +cargo run --release -- \ + --input ../original/serverb42stable \ + --output ../server/server-data \ + --server-name pzserver \ + --workshop-dir /path/to/steamapps/workshop/content/108600 \ + --force +``` + +`--input` may be a world **directory** or a **`.zip`** (opened read-only; extracted to a temp dir). Paths ending in `_player` are rejected. + +### Arguments + +| Flag | Description | +|------|-------------| +| `-i`, `--input` | Hosted world dir or zip | +| `-o`, `--output` | Dedicated `server-data` root | +| `-n`, `--server-name` | World + config basename (default: `pzserver`) | +| `--workshop-dir` | `…/workshop/content/108600` — fills `WorkshopItems=` | +| `--server-config` | Optional host `Server/` dir to copy/rename configs | +| `--password` | Server join password (`Password=` in ini); overrides `.env` | +| `--env-file` | `.env` for password (default: `/../.env`) | +| `--force` | Replace existing `Saves/Multiplayer//` | + +### What it does + +1. Copies the world into `output/Saves/Multiplayer//` +2. Skips syncthing conflict sidecars (`*-ZwS1g3qi*`), `*.db-journal`, and player-cache junk +3. Sets `networkPlayers.world` in `players.db` to `` +4. Recovers mod IDs from `WorldDictionaryReadable.lua` into `Server/.ini` (`Mods=`) +5. If `--workshop-dir` is set, resolves Steam workshop IDs (`WorkshopItems=`) +6. Optionally sets `Password=` from `--password` or `SERVER_PASSWORD` / `PASSWORD` in `.env` + +It does **not** overwrite `db/.db` (accounts / whitelist stay managed by the server). + +### Characters / login + +Characters bind to the **account username** in `players.db` (`networkPlayers.username`), not Steam display name. Players must log in with that exact username (or you rename the column before they join). + +## Dedicated server (Docker) + +```bash +cd ../server +cp .env.example .env # if needed; set passwords / SERVER_NAME +docker compose up -d +docker compose logs -f +``` + +Important `.env` keys: + +- `SERVER_NAME` — must match converter `--server-name` (default `pzserver`) +- `ADMIN_USERNAME` / `ADMIN_PASSWORD` — admin account +- `RCON_PASSWORD` — RCON +- `SERVER_PASSWORD` — optional join password for the converter (`Password=` in ini) + +Ports: `16261/udp`, `16262/udp`, `27015/tcp`. + +Stop the container before re-running the converter with `--force`. + +## Recommended migration flow + +1. Stop the dedicated server. +2. Copy the host world into `original/` (keep zips untouched as backups). +3. Ideally also copy the host’s `Zomboid/Server/.ini` (+ `_SandboxVars.lua`, spawn files) and pass `--server-config`. +4. Run the converter with `--workshop-dir` and `--force`. +5. Start Docker; wait until workshop items finish installing (`Workshop: … Installed` in logs). +6. Join with the **same username** as in the hosted `players.db`. + +## Known gaps / caveats + +| Item | Notes | +|------|--------| +| Host `SandboxVars` / spawn / `Map=` | Not in the world save. Without `--server-config`, the dedicated defaults remain. | +| Mods with no WorldDictionary entries | Pure Lua/QoL mods may not appear in recovered `Mods=`. | +| Workshop **versions** | IDs are resolved from your local workshop cache; newer mod builds than the host used can cause chunk `LENGTH mismatch` / `blam` errors and odd world state (e.g. zombies in cleared bases). | +| `ZombieMigrate` | Dedicated sandbox may allow migration even when respawn is off; align sandbox with the host when possible. | +| Running without mods first | Can rewrite `WorldDictionary` and map chunks. Re-import from `original/` if that happened. | + +## Example passwords + +```bash +# ../server/.env (next to docker-compose) +SERVER_PASSWORD=hunter2 + +# or CLI override (from this directory) +cargo run --release -- … --password 'hunter2' +```