DARKER Research archive

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.