# AI Disclosure yeah, vibed, 100%. I read the code, should be fine. Zomboid savefiles are a mess IMHO, this was the quickest way to get to the goal. If you don't like it, don't use it, idc. ---- # Zomboid Converter ## client-hosted → dedicated server Rust CLI that converts a locally **hosted** Project Zomboid multiplayer world into a **dedicated** server layout. The world save format is the same (B42 `Saves/Multiplayer/…`). Conversion is packaging + naming + `players.db` world rename + mod recovery — not a binary rewrite. This repository is **only** the converter. Sibling folders next to a checkout (e.g. `original/`, `server/`) are optional local testing aids and are not part of the git tree. ## Build ```bash cargo build --release # binary: target/release/zomboid-converter ``` Copy the binary wherever you run the dedicated server (example below: `~/docker/zomboid/bin/`). ## Inputs / outputs 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. They are **not** inside the world folder. Pass the directory that contains those files with `--server-config`. `--input` may be a world **directory** or a **`.zip`** (opened read-only; extracted to a temp dir). Paths ending in `_player` are rejected. `--output` is the dedicated **server-data** root (writes `Saves/Multiplayer//` and patches `Server/.ini`). ## Example (Hetzner / Docker layout) Assumes: | Path | Role | |------|------| | `~/docker/zomboid/` | Dedicated server (`docker-compose.yml`, `.env`) | | `~/docker/zomboid/server-data/` | Zomboid userdir (Saves, Server, db, …) | | `~/docker/zomboid/server-files/` | Game install (Steam download target) | | `~/docker/zomboid/bin/zomboid-converter` | This binary | | `~/docker/copyparty/data/zomboid/…` | Uploaded host world + configs | ```bash # stop the server first if it is running cd ~/docker/zomboid docker compose stop ./bin/zomboid-converter \ --input ~/docker/copyparty/data/zomboid/alt/serverb42stable \ --output ~/docker/zomboid/server-data \ --server-name pzserver \ --server-config ~/docker/copyparty/data/zomboid/alt \ --force docker compose up -d docker compose logs -f ``` Or edit and run `~/docker/zomboid/convert-and-start.sh` (config block at the top). `--server-name` (default `pzserver`) must match `SERVER_NAME` in the server `.env`. It is only a basename for the dedicated world and `Server/*.ini` files. `--workshop-dir` defaults to `/../server-files/steamapps/workshop/content/108600` when that directory exists (fills `WorkshopItems=`). Until Steam has downloaded mods once, re-run after the first install if needed. ## Arguments | Flag | Description | |------|-------------| | `-i`, `--input` | Hosted world dir or zip | | `-o`, `--output` | Dedicated `server-data` root | | `-n`, `--server-name` | World + config basename (default: `pzserver`) | | `--server-config` | Dir with host `.ini`, `_SandboxVars.lua`, spawn files | | `--workshop-dir` | `…/workshop/content/108600` — fills `WorkshopItems=` (auto if present next to `server-data`) | | `--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. Resolves Steam workshop IDs into `WorkshopItems=` when a workshop dir is available 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 notes Use any Project Zomboid dedicated image/layout you like. With Docker next to `server-data`: - `SERVER_NAME` must match converter `--server-name` - Optional: `SERVER_PASSWORD` in `.env` for the converter’s `Password=` patch - Also typical: `ADMIN_USERNAME` / `ADMIN_PASSWORD`, `RCON_PASSWORD`, memory, ports - Ports: `16261/udp`, `16262/udp`, `27015/tcp` (RCON) - Stop the container before re-running the converter with `--force` - After first start, wait until workshop items finish installing (`Workshop: … Installed` in logs) ## Recommended migration flow 1. Stop the dedicated server. 2. Upload/copy the host world (not the `_player` folder) and ideally the host `Server/.*` configs. 3. Run the converter with `--server-config` pointing at the config directory. 4. Start the dedicated server; wait for workshop installs if needed. 5. 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`, dedicated defaults remain. | | Mods with no WorldDictionary entries | Pure Lua/QoL mods may not appear in recovered `Mods=`. | | Workshop **versions** | IDs come from the workshop cache on disk; newer mod builds than the host used can cause chunk `LENGTH mismatch` / `blam` errors. | | `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 the original host world if that happened. | ## Example passwords ```bash # next to docker-compose (.env) SERVER_PASSWORD=hunter2 # or CLI override zomboid-converter … --password 'hunter2' ```