feat: add writeSaveContainer and readSaveContainer to SaveMigration

This commit is contained in:
dualfroz committed 2026-09-09 14:24:27 +02:00
1 parent 12f0549bbb
commit 966c022003
1 file changed
+160
+160
View File
@@ -468,3 +468,163 @@ ByteBuffer packSaveContainer(SaveVersion version, int regionX, int regionZ, uint
return out; return out;
} }
ByteBuffer writeSaveContainer(const SaveContainer& container) {
std::vector<const SaveChunkRecord*> ordered;
ordered.reserve(container.chunks.size());
for (const SaveChunkRecord& record : container.chunks) ordered.push_back(&record);
std::sort(ordered.begin(), ordered.end(),
[](const SaveChunkRecord* a, const SaveChunkRecord* b) {
return saveCoordLess(a->coord, b->coord);
});
bool compressPayloads = container.compressPayloads && container.version == SaveVersion::V4;
std::vector<Vec3i> coords;
std::vector<ByteBuffer> payloads;
coords.reserve(ordered.size());
payloads.reserve(ordered.size());
for (const SaveChunkRecord* record : ordered) {
coords.push_back(record->coord);
payloads.push_back(encodeChunkPayload(container.version, *record, compressPayloads));
}
return packSaveContainer(container.version, container.regionX, container.regionZ,
container.sectorBytes, coords, payloads, nullptr);
}
bool readSaveContainer(const uint8_t* data, std::size_t size, SaveContainer& out) {
SaveHeader header;
if (!readSaveHeader(data, size, header)) return false;
if (header.magic != saveMagic) return false;
if (!isKnownSaveVersion(header.version)) return false;
if (header.sectorBytes == 0) return false;
std::vector<SaveTableEntry> entries;
if (!readSaveTable(data, size, header, entries)) return false;
out.version = static_cast<SaveVersion>(header.version);
out.regionX = header.regionX;
out.regionZ = header.regionZ;
out.sectorBytes = header.sectorBytes;
out.compressPayloads = false;
out.chunks.clear();
out.chunks.reserve(entries.size());
for (const SaveTableEntry& entry : entries) {
std::size_t at = static_cast<std::size_t>(entry.sectorOffset) * header.sectorBytes;
if (at + entry.payloadBytes > size) return false;
SaveChunkRecord record;
SavePayloadInfo info;
record.coord = entry.coord;
if (!decodeChunkPayload(out.version, data + at, entry.payloadBytes, record, &info))
return false;
if (info.compressed) out.compressPayloads = true;
out.chunks.push_back(std::move(record));
}
return true;
}
bool readChunkPayloadBytes(const uint8_t* data, std::size_t size, const Vec3i& coord,
ByteBuffer& out) {
SaveHeader header;
if (!readSaveHeader(data, size, header)) return false;
if (header.magic != saveMagic || header.sectorBytes == 0) return false;
std::vector<SaveTableEntry> entries;
if (!readSaveTable(data, size, header, entries)) return false;
for (const SaveTableEntry& entry : entries) {
if (!(entry.coord == coord)) continue;
std::size_t at = static_cast<std::size_t>(entry.sectorOffset) * header.sectorBytes;
if (at + entry.payloadBytes > size) return false;
out.assign(data + at, data + at + entry.payloadBytes);
return true;
}
return false;
}
bool isCanonicalSaveLayout(const uint8_t* data, std::size_t size) {
SaveHeader header;
if (!readSaveHeader(data, size, header)) return false;
if (header.magic != saveMagic || !isKnownSaveVersion(header.version)) return false;
if (header.sectorBytes == 0 || size % header.sectorBytes != 0) return false;
std::vector<SaveTableEntry> entries;
if (!readSaveTable(data, size, header, entries)) return false;
ByteBuffer table(data + saveHeaderBytes, data + saveHeaderBytes + saveTableBytes(header));
SaveVersion version = static_cast<SaveVersion>(header.version);
uint32_t expectedTableChecksum =
versionHasChecksum(version) ? fnv1a32(table.data(), table.size()) : 0u;
if (header.tableChecksum != expectedTableChecksum) return false;
std::size_t cursor = saveHeaderSectors(header);
for (std::size_t i = 0; i < entries.size(); ++i) {
if (i > 0 && !saveCoordLess(entries[i - 1].coord, entries[i].coord)) return false;
if (entries[i].sectorOffset != cursor) return false;
if (entries[i].sectorCount != divideUp(entries[i].payloadBytes, header.sectorBytes))
return false;
cursor += entries[i].sectorCount;
}
return cursor * static_cast<std::size_t>(header.sectorBytes) == size;
}
SaveContainer saveContainerFromWorld(const World& world, RegionCoord region, SaveVersion version) {
SaveContainer container;
container.version = version;
container.regionX = region.x;
container.regionZ = region.z;
for (const auto& entry : world.allChunks()) {
const Vec3i& coord = entry.first;
if (!(regionForChunk(coord) == region)) continue;
if (coord.y < saveChunkYMin || coord.y > saveChunkYMax) continue;
if (entry.second->empty()) continue;
SaveChunkRecord record;
record.coord = coord;
record.blocks = *entry.second;
if (versionHasLight(version)) record.light = defaultChunkLight(record.blocks, saveDefaultSkyLight);
container.chunks.push_back(std::move(record));
}
return container;
}
void applySaveContainerToWorld(const SaveContainer& container, World& world) {
for (const SaveChunkRecord& record : container.chunks) {
if (record.empty) continue;
world.ensureChunk(record.coord) = record.blocks;
}
}
bool SaveMigrator::peekVersion(const uint8_t* data, std::size_t size, SaveVersion& out) {
SaveHeader header;
if (!readSaveHeader(data, size, header)) return false;
if (header.magic != saveMagic) return false;
if (!isKnownSaveVersion(header.version)) return false;
out = static_cast<SaveVersion>(header.version);
return true;
}
MigrationPlan SaveMigrator::planFor(SaveVersion from, SaveVersion to) {
MigrationPlan plan;
plan.from = from;
plan.to = to;
if (to < from) {
plan.possible = false;
plan.refusal = "downgrade " + versionPair(from, to) +
" is refused: the newer format carries chunk metadata, a light plane and "
"checksums that the older one cannot express, so the step would drop data "
"silently";
return plan;
}
plan.possible = true;
for (uint32_t v = static_cast<uint32_t>(from); v < static_cast<uint32_t>(to); ++v) {
MigrationStep step;
step.from = static_cast<SaveVersion>(v);
step.to = static_cast<SaveVersion>(v + 1);
if (step.from == SaveVersion::V1)
step.summary = "raw block ids replaced by a per-chunk palette and an index array";
else if (step.from == SaveVersion::V2)
step.summary = "light plane and a payload checksum added";
else
step.summary = "chunk metadata added and palette indices bit-packed";
plan.steps.push_back(step);
}
return plan;
}