feat: add readSaveTable and chunkIsAllAir to SaveMigration

This commit is contained in:
dualfroz committed 2026-09-09 13:01:49 +02:00
1 parent 48f48bf57b
commit 6ffb347552
1 file changed
+155
+155
View File
@@ -158,3 +158,158 @@ std::size_t saveHeaderSectors(const SaveHeader& header) {
return divideUp(saveHeaderBytes + saveTableBytes(header), header.sectorBytes);
}
std::size_t saveDataStart(const SaveHeader& header) {
return saveHeaderSectors(header) * static_cast<std::size_t>(header.sectorBytes);
}
bool readSaveTable(const uint8_t* data, std::size_t size, const SaveHeader& header,
std::vector<SaveTableEntry>& out) {
out.clear();
for (uint32_t i = 0; i < header.chunkCount; ++i) {
std::size_t at = saveHeaderBytes + static_cast<std::size_t>(i) * saveEntryBytes;
if (at + saveEntryBytes > size) return false;
SaveTableEntry entry;
entry.coord.x = static_cast<int32_t>(readU32Be(data + at + 0));
entry.coord.y = static_cast<int32_t>(readU32Be(data + at + 4));
entry.coord.z = static_cast<int32_t>(readU32Be(data + at + 8));
entry.sectorOffset = readU32Be(data + at + 12);
entry.sectorCount = readU32Be(data + at + 16);
entry.payloadBytes = readU32Be(data + at + 20);
out.push_back(entry);
}
return true;
}
std::vector<uint8_t> defaultChunkLight(const Chunk& chunk, uint8_t skyLevel) {
uint8_t sky = skyLevel > 15 ? 15 : skyLevel;
std::vector<uint8_t> plane(chunkVolume, 0);
const std::array<Block, chunkVolume>& blocks = chunk.raw();
for (std::size_t i = 0; i < blocks.size(); ++i) {
uint8_t skyHere = occludesLight(blocks[i]) ? 0u : sky;
uint8_t emitted = static_cast<uint8_t>(blockLightEmission(blocks[i]));
plane[i] = static_cast<uint8_t>((skyHere << 4) | (emitted & 0x0Fu));
}
return plane;
}
bool chunkIsAllAir(const Chunk& chunk) {
for (Block block : chunk.raw())
if (block != Block::Air) return false;
return true;
}
ByteBuffer encodeChunkPayload(SaveVersion version, const SaveChunkRecord& record,
bool compressBody) {
ByteBuffer out;
if (version == SaveVersion::V1) {
putU32Be(out, static_cast<uint32_t>(chunkVolume));
for (Block block : record.blocks.raw()) out.push_back(static_cast<uint8_t>(block));
return out;
}
std::vector<Block> palette;
std::vector<uint32_t> indices;
buildPalette(record.blocks, palette, indices);
if (version == SaveVersion::V2 || version == SaveVersion::V3) {
putU32Be(out, static_cast<uint32_t>(palette.size()));
for (Block block : palette) out.push_back(static_cast<uint8_t>(block));
putU32Be(out, static_cast<uint32_t>(indices.size()));
for (uint32_t index : indices) out.push_back(static_cast<uint8_t>(index));
if (version == SaveVersion::V3) {
putU32Be(out, static_cast<uint32_t>(record.light.size()));
out.insert(out.end(), record.light.begin(), record.light.end());
putU32Be(out, fnv1a32(out.data(), out.size()));
}
return out;
}
int bitWidth = BitPacker::bitWidthForPalette(palette.size());
std::vector<uint64_t> words = BitPacker::pack(indices, bitWidth);
ByteBuffer body;
putU32Be(body, static_cast<uint32_t>(palette.size()));
for (Block block : palette) body.push_back(static_cast<uint8_t>(block));
putU32Be(body, static_cast<uint32_t>(bitWidth));
putU32Be(body, static_cast<uint32_t>(words.size()));
for (uint64_t word : words) writeU64Be(body, word);
putU32Be(body, static_cast<uint32_t>(record.light.size()));
body.insert(body.end(), record.light.begin(), record.light.end());
writeU64Be(body, record.meta.inhabitedTicks);
writeU64Be(body, record.meta.lastSavedTick);
body.push_back(record.meta.generationStage);
uint32_t bodyChecksum = fnv1a32(body.data(), body.size());
ByteBuffer stored = compressBody ? compress(body) : body;
uint32_t flags = 0;
if (record.empty || chunkIsAllAir(record.blocks)) flags |= saveRecordEmpty;
if (compressBody) flags |= saveRecordCompressed;
putU32Be(out, flags);
putU32Be(out, static_cast<uint32_t>(stored.size()));
out.insert(out.end(), stored.begin(), stored.end());
putU32Be(out, bodyChecksum);
return out;
}
namespace {
bool decodeBodyV4(const uint8_t* body, std::size_t bodySize, SaveChunkRecord& out,
SavePayloadInfo* info) {
PayloadCursor cursor{body, bodySize};
uint32_t paletteCount = cursor.u32();
if (cursor.bad || paletteCount == 0 || paletteCount > 4096u) return false;
std::vector<uint8_t> paletteBytes;
if (!cursor.take(paletteCount, paletteBytes)) return false;
uint32_t bitWidth = cursor.u32();
uint32_t wordCount = cursor.u32();
if (cursor.bad || bitWidth == 0 || bitWidth > 32u) return false;
int perWord = 64 / static_cast<int>(bitWidth);
std::size_t neededWords = divideUp(chunkVolume, static_cast<std::size_t>(perWord));
if (wordCount < neededWords) return false;
if (wordCount > neededWords + 64u) return false;
std::vector<uint64_t> words;
words.reserve(wordCount);
for (uint32_t i = 0; i < wordCount; ++i) words.push_back(cursor.u64());
if (cursor.bad) return false;
uint32_t lightBytes = cursor.u32();
if (cursor.bad || lightBytes > static_cast<uint32_t>(chunkVolume)) return false;
if (!cursor.take(lightBytes, out.light)) return false;
out.meta.inhabitedTicks = cursor.u64();
out.meta.lastSavedTick = cursor.u64();
out.meta.generationStage = cursor.u8();
if (cursor.bad) return false;
std::vector<uint32_t> indices =
BitPacker::unpack(words, static_cast<int>(bitWidth), chunkVolume);
uint32_t maxIndex = 0;
std::array<Block, chunkVolume>& blocks = out.blocks.raw();
for (std::size_t i = 0; i < blocks.size(); ++i) {
uint32_t index = indices[i];
if (index > maxIndex) maxIndex = index;
blocks[i] = index < paletteCount ? static_cast<Block>(paletteBytes[index]) : Block::Air;
}
if (info) {
info->paletteCount = paletteCount;
info->maxIndex = maxIndex;
info->bitWidth = bitWidth;
info->lightBytes = lightBytes;
info->rawBodyBytes = bodySize;
}
return true;
}
} // namespace
bool decodeChunkPayload(SaveVersion version, const uint8_t* data, std::size_t size,
SaveChunkRecord& out, SavePayloadInfo* info) {
if (info) *info = SavePayloadInfo{};
out.light.clear();
out.empty = false;
out.meta = SaveChunkMeta{};