module; #include #include #include #include #include #include #include #include #include #include #include #include #include export module nbtpp; namespace nbtpp { export enum class tag : std::uint8_t { end = 0, i8 = 1, i16 = 2, i32 = 3, i64 = 4, f32 = 5, f64 = 6, i8_arr = 7, string = 8, list = 9, compound = 10, i32_arr = 11, i64_arr = 12, }; void verify(tag required, tag t) { if (required != t) { throw std::runtime_error("unexpected type on access"); } } template void verify_type(tag t) {} export struct node { std::string_view name = {}; tag type = tag::end; std::uint32_t next = std::numeric_limits::max(); private: [[nodiscard]] const std::byte *get_data() const { return reinterpret_cast(name.data() + name.size()); } template [[nodiscard]] T read() const { verify_type(type); auto value = T(); std::memcpy(&value, get_data(), sizeof(T)); return std::byteswap(value); } template [[nodiscard]] T read() const { verify_type(type); auto data = std::array(); std::memcpy(data.data(), get_data(), sizeof(T)); std::reverse(data.begin(), data.end()); return std::bit_cast(data); } public: template [[nodiscard]] T as() const; [[nodiscard]] std::size_t advance_size() const { using enum tag; switch (type) { case i8: return 1; case i16: return 2; case i32: return 4; case i64: return 8; case f32: return 4; case f64: return 8; case string: { const auto size = read(); return size + sizeof(std::uint16_t); } case i8_arr: case i32_arr: case i64_arr: { const auto size = read(); return size + sizeof(std::int32_t); }; default: throw std::runtime_error("not expected to work"); } } }; template <> std::vector node::as>() const { auto length = std::int32_t(); std::memcpy(&length, get_data(), 4); length = std::byteswap(length); auto list = std::vector(length); std::memcpy(list.data(), get_data() + 4, length); return list; } template <> std::int8_t node::as() const { return read(); } template <> std::int16_t node::as() const { return read(); } template <> std::int32_t node::as() const { return read(); } template <> std::int64_t node::as() const { return read(); } template <> std::string node::as() const { const auto length = read(); auto str = std::string(length, ' '); std::memcpy(str.data(), get_data() + 2, length); return str; } template <> float node::as() const { return read(); } template <> double node::as() const { return read(); } template <> void verify_type(tag t) { verify(tag::i8, t); } template <> void verify_type(tag t) { verify(tag::i16, t); } template <> void verify_type(tag t) { verify(tag::i32, t); } template <> void verify_type(tag t) { verify(tag::i64, t); } template <> void verify_type(tag t) { verify(tag::f32, t); } template <> void verify_type(tag t) { verify(tag::f64, t); } template <> void verify_type>(tag t) { verify(tag::i8_arr, t); } template <> void verify_type(tag t) { verify(tag::string, t); } template <> void verify_type>(tag t) { verify(tag::list, t); } template <> void verify_type>(tag t) { verify(tag::i32_arr, t); } template <> void verify_type>(tag t) { verify(tag::i64_arr, t); } void parse_node_header(std::span nodes, std::int64_t &cursor, const std::byte *bytes) { nodes[cursor].type = std::bit_cast(*bytes); if (nodes[cursor].type == tag::end) { auto name_length = std::uint16_t(); std::memcpy(&name_length, bytes + 1, sizeof(std::uint16_t)); name_length = std::byteswap(name_length); nodes[cursor].name = std::string_view( reinterpret_cast(bytes + 3), name_length); } } export [[nodiscard]] std::vector parse(std::span nbt) { const auto node_count = nbt.size() / 4; auto nodes = std::make_unique(node_count); auto span = std::span(nodes.get(), nodes.get() + node_count); auto cursor = std::int64_t(); auto node_index = std::int64_t(0); const auto read_name = [&] -> std::string_view { auto name_length = std::uint16_t(); std::memcpy(&name_length, &nbt[cursor], sizeof(std::uint16_t)); cursor += sizeof(std::uint16_t); name_length = std::byteswap(name_length); const auto string_start = reinterpret_cast(&nbt[cursor]); cursor += name_length; return std::string_view(string_start, name_length); }; auto stack = std::array(); auto stack_ptr = std::int16_t(); do { const auto node_type = std::bit_cast(nbt[cursor++]); if (node_type == tag::end) { const auto parent_index = stack[stack_ptr--]; nodes[parent_index].next = node_index; continue; } const auto node_name = read_name(); if (node_type == tag::compound) { stack[stack_ptr++] = node_index; nodes[node_index++] = { .name = node_name, .type = node_type, }; continue; } else { // Primitive node nodes[node_index++] = { .name = node_name, .type = node_type, }; const auto size = nodes[node_index - 1].advance_size(); std::println("advancing by {} for node {}", size, node_name); cursor += size; } } while (stack_ptr > 0); return {nodes.get(), nodes.get() + node_index}; } } // namespace nbtpp