feat(vf): add MigrationStep and MigrationPlan to SaveMigration

This commit is contained in:
dualfroz committed 2026-09-04 16:42:25 +02:00
1 parent d41bcfbb70
commit 894f249cb7
1 file changed
+101
+101
View File
@@ -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<SaveTableEntry>& 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<uint8_t> 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<Vec3i>& coords,
const std::vector<ByteBuffer>& payloads,
std::vector<uint32_t>* 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<MigrationStep> steps;
bool possible = false;
std::string refusal;
};
struct MigrationResult {
bool ok = false;
SaveVersion from = currentSaveVersion;
SaveVersion to = currentSaveVersion;
std::vector<MigrationStep> 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