# saveedit — Dracula: Warrior of the Carpathians A save-game editor for **Dracula : Le Guerrier des Carpates** (SoftKey, 1997), in English and French. A local server and a single page, with no dependency outside the Python standard library. ![The County tab](docs/apercu-comte.png) ## Running it ```bash python3 tools/saveedit.py ``` The page opens at `http://127.0.0.1:8765` and lists the save files in `saves/`. Two game files are included, so you can try it without owning the game. | Option | Effect | |---|---| | `--dir PATH` | starting folder (default: `saves/`) — still changeable from the page | | `--port N` | listening port, 8765 by default | | `--lang fr\|en` | language of the first render; your choice in the page is remembered afterwards | | `--sans-navigateur` | do not open a browser on startup | **Requirements:** Python 3.8 or newer. Nothing else — no `pip install`. The server binds to `127.0.0.1` only, and the chosen folder bounds everything: no filename coming from the page can escape it. ### Your own saves The game keeps them in `Saves\` under its install directory. The extension is not a format but a **slot number**: `game.0`, `game.1`, and so on. The “Folder…” button opens a directory browser that shows subfolders on the left and the save files it found on the right — clicking one opens its folder and loads it. Recognising a save file takes more than a size threshold, so the browser reads the two counter arrays in the first 316 bytes and checks that they predict the file size exactly. A format with no header and no index is recognised by its own regularity. ## What it can change | Tab | Contents | |---|---| | **Game** | the five resource stocks, calendar, current county, weaponry and architecture tiers, unit levels, history, file footer | | **County** | HP, type, position, side, **combat stance** and deletion for each unit; recruitment queues; works counters; building inventory | | **Fog & walls** | the 130 × 130 fog plane and the 50 × 50 wall grid, drawn cell by cell | | **Checks** | the six structural checks, the change log, and writing | Four actions reproduce **measured** game behaviour, and nothing else: | Action | What it does | |---|---| | heal | restores the type's maximum HP at its current level | | repair | resets every damaged wall cell to `0xff` — zero cells are left alone, a destroyed cell and an absent one being indistinguishable there | | reveal | turns black fog grey, without touching the cleared layer, which is volatile | | conquer | applies the five changes observed in a before/after conquest diff | The stance is worth a word, since it is not obvious from the game. Each unit carries one of three: coward, neutral or aggressive. Aggressive gives +2 to impact and −2 to defence and never breaks off; coward refuses to answer a neighbour's call for help and breaks off early. The AI sets all its own units to aggressive when an assault begins — which is visible in the save files shipped here, where the enemy garrison of the county under attack is aggressive to a unit. Since the field is serialised, the editor can change it either way. Building geometry, resource income (recomputed every turn by the game), the works type and site, and the file footer stay **read only** — there is no observed behaviour to reproduce for them. Editing the unit array puts the structure back in order without being asked: changing a side or deleting a unit triggers a re-sort into side 0 then side 1, a renumbering of the index stamped into the occupancy grid, and a resynchronisation of the three counters in the global block. Moving a unit rewrites all three copies of the coordinate. ## What protects your files The format has no header, no index and no checksum: it is the raw concatenation of its fields. A misplaced byte does not show — the game simply slices the file wrong and says nothing about it. Hence three safeguards. **Six structural checks**, all independent of file size, run before every write. The hardest is the round trip: decomposing the file into objects and re-emitting it must reproduce the original stream **byte for byte**. A model can be right on size while placing fields wrong; it cannot survive that test. The other five verify the partition of units into sides, the overlap of the one-byte fields, the agreement of position with destination, the tier table, and the fog visibility layer. That third one is worth a footnote: `+0x2c` is not a copy of the position but the unit's **destination**, and `+0x34` the previous one. They agree across the whole corpus because the game only saves between turns, when every unit has arrived — 15,651 units out of 15,651. The check is sound; the reason first given for it was not. The report is produced **twice**, before and after your changes, and only **regressions** block the write. An already inconsistent file would otherwise be unmodifiable, and the editor would refuse to fix the very thing that is wrong. **Two backups**, with distinct roles: `.orig` is taken on the first write and never rewritten, `.bak` follows each subsequent one. A single slot only protects against one mistake — on the second write it is overwritten, and the version that came from the game, the only one you cannot recreate, is gone. **A stateless cycle:** every request reloads the file from disk, applies the patch, re-runs the checks, and only then writes. The patch is therefore always relative to the file on disk, never to an in-memory session; “Preview” and “Save” produce exactly the same report, and replaying a patch twice yields the same result. Finally, every uninterpreted byte **passes through the edit untouched**: each record keeps its original content, and only the fields you changed are rewritten. The change log states what actually changed, not what was sent — a value rewritten identically does not appear in it — along with the number of bytes that differ from the original file and the offset of the first one. ![The wall grid and the fog](docs/apercu-murs.png) *Fagaras castle after a siege: the closed curtain wall, with the three broken gate cells in orange. On the left, the fog plane of the same county.* ## The format [`SAVEFORMAT.md`](SAVEFORMAT.md) documents the whole format — the 205,708-byte global block, unit (400 B) and building (10,192 B) records, county tails, file footer, fog of war, wall grid, stat tables, resources, recruitment, works and calendar. **It is written in French.** It was reconstructed by reading decompiled code, then confronted with a corpus of save files covering thirteen distinct game situations; every claim in it is marked as read from the code, measured on the files, or both. Three tools, of different scope: | Tool | Role | |---|---| | `tools/savefile.py` | the codec: reading, rewriting, and the six checks. `--check` proves the round trip, `--dump` decodes the contents | | `tools/savedump.py` | quick check: recomputes the expected size from the counters and compares it | | `tools/saveedit.py` | the editor, which contributes **no offset of its own** — it exposes only what the codec already knows how to read and rewrite | ```bash python3 tools/savefile.py saves/tour3.2 --check # byte-exact round trip python3 tools/savefile.py saves/tour3.2 --dump # decoded contents ``` ## Two languages The interface is in English or French, switchable at any time and remembered between sessions; `--lang` only sets the first render. The server emits **no sentence meant for the eye**. Reports and logs are (key, arguments) pairs — `{'cle': 'unite.pv', 'args': {'comte': 3, 'i': 0}}` — that the page turns into words. A hard-coded string on the Python side would be untranslatable on the page side. Game terms are the exception: `savefile.py` names them in French, since that is the language of the edition studied, so they travel already declined in both languages. The page's catalogue keeps both languages **on the same line**: ```js 'ctl.alias': ["recouvrement des champs d'un octet", 'overlap of the one-byte fields'], ``` One dictionary per language would let you translate one and forget the other; here a line is either complete or absent. ## Repository contents ``` tools/saveedit.py the server and the patch engine tools/saveedit.html the interface, a single file tools/savefile.py the codec — all knowledge of the format lives here tools/savedump.py the quick check saves/ two game files, to try it out SAVEFORMAT.md the format, documented (in French) ``` **This repository contains no game data**: no executable, no disc image, no extracted graphics, no disassembly. Only tools, the description of a data format, and two save files produced by playing. The format documentation is interoperability analysis under Article L122-6-1 IV of the French Intellectual Property Code and Article 6 of Directive 2009/24/EC. The offsets describe a data format, not reusable code. The game and its name remain the property of their rights holders; this project is neither affiliated with nor endorsed by them. ## Licence MIT — see [`LICENSE`](LICENSE).