feat: add readSaveTable and chunkIsAllAir to SaveMigration
This commit is contained in:
1 parent
48f48bf57b
commit
6ffb347552
1 file changed
+155
@@ -158,3 +158,158 @@ std::size_t saveHeaderSectors(const SaveHeader& header) {
|
|||||||
return divideUp(saveHeaderBytes + saveTableBytes(header), header.sectorBytes);
|
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{};
|
||||||
|
|
||||||
Reference in new issue
Block a user