From 171f9e5d5aad625ddf32a2eb638218db338349b3 Mon Sep 17 00:00:00 2001 From: ktlo Date: Sun, 16 Aug 2020 17:41:33 +0000 Subject: [PATCH] NBT --- README.md | 46 +++ include/meson.build | 2 +- include/nbt.hpp | 918 +++++++++++++++++++++++++++++++++++++++++ meson.build | 6 +- src/config.cpp.in | 10 - src/config.hpp | 10 - src/meson.build | 13 +- src/nbt.cpp | 869 ++++++++++++++++++++++++++++++++++++++ src/nbtc.cpp | 87 ++++ src/sample_source.cpp | 5 - test/bedrock_level.dat | Bin 0 -> 1666 bytes test/bigtest.nbt | Bin 0 -> 1544 bytes test/meson.build | 4 +- test/nbt.cpp | 48 +++ test/sample_test.cpp | 5 - 15 files changed, 1977 insertions(+), 46 deletions(-) create mode 100644 README.md create mode 100644 include/nbt.hpp delete mode 100644 src/config.cpp.in delete mode 100644 src/config.hpp create mode 100644 src/nbt.cpp create mode 100644 src/nbtc.cpp delete mode 100644 src/sample_source.cpp create mode 100644 test/bedrock_level.dat create mode 100644 test/bigtest.nbt create mode 100644 test/nbt.cpp delete mode 100644 test/sample_test.cpp diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a7cabf --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +NBT library for C++17 +======================================= + +You can read and save files in NBT format with this library. It supports different formats: +NBT Java Edition, NBT Bedrock Edition (both network and file formats) and Mojangson. + +Usage +--------------------------------------- + +```c++ +#include +#include + +using namespace nbt; + +int main() { + std::ifstream input("java.nbt"); + tags::compound_tag root; + input >> contexts::java >> root; + std::ofstream output("mojangson.txt"); + output << contexts::mojangson << root; +} +``` + +By default all lists have their own specialization for each tag. If you want to get something simple +like `std::vector` as list values you can use `nbt::tags::tag_compound::make_heavy` +and `nbt::tags::list_tag::as_tags`. After that all lists in NBT subtree would be +`nbt::tags::tag_list_tag` objects. + +NBTC +--------------------------------------- + +There are also a simple converter between NBT formats: `nbtc`. + + ./nbtc FROM TO + +### Parameters: +- FROM read stdin as FROM NBT format +- TO write NBT tag to stdout as TO format + +### NBT formats: +- *java* Minecraft Java Edition NBT +- *bedrock* or *bedrock-disk* Minecraft Bedrock Edition storage NBT format +- *bedrock-net* Minecraft Bedrock Edition network NBT format +- *mojangson* text NBT representation +- *kbt* HandTruth NBT extension diff --git a/include/meson.build b/include/meson.build index b29eb0e..1b2a069 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1,5 +1,5 @@ includes = include_directories('.') install_headers([ - + 'nbt.hpp' ]) diff --git a/include/nbt.hpp b/include/nbt.hpp new file mode 100644 index 0000000..f0fac04 --- /dev/null +++ b/include/nbt.hpp @@ -0,0 +1,918 @@ +#ifndef NBT_HEAD_BNTYGTFREQXCPVMFFG +#define NBT_HEAD_BNTYGTFREQXCPVMFFG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \ + defined(__BIG_ENDIAN__) || \ + defined(__ARMEB__) || \ + defined(__THUMBEB__) || \ + defined(__AARCH64EB__) || \ + defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) +# define NBT_BIG_ENDIAN +#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \ + defined(__LITTLE_ENDIAN__) || \ + defined(__ARMEL__) || \ + defined(__THUMBEL__) || \ + defined(__AARCH64EL__) || \ + defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) + +#else +# error "unknown architecture" +#endif + +namespace nbt { + +enum class tag_id : std::uint8_t { + tag_end, tag_byte, tag_short, tag_int, tag_long, tag_float, + tag_double, tag_bytearray, tag_string, tag_list, tag_compound, + tag_intarray, tag_longarray +}; + +} // namespace nbt + +namespace std { + +string to_string(nbt::tag_id tid); + +} // namespace std + +namespace nbt { + +inline int cheof(std::istream & input) { + int value = input.get(); + if (value == EOF) + throw std::runtime_error("EOF"); + return value; +} + +std::ostream & operator<<(std::ostream & output, tag_id tid); + +template +number_t reverse(number_t number) { + constexpr std::size_t n = sizeof(number_t); + union { + number_t number; + std::uint8_t data[n]; + } tmp { number }; + for (std::size_t i = 0; i < n/2; ++i) { + std::uint8_t z = tmp.data[i]; + tmp.data[i] = tmp.data[n - 1 - i]; + tmp.data[n - 1 - i] = z; + } + return tmp.number; +} + +template +number_t net_order(number_t number) { +#ifndef NBT_BIG_ENDIAN + return reverse(number); +#else + return number; +#endif +} + +template +number_t disk_order(number_t number) { +#ifdef NBT_BIG_ENDIAN + return reverse(number); +#else + return number; +#endif +} + +struct context { + enum class orders { + big_endian, little_endian + } order; + enum class formats { + bin, zigzag, mojangson + } format; + static const context & get(std::ios_base & ios); + void set(std::ios_base & ios) const; +}; + +inline std::istream & operator>>(std::istream & input, const context & ctxt) { + ctxt.set(input); + return input; +} + +inline std::ostream & operator<<(std::ostream & output, const context & ctxt) { + ctxt.set(output); + return output; +} + +namespace contexts { + +inline const context java { + context::orders::big_endian, + context::formats::bin +}, bedrock_net { + context::orders::little_endian, + context::formats::zigzag +}, bedrock_disk { + context::orders::little_endian, + context::formats::bin +}, kbt { + context::orders::big_endian, + context::formats::zigzag +}, mojangson { + context::orders::big_endian, + context::formats::mojangson +}; + +} // namespace contexts + +template +number_t correct_order(number_t number, const context::orders order) { + switch (order) { + case context::orders::little_endian: + return disk_order(number); + case context::orders::big_endian: + default: + return net_order(number); + } +} + +std::int32_t load_varint(std::istream & input); + +void dump_varint(std::ostream & output, std::int32_t value); + +std::int64_t load_varlong(std::istream & input); + +void dump_varlong(std::ostream & output, std::int64_t value); + +template +number_t load_flat(std::istream & input, const context::orders order) { + constexpr std::size_t n = sizeof(number_t); + union { + number_t number; + char data[n]; + } tmp; + input.read(tmp.data, n); + return correct_order(tmp.number, order); +} + +template +number_t load_text(std::istream & input) { + return static_cast(load_text>(input)); +} + +template <> +std::int8_t load_text(std::istream & input); + +template <> +std::int16_t load_text(std::istream & input); + +template <> +std::int32_t load_text(std::istream & input); + +template <> +std::int64_t load_text(std::istream & input); + +template <> +float load_text(std::istream & input); + +template <> +double load_text(std::istream & input); + +template +number_t load(std::istream & input, const context & ctxt) { + if (ctxt.format == context::formats::mojangson) + return load_text(input); + else + return load_flat(input, ctxt.order); +} + +template <> +std::int32_t load(std::istream & input, const context & ctxt); + +template <> +inline std::uint32_t load(std::istream & input, const context & ctxt) { + return static_cast(load(input, ctxt)); +} + +template <> +std::int64_t load(std::istream & input, const context & ctxt); + +template <> +inline std::uint64_t load(std::istream & input, const context & ctxt) { + return static_cast(load(input, ctxt)); +} + +void skip_space(std::istream & input); + +template +std::vector load_array_text(std::istream & input) { + std::vector result; + for (;;) { + result.push_back(load_text(input)); + skip_space(input); + char next = cheof(input); + if (next != ',') { + input.putback(next); + break; + } + } + result.shrink_to_fit(); + return result; +} + +template +std::vector load_array_bin(std::istream & input, const context & ctxt) { + auto size = load(input, ctxt); + std::vector result; + result.reserve(size); + for (std::uint32_t i = 0; i < size; i++) + result.emplace_back(load(input, ctxt)); + return result; +} + +template +std::vector load_array(std::istream & input, const context & ctxt) { + if (ctxt.format == context::formats::mojangson) + return load_array_text(input); + else + return load_array_bin(input, ctxt); +} + +template +void dump_flat(std::ostream & output, number_t number, const context::orders order) { + constexpr std::size_t n = sizeof(number_t); + union { + number_t number; + char data[n]; + } tmp { correct_order(number, order) }; + output.write(tmp.data, n); +} + +template +void dump_text(std::ostream & output, number_t number) { + dump_text(output, std::make_signed_t(number)); +} + +template <> +void dump_text(std::ostream & output, std::int8_t number); + +template <> +void dump_text(std::ostream & output, std::int16_t number); + +template <> +void dump_text(std::ostream & output, std::int32_t number); + +template <> +void dump_text(std::ostream & output, std::int64_t number); + +template <> +void dump_text(std::ostream & output, float number); + +template <> +void dump_text(std::ostream & output, double number); + +template +void dump(std::ostream & output, number_t number, const context & ctxt) { + if (ctxt.format == context::formats::mojangson) + dump_text(output, number); + else + dump_flat(output, number, ctxt.order); +} + +template <> +void dump(std::ostream & output, std::int32_t number, const context & ctxt); + +template <> +inline void dump(std::ostream & output, std::uint32_t number, const context & ctxt) { + dump(output, static_cast(number), ctxt); +} + +template <> +void dump(std::ostream & output, std::int64_t number, const context & ctxt); + +template <> +inline void dump(std::ostream & output, std::uint64_t number, const context & ctxt) { + dump(output, static_cast(number), ctxt); +} + +template +void dump_array_text(std::ostream & output, const std::vector & array) { + auto iter = array.cbegin(); + auto end = array.cend(); + if (iter == end) + return; + dump_text(output, *iter); + for (++iter; iter != end; ++iter) { + output << ','; + dump_text(output, *iter); + } +} + +template +void dump_array_bin(std::ostream & output, const std::vector & array, const context & ctxt) { + dump(output, static_cast(array.size()), ctxt); + for (const auto & element : array) + dump(output, element, ctxt); +} + +template +void dump_array(std::ostream & output, const std::vector & array, const context & ctxt) { + if (ctxt.format == context::formats::mojangson) + dump_array_text(output, array); + else + dump_array_bin(output, array, ctxt); +} + +template +void dump_list(std::ostream & output, tag_id aid, const std::vector & list, F action) { + const context & ctxt = context::get(output); + if (ctxt.format == context::formats::mojangson) { + output << '['; + } else { + output.put(static_cast(aid)); + dump(output, static_cast(list.size()), ctxt); + } + auto iter = list.cbegin(); + auto end = list.cend(); + if (iter != end) { + action(*iter, ctxt); + for (++iter; iter != end; ++iter) { + if (ctxt.format == context::formats::mojangson) + output << ','; + action(*iter, ctxt); + } + } + if (ctxt.format == context::formats::mojangson) + output << ']'; +} + +template +std::unique_ptr load_list(std::istream & input, F action) { + const context & ctxt = context::get(input); + auto ptr = std::make_unique(); + typename tag_type::value_type & result = ptr->value; + if (ctxt.format != context::formats::mojangson) { + auto size = load(input, ctxt); + result.reserve(size); + for (std::uint32_t i = 0; i < size; i++) + result.emplace_back(action(ctxt)); + } else { + for (;;) { + result.emplace_back(action(ctxt)); + skip_space(input); + int next = cheof(input); + if (next != ',') { + input.putback(next); + break; + } + } + } + return ptr; +} + +tag_id deduce_tag(std::istream & input); + +namespace tags { + + struct tag { + virtual tag_id id() const noexcept = 0; + virtual void write(std::ostream & output) const = 0; + virtual std::unique_ptr copy() const = 0; + virtual ~tag() {} + }; + + template + struct find_by; + + template + struct find_of; + + std::unique_ptr read(tag_id tid, std::istream & input); + + template + std::unique_ptr cast(std::unique_ptr && ptr) { + static_assert(std::is_base_of::value); + std::unique_ptr result(dynamic_cast(ptr.release())); + return result; + } + + template + std::unique_ptr cast(std::unique_ptr & ptr) { + static_assert(std::is_base_of::value); + std::unique_ptr result(dynamic_cast(ptr.release())); + return result; + } + +# define TAG_FIND(T) \ + template <> \ + struct find_by final { \ + typedef T type; \ + }; \ + template <> \ + struct find_of final { \ + typedef T type; \ + }; + + template + using tag_by = typename find_by::type; + + template + using tag_of = typename find_of::type; + + struct end_tag final : public tag { + typedef std::nullptr_t value_type; + static constexpr tag_id tid = tag_id::tag_end; + virtual tag_id id() const noexcept override { + return tid; + } + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + }; + inline end_tag end; + TAG_FIND(end_tag) + + template + struct numeric_tag : public tag { + typedef number_t value_type; + value_type value; + static constexpr tag_id tid = TID; + virtual tag_id id() const noexcept override { + return tid; + } + numeric_tag() = default; + constexpr numeric_tag(value_type number) : value(number) {} + static std::unique_ptr read(std::istream & input) { + return std::make_unique(load(input, context::get(input))); + } + virtual void write(std::ostream & output) const override { + dump(output, value, context::get(output)); + } + virtual std::unique_ptr copy() const override { + return std::make_unique(value); + } + }; + +# define NUMERIC_TAG(name, tid, number_t) \ + using name = numeric_tag; \ + TAG_FIND(name) + + NUMERIC_TAG(byte_tag, tag_id::tag_byte, std::int8_t) + NUMERIC_TAG(short_tag, tag_id::tag_short, std::int16_t) + NUMERIC_TAG(int_tag, tag_id::tag_int, std::int32_t) + NUMERIC_TAG(long_tag, tag_id::tag_long, std::int64_t) + NUMERIC_TAG(float_tag, tag_id::tag_float, float) + NUMERIC_TAG(double_tag, tag_id::tag_double, double) + +# undef NUMERIC_TAG + + template + struct array_tag final : public tag { + typedef number_t element_type; + typedef std::vector value_type; + value_type value; + static constexpr tag_id tid = TID; + virtual tag_id id() const noexcept override { + return tid; + } + array_tag() = default; + array_tag(const value_type & array) : value(array) {} + array_tag(value_type && array) : value(std::move(array)) {} + static std::unique_ptr read(std::istream & input) { + const context & ctxt = context::get(input); + if (ctxt.format == context::formats::mojangson) { + skip_space(input); + char a = cheof(input); + if (a != '[') + throw std::runtime_error("failed to open array tag"); + a = cheof(input); + if (a != prefix) + throw std::runtime_error("wrong array tag type"); + a = cheof(input); + if (a != ';') + throw std::runtime_error("unexpected symbol in array tag"); + } + auto result = std::make_unique(load_array(input, ctxt)); + if (ctxt.format == context::formats::mojangson) { + char a = cheof(input); + if (a != ']') + throw std::runtime_error("failed to close array tag"); + } + return result; + } + virtual void write(std::ostream & output) const override { + const context & ctxt = context::get(output); + if (ctxt.format == context::formats::mojangson) + output << '[' << prefix << ';'; + dump_array(output, value, ctxt); + if (ctxt.format == context::formats::mojangson) + output << ']'; + } + virtual std::unique_ptr copy() const override { + return std::make_unique(value); + } + }; + +# define ARRAY_TAG(name, tid, number_t, prefix) \ + using name = array_tag; \ + TAG_FIND(name) + + ARRAY_TAG(bytearray_tag, tag_id::tag_bytearray, std::int8_t, 'B') + ARRAY_TAG(intarray_tag, tag_id::tag_intarray, std::int32_t, 'I') + ARRAY_TAG(longarray_tag, tag_id::tag_longarray, std::int64_t, 'L') + +# undef ARRAY_TAG + + struct string_tag final : public tag { + typedef std::string value_type; + value_type value; + static constexpr tag_id tid = tag_id::tag_string; + virtual tag_id id() const noexcept override { + return tag_id::tag_string; + } + string_tag() = default; + string_tag(const value_type & string); + string_tag(value_type && string); + static std::unique_ptr read(std::istream & input); + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + }; + + TAG_FIND(string_tag) + + struct tag_list_tag; + + struct list_tag : public tag { + static constexpr tag_id tid = tag_id::tag_list; + virtual tag_id id() const noexcept override { + return tag_id::tag_list; + } + virtual tag_id element_id() const noexcept = 0; + virtual size_t size() const noexcept = 0; + virtual bool heavy() const noexcept = 0; + virtual std::unique_ptr operator[](size_t i) const = 0; + static std::unique_ptr read(std::istream & input); + virtual tag_list_tag as_tags() = 0; + }; + + template <> + struct find_by final { + typedef list_tag type; + }; + + struct tag_list_tag final : public list_tag { + tag_id eid; + typedef std::unique_ptr element_type; + typedef std::vector value_type; + value_type value; + virtual tag_id element_id() const noexcept override { + return value.empty() ? eid : (eid != tag_id::tag_end ? eid : value[0]->id()); + } + virtual size_t size() const noexcept override { + return value.size(); + } + virtual bool heavy() const noexcept override { + return true; + } + virtual std::unique_ptr operator[](size_t i) const override; + tag_list_tag(); + explicit tag_list_tag(tag_id tid); + tag_list_tag(const tag_list_tag & other); + tag_list_tag(const value_type & list, tag_id tid = tag_id::tag_end); + tag_list_tag(value_type && list, tag_id tid = tag_id::tag_end); + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + virtual tag_list_tag as_tags() override; + }; + + template + struct find_list_by; + + template + struct find_list_of; + + template + using list_by = typename find_list_by::type; + + template + using list_of = typename find_list_of::type; + +# define FIND_LIST_TAG(name) \ + template <> \ + struct find_list_by { \ + typedef name type; \ + }; \ + template <> \ + struct find_list_of { \ + typedef name type; \ + }; + + struct end_list_tag final : public list_tag { + typedef end_tag tag_type; + static_assert(std::is_base_of::value); + typedef typename end_tag::value_type element_type; + typedef std::vector value_type; + static constexpr tag_id eid = tag_type::tid; + virtual tag_id element_id() const noexcept override { + return eid; + } + virtual size_t size() const noexcept override { + return 0u; + } + virtual bool heavy() const noexcept override { + return false; + } + virtual std::unique_ptr operator[](size_t i) const override; + end_list_tag() = default; + static std::unique_ptr read_content(std::istream & input); + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + virtual tag_list_tag as_tags() override; + }; + inline end_list_tag end_list; + + FIND_LIST_TAG(end_list_tag) + + template + struct number_list_tag final : public list_tag { + typedef T tag_type; + static_assert(std::is_base_of::value); + typedef typename T::value_type element_type; + typedef std::vector value_type; + static constexpr tag_id eid = tag_type::tid; + virtual tag_id element_id() const noexcept override { + return eid; + } + value_type value; + virtual size_t size() const noexcept override { + return value.size(); + } + virtual bool heavy() const noexcept override { + return false; + } + virtual std::unique_ptr operator[](size_t i) const override { + return std::make_unique(value.at(i)); + } + number_list_tag() = default; + number_list_tag(const value_type & list) : value(list) {} + number_list_tag(value_type && list) : value(std::move(list)) {} + static std::unique_ptr read_content(std::istream & input) { + return load_list(input, [&input](const context & ctxt) -> element_type { + return load(input, ctxt); + }); + } + virtual void write(std::ostream & output) const override { + dump_list(output, eid, value, [&output](const element_type & number, const context & ctxt) { + dump(output, number, ctxt); + }); + } + virtual std::unique_ptr copy() const override { + return std::make_unique(*this); + } + virtual tag_list_tag as_tags() override { + tag_list_tag result(eid); + result.value.reserve(value.size()); + for (auto each : value) + result.value.push_back(std::make_unique(each)); + value.clear(); + value.shrink_to_fit(); + return result; + } + }; + +# define NUMBER_LIST_TAG(name, tag_type) \ + using name = number_list_tag; \ + FIND_LIST_TAG(name) + + NUMBER_LIST_TAG(byte_list_tag, byte_tag) + NUMBER_LIST_TAG(short_list_tag, short_tag) + NUMBER_LIST_TAG(int_list_tag, int_tag) + NUMBER_LIST_TAG(long_list_tag, long_tag) + NUMBER_LIST_TAG(float_list_tag, float_tag) + NUMBER_LIST_TAG(double_list_tag, double_tag) + +# undef NUMBER_LIST_TAG + + template + struct array_list_tag final : public list_tag { + typedef T tag_type; + static_assert(std::is_base_of::value); + typedef typename T::value_type element_type; + typedef std::vector value_type; + static constexpr tag_id eid = tag_type::tid; + virtual tag_id element_id() const noexcept override { + return eid; + } + value_type value; + virtual size_t size() const noexcept override { + return value.size(); + } + virtual bool heavy() const noexcept override { + return false; + } + virtual std::unique_ptr operator[](size_t i) const override { + return std::make_unique(value.at(i)); + } + array_list_tag() = default; + array_list_tag(const value_type & list) : value(list) {} + array_list_tag(value_type && list) : value(std::move(list)) {} + static std::unique_ptr read_content(std::istream & input) { + return load_list(input, [&input] (const context & ctxt) -> element_type { + return load_array(input, ctxt); + }); + } + virtual void write(std::ostream & output) const override { + dump_list(output, eid, value, [&output] + (const element_type & element, const context & ctxt) { + dump_array(output, element, ctxt); + }); + } + virtual std::unique_ptr copy() const override { + return std::make_unique(*this); + } + virtual tag_list_tag as_tags() override { + tag_list_tag result(eid); + result.value.reserve(value.size()); + for (auto & each : value) + result.value.push_back(std::make_unique(std::move(each))); + value.clear(); + value.shrink_to_fit(); + return result; + } + }; +# define ARRAY_LIST_TAG(name, tag_type) \ + using name = array_list_tag; \ + FIND_LIST_TAG(name) + + ARRAY_LIST_TAG(bytearray_list_tag, bytearray_tag) + ARRAY_LIST_TAG(intarray_list_tag, intarray_tag) + ARRAY_LIST_TAG(longarray_list_tag, longarray_tag) + +# undef ARRAY_LIST_TAG + + struct string_list_tag final : public list_tag { + typedef string_tag tag_type; + typedef std::string element_type; + typedef std::vector value_type; + static constexpr tag_id eid = tag_type::tid; + virtual tag_id element_id() const noexcept override { + return eid; + } + value_type value; + virtual size_t size() const noexcept override { + return value.size(); + } + virtual bool heavy() const noexcept override { + return false; + } + virtual std::unique_ptr operator[](size_t i) const override; + string_list_tag() = default; + string_list_tag(const value_type & list); + string_list_tag(value_type && list); + static std::unique_ptr read_content(std::istream & input); + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + virtual tag_list_tag as_tags() override; + }; + + FIND_LIST_TAG(string_list_tag) + + struct list_list_tag final : public list_tag { + typedef list_tag tag_type; + typedef std::unique_ptr element_type; + typedef std::vector value_type; + static constexpr tag_id eid = tag_type::tid; + virtual tag_id element_id() const noexcept override { + return eid; + } + value_type value; + virtual size_t size() const noexcept override { + return value.size(); + } + virtual bool heavy() const noexcept override { + return false; + } + virtual std::unique_ptr operator[](size_t i) const override; + list_list_tag() = default; + list_list_tag(const list_list_tag & other); + list_list_tag(const value_type & list); + list_list_tag(value_type && list); + static std::unique_ptr read_content(std::istream & input); + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + virtual tag_list_tag as_tags() override; + }; + + FIND_LIST_TAG(list_list_tag) + + struct compound_tag final : public tag { + typedef std::map> value_type; + bool is_root = false; + value_type value; + static constexpr tag_id tid = tag_id::tag_compound; + virtual tag_id id() const noexcept override { + return tag_id::tag_compound; + } + compound_tag() = default; + compound_tag(const compound_tag & other); + explicit compound_tag(bool root); + compound_tag(const value_type & map, bool root = false); + compound_tag(value_type && map, bool root = false); + static std::unique_ptr read(std::istream & input); + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + compound_tag & operator=(const compound_tag & other); + void make_heavy(); + + template + auto put(std::string && name, T && item) { + return value.insert_or_assign(std::move(name), std::make_unique>(std::move(item))); + } + + template + auto put(const std::string & name, T && item) { + return value.insert_or_assign(name, std::make_unique>(item)); + } + + template + typename tag_of::value_type & get(const std::string & name) { + return dynamic_cast &>(*value.at(name)).value; + } + + template + const typename tag_of::value_type & get(const std::string & name) const { + return dynamic_cast &>(*value.at(name)).value; + } + + template + typename tag_of::value_type & tag(const std::string & name) { + auto iter = value.find(name); + if (iter == value.end()) { + auto ptr = std::make_unique>(); + typename tag_of::value_type & result = *ptr->value; + value.insert(name, std::move(ptr)); + return result; + } else { + return dynamic_cast &>(*iter->second).value; + } + } + + bool erase(const std::string & name); + }; + + TAG_FIND(compound_tag) + + struct compound_list_tag final : public list_tag { + typedef compound_tag tag_type; + typedef tag_type element_type; + typedef std::vector value_type; + static constexpr tag_id eid = tag_type::tid; + virtual tag_id element_id() const noexcept override { + return eid; + } + value_type value; + virtual size_t size() const noexcept override { + return value.size(); + } + virtual bool heavy() const noexcept override { + return false; + } + virtual std::unique_ptr operator[](size_t i) const override; + compound_list_tag() = default; + compound_list_tag(const value_type & list); + compound_list_tag(value_type && list); + static std::unique_ptr read_content(std::istream & input); + virtual void write(std::ostream & output) const override; + virtual std::unique_ptr copy() const override; + virtual tag_list_tag as_tags() override; + }; + + FIND_LIST_TAG(compound_list_tag) + +# undef FIND_LIST_TAG +# undef TAG_FIND + + + template + tag_of wrap(T && value) { + return tag_of(value); + } + + template + std::unique_ptr read(std::istream & input) { + return tag_by::read(input); + } + +} // namespace tags + +std::istream & operator>>(std::istream & input, tags::compound_tag & compound); +std::ostream & operator<<(std::ostream & output, const tags::tag & tag); + +} // namespace nbt + +#endif // NBT_HEAD_BNTYGTFREQXCPVMFFG diff --git a/meson.build b/meson.build index 4debcd0..4cf8a29 100644 --- a/meson.build +++ b/meson.build @@ -1,10 +1,10 @@ -project('cpp-meson', 'cpp', +project('nbt-cpp', 'cpp', version : run_command('git', 'describe', '--abbrev=0', '--tags').stdout().strip(), default_options : ['warning_level=3', 'cpp_std=c++17']) -group = 'com.handtruth' -maintainer = 'someone ' +group = 'com.handtruth.mc' +maintainer = 'ktlo ' modules = [ ] diff --git a/src/config.cpp.in b/src/config.cpp.in deleted file mode 100644 index 7443dae..0000000 --- a/src/config.cpp.in +++ /dev/null @@ -1,10 +0,0 @@ -#include "config.hpp" - -namespace config { - const std::string group = "@group@"; - const std::string version = "@version@"; - const std::string project = "@project@"; - - const std::string build = "@build@"; - const std::string maintainer = "@maintainer@"; -} diff --git a/src/config.hpp b/src/config.hpp deleted file mode 100644 index 98c972d..0000000 --- a/src/config.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -namespace config { - extern const std::string group; - extern const std::string version; - extern const std::string project; - - extern const std::string build; - extern const std::string maintainer; -} diff --git a/src/meson.build b/src/meson.build index 1745a57..f3bb1ab 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,16 +1,9 @@ sources = files([ - 'sample_source.cpp' + 'nbt.cpp' ]) src = include_directories('.') -sources += configure_file(output : 'config.cpp', input : 'config.cpp.in', - configuration : { - 'group' : group, - 'project' : meson.project_name(), - 'version' : meson.project_version(), - 'build' : run_command('git', 'describe').stdout().strip(), - 'maintainer' : maintainer, -}) - lib = library(meson.project_name(), sources, include_directories : includes, install: true, dependencies: module_deps) + +nbtc = executable('nbtc', ['nbtc.cpp'], include_directories : includes, link_with : lib, install : true, dependencies : [module_deps]) diff --git a/src/nbt.cpp b/src/nbt.cpp new file mode 100644 index 0000000..3f98ca0 --- /dev/null +++ b/src/nbt.cpp @@ -0,0 +1,869 @@ +#include "nbt.hpp" + +#include +#include +#include + +namespace nbt { + +void skip_space(std::istream & input) { + for (;;) { + int next = cheof(input); + if (!std::isspace(next)) { + input.putback(next); + return; + } + } +} + +int context_id() { + static const int i = std::ios_base::xalloc(); + return i; +} + +context *& context_storage(std::ios_base & ios) { + int i = context_id(); + return reinterpret_cast(ios.pword(i)); +} + +const context & context::get(std::ios_base & ios) { + auto ctxt = context_storage(ios); + if (ctxt == nullptr) + return contexts::java; + else + return *ctxt; +} + +void ios_callback(std::ios_base::event event, std::ios_base & ios, int) { + if (event == std::ios_base::event::erase_event) + delete context_storage(ios); +} + +void context::set(std::ios_base & ios) const { + context *& ctxt = context_storage(ios); + if (ctxt == nullptr) { + ios.register_callback(ios_callback, context_id()); + ctxt = new context(*this); + } else { + *ctxt = *this; + } +} + +template +constexpr std::size_t varnum_max() { + return sizeof(number_t) * 8 / 7 + 1; +} + +template +number_t load_varnum(std::istream & input) { + std::size_t numRead = 0; + number_t value = 0; + int read; + do { + read = cheof(input); + number_t tmp = static_cast(read & 0b01111111); + value |= (tmp << (7 * numRead)); + numRead++; + if (numRead > varnum_max()) + throw std::runtime_error("varint is too big"); + } while ((read & 0b10000000) != 0); + return value; +} + +template +void dump_varnum(std::ostream & output, number_t value) { + std::make_unsigned_t uval = value; + do { + auto temp = static_cast(uval & 0b01111111u); + uval >>= 7; + if (uval != 0) + temp |= 0b10000000; + output.put(temp); + } while (uval != 0); +} + +std::int32_t load_varint(std::istream & input) { + return load_varnum(input); +} + +void dump_varint(std::ostream & output, std::int32_t value) { + dump_varnum(output, value); +} + +std::int64_t load_varlong(std::istream & input) { + return load_varnum(input); +} + +void dump_varlong(std::ostream & output, std::int64_t value) { + dump_varnum(output, value); +} + +template +number_t load_text_simple(std::istream & input) { + number_t value; + input >> value; + int next = cheof(input); + if (next != suffix) + input.putback(next); + return value; +} + +template <> +std::int8_t load_text(std::istream & input) { + int value; + input >> value; + int next = cheof(input); + if (next != 'b') + input.putback(next); + return static_cast(value); +} + +template <> +std::int16_t load_text(std::istream & input) { + return load_text_simple(input); +} + +template <> +std::int32_t load_text(std::istream & input) { + std::int32_t value; + input >> value; + return value; +} + +template <> +std::int64_t load_text(std::istream & input) { + return load_text_simple(input); +} + +template <> +float load_text(std::istream & input) { + return load_text_simple(input); +} + +template <> +double load_text(std::istream & input) { + return load_text_simple(input); +} + +template <> +std::int32_t load(std::istream & input, const context & ctxt) { + switch (ctxt.format) { + case context::formats::bin: + return load_flat(input, ctxt.order); + case context::formats::zigzag: + return load_varint(input); + case context::formats::mojangson: + default: + return load_text(input); + } +} + +template <> +std::int64_t load(std::istream & input, const context & ctxt) { + switch (ctxt.format) { + case context::formats::bin: + return load_flat(input, ctxt.order); + case context::formats::zigzag: + return load_varlong(input); + case context::formats::mojangson: + default: + return load_text(input); + } +} + +template <> +void dump(std::ostream & output, std::int32_t number, const context & ctxt) { + switch (ctxt.format) { + case context::formats::bin: + return dump_flat(output, number, ctxt.order); + case context::formats::zigzag: + return dump_varint(output, number); + case context::formats::mojangson: + default: + return dump_text(output, number); + } +} + +template <> +void dump(std::ostream & output, std::int64_t number, const context & ctxt) { + switch (ctxt.format) { + case context::formats::bin: + return dump_flat(output, number, ctxt.order); + case context::formats::zigzag: + return dump_varlong(output, number); + case context::formats::mojangson: + default: + return dump_text(output, number); + } +} + +template <> +void dump_text(std::ostream & output, std::int8_t number) { + output << int(number) << 'b'; +} + +template <> +void dump_text(std::ostream & output, std::int16_t number) { + output << number << 's'; +} + +template <> +void dump_text(std::ostream & output, std::int32_t number) { + output << number; +} + +template <> +void dump_text(std::ostream & output, std::int64_t number) { + output << number << 'l'; +} + +template <> +void dump_text(std::ostream & output, float number) { + output << number << 'f'; +} + +template <> +void dump_text(std::ostream & output, double number) { + output << number; +} + +std::ostream & operator<<(std::ostream & output, tag_id tid) { + return output << std::to_string(tid); +} + +inline bool is_valid_char(char c) { + return std::isalnum(c) || c == '-' || c == '_' || c == '+' || c == '.'; +} + +tag_id deduce_tag(std::istream & input) { + skip_space(input); + char a = cheof(input); + if (a == '[') { + char b = cheof(input); + tag_id id; + switch (b) { + case 'B': + id = tag_id::tag_bytearray; break; + case 'I': + id = tag_id::tag_intarray; break; + case 'L': + id = tag_id::tag_longarray; break; + default: + id = tag_id::tag_list; break; + } + if (id != tag_id::tag_list) { + char c = input.peek(); + if (c != ';') + id = tag_id::tag_list; + } + input.putback(b); + input.putback(a); + return id; + } + if (a == '{') { + input.putback(a); + return tag_id::tag_compound; + } + if (std::isdigit(a) || a == '-' || a == '+') { + std::string buffer(&a, 1); + tag_id deduced; + for (;;) { + char b = cheof(input); + buffer.push_back(b); + if (std::isdigit(b)) { + continue; + } else if (b == 'b') { + deduced = tag_id::tag_byte; + } else if (b == 's') { + deduced = tag_id::tag_short; + } else if (b == 'l') { + deduced = tag_id::tag_long; + } else if (b == 'f') { + deduced = tag_id::tag_float; + } else if (b == 'd') { + deduced = tag_id::tag_double; + } else if (b == '.') { + for (;;) { + char c = cheof(input); + buffer.push_back(c); + if (std::isdigit(c)) { + continue; + } else if (c == 'e' || c == 'E') { + char d = cheof(input); + buffer.push_back(d); + if (std::isdigit(a) || a == '-' || a == '+') + continue; + } else if (c == 'f') { + deduced = tag_id::tag_float; + } else if (c == 'd') { + deduced = tag_id::tag_double; + } else { + deduced = tag_id::tag_double; + } + break; + } + } else { + deduced = tag_id::tag_int; + } + break; + } + for (auto iter = buffer.crbegin(), end = buffer.crend(); iter != end; ++iter) + input.putback(*iter); + return deduced; + } + if (a == '"' || is_valid_char(a)) { + input.putback(a); + return tag_id::tag_string; + } + input.putback(a); + return tag_id::tag_end; +} + +namespace tags { + +std::string read_string_text(std::istream & input) { + skip_space(input); + char first = cheof(input); + if (first == '"') { + std::string result; + for (;;) { + int c = cheof(input); + switch (c) { + case '"': + return result; + case '\\': { + int s = cheof(input); + switch (s) { + case '"': + result.push_back('"'); break; + case '\\': + result.push_back('\\'); break; + case '/': + result.push_back('/'); break; + case 'b': + result.push_back('\b'); break; + case 'f': + result.push_back('\f'); break; + case 'n': + result.push_back('\n'); break; + case 'r': + result.push_back('\r'); break; + case 't': + result.push_back('\t'); break; + case 'u': + throw std::runtime_error("TODO"); + default: + result.push_back('\\'); + result.push_back(s); + break; + } + break; + } + default: + result.push_back(c); + break; + } + } + } else { + std::string result(&first, 1); + for (;;) { + int c = cheof(input); + if (is_valid_char(c)) { + result.push_back(c); + } else { + input.putback(c); + return result; + } + } + } +} + +std::string read_string_bin(std::istream & input, const context & ctxt) { + auto size = ctxt.format == context::formats::zigzag ? + load_varint(input) : load(input, ctxt); + std::string result; + result.resize(size); + input.read(result.data(), size); + return result; +} + +std::string read_string(std::istream & input, const context & ctxt) { + if (ctxt.format == context::formats::mojangson) + return read_string_text(input); + else + return read_string_bin(input, ctxt); +} + +void write_string_text(std::ostream & output, const std::string & string) { + output << '"'; + for (char c : string) { + switch (c) { + case '"': + output << "\\\""; break; + case '\\': + output << "\\\\"; break; + case '\b': + output << "\\b"; break; + case '\f': + output << "\\f"; break; + case '\n': + output << "\\n"; break; + case '\r': + output << "\\r"; break; + case '\t': + output << "\\t"; break; + default: + output << c; + } + } + output << '"'; +} + +void write_string(std::ostream & output, const std::string & string, const context & ctxt) { + switch (ctxt.format) { + case context::formats::bin: + dump(output, static_cast(string.size()), ctxt); break; + case context::formats::zigzag: + dump_varint(output, static_cast(string.size())); break; + case context::formats::mojangson: + default: + write_string_text(output, string); return; + } + output << string; +} + +template +std::unique_ptr read_bridge(tag_id id, std::istream & input) { + if (id == tid) + return read(input); + else + return read_bridge(static_cast(tid) - 1)>(id, input); +} + +template <> +std::unique_ptr read_bridge(tag_id, std::istream &) { + throw std::runtime_error("end tag not for reading"); +} + +std::unique_ptr read(tag_id tid, std::istream & input) { + return read_bridge(tid, input); +} + +template +std::unique_ptr read_list_content_bridge(tag_id id, std::istream & input) { + if (id == tid) + return list_by::read_content(input); + else + return read_list_content_bridge(static_cast(tid) - 1)>(id, input); +} + +template <> +std::unique_ptr read_list_content_bridge(tag_id id, std::istream & input) { + assert(id == tag_id::tag_end); + return list_by::read_content(input); +} + +std::unique_ptr read_list_content(tag_id tid, std::istream & input) { + return read_list_content_bridge(tid, input); +} + +void end_tag::write(std::ostream & output) const { + output.put('\0'); +} + +std::unique_ptr end_tag::copy() const { + return std::make_unique(); +} + +string_tag::string_tag(const value_type & string) : value(string) {} + +string_tag::string_tag(value_type && string) : value(std::move(string)) {} + +std::unique_ptr string_tag::read(std::istream & input) { + const context & ctxt = context::get(input); + return std::make_unique(read_string(input, ctxt)); +} + +void string_tag::write(std::ostream & output) const { + const context & ctxt = context::get(output); + write_string(output, value, ctxt); +} + +std::unique_ptr string_tag::copy() const { + return std::make_unique(value); +} + +std::unique_ptr list_tag::read(std::istream & input) { + const context & ctxt = context::get(input); + if (ctxt.format == context::formats::mojangson) { + skip_space(input); + char a = cheof(input); + if (a != '[') + throw std::runtime_error("failed to open list tag"); + tag_id type = deduce_tag(input); + auto result = read_list_content(type, input); + skip_space(input); + a = cheof(input); + if (a != ']') + throw std::runtime_error(std::string("failed to close list tag, got: ") + a); + return result; + } else { + tag_id type = static_cast(cheof(input)); + return read_list_content(type, input); + } +} + +std::unique_ptr tag_list_tag::operator[](size_t i) const { + return value.at(i)->copy(); +} + +tag_list_tag::tag_list_tag() : eid(tag_id::tag_end) {} + +tag_list_tag::tag_list_tag(tag_id tid) : eid(tid) {} + +tag_list_tag::tag_list_tag(const tag_list_tag & other) : eid(other.eid) { + value.reserve(other.size()); + for (const auto & each : other.value) + value.emplace_back(each->copy()); +} + +tag_list_tag::tag_list_tag(const value_type & list, tag_id tid) : eid(tid) { + value.reserve(list.size()); + for (const auto & each : list) + value.emplace_back(each->copy()); +} + +tag_list_tag::tag_list_tag(value_type && list, tag_id tid) : eid(tid), value(std::move(list)) {} + +void tag_list_tag::write(std::ostream & output) const { + tag_id aid = element_id(); + dump_list(output, aid, value, [&output, aid](const std::unique_ptr & tag, const context &) { + if (aid != tag->id()) + throw std::runtime_error("wrong tag id"); + tag->write(output); + }); +} + +std::unique_ptr tag_list_tag::copy() const { + return std::make_unique(*this); +} + +tag_list_tag tag_list_tag::as_tags() { + return std::move(*this); +} + +std::unique_ptr end_list_tag::operator[](size_t) const { + throw std::out_of_range("end list tag always empty"); +} + +std::unique_ptr end_list_tag::read_content(std::istream & input) { + if (context::get(input).format != context::formats::mojangson) { + char c = cheof(input); + if (c != '\0') + throw std::runtime_error("end list tag should be empty"); + } + return std::make_unique(); +} + +void end_list_tag::write(std::ostream & output) const { + if (context::get(output).format == context::formats::mojangson) { + output << "[]"; + } else { + output.put(static_cast(eid)); + output.put('\0'); + } +} + +std::unique_ptr end_list_tag::copy() const { + return std::make_unique(); +} + +tag_list_tag end_list_tag::as_tags() { + return tag_list_tag(tag_id::tag_end); +} + +std::unique_ptr string_list_tag::operator[](size_t i) const { + return std::make_unique(value.at(i)); +} + +string_list_tag::string_list_tag(const value_type & list) : value(list) {} + +string_list_tag::string_list_tag(value_type && list) : value(std::move(list)) {} + +std::unique_ptr string_list_tag::read_content(std::istream & input) { + return load_list(input, [&input](const context & ctxt) -> std::string { + return read_string(input, ctxt); + }); +} + +void string_list_tag::write(std::ostream & output) const { + tag_id aid = element_id(); + dump_list(output, aid, value, [&output](const std::string & string, const context & ctxt) { + write_string(output, string, ctxt); + }); +} + +std::unique_ptr string_list_tag::copy() const { + return std::make_unique(*this); +} + +tag_list_tag string_list_tag::as_tags() { + tag_list_tag result(eid); + result.value.reserve(value.size()); + for (auto & each : value) + result.value.push_back(std::make_unique(std::move(each))); + value.clear(); + value.shrink_to_fit(); + return result; +} + +std::unique_ptr list_list_tag::operator[](size_t i) const { + return value.at(i)->copy(); +} + +list_list_tag::list_list_tag(const list_list_tag & other) { + value.reserve(other.value.size()); + for (const auto & element : other.value) + value.emplace_back(cast(element->copy())); +} + +list_list_tag::list_list_tag(const value_type & list) { + value.reserve(list.size()); + for (const auto & element : list) + value.emplace_back(cast(element->copy())); +} + +list_list_tag::list_list_tag(value_type && list) : value(std::move(list)) {} + +std::unique_ptr list_list_tag::read_content(std::istream & input) { + return load_list(input, [&input](const context &) -> element_type { + return list_tag::read(input); + }); +} + +void list_list_tag::write(std::ostream & output) const { + dump_list(output, eid, value, [&output](const element_type & list, const context &) { + list->write(output); + }); +} + +std::unique_ptr list_list_tag::copy() const { + return std::make_unique(*this); +} + +tag_list_tag list_list_tag::as_tags() { + tag_list_tag result(eid); + result.value.reserve(value.size()); + for (auto & each : value) + result.value.push_back(std::move(each)); + value.clear(); + value.shrink_to_fit(); + return result; +} + +compound_tag::compound_tag(const compound_tag & other) : is_root(other.is_root) { + for (const auto & pair : other.value) + value.emplace(pair.first, pair.second->copy()); +} + +compound_tag::compound_tag(bool root) : is_root(root) {} + +compound_tag::compound_tag(const value_type & map, bool root) : is_root(root) { + for (const auto & pair : map) + value.emplace(pair.first, pair.second->copy()); +} + +compound_tag::compound_tag(value_type && map, bool root) : is_root(root), value(std::move(map)) {} + +std::unique_ptr compound_tag::read(std::istream & input) { + auto ptr = std::make_unique(); + input >> *ptr; + return ptr; +} + +void compound_tag::write(std::ostream & output) const { + const context & ctxt = context::get(output); + if (ctxt.format == context::formats::mojangson) + output << '{'; + auto iter = value.cbegin(); + auto end = value.cend(); + if (iter != end) { + auto action = [&](const compound_tag::value_type::value_type & entry) { + if (ctxt.format != context::formats::mojangson) + output.put(static_cast(entry.second->id())); + write_string(output, entry.first, ctxt); + if (ctxt.format == context::formats::mojangson) + output << ':'; + entry.second->write(output); + }; + action(*iter); + for (++iter; iter != end; ++iter) { + if (ctxt.format == context::formats::mojangson) + output << ','; + action(*iter); + } + } + if (ctxt.format == context::formats::mojangson) { + output << '}'; + } else { + if (!is_root) + tags::end.write(output); + } +} + +std::unique_ptr compound_tag::copy() const { + return std::make_unique(*this); +} + +compound_tag & compound_tag::operator=(const compound_tag & other) { + is_root = other.is_root; + for (const auto & pair : other.value) + value.emplace(pair.first, pair.second->copy()); + return *this; +} + +void compound_tag::make_heavy() { + for (auto & pair : value) { + tags::tag & each = *pair.second; + if (each.id() == tag_id::tag_list) { + list_tag & list = dynamic_cast(each); + if (!list.heavy()) + pair.second = std::make_unique(list.as_tags()); + } + } +} + +std::unique_ptr compound_list_tag::operator[](size_t i) const { + return value.at(i).copy(); +} + +compound_list_tag::compound_list_tag(const value_type & list) : value(list) {} + +compound_list_tag::compound_list_tag(value_type && list) : value(std::move(list)) {} + +std::unique_ptr compound_list_tag::read_content(std::istream & input) { + return load_list(input, [&input](const context &) -> compound_tag { + compound_tag element; + input >> element; + return element; + }); +} + +void compound_list_tag::write(std::ostream & output) const { + dump_list(output, eid, value, [&output](const element_type & element, const context &) { + element.write(output); + }); +} + +std::unique_ptr compound_list_tag::copy() const { + return std::make_unique(*this); +} + +tag_list_tag compound_list_tag::as_tags() { + tag_list_tag result(eid); + result.value.reserve(value.size()); + for (auto & each : value) { + each.make_heavy(); + result.value.push_back(std::make_unique(std::move(each))); + } + value.clear(); + value.shrink_to_fit(); + return result; +} + +} // namespace tags + +void read_compound_text(std::istream & input, tags::compound_tag & compound, const context & ctxt) { + skip_space(input); + char a = cheof(input); + if (a != '{') + throw std::runtime_error("failed to open compound"); + for (;;) { + tag_id key_type = deduce_tag(input); + switch (key_type) { + case tag_id::tag_end: + return; + case tag_id::tag_string: + break; + default: + throw std::runtime_error( + "invalid key tag (tag_string expected, got " + std::to_string(key_type) + ')' + ); + } + std::string key = tags::read_string(input, ctxt); + skip_space(input); + char a = cheof(input); + if (a != ':') + throw std::runtime_error(std::string("key-value delimiter expected, got ") + a); + tag_id value_type = deduce_tag(input); + if (value_type == tag_id::tag_end) + throw std::runtime_error(std::string("value expected")); + compound.value.emplace(std::move(key), tags::read(value_type, input)); + skip_space(input); + a = cheof(input); + if (a == '}') + return; + if (a != ',' && a != ';') + throw std::runtime_error(std::string("next tag or end expected, got ") + a); + } +} + +void read_compound_bin(std::istream & input, tags::compound_tag & compound, const context & ctxt) { + for (;;) { + auto id_numeric = input.get(); + if (!input) { + compound.is_root = true; + break; + } + auto id = static_cast(id_numeric); + if (id > tag_id::tag_longarray) + throw std::out_of_range("unknown tag id: " + std::to_string(id_numeric)); + if (id == tag_id::tag_end) + break; + std::string key = tags::read_string(input, ctxt); + compound.value.emplace(std::move(key), tags::read(id, input)); + } +} + +std::istream & operator>>(std::istream & input, tags::compound_tag & compound) { + const context & ctxt = context::get(input); + if (ctxt.format == context::formats::mojangson) + read_compound_text(input, compound, ctxt); + else + read_compound_bin(input, compound, ctxt); + return input; +} + +std::ostream & operator<<(std::ostream & output, const tags::tag & tag) { + tag.write(output); + return output; +} + +} // namespace nbt + +namespace std { + +string to_string(nbt::tag_id tid) { +# define TAG_ID_CASE(name) \ + case nbt::tag_id::name: return #name + switch (tid) { + TAG_ID_CASE(tag_end); + TAG_ID_CASE(tag_byte); + TAG_ID_CASE(tag_short); + TAG_ID_CASE(tag_int); + TAG_ID_CASE(tag_long); + TAG_ID_CASE(tag_float); + TAG_ID_CASE(tag_double); + TAG_ID_CASE(tag_bytearray); + TAG_ID_CASE(tag_string); + TAG_ID_CASE(tag_list); + TAG_ID_CASE(tag_compound); + TAG_ID_CASE(tag_intarray); + TAG_ID_CASE(tag_longarray); + default: + abort(); // unreachable + } +# undef TAG_ID_CASE +} + +} // namespace std diff --git a/src/nbtc.cpp b/src/nbtc.cpp new file mode 100644 index 0000000..1726baa --- /dev/null +++ b/src/nbtc.cpp @@ -0,0 +1,87 @@ +#include +#include +#include + +const std::map contexts_by_name { + { "java", nbt::contexts::java }, + { "bedrock-net", nbt::contexts::bedrock_net }, + { "bedrock", nbt::contexts::bedrock_disk }, + { "bedrock-disk", nbt::contexts::bedrock_disk }, + { "kbt", nbt::contexts::kbt }, + { "mojangson", nbt::contexts::mojangson } +}; + +void usage(std::ostream & output, const char * program) { + output << "Usage: " << program << R"===( + +Parameters: + FROM read stdin as FROM NBT format + TO write NBT tag to stdout as TO format +NBT formats: + java Minecraft Java Edition NBT + bedrock Minecraft Bedrock Edition storage NBT format + bedrock-disk + bedrock-net Minecraft Bedrock Edition network NBT format + mojangson text NBT representation + kbt handtruth NBT extension +)==="; +} + +int main(int argc, char ** argv) { + if (argc == 2 && std::string(argv[1]) == "--help") { + usage(std::cout, argv[0]); + return EXIT_SUCCESS; + } + if (argc != 3) { + std::cerr << "exactly 2 arguments required" << std::endl; + usage(std::cerr, argv[0]); + return EXIT_FAILURE; + } + auto iter_from = contexts_by_name.find(argv[1]); + auto iter_to = contexts_by_name.find(argv[2]); + if (iter_from == contexts_by_name.end()) { + std::cerr << "unknown FROM format: " << argv[1] << std::endl; + usage(std::cerr, argv[0]); + return EXIT_FAILURE; + } + if (iter_to == contexts_by_name.end()) { + std::cerr << "unknown TO format: " << argv[2] << std::endl; + usage(std::cerr, argv[0]); + return EXIT_FAILURE; + } + using namespace nbt; + const context & from = iter_from->second; + const context & to = iter_to->second; + std::unique_ptr root; + std::cin >> from; + std::cout << to; + try { + if (from.format == context::formats::mojangson) { + tag_id id = deduce_tag(std::cin); + std::unique_ptr tag = tags::read(id, std::cin); + if (tag->id() == tag_id::tag_compound) { + root = std::move(tag); + } else { + auto compound = std::make_unique(true); + compound->value[""] = std::move(tag); + root = std::move(compound); + } + } else { + root = tags::read(std::cin); + } + } catch (const std::exception & e) { + std::cerr + << "failed to read NBT (stream position " + << std::cin.tellg() << "): " << e.what() << std::endl; + return EXIT_FAILURE; + } + try { + std::cout << *root << std::endl; + } catch (const std::exception & e) { + std::cerr + << "failed to write NBT (stream position " + << std::cout.tellp() << "): " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/sample_source.cpp b/src/sample_source.cpp deleted file mode 100644 index 5977a32..0000000 --- a/src/sample_source.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "config.hpp" - -void sample_function() { - -} diff --git a/test/bedrock_level.dat b/test/bedrock_level.dat new file mode 100644 index 0000000000000000000000000000000000000000..f5240ecc271145fc1cf73bcc10079f31b39b7b72 GIT binary patch literal 1666 zcmZ`)O>ZMb5Ut*^Bis2TAHabFzhEVVkhtt&6UZu(lZ|2v!g6ZQlE0h)1y42@6+Y6-IrrYmsf4=?k{o9{zfB5s8AM~d4K&%%e7ibe8*3sUU>LY8ZoK zi$;~P8EVOmW2N>e^FEZY%Y}*Q6cO4T8r$WdK&8LG=s!{$Vj4Fg#5S&sx{e@_jhk0i zbTqO|s!P^PrqR_`Hz?Ojyd(91d_iglf7zwpMUA|nlv)Z7xhX|$Wvw3}B6*t{te5{d z6rHLLMYde{5UJgzg|@R5%omTOrz7vG@&q-+Pn}@O-gMT@66ZdpKF?9}$oh2Vt5{S~ zL0l_uxq7Wyo0L`|fcl|HXTQED0BaRyKGVy(+H{#p?c1Oc*Gyby!584U2gle1j(Dg* z4W#3d%_EQ8mF|diz?D*)a6|*R&C?y23)Uj)I=TXRII1saI){!Fh~TVhNK^01_f$vl zc#3CL;QInd9TMMwW+)BB+X*H{*cEtVZ6Sju8Z1D)x89+tE{~aAA5+iSnL7yxYvxFM zBu9O4#@R7X_1ySy39ZrV2UlGB3d^TC*ddeW6*ObH-T(s0l&8VxO_(9)Kon~VXUqj8 z1*q6Vs(VBhtL7wu@(qFyy(uSv5MH zLX4K(Bu?+{(AZ5vUnaruxbDjVIiXYAz>{WOf@0NaQ4vgr>j@R}LP3ZDVb)nYwJiz% z_#U?osAQL|GLA7+BQU0tL%747!^yOAGm#he%cNdl5Mnv?ixhL286e+# Df|2~z literal 0 HcmV?d00001 diff --git a/test/bigtest.nbt b/test/bigtest.nbt new file mode 100644 index 0000000000000000000000000000000000000000..a2021df7cb5ee7125965c18aaaf45fa60a3e2759 GIT binary patch literal 1544 zcmeHHUr*Xl6hA_N(lIcO7-Eb$bB?sEYYIao#vwXzXc{!os4<4v%B7&CHEnV6X)oL3 zvbTMiJ@Og$yw`mTb}8Uqw&%TYbCYxK@BHrhcmHq@Qpi0q8DLG@GF0MvA4iiBfT-KG z9dClMz`35&n@~WgRTbqJ&yE{P1=ZSN6Y1PTwc1p%sWuMl?{WBf_-XjG*e@df_D&i}gqPGPqU&jnM^6kt^t>)wZ)M~uTF8m` zCNE;PAYxHONsx(S6EGkO}$eh+sF5}WUA2=$?xstlL&DF$}r6d+kS5GqW ztMiLMs;K8;>i+glt+i4~HVPZ7Ynk#@`XZkS>kwPtXLpucO68!zY}D5-%Tk)pABE+4 fjCrr-_v-(?k1%j;d!U>C53~F8>+Sc^O$&Yl_JmkZ literal 0 HcmV?d00001 diff --git a/test/meson.build b/test/meson.build index a4660f8..7f4afe0 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,5 +1,5 @@ test_names = [ - 'sample_test' + 'nbt' ] test_files = [] @@ -7,5 +7,5 @@ test_files = [] foreach test_name : test_names test_files += files(test_name + '.cpp') test_exe = executable(test_name + '.test', test_files[-1], link_with : lib, include_directories : [includes, src], dependencies : module_deps) - test(test_name, test_exe, suite : 'regular') + test(test_name, test_exe, suite : 'regular', workdir : meson.current_source_dir()) endforeach diff --git a/test/nbt.cpp b/test/nbt.cpp new file mode 100644 index 0000000..2a9a97c --- /dev/null +++ b/test/nbt.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include "nbt.hpp" + +#include "test.hpp" + +std::string read_content(const std::string & filename) { + std::ifstream file(filename); + return std::string((std::istreambuf_iterator(file)),std::istreambuf_iterator()); +} + +template +void test_for(const std::string & filename, const nbt::context & ctxt, const std::string & tagname) { + using namespace nbt; + std::string content = read_content(filename); + std::stringstream input(content); + std::array header; + input.read(header.data(), header_size); + tags::compound_tag root; + input >> ctxt >> root; + assert_equals(1u, root.value.size()); + //const auto & values = root.get>>(tagname); + //for (const auto & each : values) + // std::cerr << each.first << ' ' << each.second->id() << std::endl; + std::stringstream buffer; + //output.write(header.data(), header_size); + //output << ctxt << root; + std::cout << contexts::mojangson << root << std::endl; + buffer << contexts::mojangson << root; + tags::compound_tag new_root(true); + try { + buffer >> contexts::mojangson >> new_root; + } catch (...) { + std::cerr << "POSITION: " << buffer.tellg() << std::endl; + throw; + } + std::cout << contexts::mojangson << new_root; + //assert_equals(content, result); +} + +test { + using namespace nbt; + using namespace std::string_literals; + test_for<0>("bigtest.nbt", contexts::java, "Level"); + test_for<8>("bedrock_level.dat", contexts::bedrock_disk, ""); +} diff --git a/test/sample_test.cpp b/test/sample_test.cpp deleted file mode 100644 index 906a0d4..0000000 --- a/test/sample_test.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "test.hpp" - -test { - assert_equals(2, 1 + 1); -}