Normal DARKER.SAV format and editor
Open the offline save viewer/editor and choose a
save file. No save contents are embedded in the page. It downloads a separate
*-edited.SAV, preserves unknown bytes, and recalculates the checksum. The
original installation and supplied save are not modified by the tools.
File layout
The normal file is exactly 6,600 bytes. Startup 0364–038A reads it into
SS:0FB0, checks the returned length, computes the checksum and compares it
with the final word. Writer A110–A154 calculates the checksum in memory and
writes that same complete buffer.
| File range / record offset | Meaning |
|---|---|
| 0–6595 | Four records, each 1649 (0671h) bytes |
| Record +0 | Internal stage byte; 0 is empty, 1 skips packed city-state restoration |
| Record +1 onward | Null-terminated displayed name; viewer limits display to the bytes before +1E and leaves it read-only |
| Record +1E | Little-endian weapon mask |
| Record +20 | Return-site packed cell, low byte X and high byte Y |
| Record +22 | Delphi packed city state: 953 bytes |
| Record +3DB | Halon packed city state: 468 bytes |
| Record +5AF through +670 | Not interpreted by this editor; preserved byte-for-byte |
| 6596–6597 | Special-selection trailer; byte 6596 is displayed by the Nightmare menu path (08BF reads SS:2974); remaining meaning not fully established |
| 6598–6599 | Little-endian checksum over bytes 0–6597 |
The record layout is supported by the original selection, mission-exit and city restore routines; see city retry and selection and persistent cell state. The trailer is not a fifth complete save slot. No claim is made here that every internal stage byte corresponds directly to the level number printed to the player.
Checksum
Routine 9325 implements the result commonly called CRC-16/XMODEM:
polynomial 1021h, initial value zero, no reflected input/output and no final
XOR. The file stores the resulting word little-endian. In Python it matches:
binascii.crc_hqx(payload[:6598], 0)
The native algorithm seeds DX from the first two bytes in big-endian order, shifts the remaining input through the polynomial, then appends sixteen zero bits. Do not confuse this internal shift-register arrangement with an initial value of FFFF or with the file's little-endian checksum storage.
The supplied save has a valid checksum. Eleven complete native writer/loader roundtrips, including random payloads, agree with the standard reference. Nine single-byte corruptions are rejected, including edits in both ordinary record data and the trailer/checksum. Four short-file cases reach the alternate length branch. These tests intercept DOS I/O in memory and never write to the installation. They check byte format and acceptance, not semantic validity of random save contents.
City editing
The editor uses the same eligible-cell order verified against the original packing/restoration routines: row-major cells whose type descriptor byte +4 is not FF, excluding empty type 0. Each byte stores four 2-bit codes, most significant pair first. Delphi has 3,811 eligible cells; Halon has 1,870.
An ordinary code restores state bits 20h/40h. Their meanings differ by city:
| Saved code | Map colour | Delphi | Halon |
|---|---|---|---|
0 (00h) |
Grey | Intact, not marked as a ground target | Damage stage 0 |
1 (20h) |
Orange | Damaged, not marked as a ground target | Damage stage 1 |
2 (40h) |
Blue | Intact, marked as a ground target | Damage stage 2 |
3 (60h) |
Purple | Damaged, still marked as a ground target | Damage stage 3 |
In Delphi, only bit 20h selects the damage link; 40h is the ground-target
permission checked by qualifying Caero ground weapons. Thus 1 and 2 are not
two kinds of permission, and 3 is not a universal destroyed value. Marking a
cell does not make every weapon effective: collision category and a damage link
still matter. Streetlights have a separate eligibility rule and do not need
this permission. See weapon behaviour and
streetlight collisions.
Halon uses both bits as a damage-stage number. Model links determine the actual
appearance: types without links cannot acquire a destroyed appearance just by
editing these bits, and successive stages may resolve to the same terminal
model. See damage model selection.
Low variant bits are regenerated; the separate 80h alternate-model bit is not
stored by this ordinary two-bit representation.
Delphi lattice beacons instead use their initialized high-bit template: zero code restores 00 (off, gold on the map); any nonzero code restores FF (full power, white). The dropdown switches to beacon-specific labels when one is selected. Their regional effect on streetlights is described under beacon lighting.
Does Delphi distinguish damaged codes 1 and 3?
Usually the visible result is the same: both select the same damaged model,
collision geometry and destruction-objective completion. Destruction adds
20h (67C7–67CA) rather than clearing 40h, so the marking survives without
requiring a separate record of why the object was destroyed.
However, 40h is not exclusively a targeting permission. In scenario
04_009 / 4, two Oppressor scripts explicitly wait for that bit at type-80
chimneys (52,15) and (53,14). Instructions at shared-block offsets
11376 and 11385 are 1D 69 0F 40 and 1D 6B 0E 40; after the wait, each sets
that cell as its target. The condition ignores 20h: code 3 passes while
code 1 does not. This is a concrete scripted distinction even if the building
already has damaged geometry. The mission's later factory-target marking can
set the bit again, so manually clearing it need not prevent the wait forever.
Warehouse launch-site setup (8EEE) also reads 40h when initialising platform
animation parameters. Aircraft spawning itself (8E3F) rejects damaged cells
via 20h, so neither damaged code permits further launches there. This is not
evidence that code 3 spawns enemies from ruins.
Stage 1 bypasses city-state restoration. Mission setup and scripts can also change states after load. Editing a saved cell therefore does not guarantee that an arbitrary mission will display that state. No tunnel stream is exposed: the normal persistence selector does not save it in these city areas.
Editing record fields does not initialize other state needed for a different craft or mission. The tool exposes raw established fields, not a campaign conversion or character creation facility. Names, unknown fields and all untouched map bits remain unchanged. A bad-CRC file of the normal size can be inspected, but recalculating its CRC does not repair other corruption.
Unsupported alternate loading path
A nonstandard file length goes to 0349, which can recognize an executable
bootstrap-like payload. This is not the normal save format. The viewer rejects
other lengths; the verification harness stops at that branch without executing
its loaded buffer.
Reproduction
PYTHONPATH=/tmp/darker-python python3 tools/verify_save_file.py
python3 tools/build_save_viewer.py
NODE_PATH=/tmp/riotfire-browser/node_modules node tools/verify_save_viewer.js
Native file verification. Browser checks include no-change byte roundtrip, single-cell edit isolation, download, reset, field bounds and preservation of the supplied original.
The browser-exported one-cell edit was additionally fed through the native normal loader and accepted. This validates its checksum/byte-format compatibility; it does not constitute a DOSBox playthrough of the edited slot.