diff --git a/core/src/SaveMigration.cpp b/core/src/SaveMigration.cpp new file mode 100644 index 0000000..5d3f103 --- /dev/null +++ b/core/src/SaveMigration.cpp @@ -0,0 +1,160 @@ +#include "vf/SaveMigration.h" + +#include +#include + +#include "vf/Anvil.h" +#include "vf/Block.h" + +namespace vf { + +namespace { + +std::size_t divideUp(std::size_t value, std::size_t unit) { + return unit == 0 ? 0 : (value + unit - 1) / unit; +} + +void putU32Be(std::vector& out, uint32_t value) { + out.push_back(static_cast((value >> 24) & 0xFFu)); + out.push_back(static_cast((value >> 16) & 0xFFu)); + out.push_back(static_cast((value >> 8) & 0xFFu)); + out.push_back(static_cast(value & 0xFFu)); +} + +uint32_t readU32Be(const uint8_t* at) { + return (static_cast(at[0]) << 24) | (static_cast(at[1]) << 16) | + (static_cast(at[2]) << 8) | static_cast(at[3]); +} + +struct PayloadCursor { + const uint8_t* data; + std::size_t size; + std::size_t at = 0; + bool bad = false; + + uint32_t u32() { + if (bad || at + 4 > size) { + bad = true; + return 0; + } + uint32_t value = readU32Be(data + at); + at += 4; + return value; + } + uint64_t u64() { + uint32_t high = u32(); + uint32_t low = u32(); + return (static_cast(high) << 32) | static_cast(low); + } + uint8_t u8() { + if (bad || at + 1 > size) { + bad = true; + return 0; + } + return data[at++]; + } + bool take(std::size_t count, std::vector& out) { + if (bad || at + count > size) { + bad = true; + return false; + } + out.assign(data + at, data + at + count); + at += count; + return true; + } + bool skip(std::size_t count) { + if (bad || at + count > size) { + bad = true; + return false; + } + at += count; + return true; + } +}; + +void writeU64Be(std::vector& out, uint64_t value) { + putU32Be(out, static_cast(value >> 32)); + putU32Be(out, static_cast(value & 0xFFFFFFFFull)); +} + +void buildPalette(const Chunk& chunk, std::vector& palette, std::vector& indices) { + std::array(Block::Count)> slotOf; + slotOf.fill(-1); + palette.clear(); + indices.clear(); + indices.reserve(chunkVolume); + for (Block block : chunk.raw()) { + int& slot = slotOf[static_cast(block)]; + if (slot < 0) { + slot = static_cast(palette.size()); + palette.push_back(block); + } + indices.push_back(static_cast(slot)); + } +} + +std::string versionPair(SaveVersion from, SaveVersion to) { + return std::string(saveVersionName(from)) + " -> " + saveVersionName(to); +} + +} // namespace + +const SaveChunkRecord* SaveContainer::find(const Vec3i& coord) const { + for (const SaveChunkRecord& record : chunks) + if (record.coord == coord) return &record; + return nullptr; +} + +bool saveReadU32At(const uint8_t* data, std::size_t size, std::size_t at, uint32_t& out) { + if (at + 4 > size) return false; + out = readU32Be(data + at); + return true; +} + +bool saveWriteU32At(uint8_t* data, std::size_t size, std::size_t at, uint32_t value) { + if (at + 4 > size) return false; + data[at + 0] = static_cast((value >> 24) & 0xFFu); + data[at + 1] = static_cast((value >> 16) & 0xFFu); + data[at + 2] = static_cast((value >> 8) & 0xFFu); + data[at + 3] = static_cast(value & 0xFFu); + return true; +} + +bool isKnownSaveVersion(uint32_t version) { return version >= 1u && version <= 4u; } + +bool versionHasPalette(SaveVersion version) { return version >= SaveVersion::V2; } +bool versionHasLight(SaveVersion version) { return version >= SaveVersion::V3; } +bool versionHasChecksum(SaveVersion version) { return version >= SaveVersion::V3; } +bool versionHasMetadata(SaveVersion version) { return version >= SaveVersion::V4; } + +const char* saveVersionName(SaveVersion version) { + switch (version) { + case SaveVersion::V1: return "v1"; + case SaveVersion::V2: return "v2"; + case SaveVersion::V3: return "v3"; + case SaveVersion::V4: return "v4"; + } + return "v?"; +} + +bool readSaveHeader(const uint8_t* data, std::size_t size, SaveHeader& out) { + if (size < saveHeaderBytes) return false; + out.magic = readU32Be(data + 0); + out.version = readU32Be(data + 4); + out.regionX = static_cast(readU32Be(data + 8)); + out.regionZ = static_cast(readU32Be(data + 12)); + out.chunkCount = readU32Be(data + 16); + out.sectorBytes = readU32Be(data + 20); + out.tableChecksum = readU32Be(data + 24); + return true; +} + +std::size_t saveTableBytes(const SaveHeader& header) { + return static_cast(header.chunkCount) * saveEntryBytes; +} + +std::size_t saveHeaderSectors(const SaveHeader& header) { + if (header.sectorBytes == 0) return 0; + return divideUp(saveHeaderBytes + saveTableBytes(header), header.sectorBytes); +} +