feat(vf): add MigrationStep and MigrationPlan to SaveMigration
This commit is contained in:
1 parent
d41bcfbb70
commit
894f249cb7
1 file changed
+101
@@ -156,3 +156,104 @@ std::size_t saveTableBytes(const SaveHeader& header);
|
|||||||
std::size_t saveHeaderSectors(const SaveHeader& header);
|
std::size_t saveHeaderSectors(const SaveHeader& header);
|
||||||
std::size_t saveDataStart(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
|
||||||
Reference in new issue
Block a user