panel v0.9.1 — open-source game server manager

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 23:37:25 -07:00
commit 582b5a6b08
2161 changed files with 300950 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
// 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