initial commit (compound / primitive - list)
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
8
CMakeLists.txt
Normal file
8
CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
cmake_minimum_required(VERSION 4.2.0)
|
||||||
|
project(nbtpp)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
add_subdirectory(source)
|
||||||
|
add_subdirectory(test)
|
||||||
|
|
||||||
6
source/CMakeLists.txt
Normal file
6
source/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
add_library(nbtpp STATIC)
|
||||||
|
target_sources(nbtpp
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET nbtpp_modules TYPE CXX_MODULES FILES
|
||||||
|
nbt.ixx
|
||||||
|
)
|
||||||
242
source/nbt.ixx
Normal file
242
source/nbt.ixx
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <bit>
|
||||||
|
#include <concepts>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <print>
|
||||||
|
#include <span>
|
||||||
|
#include <stack>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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 <typename T> void verify_type(tag t) {}
|
||||||
|
|
||||||
|
export struct node {
|
||||||
|
std::string_view name = {};
|
||||||
|
tag type = tag::end;
|
||||||
|
|
||||||
|
std::uint32_t next = std::numeric_limits<std::uint32_t>::max();
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] const std::byte *get_data() const {
|
||||||
|
return reinterpret_cast<const std::byte *>(name.data() + name.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::integral T> [[nodiscard]] T read() const {
|
||||||
|
verify_type<T>(type);
|
||||||
|
auto value = T();
|
||||||
|
std::memcpy(&value, get_data(), sizeof(T));
|
||||||
|
return std::byteswap(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::floating_point T> [[nodiscard]] T read() const {
|
||||||
|
verify_type<T>(type);
|
||||||
|
auto data = std::array<std::byte, sizeof(T)>();
|
||||||
|
std::memcpy(data.data(), get_data(), sizeof(T));
|
||||||
|
std::reverse(data.begin(), data.end());
|
||||||
|
return std::bit_cast<T>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename T> [[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<std::uint16_t>();
|
||||||
|
return size + sizeof(std::uint16_t);
|
||||||
|
}
|
||||||
|
case i8_arr:
|
||||||
|
case i32_arr:
|
||||||
|
case i64_arr: {
|
||||||
|
const auto size = read<std::int32_t>();
|
||||||
|
return size + sizeof(std::int32_t);
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("not expected to work");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::vector<std::int8_t> node::as<std::vector<std::int8_t>>() const {
|
||||||
|
auto length = std::int32_t();
|
||||||
|
std::memcpy(&length, get_data(), 4);
|
||||||
|
length = std::byteswap(length);
|
||||||
|
|
||||||
|
auto list = std::vector<std::int8_t>(length);
|
||||||
|
std::memcpy(list.data(), get_data() + 4, length);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::int8_t node::as<std::int8_t>() const {
|
||||||
|
return read<std::int8_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::int16_t node::as<std::int16_t>() const {
|
||||||
|
return read<std::int16_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::int32_t node::as<std::int32_t>() const {
|
||||||
|
return read<std::int32_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::int64_t node::as<std::int64_t>() const {
|
||||||
|
return read<std::int64_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::string node::as<std::string>() const {
|
||||||
|
const auto length = read<std::uint16_t>();
|
||||||
|
auto str = std::string(length, ' ');
|
||||||
|
std::memcpy(str.data(), get_data() + 2, length);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> float node::as<float>() const { return read<float>(); }
|
||||||
|
|
||||||
|
template <> double node::as<double>() const { return read<double>(); }
|
||||||
|
|
||||||
|
template <> void verify_type<std::int8_t>(tag t) { verify(tag::i8, t); }
|
||||||
|
|
||||||
|
template <> void verify_type<std::int16_t>(tag t) { verify(tag::i16, t); }
|
||||||
|
|
||||||
|
template <> void verify_type<std::int32_t>(tag t) { verify(tag::i32, t); }
|
||||||
|
|
||||||
|
template <> void verify_type<std::int64_t>(tag t) { verify(tag::i64, t); }
|
||||||
|
|
||||||
|
template <> void verify_type<float>(tag t) { verify(tag::f32, t); }
|
||||||
|
|
||||||
|
template <> void verify_type<double>(tag t) { verify(tag::f64, t); }
|
||||||
|
|
||||||
|
template <> void verify_type<std::vector<std::int8_t>>(tag t) {
|
||||||
|
verify(tag::i8_arr, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> void verify_type<std::string_view>(tag t) {
|
||||||
|
verify(tag::string, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> void verify_type<std::vector<node>>(tag t) { verify(tag::list, t); }
|
||||||
|
|
||||||
|
template <> void verify_type<std::vector<std::int32_t>>(tag t) {
|
||||||
|
verify(tag::i32_arr, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> void verify_type<std::vector<std::int64_t>>(tag t) {
|
||||||
|
verify(tag::i64_arr, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_node_header(std::span<node> nodes, std::int64_t &cursor,
|
||||||
|
const std::byte *bytes) {
|
||||||
|
nodes[cursor].type = std::bit_cast<tag>(*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<const char *>(bytes + 3), name_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export [[nodiscard]] std::vector<node> parse(std::span<const std::byte> nbt) {
|
||||||
|
const auto node_count = nbt.size() / 4;
|
||||||
|
auto nodes = std::make_unique<node[]>(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<const char *>(&nbt[cursor]);
|
||||||
|
cursor += name_length;
|
||||||
|
|
||||||
|
return std::string_view(string_start, name_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto stack = std::array<std::uint32_t, 128>();
|
||||||
|
auto stack_ptr = std::int16_t();
|
||||||
|
|
||||||
|
do {
|
||||||
|
const auto node_type = std::bit_cast<tag>(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
|
||||||
13
test/CMakeLists.txt
Normal file
13
test/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.8.1
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
|
add_executable(tests test.cpp)
|
||||||
|
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain nbtpp)
|
||||||
|
|
||||||
BIN
test/data/bigtest.nbt
Normal file
BIN
test/data/bigtest.nbt
Normal file
Binary file not shown.
BIN
test/data/hello_world.nbt
Normal file
BIN
test/data/hello_world.nbt
Normal file
Binary file not shown.
BIN
test/data/random_nbt.nbt
Normal file
BIN
test/data/random_nbt.nbt
Normal file
Binary file not shown.
34
test/test.cpp
Normal file
34
test/test.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
import nbtpp;
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<std::byte> load_binary(std::filesystem::path path) {
|
||||||
|
auto stream = std::ifstream(path, std::ios::binary);
|
||||||
|
|
||||||
|
const auto file_size = std::filesystem::file_size(path);
|
||||||
|
|
||||||
|
auto buffer = std::vector<std::byte>(file_size);
|
||||||
|
stream.read(reinterpret_cast<char *>(buffer.data()), file_size);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("hello world", "[nbt.read]") {
|
||||||
|
const auto data = ::load_binary("test/data/hello_world.nbt");
|
||||||
|
const auto nodes = nbtpp::parse(data);
|
||||||
|
REQUIRE(nodes.size() == 2);
|
||||||
|
REQUIRE(nodes[0].type == nbtpp::tag::compound);
|
||||||
|
REQUIRE(nodes[0].name == "hello world");
|
||||||
|
REQUIRE(nodes[1].type == nbtpp::tag::string);
|
||||||
|
REQUIRE(nodes[1].name == "name");
|
||||||
|
REQUIRE(nodes[1].as<std::string>() == "Bananrama");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TAG_Compound('hello world'): 1 entry
|
||||||
|
{
|
||||||
|
TAG_String('name'): 'Bananrama'
|
||||||
|
}
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user