#include "vf/Anvil.h" #include namespace vf { int BitPacker::bitWidthForPalette(std::size_t paletteSize) { if (paletteSize <= 1) return 1; int bits = 1; while ((static_cast(1) << bits) < paletteSize) ++bits; return bits; } namespace { uint64_t widthMask(int bitWidth) { return bitWidth >= 64 ? ~0ull : ((1ull << bitWidth) - 1ull); } } // namespace std::vector BitPacker::pack(const std::vector& values, int bitWidth) { int perWord = 64 / bitWidth; std::size_t wordCount = (values.size() + static_cast(perWord) - 1) / static_cast(perWord); std::vector words(wordCount, 0); uint64_t mask = widthMask(bitWidth); for (std::size_t i = 0; i < values.size(); ++i) { std::size_t word = i / static_cast(perWord); int slot = static_cast(i % static_cast(perWord)); words[word] |= (static_cast(values[i]) & mask) << (slot * bitWidth); } return words; } std::vector BitPacker::unpack(const std::vector& words, int bitWidth, std::size_t count) { int perWord = 64 / bitWidth; uint64_t mask = widthMask(bitWidth); std::vector values(count, 0); for (std::size_t i = 0; i < count; ++i) { std::size_t word = i / static_cast(perWord); int slot = static_cast(i % static_cast(perWord)); if (word >= words.size()) break; values[i] = static_cast((words[word] >> (slot * bitWidth)) & mask); } return values; } void AnvilSection::encodeFrom(const Chunk& chunk) { palette.clear(); std::array(Block::Count)> indexOf; indexOf.fill(-1); std::vector indices; indices.reserve(chunkVolume); for (Block block : chunk.raw()) { int& slot = indexOf[static_cast(block)]; if (slot < 0) { slot = static_cast(palette.size()); palette.push_back(block); } indices.push_back(static_cast(slot)); } bitWidth = BitPacker::bitWidthForPalette(palette.size()); packed = BitPacker::pack(indices, bitWidth); } void AnvilSection::decodeTo(Chunk& chunk) const { std::vector indices = BitPacker::unpack(packed, bitWidth, chunkVolume); std::array& blocks = chunk.raw(); for (std::size_t i = 0; i < blocks.size(); ++i) { uint32_t index = indices[i]; blocks[i] = index < palette.size() ? palette[index] : Block::Air; } } const AnvilSection* AnvilColumn::section(int sectionY) const { for (const AnvilSection& s : sections) if (s.sectionY == sectionY) return &s; return nullptr; } AnvilColumn& AnvilRegion::columnFor(int localX, int localZ) { for (AnvilColumn& column : columns) if (column.localX == localX && column.localZ == localZ) return column; columns.push_back(AnvilColumn{}); AnvilColumn& created = columns.back(); created.localX = localX; created.localZ = localZ; return created; } const AnvilColumn* AnvilRegion::findColumn(int localX, int localZ) const { for (const AnvilColumn& column : columns) if (column.localX == localX && column.localZ == localZ) return &column; return nullptr; } void AnvilRegion::putChunk(const Vec3i& chunkCoord, const Chunk& chunk) { int localX = floorMod(chunkCoord.x, anvilRegionSpan); int localZ = floorMod(chunkCoord.z, anvilRegionSpan); AnvilColumn& column = columnFor(localX, localZ); AnvilSection encoded; encoded.sectionY = chunkCoord.y; encoded.encodeFrom(chunk); for (AnvilSection& existing : column.sections) { if (existing.sectionY == chunkCoord.y) { existing = std::move(encoded); return; } } column.sections.push_back(std::move(encoded)); std::sort(column.sections.begin(), column.sections.end(), [](const AnvilSection& a, const AnvilSection& b) { return a.sectionY < b.sectionY; }); } bool AnvilRegion::hasChunk(const Vec3i& chunkCoord) const { int localX = floorMod(chunkCoord.x, anvilRegionSpan); int localZ = floorMod(chunkCoord.z, anvilRegionSpan); const AnvilColumn* column = findColumn(localX, localZ); return column && column->section(chunkCoord.y) != nullptr; } bool AnvilRegion::getChunk(const Vec3i& chunkCoord, Chunk& out) const { int localX = floorMod(chunkCoord.x, anvilRegionSpan); int localZ = floorMod(chunkCoord.z, anvilRegionSpan); const AnvilColumn* column = findColumn(localX, localZ); if (!column) return false; const AnvilSection* section = column->section(chunkCoord.y); if (!section) return false; section->decodeTo(out); return true; } std::size_t AnvilRegion::sectionCount() const { std::size_t total = 0; for (const AnvilColumn& column : columns) total += column.sections.size(); return total; } int32_t AnvilRegion::heightAt(int localX, int localZ, int x, int z) const { const AnvilColumn* column = findColumn(localX, localZ); if (!column || column->heightmap.empty()) return heightNone; return column->heightmap[static_cast(z * chunkSize + x)]; } namespace { std::vector computeHeightmap(const AnvilColumn& column) { std::vector heights(static_cast(chunkSize * chunkSize), heightNone); for (const AnvilSection& section : column.sections) { Chunk decoded; section.decodeTo(decoded); for (int x = 0; x < chunkSize; ++x) for (int z = 0; z < chunkSize; ++z) for (int y = 0; y < chunkSize; ++y) { if (decoded.at(x, y, z) == Block::Air) continue; int32_t worldY = section.sectionY * chunkSize + y; int32_t& slot = heights[static_cast(z * chunkSize + x)]; if (slot == heightNone || worldY > slot) slot = worldY; } } return heights; } void writeColumnBody(ByteWriter& body, const AnvilColumn& column) { std::vector heights = computeHeightmap(column); for (int32_t h : heights) body.putVarI32(h); body.putVarU32(static_cast(column.sections.size())); for (const AnvilSection& section : column.sections) { body.putVarI32(section.sectionY); body.putVarU32(static_cast(section.palette.size())); for (Block block : section.palette) body.putU8(static_cast(block)); body.putU8(static_cast(section.bitWidth)); body.putVarU32(static_cast(section.packed.size())); for (uint64_t word : section.packed) body.putU64(word); } } bool readColumnBody(ByteReader& reader, AnvilColumn& column) { column.heightmap.assign(static_cast(chunkSize * chunkSize), heightNone); for (std::size_t i = 0; i < column.heightmap.size(); ++i) { int32_t h; if (!reader.getVarI32(h)) return false; column.heightmap[i] = h; } uint32_t sectionCount; if (!reader.getVarU32(sectionCount)) return false; column.sections.clear(); column.sections.reserve(sectionCount); for (uint32_t s = 0; s < sectionCount; ++s) { AnvilSection section; int32_t sectionY; if (!reader.getVarI32(sectionY)) return false; section.sectionY = sectionY; uint32_t paletteSize; if (!reader.getVarU32(paletteSize)) return false; section.palette.reserve(paletteSize); for (uint32_t p = 0; p < paletteSize; ++p) { uint8_t block; if (!reader.getU8(block)) return false; section.palette.push_back(static_cast(block)); } uint8_t bitWidth; if (!reader.getU8(bitWidth)) return false; if (bitWidth < 1 || bitWidth > 64) return false; section.bitWidth = bitWidth; uint32_t wordCount; if (!reader.getVarU32(wordCount)) return false; section.packed.reserve(wordCount); for (uint32_t w = 0; w < wordCount; ++w) { uint64_t word; if (!reader.getU64(word)) return false; section.packed.push_back(word); } column.sections.push_back(std::move(section)); } return true; } const uint8_t anvilMagic[4] = {'V', 'X', 'A', '5'}; } // namespace ByteBuffer AnvilRegion::serialize() const { ByteWriter out; for (uint8_t byte : anvilMagic) out.putU8(byte); out.putU8(5); out.putU32(static_cast(regionX)); out.putU32(static_cast(regionZ)); out.putVarU32(static_cast(columns.size())); for (const AnvilColumn& column : columns) { out.putU8(static_cast(column.localX)); out.putU8(static_cast(column.localZ)); ByteWriter body; writeColumnBody(body, column); ByteBuffer compressed = compress(body.bytes()); out.putVarU32(static_cast(compressed.size())); out.putBytes(compressed.data(), compressed.size()); } return out.bytes(); } bool AnvilRegion::deserialize(const uint8_t* data, std::size_t size, AnvilRegion& out) { ByteReader reader(data, size); for (uint8_t expected : anvilMagic) { uint8_t got; if (!reader.getU8(got) || got != expected) return false; } uint8_t version; if (!reader.getU8(version) || version != 5) return false; uint32_t rx, rz; if (!reader.getU32(rx) || !reader.getU32(rz)) return false; out.regionX = static_cast(rx); out.regionZ = static_cast(rz); uint32_t columnCount; if (!reader.getVarU32(columnCount)) return false; out.columns.clear(); out.columns.reserve(columnCount); for (uint32_t c = 0; c < columnCount; ++c) { uint8_t localX, localZ; if (!reader.getU8(localX) || !reader.getU8(localZ)) return false; if (localX >= anvilRegionSpan || localZ >= anvilRegionSpan) return false; uint32_t compressedLen; if (!reader.getVarU32(compressedLen)) return false; if (reader.remaining() < compressedLen) return false; ByteBuffer body; if (!decompress(data + reader.position(), compressedLen, body)) return false; std::vector skip(compressedLen); if (!reader.getBytes(skip.data(), compressedLen)) return false; AnvilColumn column; column.localX = localX; column.localZ = localZ; ByteReader bodyReader(body.data(), body.size()); if (!readColumnBody(bodyReader, column)) return false; out.columns.push_back(std::move(column)); } return true; } std::vector anvilRegionsFromWorld(const World& world) { std::vector regions; for (const auto& entry : world.allChunks()) { const Vec3i& coord = entry.first; int regionX = floorDiv(coord.x, anvilRegionSpan); int regionZ = floorDiv(coord.z, anvilRegionSpan); AnvilRegion* target = nullptr; for (AnvilRegion& region : regions) { if (region.rx() == regionX && region.rz() == regionZ) { target = ®ion; break; } } if (!target) { regions.emplace_back(regionX, regionZ); target = ®ions.back(); } target->putChunk(coord, *entry.second); } return regions; } ByteBuffer anvilSerializeWorld(const World& world) { std::vector regions = anvilRegionsFromWorld(world); ByteWriter out; out.putVarU32(static_cast(regions.size())); for (const AnvilRegion& region : regions) { ByteBuffer blob = region.serialize(); out.putVarU32(static_cast(blob.size())); out.putBytes(blob.data(), blob.size()); } return out.bytes(); } bool anvilDeserializeWorld(const uint8_t* data, std::size_t size, World& out) { ByteReader reader(data, size); uint32_t regionCount; if (!reader.getVarU32(regionCount)) return false; for (uint32_t r = 0; r < regionCount; ++r) { uint32_t blobLen; if (!reader.getVarU32(blobLen)) return false; if (reader.remaining() < blobLen) return false; AnvilRegion region; if (!AnvilRegion::deserialize(data + reader.position(), blobLen, region)) return false; std::vector skip(blobLen); if (!reader.getBytes(skip.data(), blobLen)) return false; for (const AnvilColumn& column : region.allColumns()) { int chunkX = region.rx() * anvilRegionSpan + column.localX; int chunkZ = region.rz() * anvilRegionSpan + column.localZ; for (const AnvilSection& section : column.sections) { Chunk chunk; section.decodeTo(chunk); out.ensureChunk(Vec3i{chunkX, section.sectionY, chunkZ}) = chunk; } } } return true; } } // namespace vf