feat: add verifyChunkPayloadChecksum and saveCoordLess to SaveMigration

This commit is contained in:
dualfroz committed 2026-09-09 13:44:06 +02:00
1 parent 6ffb347552
commit 12f0549bbb
1 file changed
+155
+155
View File
@@ -313,3 +313,158 @@ bool decodeChunkPayload(SaveVersion version, const uint8_t* data, std::size_t si
out.empty = false; out.empty = false;
out.meta = SaveChunkMeta{}; out.meta = SaveChunkMeta{};
if (version == SaveVersion::V1) {
PayloadCursor cursor{data, size};
uint32_t blockBytes = cursor.u32();
if (cursor.bad || blockBytes != static_cast<uint32_t>(chunkVolume)) return false;
std::vector<uint8_t> raw;
if (!cursor.take(blockBytes, raw)) return false;
std::array<Block, chunkVolume>& blocks = out.blocks.raw();
for (std::size_t i = 0; i < blocks.size(); ++i) blocks[i] = static_cast<Block>(raw[i]);
out.empty = chunkIsAllAir(out.blocks);
if (info) info->rawBodyBytes = size;
return true;
}
if (version == SaveVersion::V2 || version == SaveVersion::V3) {
PayloadCursor cursor{data, size};
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 indexBytes = cursor.u32();
if (cursor.bad || indexBytes != static_cast<uint32_t>(chunkVolume)) return false;
std::vector<uint8_t> indices;
if (!cursor.take(indexBytes, indices)) return false;
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;
}
uint32_t lightBytes = 0;
if (version == SaveVersion::V3) {
lightBytes = cursor.u32();
if (cursor.bad || lightBytes > static_cast<uint32_t>(chunkVolume)) return false;
if (!cursor.take(lightBytes, out.light)) return false;
if (!cursor.skip(4)) return false;
}
out.empty = chunkIsAllAir(out.blocks);
if (info) {
info->paletteCount = paletteCount;
info->maxIndex = maxIndex;
info->bitWidth = 8;
info->lightBytes = lightBytes;
info->rawBodyBytes = size;
}
return true;
}
PayloadCursor cursor{data, size};
uint32_t flags = cursor.u32();
uint32_t storedBytes = cursor.u32();
if (cursor.bad || storedBytes > size) return false;
std::vector<uint8_t> stored;
if (!cursor.take(storedBytes, stored)) return false;
if (!cursor.skip(4)) return false;
ByteBuffer body;
if ((flags & saveRecordCompressed) != 0) {
if (!decompress(stored.data(), stored.size(), body)) return false;
} else {
body = std::move(stored);
}
if (!decodeBodyV4(body.data(), body.size(), out, info)) return false;
out.empty = (flags & saveRecordEmpty) != 0;
if (info) {
info->compressed = (flags & saveRecordCompressed) != 0;
info->empty = out.empty;
}
return true;
}
bool verifyChunkPayloadChecksum(SaveVersion version, const uint8_t* data, std::size_t size,
uint32_t& stored, uint32_t& computed) {
if (!versionHasChecksum(version)) return false;
if (version == SaveVersion::V3) {
if (size < 4) return false;
stored = readU32Be(data + size - 4);
computed = fnv1a32(data, size - 4);
return true;
}
if (size < 12) return false;
uint32_t flags = readU32Be(data + 0);
uint32_t storedBytes = readU32Be(data + 4);
if (static_cast<std::size_t>(storedBytes) + 12 > size) return false;
stored = readU32Be(data + 8 + storedBytes);
if ((flags & saveRecordCompressed) != 0) {
ByteBuffer body;
if (!decompress(data + 8, storedBytes, body)) return false;
computed = fnv1a32(body.data(), body.size());
} else {
computed = fnv1a32(data + 8, storedBytes);
}
return true;
}
bool saveCoordLess(const Vec3i& a, const Vec3i& b) {
if (a.x != b.x) return a.x < b.x;
if (a.y != b.y) return a.y < b.y;
return a.z < b.z;
}
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) {
uint32_t sectors = sectorBytes == 0 ? saveSectorBytes : sectorBytes;
SaveHeader header;
header.magic = saveMagic;
header.version = static_cast<uint32_t>(version);
header.regionX = regionX;
header.regionZ = regionZ;
header.chunkCount = static_cast<uint32_t>(coords.size());
header.sectorBytes = sectors;
std::size_t sectorCursor = saveHeaderSectors(header);
if (sectorOffsets) sectorOffsets->clear();
ByteBuffer table;
table.reserve(coords.size() * saveEntryBytes);
std::vector<std::size_t> starts;
starts.reserve(coords.size());
for (std::size_t i = 0; i < coords.size(); ++i) {
uint32_t sectorCount = static_cast<uint32_t>(divideUp(payloads[i].size(), sectors));
putU32Be(table, static_cast<uint32_t>(coords[i].x));
putU32Be(table, static_cast<uint32_t>(coords[i].y));
putU32Be(table, static_cast<uint32_t>(coords[i].z));
putU32Be(table, static_cast<uint32_t>(sectorCursor));
putU32Be(table, sectorCount);
putU32Be(table, static_cast<uint32_t>(payloads[i].size()));
if (sectorOffsets) sectorOffsets->push_back(static_cast<uint32_t>(sectorCursor));
starts.push_back(sectorCursor * static_cast<std::size_t>(sectors));
sectorCursor += sectorCount;
}
header.tableChecksum =
versionHasChecksum(version) ? fnv1a32(table.data(), table.size()) : 0u;
ByteBuffer out(sectorCursor * static_cast<std::size_t>(sectors), 0);
saveWriteU32At(out.data(), out.size(), 0, header.magic);
saveWriteU32At(out.data(), out.size(), 4, header.version);
saveWriteU32At(out.data(), out.size(), 8, static_cast<uint32_t>(header.regionX));
saveWriteU32At(out.data(), out.size(), 12, static_cast<uint32_t>(header.regionZ));
saveWriteU32At(out.data(), out.size(), 16, header.chunkCount);
saveWriteU32At(out.data(), out.size(), 20, header.sectorBytes);
saveWriteU32At(out.data(), out.size(), 24, header.tableChecksum);
std::copy(table.begin(), table.end(), out.begin() + static_cast<std::ptrdiff_t>(saveHeaderBytes));
for (std::size_t i = 0; i < payloads.size(); ++i)
std::copy(payloads[i].begin(), payloads[i].end(),
out.begin() + static_cast<std::ptrdiff_t>(starts[i]));
return out;
}