The one specification of a darkfix patch artifact: what a fix
consists of, what the applier actually runs, and which authoring
formats feed it. This document absorbs the earlier competing
sketches (binary-patching.md §3.4's TOML applier, which never had
an implementation, and the gff-tool routing in
patch-workflow.md §4.2, which predates the
toolkit).
Authoring layer (per surface, tool-supported):
- GPL data fixes:
gpl-asm --patchapplies a TOML script of[[edit]]records to one chunk (see below), addressing by disassembler label or curated name instead of hand-counted offsets. - EXE fixes:
exe-patch(Phase 5.7, shipped) applies a TOML script of the same[[edit]]shape, addressing byovr:seg+off, catalogued symbol name, or raw offset, with a mandatorybytes_oldfingerprint and a--verifygate (drift, segment straddle, padding, and any length change are hard failures).
Distribution layer (one format, the only one apply.py runs):
the darkfix fix script, below.
One Python file per fix, at dsN-patch/fixes/NNN-<short-id>.py,
plus a markdown writeup beside it (NNN-<short-id>.md: symptom,
root cause, the fix, evidence). Python 3, stdlib-only. Live
example: ds1-patch/fixes/000-noop.py.
"""fix.ds1.<short-id>: one-line summary"""
from darkfix.patcher import apply_bytes, apply_gff_chunk
ID = "fix.ds1.<short-id>"
TARGET = "DSUN.EXE" # or "GPLDATA.GFF", "RESOURCE.GFF", ...
SOURCE_SHA256 = "<canonical install hash of TARGET>"
EDITS = [
# binary fix: exact-length in-place byte replacement
{"offset": 0x1234, "expect": b"\x74\x0a", "replace": b"\x75\x0a"},
]
def apply(source_path, dest_path):
apply_bytes(source_path, dest_path, EDITS)Contract:
IDmatchesfix.dsN.<short-id>and the[[fixes]]entry in the package'smanifest.toml(spec.md §4).SOURCE_SHA256documents the canonical hash the fix targets; the umbrella applier (ds1-patch/scripts/apply.py) verifies the install againstmanifest.toml's hashes before running any fix. The per-edit fingerprints are the second, byte-level gate.EDITSentries are{"offset", "expect", "replace"}: seek, verifyexpectbyte-for-byte (refuse on mismatch:FingerprintMismatch), writereplace. Lengths must be equal; EXE edits are in-place only; an inserted byte shifts every later overlay payload into garbage.- GPL chunk fixes use
apply_gff_chunk(chunk replacement, in-place if the new bytes fit, append otherwise, per thegff-editwriter policy). - The script is journal-gated: the applier's journal
(
darkfix-applied.json) plusAlreadyApplied/NotAppliedbookkeeping make re-runs and--unapplyexact. - The
darkfix.patcherexception taxonomy a fix author codes against:PatchError,HashMismatch,FingerprintMismatch,AlreadyApplied,NotApplied(plusManifestErrorat the umbrella layer). - A player on a non-canonical install (any variant other than
the targeted GOG 1.10 base) gets the hash-mismatch refusal;
that is the documented policy, not a bug
(
install-variants.mdsection 7).
Chunk-scoped, authoring-time; its output is what you paste into a
fix script's EDITS (offsets resolved to numbers, fingerprints
carried over):
[[edit]]
at = "label_0x0042 + 3" # or: at = "iniya_first_meeting + 3"
bytes_old = "3a" # fingerprint check, mandatory
bytes_new = "3b"
reason = "retarget the immediate after the label"attakes"<base>"or"<base> + N"; the base is alabel_0xNNNN/entry_0xNNNNblock leader or a name fromgpl-disasm'ssyms/functions.toml. The resolver requires the base to be a real block leader in the target chunk, so a patch cannot silently address the wrong chunk.at_offset(chunk-relative absolute) is the non-symbolic form; exactly one ofat/at_offsetper edit.--dry-runpreviews;bytes_oldmismatch refuses the edit.
binary-patching.md§3.4's TOML ([[patch]]withoffset/expect/replaceand a per-filetarget_sha256) described an applier that was never written; its semantics (fingerprint-gated in-place edits) are preserved exactly in the fix-scriptEDITSabove. That section now points here.patch-workflow.md§4.2's skeleton is the same contract; its older prose about routing GPL fixes throughgff-tool(the dsun_music Java tool) is historical: GPL fixes usegff-edit+gpl-asm, per spec §7a.