NBT
This commit is contained in:
46
README.md
Normal file
46
README.md
Normal file
@@ -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 <nbt.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
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<nbt::tags::tag>` 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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
includes = include_directories('.')
|
includes = include_directories('.')
|
||||||
|
|
||||||
install_headers([
|
install_headers([
|
||||||
|
'nbt.hpp'
|
||||||
])
|
])
|
||||||
|
|||||||
918
include/nbt.hpp
Normal file
918
include/nbt.hpp
Normal file
@@ -0,0 +1,918 @@
|
|||||||
|
#ifndef NBT_HEAD_BNTYGTFREQXCPVMFFG
|
||||||
|
#define NBT_HEAD_BNTYGTFREQXCPVMFFG
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <ostream>
|
||||||
|
#include <istream>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
#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 <typename number_t>
|
||||||
|
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 <typename number_t>
|
||||||
|
number_t net_order(number_t number) {
|
||||||
|
#ifndef NBT_BIG_ENDIAN
|
||||||
|
return reverse(number);
|
||||||
|
#else
|
||||||
|
return number;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
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 <typename number_t>
|
||||||
|
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 <typename number_t>
|
||||||
|
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 <typename number_t>
|
||||||
|
number_t load_text(std::istream & input) {
|
||||||
|
return static_cast<number_t>(load_text<std::make_signed_t<number_t>>(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int8_t load_text<std::int8_t>(std::istream & input);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int16_t load_text<std::int16_t>(std::istream & input);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int32_t load_text<std::int32_t>(std::istream & input);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int64_t load_text<std::int64_t>(std::istream & input);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
float load_text<float>(std::istream & input);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
double load_text<double>(std::istream & input);
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
number_t load(std::istream & input, const context & ctxt) {
|
||||||
|
if (ctxt.format == context::formats::mojangson)
|
||||||
|
return load_text<number_t>(input);
|
||||||
|
else
|
||||||
|
return load_flat<number_t>(input, ctxt.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int32_t load<std::int32_t>(std::istream & input, const context & ctxt);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::uint32_t load<std::uint32_t>(std::istream & input, const context & ctxt) {
|
||||||
|
return static_cast<std::uint32_t>(load<std::int32_t>(input, ctxt));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int64_t load<std::int64_t>(std::istream & input, const context & ctxt);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::uint64_t load<std::uint64_t>(std::istream & input, const context & ctxt) {
|
||||||
|
return static_cast<std::uint64_t>(load<std::int64_t>(input, ctxt));
|
||||||
|
}
|
||||||
|
|
||||||
|
void skip_space(std::istream & input);
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
std::vector<number_t> load_array_text(std::istream & input) {
|
||||||
|
std::vector<number_t> result;
|
||||||
|
for (;;) {
|
||||||
|
result.push_back(load_text<number_t>(input));
|
||||||
|
skip_space(input);
|
||||||
|
char next = cheof(input);
|
||||||
|
if (next != ',') {
|
||||||
|
input.putback(next);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.shrink_to_fit();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
std::vector<number_t> load_array_bin(std::istream & input, const context & ctxt) {
|
||||||
|
auto size = load<std::uint32_t>(input, ctxt);
|
||||||
|
std::vector<number_t> result;
|
||||||
|
result.reserve(size);
|
||||||
|
for (std::uint32_t i = 0; i < size; i++)
|
||||||
|
result.emplace_back(load<number_t>(input, ctxt));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
std::vector<number_t> load_array(std::istream & input, const context & ctxt) {
|
||||||
|
if (ctxt.format == context::formats::mojangson)
|
||||||
|
return load_array_text<number_t>(input);
|
||||||
|
else
|
||||||
|
return load_array_bin<number_t>(input, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
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 <typename number_t>
|
||||||
|
void dump_text(std::ostream & output, number_t number) {
|
||||||
|
dump_text(output, std::make_signed_t<number_t>(number));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<std::int8_t>(std::ostream & output, std::int8_t number);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<std::int16_t>(std::ostream & output, std::int16_t number);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<std::int32_t>(std::ostream & output, std::int32_t number);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<std::int64_t>(std::ostream & output, std::int64_t number);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<float>(std::ostream & output, float number);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<double>(std::ostream & output, double number);
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
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::int32_t>(std::ostream & output, std::int32_t number, const context & ctxt);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void dump<std::uint32_t>(std::ostream & output, std::uint32_t number, const context & ctxt) {
|
||||||
|
dump(output, static_cast<std::int32_t>(number), ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump<std::int64_t>(std::ostream & output, std::int64_t number, const context & ctxt);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void dump<std::uint64_t>(std::ostream & output, std::uint64_t number, const context & ctxt) {
|
||||||
|
dump(output, static_cast<std::int64_t>(number), ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
void dump_array_text(std::ostream & output, const std::vector<number_t> & 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 <typename number_t>
|
||||||
|
void dump_array_bin(std::ostream & output, const std::vector<number_t> & array, const context & ctxt) {
|
||||||
|
dump(output, static_cast<std::uint32_t>(array.size()), ctxt);
|
||||||
|
for (const auto & element : array)
|
||||||
|
dump(output, element, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
void dump_array(std::ostream & output, const std::vector<number_t> & array, const context & ctxt) {
|
||||||
|
if (ctxt.format == context::formats::mojangson)
|
||||||
|
dump_array_text(output, array);
|
||||||
|
else
|
||||||
|
dump_array_bin(output, array, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename element_type, typename F>
|
||||||
|
void dump_list(std::ostream & output, tag_id aid, const std::vector<element_type> & list, F action) {
|
||||||
|
const context & ctxt = context::get(output);
|
||||||
|
if (ctxt.format == context::formats::mojangson) {
|
||||||
|
output << '[';
|
||||||
|
} else {
|
||||||
|
output.put(static_cast<char>(aid));
|
||||||
|
dump(output, static_cast<std::uint32_t>(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 <typename tag_type, typename F>
|
||||||
|
std::unique_ptr<tag_type> load_list(std::istream & input, F action) {
|
||||||
|
const context & ctxt = context::get(input);
|
||||||
|
auto ptr = std::make_unique<tag_type>();
|
||||||
|
typename tag_type::value_type & result = ptr->value;
|
||||||
|
if (ctxt.format != context::formats::mojangson) {
|
||||||
|
auto size = load<std::uint32_t>(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<tag> copy() const = 0;
|
||||||
|
virtual ~tag() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <tag_id tid>
|
||||||
|
struct find_by;
|
||||||
|
|
||||||
|
template <typename value_type>
|
||||||
|
struct find_of;
|
||||||
|
|
||||||
|
std::unique_ptr<tag> read(tag_id tid, std::istream & input);
|
||||||
|
|
||||||
|
template <typename tag_type>
|
||||||
|
std::unique_ptr<tag_type> cast(std::unique_ptr<tag> && ptr) {
|
||||||
|
static_assert(std::is_base_of<tag, tag_type>::value);
|
||||||
|
std::unique_ptr<tag_type> result(dynamic_cast<tag_type *>(ptr.release()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename tag_type>
|
||||||
|
std::unique_ptr<const tag_type> cast(std::unique_ptr<const tag> & ptr) {
|
||||||
|
static_assert(std::is_base_of<tag, tag_type>::value);
|
||||||
|
std::unique_ptr<const tag_type> result(dynamic_cast<const tag_type *>(ptr.release()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
# define TAG_FIND(T) \
|
||||||
|
template <> \
|
||||||
|
struct find_by<T::tid> final { \
|
||||||
|
typedef T type; \
|
||||||
|
}; \
|
||||||
|
template <> \
|
||||||
|
struct find_of<T::value_type> final { \
|
||||||
|
typedef T type; \
|
||||||
|
};
|
||||||
|
|
||||||
|
template <tag_id tid>
|
||||||
|
using tag_by = typename find_by<tid>::type;
|
||||||
|
|
||||||
|
template <typename value_type>
|
||||||
|
using tag_of = typename find_of<value_type>::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<tag> copy() const override;
|
||||||
|
};
|
||||||
|
inline end_tag end;
|
||||||
|
TAG_FIND(end_tag)
|
||||||
|
|
||||||
|
template <tag_id TID, typename number_t>
|
||||||
|
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<numeric_tag> read(std::istream & input) {
|
||||||
|
return std::make_unique<numeric_tag>(load<value_type>(input, context::get(input)));
|
||||||
|
}
|
||||||
|
virtual void write(std::ostream & output) const override {
|
||||||
|
dump(output, value, context::get(output));
|
||||||
|
}
|
||||||
|
virtual std::unique_ptr<tag> copy() const override {
|
||||||
|
return std::make_unique<numeric_tag>(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
# define NUMERIC_TAG(name, tid, number_t) \
|
||||||
|
using name = numeric_tag<tid, number_t>; \
|
||||||
|
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 <tag_id TID, typename number_t, char prefix>
|
||||||
|
struct array_tag final : public tag {
|
||||||
|
typedef number_t element_type;
|
||||||
|
typedef std::vector<element_type> 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<array_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 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<array_tag>(load_array<element_type>(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<tag> copy() const override {
|
||||||
|
return std::make_unique<array_tag>(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
# define ARRAY_TAG(name, tid, number_t, prefix) \
|
||||||
|
using name = array_tag<tid, number_t, prefix>; \
|
||||||
|
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<string_tag> read(std::istream & input);
|
||||||
|
virtual void write(std::ostream & output) const override;
|
||||||
|
virtual std::unique_ptr<tag> 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<tag> operator[](size_t i) const = 0;
|
||||||
|
static std::unique_ptr<list_tag> read(std::istream & input);
|
||||||
|
virtual tag_list_tag as_tags() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct find_by<tag_id::tag_list> final {
|
||||||
|
typedef list_tag type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tag_list_tag final : public list_tag {
|
||||||
|
tag_id eid;
|
||||||
|
typedef std::unique_ptr<tag> element_type;
|
||||||
|
typedef std::vector<element_type> 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<tag> 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<tag> copy() const override;
|
||||||
|
virtual tag_list_tag as_tags() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <tag_id tid>
|
||||||
|
struct find_list_by;
|
||||||
|
|
||||||
|
template <typename value_type>
|
||||||
|
struct find_list_of;
|
||||||
|
|
||||||
|
template <tag_id tid>
|
||||||
|
using list_by = typename find_list_by<tid>::type;
|
||||||
|
|
||||||
|
template <typename value_type>
|
||||||
|
using list_of = typename find_list_of<value_type>::type;
|
||||||
|
|
||||||
|
# define FIND_LIST_TAG(name) \
|
||||||
|
template <> \
|
||||||
|
struct find_list_by<name::eid> { \
|
||||||
|
typedef name type; \
|
||||||
|
}; \
|
||||||
|
template <> \
|
||||||
|
struct find_list_of<name::element_type> { \
|
||||||
|
typedef name type; \
|
||||||
|
};
|
||||||
|
|
||||||
|
struct end_list_tag final : public list_tag {
|
||||||
|
typedef end_tag tag_type;
|
||||||
|
static_assert(std::is_base_of<tag, tag_type>::value);
|
||||||
|
typedef typename end_tag::value_type element_type;
|
||||||
|
typedef std::vector<element_type> 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<tag> operator[](size_t i) const override;
|
||||||
|
end_list_tag() = default;
|
||||||
|
static std::unique_ptr<end_list_tag> read_content(std::istream & input);
|
||||||
|
virtual void write(std::ostream & output) const override;
|
||||||
|
virtual std::unique_ptr<tag> copy() const override;
|
||||||
|
virtual tag_list_tag as_tags() override;
|
||||||
|
};
|
||||||
|
inline end_list_tag end_list;
|
||||||
|
|
||||||
|
FIND_LIST_TAG(end_list_tag)
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct number_list_tag final : public list_tag {
|
||||||
|
typedef T tag_type;
|
||||||
|
static_assert(std::is_base_of<tag, tag_type>::value);
|
||||||
|
typedef typename T::value_type element_type;
|
||||||
|
typedef std::vector<element_type> 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<tag> operator[](size_t i) const override {
|
||||||
|
return std::make_unique<tag_type>(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<number_list_tag> read_content(std::istream & input) {
|
||||||
|
return load_list<number_list_tag>(input, [&input](const context & ctxt) -> element_type {
|
||||||
|
return load<element_type>(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<tag> copy() const override {
|
||||||
|
return std::make_unique<number_list_tag>(*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<tag_type>(each));
|
||||||
|
value.clear();
|
||||||
|
value.shrink_to_fit();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
# define NUMBER_LIST_TAG(name, tag_type) \
|
||||||
|
using name = number_list_tag<tag_type>; \
|
||||||
|
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 <typename T>
|
||||||
|
struct array_list_tag final : public list_tag {
|
||||||
|
typedef T tag_type;
|
||||||
|
static_assert(std::is_base_of<tag, tag_type>::value);
|
||||||
|
typedef typename T::value_type element_type;
|
||||||
|
typedef std::vector<element_type> 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<tag> operator[](size_t i) const override {
|
||||||
|
return std::make_unique<tag_type>(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<array_list_tag> read_content(std::istream & input) {
|
||||||
|
return load_list<array_list_tag>(input, [&input] (const context & ctxt) -> element_type {
|
||||||
|
return load_array<typename element_type::value_type>(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<tag> copy() const override {
|
||||||
|
return std::make_unique<array_list_tag>(*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<tag_type>(std::move(each)));
|
||||||
|
value.clear();
|
||||||
|
value.shrink_to_fit();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
# define ARRAY_LIST_TAG(name, tag_type) \
|
||||||
|
using name = array_list_tag<tag_type>; \
|
||||||
|
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<element_type> 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<tag> 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<string_list_tag> read_content(std::istream & input);
|
||||||
|
virtual void write(std::ostream & output) const override;
|
||||||
|
virtual std::unique_ptr<tag> 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<tag_type> element_type;
|
||||||
|
typedef std::vector<element_type> 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<tag> 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<list_list_tag> read_content(std::istream & input);
|
||||||
|
virtual void write(std::ostream & output) const override;
|
||||||
|
virtual std::unique_ptr<tag> 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<std::string, std::unique_ptr<tag>> 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<compound_tag> read(std::istream & input);
|
||||||
|
virtual void write(std::ostream & output) const override;
|
||||||
|
virtual std::unique_ptr<tag> copy() const override;
|
||||||
|
compound_tag & operator=(const compound_tag & other);
|
||||||
|
void make_heavy();
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
auto put(std::string && name, T && item) {
|
||||||
|
return value.insert_or_assign(std::move(name), std::make_unique<tag_of<T>>(std::move(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
auto put(const std::string & name, T && item) {
|
||||||
|
return value.insert_or_assign(name, std::make_unique<tag_of<T>>(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
typename tag_of<T>::value_type & get(const std::string & name) {
|
||||||
|
return dynamic_cast<tag_of<T> &>(*value.at(name)).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
const typename tag_of<T>::value_type & get(const std::string & name) const {
|
||||||
|
return dynamic_cast<const tag_of<T> &>(*value.at(name)).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
typename tag_of<T>::value_type & tag(const std::string & name) {
|
||||||
|
auto iter = value.find(name);
|
||||||
|
if (iter == value.end()) {
|
||||||
|
auto ptr = std::make_unique<tag_of<T>>();
|
||||||
|
typename tag_of<T>::value_type & result = *ptr->value;
|
||||||
|
value.insert(name, std::move(ptr));
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return dynamic_cast<tag_of<T> &>(*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<element_type> 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<tag> 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<compound_list_tag> read_content(std::istream & input);
|
||||||
|
virtual void write(std::ostream & output) const override;
|
||||||
|
virtual std::unique_ptr<tag> copy() const override;
|
||||||
|
virtual tag_list_tag as_tags() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
FIND_LIST_TAG(compound_list_tag)
|
||||||
|
|
||||||
|
# undef FIND_LIST_TAG
|
||||||
|
# undef TAG_FIND
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
tag_of<T> wrap(T && value) {
|
||||||
|
return tag_of<T>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <tag_id tid>
|
||||||
|
std::unique_ptr<tag> read(std::istream & input) {
|
||||||
|
return tag_by<tid>::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
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
project('cpp-meson', 'cpp',
|
project('nbt-cpp', 'cpp',
|
||||||
version : run_command('git', 'describe', '--abbrev=0', '--tags').stdout().strip(),
|
version : run_command('git', 'describe', '--abbrev=0', '--tags').stdout().strip(),
|
||||||
default_options : ['warning_level=3',
|
default_options : ['warning_level=3',
|
||||||
'cpp_std=c++17'])
|
'cpp_std=c++17'])
|
||||||
|
|
||||||
group = 'com.handtruth'
|
group = 'com.handtruth.mc'
|
||||||
maintainer = 'someone <someone@handtruth.com>'
|
maintainer = 'ktlo <ktlo@handtruth.com>'
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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@";
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#include <string>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,9 @@
|
|||||||
sources = files([
|
sources = files([
|
||||||
'sample_source.cpp'
|
'nbt.cpp'
|
||||||
])
|
])
|
||||||
|
|
||||||
src = include_directories('.')
|
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)
|
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])
|
||||||
|
|||||||
869
src/nbt.cpp
Normal file
869
src/nbt.cpp
Normal file
@@ -0,0 +1,869 @@
|
|||||||
|
#include "nbt.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
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<context *&>(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 <typename number_t>
|
||||||
|
constexpr std::size_t varnum_max() {
|
||||||
|
return sizeof(number_t) * 8 / 7 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
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<number_t>(read & 0b01111111);
|
||||||
|
value |= (tmp << (7 * numRead));
|
||||||
|
numRead++;
|
||||||
|
if (numRead > varnum_max<number_t>())
|
||||||
|
throw std::runtime_error("varint is too big");
|
||||||
|
} while ((read & 0b10000000) != 0);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t>
|
||||||
|
void dump_varnum(std::ostream & output, number_t value) {
|
||||||
|
std::make_unsigned_t<number_t> uval = value;
|
||||||
|
do {
|
||||||
|
auto temp = static_cast<std::int8_t>(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<std::int32_t>(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<std::int64_t>(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump_varlong(std::ostream & output, std::int64_t value) {
|
||||||
|
dump_varnum(output, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename number_t, char suffix>
|
||||||
|
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::int8_t>(std::istream & input) {
|
||||||
|
int value;
|
||||||
|
input >> value;
|
||||||
|
int next = cheof(input);
|
||||||
|
if (next != 'b')
|
||||||
|
input.putback(next);
|
||||||
|
return static_cast<std::int8_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int16_t load_text<std::int16_t>(std::istream & input) {
|
||||||
|
return load_text_simple<std::int16_t, 's'>(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int32_t load_text<std::int32_t>(std::istream & input) {
|
||||||
|
std::int32_t value;
|
||||||
|
input >> value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int64_t load_text<std::int64_t>(std::istream & input) {
|
||||||
|
return load_text_simple<std::int64_t, 'l'>(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
float load_text<float>(std::istream & input) {
|
||||||
|
return load_text_simple<float, 'f'>(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
double load_text<double>(std::istream & input) {
|
||||||
|
return load_text_simple<double, 'd'>(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int32_t load<std::int32_t>(std::istream & input, const context & ctxt) {
|
||||||
|
switch (ctxt.format) {
|
||||||
|
case context::formats::bin:
|
||||||
|
return load_flat<std::int32_t>(input, ctxt.order);
|
||||||
|
case context::formats::zigzag:
|
||||||
|
return load_varint(input);
|
||||||
|
case context::formats::mojangson:
|
||||||
|
default:
|
||||||
|
return load_text<std::int32_t>(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::int64_t load<std::int64_t>(std::istream & input, const context & ctxt) {
|
||||||
|
switch (ctxt.format) {
|
||||||
|
case context::formats::bin:
|
||||||
|
return load_flat<std::int64_t>(input, ctxt.order);
|
||||||
|
case context::formats::zigzag:
|
||||||
|
return load_varlong(input);
|
||||||
|
case context::formats::mojangson:
|
||||||
|
default:
|
||||||
|
return load_text<std::int64_t>(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump<std::int32_t>(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::int64_t>(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::int8_t>(std::ostream & output, std::int8_t number) {
|
||||||
|
output << int(number) << 'b';
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<std::int16_t>(std::ostream & output, std::int16_t number) {
|
||||||
|
output << number << 's';
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<std::int32_t>(std::ostream & output, std::int32_t number) {
|
||||||
|
output << number;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<std::int64_t>(std::ostream & output, std::int64_t number) {
|
||||||
|
output << number << 'l';
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<float>(std::ostream & output, float number) {
|
||||||
|
output << number << 'f';
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void dump_text<double>(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<std::uint16_t>(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<std::uint16_t>(string.size()), ctxt); break;
|
||||||
|
case context::formats::zigzag:
|
||||||
|
dump_varint(output, static_cast<std::int32_t>(string.size())); break;
|
||||||
|
case context::formats::mojangson:
|
||||||
|
default:
|
||||||
|
write_string_text(output, string); return;
|
||||||
|
}
|
||||||
|
output << string;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <tag_id tid>
|
||||||
|
std::unique_ptr<tag> read_bridge(tag_id id, std::istream & input) {
|
||||||
|
if (id == tid)
|
||||||
|
return read<tid>(input);
|
||||||
|
else
|
||||||
|
return read_bridge<static_cast<tag_id>(static_cast<std::int8_t>(tid) - 1)>(id, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::unique_ptr<tag> read_bridge<tag_id::tag_end>(tag_id, std::istream &) {
|
||||||
|
throw std::runtime_error("end tag not for reading");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> read(tag_id tid, std::istream & input) {
|
||||||
|
return read_bridge<tag_id::tag_longarray>(tid, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <tag_id tid>
|
||||||
|
std::unique_ptr<list_tag> read_list_content_bridge(tag_id id, std::istream & input) {
|
||||||
|
if (id == tid)
|
||||||
|
return list_by<tid>::read_content(input);
|
||||||
|
else
|
||||||
|
return read_list_content_bridge<static_cast<tag_id>(static_cast<std::int8_t>(tid) - 1)>(id, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::unique_ptr<list_tag> read_list_content_bridge<tag_id::tag_end>(tag_id id, std::istream & input) {
|
||||||
|
assert(id == tag_id::tag_end);
|
||||||
|
return list_by<tag_id::tag_end>::read_content(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<list_tag> read_list_content(tag_id tid, std::istream & input) {
|
||||||
|
return read_list_content_bridge<tag_id::tag_longarray>(tid, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void end_tag::write(std::ostream & output) const {
|
||||||
|
output.put('\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> end_tag::copy() const {
|
||||||
|
return std::make_unique<end_tag>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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> string_tag::read(std::istream & input) {
|
||||||
|
const context & ctxt = context::get(input);
|
||||||
|
return std::make_unique<string_tag>(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<tag> string_tag::copy() const {
|
||||||
|
return std::make_unique<string_tag>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<list_tag> 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<tag_id>(cheof(input));
|
||||||
|
return read_list_content(type, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> 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> & tag, const context &) {
|
||||||
|
if (aid != tag->id())
|
||||||
|
throw std::runtime_error("wrong tag id");
|
||||||
|
tag->write(output);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> tag_list_tag::copy() const {
|
||||||
|
return std::make_unique<tag_list_tag>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
tag_list_tag tag_list_tag::as_tags() {
|
||||||
|
return std::move(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> end_list_tag::operator[](size_t) const {
|
||||||
|
throw std::out_of_range("end list tag always empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<end_list_tag> 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<end_list_tag>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void end_list_tag::write(std::ostream & output) const {
|
||||||
|
if (context::get(output).format == context::formats::mojangson) {
|
||||||
|
output << "[]";
|
||||||
|
} else {
|
||||||
|
output.put(static_cast<char>(eid));
|
||||||
|
output.put('\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> end_list_tag::copy() const {
|
||||||
|
return std::make_unique<end_list_tag>();
|
||||||
|
}
|
||||||
|
|
||||||
|
tag_list_tag end_list_tag::as_tags() {
|
||||||
|
return tag_list_tag(tag_id::tag_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> string_list_tag::operator[](size_t i) const {
|
||||||
|
return std::make_unique<string_tag>(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> string_list_tag::read_content(std::istream & input) {
|
||||||
|
return load_list<string_list_tag>(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<tag> string_list_tag::copy() const {
|
||||||
|
return std::make_unique<string_list_tag>(*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<tag_type>(std::move(each)));
|
||||||
|
value.clear();
|
||||||
|
value.shrink_to_fit();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> 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<list_tag>(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<list_tag>(element->copy()));
|
||||||
|
}
|
||||||
|
|
||||||
|
list_list_tag::list_list_tag(value_type && list) : value(std::move(list)) {}
|
||||||
|
|
||||||
|
std::unique_ptr<list_list_tag> list_list_tag::read_content(std::istream & input) {
|
||||||
|
return load_list<list_list_tag>(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<tag> list_list_tag::copy() const {
|
||||||
|
return std::make_unique<list_list_tag>(*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> compound_tag::read(std::istream & input) {
|
||||||
|
auto ptr = std::make_unique<compound_tag>();
|
||||||
|
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<char>(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<tag> compound_tag::copy() const {
|
||||||
|
return std::make_unique<compound_tag>(*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<list_tag &>(each);
|
||||||
|
if (!list.heavy())
|
||||||
|
pair.second = std::make_unique<tag_list_tag>(list.as_tags());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<tag> 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> compound_list_tag::read_content(std::istream & input) {
|
||||||
|
return load_list<compound_list_tag>(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<tag> compound_list_tag::copy() const {
|
||||||
|
return std::make_unique<compound_list_tag>(*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<tag_type>(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<tag_id>(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
|
||||||
87
src/nbtc.cpp
Normal file
87
src/nbtc.cpp
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <nbt.hpp>
|
||||||
|
|
||||||
|
const std::map<std::string, nbt::context> 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"===( <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 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<tags::tag> root;
|
||||||
|
std::cin >> from;
|
||||||
|
std::cout << to;
|
||||||
|
try {
|
||||||
|
if (from.format == context::formats::mojangson) {
|
||||||
|
tag_id id = deduce_tag(std::cin);
|
||||||
|
std::unique_ptr<tags::tag> tag = tags::read(id, std::cin);
|
||||||
|
if (tag->id() == tag_id::tag_compound) {
|
||||||
|
root = std::move(tag);
|
||||||
|
} else {
|
||||||
|
auto compound = std::make_unique<tags::compound_tag>(true);
|
||||||
|
compound->value[""] = std::move(tag);
|
||||||
|
root = std::move(compound);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root = tags::read<tag_id::tag_compound>(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;
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#include "config.hpp"
|
|
||||||
|
|
||||||
void sample_function() {
|
|
||||||
|
|
||||||
}
|
|
||||||
BIN
test/bedrock_level.dat
Normal file
BIN
test/bedrock_level.dat
Normal file
Binary file not shown.
BIN
test/bigtest.nbt
Normal file
BIN
test/bigtest.nbt
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
test_names = [
|
test_names = [
|
||||||
'sample_test'
|
'nbt'
|
||||||
]
|
]
|
||||||
|
|
||||||
test_files = []
|
test_files = []
|
||||||
@@ -7,5 +7,5 @@ test_files = []
|
|||||||
foreach test_name : test_names
|
foreach test_name : test_names
|
||||||
test_files += files(test_name + '.cpp')
|
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_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
|
endforeach
|
||||||
|
|||||||
48
test/nbt.cpp
Normal file
48
test/nbt.cpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "nbt.hpp"
|
||||||
|
|
||||||
|
#include "test.hpp"
|
||||||
|
|
||||||
|
std::string read_content(const std::string & filename) {
|
||||||
|
std::ifstream file(filename);
|
||||||
|
return std::string((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t header_size>
|
||||||
|
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<char, header_size> 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<std::map<std::string, std::unique_ptr<tags::tag>>>(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, "");
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#include "test.hpp"
|
|
||||||
|
|
||||||
test {
|
|
||||||
assert_equals(2, 1 + 1);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user