From 12f0549bbbccb947737a9808587ad69831433fce Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 13:44:06 +0200 Subject: [PATCH] feat: add verifyChunkPayloadChecksum and saveCoordLess to SaveMigration --- core/src/SaveMigration.cpp | 155 +++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/core/src/SaveMigration.cpp b/core/src/SaveMigration.cpp index 414403b..3c5988f 100644 --- a/core/src/SaveMigration.cpp +++ b/core/src/SaveMigration.cpp @@ -313,3 +313,158 @@ bool decodeChunkPayload(SaveVersion version, const uint8_t* data, std::size_t si out.empty = false; out.meta = SaveChunkMeta{}; + if (version == SaveVersion::V1) { + PayloadCursor cursor{data, size}; + uint32_t blockBytes = cursor.u32(); + if (cursor.bad || blockBytes != static_cast(chunkVolume)) return false; + std::vector raw; + if (!cursor.take(blockBytes, raw)) return false; + std::array& blocks = out.blocks.raw(); + for (std::size_t i = 0; i < blocks.size(); ++i) blocks[i] = static_cast(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 paletteBytes; + if (!cursor.take(paletteCount, paletteBytes)) return false; + uint32_t indexBytes = cursor.u32(); + if (cursor.bad || indexBytes != static_cast(chunkVolume)) return false; + std::vector indices; + if (!cursor.take(indexBytes, indices)) return false; + + uint32_t maxIndex = 0; + std::array& 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(paletteBytes[index]) : Block::Air; + } + uint32_t lightBytes = 0; + if (version == SaveVersion::V3) { + lightBytes = cursor.u32(); + if (cursor.bad || lightBytes > static_cast(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 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(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& coords, + const std::vector& payloads, + std::vector* sectorOffsets) { + uint32_t sectors = sectorBytes == 0 ? saveSectorBytes : sectorBytes; + + SaveHeader header; + header.magic = saveMagic; + header.version = static_cast(version); + header.regionX = regionX; + header.regionZ = regionZ; + header.chunkCount = static_cast(coords.size()); + header.sectorBytes = sectors; + + std::size_t sectorCursor = saveHeaderSectors(header); + if (sectorOffsets) sectorOffsets->clear(); + + ByteBuffer table; + table.reserve(coords.size() * saveEntryBytes); + std::vector starts; + starts.reserve(coords.size()); + for (std::size_t i = 0; i < coords.size(); ++i) { + uint32_t sectorCount = static_cast(divideUp(payloads[i].size(), sectors)); + putU32Be(table, static_cast(coords[i].x)); + putU32Be(table, static_cast(coords[i].y)); + putU32Be(table, static_cast(coords[i].z)); + putU32Be(table, static_cast(sectorCursor)); + putU32Be(table, sectorCount); + putU32Be(table, static_cast(payloads[i].size())); + if (sectorOffsets) sectorOffsets->push_back(static_cast(sectorCursor)); + starts.push_back(sectorCursor * static_cast(sectors)); + sectorCursor += sectorCount; + } + + header.tableChecksum = + versionHasChecksum(version) ? fnv1a32(table.data(), table.size()) : 0u; + + ByteBuffer out(sectorCursor * static_cast(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(header.regionX)); + saveWriteU32At(out.data(), out.size(), 12, static_cast(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(saveHeaderBytes)); + + for (std::size_t i = 0; i < payloads.size(); ++i) + std::copy(payloads[i].begin(), payloads[i].end(), + out.begin() + static_cast(starts[i])); + return out; +} +