diff --git a/core/src/SaveIntegrity.cpp b/core/src/SaveIntegrity.cpp new file mode 100644 index 0000000..1387ee7 --- /dev/null +++ b/core/src/SaveIntegrity.cpp @@ -0,0 +1,161 @@ +#include "vf/SaveIntegrity.h" + +#include +#include + +#include "vf/Block.h" +#include "vf/Region.h" + +namespace vf { + +namespace { + +std::string decimal(long long value) { return std::to_string(value); } + +std::string coordText(const Vec3i& coord) { + return "(" + decimal(coord.x) + ", " + decimal(coord.y) + ", " + decimal(coord.z) + ")"; +} + +uint64_t mixBits(uint64_t value) { + value += 0x9E3779B97F4A7C15ull; + value = (value ^ (value >> 30)) * 0xBF58476D1CE4E5B9ull; + value = (value ^ (value >> 27)) * 0x94D049BB133111EBull; + return value ^ (value >> 31); +} + +uint64_t latticeHash(uint64_t seed, int gx, int gz) { + uint64_t h = seed; + h = mixBits(h ^ (static_cast(static_cast(gx)) * 0x2545F4914F6CDD1Dull)); + h = mixBits(h ^ (static_cast(static_cast(gz)) * 0x9E3779B97F4A7C15ull)); + return h; +} + +int latticeHeight(uint64_t seed, int gx, int gz) { + return 32 + static_cast(latticeHash(seed, gx, gz) % 33ull); +} + +struct SectorSpan { + uint32_t first = 0; + uint32_t last = 0; + int entryIndex = -1; +}; + +IntegrityIssue makeIssue(IntegrityIssueKind kind, IssueSeverity severity, std::size_t byteOffset, + std::string detail) { + IntegrityIssue issue; + issue.kind = kind; + issue.severity = severity; + issue.byteOffset = byteOffset; + issue.detail = std::move(detail); + return issue; +} + +bool actionOutranks(RepairActionKind candidate, RepairActionKind existing) { + auto rank = [](RepairActionKind kind) { + switch (kind) { + case RepairActionKind::RecomputeChecksum: return 0; + case RepairActionKind::RegenerateChunk: return 1; + case RepairActionKind::RemoveOrphanEntry: return 2; + case RepairActionKind::DropChunk: return 3; + case RepairActionKind::TruncateBuffer: return 4; + } + return 0; + }; + return rank(candidate) > rank(existing); +} + +} // namespace + +const char* issueSeverityName(IssueSeverity severity) { + switch (severity) { + case IssueSeverity::Info: return "info"; + case IssueSeverity::Warning: return "warning"; + case IssueSeverity::Recoverable: return "recoverable"; + case IssueSeverity::Fatal: return "fatal"; + } + return "unknown"; +} + +const char* integrityIssueName(IntegrityIssueKind kind) { + switch (kind) { + case IntegrityIssueKind::BadMagic: return "badMagic"; + case IntegrityIssueKind::UnknownVersion: return "unknownVersion"; + case IntegrityIssueKind::MalformedHeader: return "malformedHeader"; + case IntegrityIssueKind::TruncatedBuffer: return "truncatedBuffer"; + case IntegrityIssueKind::ChunkCountOverrun: return "chunkCountOverrun"; + case IntegrityIssueKind::OrphanEntry: return "orphanEntry"; + case IntegrityIssueKind::OverlappingEntries: return "overlappingEntries"; + case IntegrityIssueKind::ChunkOutsideRegion: return "chunkOutsideRegion"; + case IntegrityIssueKind::ChunkHeightOutOfRange: return "chunkHeightOutOfRange"; + case IntegrityIssueKind::MalformedRecord: return "malformedRecord"; + case IntegrityIssueKind::PaletteIndexOutOfRange: return "paletteIndexOutOfRange"; + case IntegrityIssueKind::ChecksumMismatch: return "checksumMismatch"; + case IntegrityIssueKind::LightingInconsistent: return "lightingInconsistent"; + case IntegrityIssueKind::TrailingBytes: return "trailingBytes"; + } + return "unknown"; +} + +const char* repairActionName(RepairActionKind kind) { + switch (kind) { + case RepairActionKind::DropChunk: return "dropChunk"; + case RepairActionKind::TruncateBuffer: return "truncateBuffer"; + case RepairActionKind::RecomputeChecksum: return "recomputeChecksum"; + case RepairActionKind::RegenerateChunk: return "regenerateChunk"; + case RepairActionKind::RemoveOrphanEntry: return "removeOrphanEntry"; + } + return "unknown"; +} + +bool IntegrityReport::fatal() const { + for (const IntegrityIssue& issue : issues) + if (issue.severity == IssueSeverity::Fatal) return true; + return false; +} + +bool IntegrityReport::repairable() const { + if (fatal()) return false; + for (const IntegrityIssue& issue : issues) + if (issue.severity == IssueSeverity::Recoverable) return true; + return false; +} + +std::size_t IntegrityReport::countOf(IntegrityIssueKind kind) const { + std::size_t total = 0; + for (const IntegrityIssue& issue : issues) + if (issue.kind == kind) ++total; + return total; +} + +std::size_t IntegrityReport::countAtLeast(IssueSeverity severity) const { + std::size_t total = 0; + for (const IntegrityIssue& issue : issues) + if (issue.severity >= severity) ++total; + return total; +} + +const IntegrityIssue* IntegrityReport::firstOf(IntegrityIssueKind kind) const { + for (const IntegrityIssue& issue : issues) + if (issue.kind == kind) return &issue; + return nullptr; +} + +const RepairAction* RepairPlan::forEntry(int entryIndex) const { + for (const RepairAction& action : actions) + if (action.entryIndex == entryIndex) return &action; + return nullptr; +} + +std::size_t RepairPlan::countOf(RepairActionKind kind) const { + std::size_t total = 0; + for (const RepairAction& action : actions) + if (action.kind == kind) ++total; + return total; +} + +int regeneratedSurfaceHeight(uint64_t seed, int worldX, int worldZ) { + int gx = floorDiv(worldX, chunkSize); + int gz = floorDiv(worldZ, chunkSize); + int fx = floorMod(worldX, chunkSize); + int fz = floorMod(worldZ, chunkSize); +