diff --git a/core/include/vf/SaveMigration.h b/core/include/vf/SaveMigration.h index be2115d..30331f1 100644 --- a/core/include/vf/SaveMigration.h +++ b/core/include/vf/SaveMigration.h @@ -156,3 +156,104 @@ std::size_t saveTableBytes(const SaveHeader& header); std::size_t saveHeaderSectors(const SaveHeader& header); std::size_t saveDataStart(const SaveHeader& header); +// Reads as many entries as the buffer actually holds; `out` may end up shorter +// than header.chunkCount, which is itself a finding for the scanner. +bool readSaveTable(const uint8_t* data, std::size_t size, const SaveHeader& header, + std::vector& out); + +// The light plane a chunk gets when a version that has none is lifted: sky is +// blocked by anything opaque, block light is the emission of the block itself. +std::vector defaultChunkLight(const Chunk& chunk, uint8_t skyLevel); +bool chunkIsAllAir(const Chunk& chunk); + +ByteBuffer encodeChunkPayload(SaveVersion version, const SaveChunkRecord& record, + bool compressBody); + +// Structural decode. A wrong checksum does NOT fail the decode - the scanner +// reports it separately, and repair needs the blocks out of a record whose +// checksum went stale. +bool decodeChunkPayload(SaveVersion version, const uint8_t* data, std::size_t size, + SaveChunkRecord& out, SavePayloadInfo* info = nullptr); + +// Pulls the stored checksum and the one the bytes actually hash to. False for +// v1/v2, which carry none. +bool verifyChunkPayloadChecksum(SaveVersion version, const uint8_t* data, std::size_t size, + uint32_t& stored, uint32_t& computed); + +// Lays out a container from payloads that are already encoded: header, entry +// table, then every payload back to back on sector boundaries. `coords` must +// already be in ascending coordinate order - this is the one place that decides +// what a canonical buffer looks like, and the writer, the compactor and the +// backup restore all go through it so their output is comparable byte for byte. +ByteBuffer packSaveContainer(SaveVersion version, int regionX, int regionZ, uint32_t sectorBytes, + const std::vector& coords, + const std::vector& payloads, + std::vector* sectorOffsets = nullptr); + +bool saveCoordLess(const Vec3i& a, const Vec3i& b); + +// Writes a canonical container: entries sorted by coordinate, payloads packed +// back to back on sector boundaries, no holes, buffer ending on a sector edge. +// Deterministic - the same container always produces the same bytes. +ByteBuffer writeSaveContainer(const SaveContainer& container); +bool readSaveContainer(const uint8_t* data, std::size_t size, SaveContainer& out); + +// Copies one chunk's payload record out verbatim, for byte-level comparison. +bool readChunkPayloadBytes(const uint8_t* data, std::size_t size, const Vec3i& coord, + ByteBuffer& out); + +// True when the layout matches what writeSaveContainer would produce. Backups +// require it, because they store payloads rather than layout. +bool isCanonicalSaveLayout(const uint8_t* data, std::size_t size); + +SaveContainer saveContainerFromWorld(const World& world, RegionCoord region, SaveVersion version); +void applySaveContainerToWorld(const SaveContainer& container, World& world); + +struct MigrationStep { + SaveVersion from = SaveVersion::V1; + SaveVersion to = SaveVersion::V2; + std::string summary; +}; + +struct MigrationPlan { + SaveVersion from = currentSaveVersion; + SaveVersion to = currentSaveVersion; + std::vector steps; + bool possible = false; + std::string refusal; +}; + +struct MigrationResult { + bool ok = false; + SaveVersion from = currentSaveVersion; + SaveVersion to = currentSaveVersion; + std::vector applied; + std::size_t chunksMigrated = 0; + std::size_t paletteEntriesCreated = 0; + std::size_t lightCellsInitialized = 0; + std::size_t metadataRecordsCreated = 0; + std::string failure; + ByteBuffer output; +}; + +// Lifts a container one version at a time. Every step is a real transcode: the +// intermediate buffer is a valid container of the intermediate version, so a +// v1 save passes through readable v2 and v3 states on its way to v4. +class SaveMigrator { +public: + uint8_t skyLightForNewPlanes = saveDefaultSkyLight; + SaveChunkMeta metadataDefaults; + bool compressMigratedPayloads = false; + + static bool peekVersion(const uint8_t* data, std::size_t size, SaveVersion& out); + static MigrationPlan planFor(SaveVersion from, SaveVersion to); + + MigrationResult migrate(const uint8_t* data, std::size_t size, SaveVersion target) const; + MigrationResult migrateToCurrent(const ByteBuffer& buffer) const; + +private: + bool applyStep(const ByteBuffer& in, const MigrationStep& step, MigrationResult& result, + ByteBuffer& out) const; +}; + +} // namespace vf