diff --git a/core/src/SaveMigration.cpp b/core/src/SaveMigration.cpp index 5d3f103..414403b 100644 --- a/core/src/SaveMigration.cpp +++ b/core/src/SaveMigration.cpp @@ -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(header.sectorBytes); +} + +bool readSaveTable(const uint8_t* data, std::size_t size, const SaveHeader& header, + std::vector& out) { + out.clear(); + for (uint32_t i = 0; i < header.chunkCount; ++i) { + std::size_t at = saveHeaderBytes + static_cast(i) * saveEntryBytes; + if (at + saveEntryBytes > size) return false; + SaveTableEntry entry; + entry.coord.x = static_cast(readU32Be(data + at + 0)); + entry.coord.y = static_cast(readU32Be(data + at + 4)); + entry.coord.z = static_cast(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 defaultChunkLight(const Chunk& chunk, uint8_t skyLevel) { + uint8_t sky = skyLevel > 15 ? 15 : skyLevel; + std::vector plane(chunkVolume, 0); + const std::array& 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(blockLightEmission(blocks[i])); + plane[i] = static_cast((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(chunkVolume)); + for (Block block : record.blocks.raw()) out.push_back(static_cast(block)); + return out; + } + + std::vector palette; + std::vector indices; + buildPalette(record.blocks, palette, indices); + + if (version == SaveVersion::V2 || version == SaveVersion::V3) { + putU32Be(out, static_cast(palette.size())); + for (Block block : palette) out.push_back(static_cast(block)); + putU32Be(out, static_cast(indices.size())); + for (uint32_t index : indices) out.push_back(static_cast(index)); + if (version == SaveVersion::V3) { + putU32Be(out, static_cast(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 words = BitPacker::pack(indices, bitWidth); + + ByteBuffer body; + putU32Be(body, static_cast(palette.size())); + for (Block block : palette) body.push_back(static_cast(block)); + putU32Be(body, static_cast(bitWidth)); + putU32Be(body, static_cast(words.size())); + for (uint64_t word : words) writeU64Be(body, word); + putU32Be(body, static_cast(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(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 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(bitWidth); + std::size_t neededWords = divideUp(chunkVolume, static_cast(perWord)); + if (wordCount < neededWords) return false; + if (wordCount > neededWords + 64u) return false; + + std::vector 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(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 indices = + BitPacker::unpack(words, static_cast(bitWidth), chunkVolume); + 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; + } + 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{}; +