0f6aea796c
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
57 lines
3.2 KiB
Go
57 lines
3.2 KiB
Go
// Package regionmedic detects and surgically repairs corrupted 7 Days to Die
|
||
// region (".7rg") files using the panel's own backups.
|
||
//
|
||
// Background
|
||
//
|
||
// 7DTD stores the world as a grid of 512×512-block "region" files, each
|
||
// holding a 32×32 grid of chunks. When the server process dies mid-save (a
|
||
// bare `docker stop` SIGKILL with no `saveworld`, an OOM kill, a crash) a
|
||
// region file can be left with a torn chunk: the chunk's bytes no longer begin
|
||
// with the expected header. On the next load 7DTD logs "Wrong chunk header!",
|
||
// writes an `error_backup_<cx>_<cz>` salvage file, and drops the chunk — so a
|
||
// base straddling the damaged region loses exactly the half that lived in it.
|
||
//
|
||
// This package reproduces, as deterministic code, the manual recovery that
|
||
// works: localize the damaged region(s) from the error_backup files, find the
|
||
// newest pre-corruption backup whose copy of that region validates clean,
|
||
// snapshot the corrupt live file, swap the clean copy in (in place, preserving
|
||
// permissions), and quarantine the error_backup salvage files.
|
||
//
|
||
// The .7rg on-disk format (reverse-engineered + verified byte-for-byte against
|
||
// real region files, see RE notes in the repo history):
|
||
//
|
||
// Sector size = 4096 bytes; file size is always a multiple of 4096.
|
||
// Region = 32×32 = 1024 chunks.
|
||
// Sector 0 = header: magic "7rg"+0x01 at 0x000; bytes 0x004..0xFFF zero.
|
||
// Sector 1 = location table at 0x1000: 1024 entries × 4 bytes, each
|
||
// [3-byte little-endian sector offset][1-byte sector count];
|
||
// an absent chunk is 0x00000000. Table index = localX + localZ*32
|
||
// (X varies fastest). Chunk blob lives at sectorOffset*4096.
|
||
// Chunk blob = [u32 LE blockLength][12 bytes zero][magic "ttc\0"]
|
||
// [u32 LE const 47][raw DEFLATE stream].
|
||
// blockLength = 8 + len(deflate); the on-disk blob occupies
|
||
// 16 + blockLength bytes, rounded up to whole sectors (the
|
||
// stored sector count may be one larger — allocator slack).
|
||
// The decompressed payload begins int32 chunkX, int32 0,
|
||
// int32 chunkZ.
|
||
//
|
||
// The "ttc\0" magic at blob offset 0x10 is the exact byte sequence 7DTD checks
|
||
// when it logs "Wrong chunk header!", so it is both what Validate flags and the
|
||
// minimal faithful corruption used by the test harness.
|
||
//
|
||
// Frozen API surface (do not break callers — the agent dispatch handler, the
|
||
// region-medic CLI, and the unit tests all depend on these signatures):
|
||
//
|
||
// Validation: ValidateRegionBytes, ValidateRegionFile -> RegionReport
|
||
// Localize: RegionCoord, RegionForChunk, ParseErrorBackup, ParseRegionFileName
|
||
// Scan: ScanRegionDir -> RegionDirScan / AffectedRegion
|
||
// Backups: BackupRef, ListPanelBackups, ExtractRegionFromBackup,
|
||
// FindLastGoodRegion -> Candidate
|
||
// Heal: PlanHeal -> HealPlan; ApplyHeal -> HealResult
|
||
//
|
||
// Everything here is pure (filesystem + archive/tar + compress/flate, stdlib
|
||
// only). It performs no Docker or network actions; orchestration (graceful
|
||
// save, stop, start, log-watch) is the caller's job, which keeps the decision
|
||
// logic fully unit-testable.
|
||
package regionmedic
|