Files
panel/pkg/regionmedic/doc.go
T
2026-07-14 19:19:43 -07:00

57 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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