diff --git a/include/nbt.hpp b/include/nbt.hpp index 525fb7c..306369c 100644 --- a/include/nbt.hpp +++ b/include/nbt.hpp @@ -94,7 +94,7 @@ struct context { big_endian, little_endian } order; enum class formats { - bin, zigzag, mojangson + bin, zigzag, mojangson, zint } format; static const context & get(std::ios_base & ios); void set(std::ios_base & ios) const; @@ -123,7 +123,7 @@ inline const context java { context::formats::bin }, kbt { context::orders::big_endian, - context::formats::zigzag + context::formats::zint }, mojangson { context::orders::big_endian, context::formats::mojangson @@ -210,6 +210,9 @@ inline std::uint64_t load(std::istream & input, const context & c void skip_space(std::istream & input); +std::size_t load_size(std::istream & input, const context & ctxt); +void dump_size(std::ostream & output, const context & ctxt, std::size_t size); + template std::vector load_array_text(std::istream & input) { std::vector result; @@ -228,10 +231,10 @@ std::vector load_array_text(std::istream & input) { template std::vector load_array_bin(std::istream & input, const context & ctxt) { - auto size = load(input, ctxt); + auto size = load_size(input, ctxt); std::vector result; result.reserve(size); - for (std::uint32_t i = 0; i < size; i++) + for (std::size_t i = 0; i < size; i++) result.emplace_back(load(input, ctxt)); return result; } @@ -316,7 +319,7 @@ void dump_array_text(std::ostream & output, const std::vector & array) template void dump_array_bin(std::ostream & output, const std::vector & array, const context & ctxt) { - dump(output, static_cast(array.size()), ctxt); + dump_size(output, ctxt, array.size()); for (const auto & element : array) dump(output, element, ctxt); } @@ -336,7 +339,8 @@ void dump_list(std::ostream & output, tag_id aid, const std::vector(aid)); - dump(output, static_cast(list.size()), ctxt); + auto size = static_cast(list.size()); + dump_size(output, ctxt, size); } auto iter = list.cbegin(); auto end = list.cend(); @@ -358,9 +362,9 @@ std::unique_ptr load_list(std::istream & input, F action) { auto ptr = std::make_unique(); typename tag_type::value_type & result = ptr->value; if (ctxt.format != context::formats::mojangson) { - auto size = load(input, ctxt); + std::size_t size = load_size(input, ctxt); result.reserve(size); - for (std::uint32_t i = 0; i < size; i++) + for (std::size_t i = 0; i < size; i++) result.emplace_back(action(ctxt)); } else { for (;;) { @@ -915,4 +919,10 @@ std::ostream & operator<<(std::ostream & output, const tags::tag & tag); } // namespace nbt +namespace std { + +string to_string(const nbt::tags::tag & tag); + +} // namespace std + #endif // NBT_HEAD_BNTYGTFREQXCPVMFFG diff --git a/src/nbt.cpp b/src/nbt.cpp index 3f98ca0..9ac4d60 100644 --- a/src/nbt.cpp +++ b/src/nbt.cpp @@ -1,8 +1,9 @@ -#include "nbt.hpp" +#include "nbt_internal.hpp" #include #include #include +#include namespace nbt { @@ -49,39 +50,6 @@ void context::set(std::ios_base & ios) const { } } -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); } @@ -153,8 +121,10 @@ std::int32_t load(std::istream & input, const context & ctxt) { case context::formats::zigzag: return load_varint(input); case context::formats::mojangson: - default: return load_text(input); + case context::formats::zint: + default: + return load_zint(input); } } @@ -166,8 +136,10 @@ std::int64_t load(std::istream & input, const context & ctxt) { case context::formats::zigzag: return load_varlong(input); case context::formats::mojangson: - default: return load_text(input); + case context::formats::zint: + default: + return load_zint(input); } } @@ -179,8 +151,10 @@ void dump(std::ostream & output, std::int32_t number, const contex case context::formats::zigzag: return dump_varint(output, number); case context::formats::mojangson: - default: return dump_text(output, number); + case context::formats::zint: + default: + return dump_zint(output, number); } } @@ -192,8 +166,10 @@ void dump(std::ostream & output, std::int64_t number, const contex case context::formats::zigzag: return dump_varlong(output, number); case context::formats::mojangson: - default: return dump_text(output, number); + case context::formats::zint: + default: + return dump_zint(output, number); } } @@ -227,6 +203,21 @@ void dump_text(std::ostream & output, double number) { output << number; } +std::size_t load_size(std::istream & input, const context & ctxt) { + if (ctxt.format == context::formats::bin) + return load_flat(input, ctxt.order); + else + return static_cast(load_varint(input)); +} + +void dump_size(std::ostream & output, const context & ctxt, std::size_t size) { + auto sz = static_cast(size); + if (ctxt.format == context::formats::bin) + dump_flat(output, sz, ctxt.order); + else + dump_varint(output, sz); +} + std::ostream & operator<<(std::ostream & output, tag_id tid) { return output << std::to_string(tid); } @@ -238,6 +229,10 @@ inline bool is_valid_char(char c) { tag_id deduce_tag(std::istream & input) { skip_space(input); char a = cheof(input); + if (a == '}' || a == ']') { + input.putback(a); + return tag_id::tag_end; + } if (a == '[') { char b = cheof(input); tag_id id; @@ -282,6 +277,27 @@ tag_id deduce_tag(std::istream & input) { deduced = tag_id::tag_float; } else if (b == 'd') { deduced = tag_id::tag_double; + } else if (b == 'e') { + char c = cheof(input); + buffer.push_back(c); + if (std::isdigit(c) || c == '-' || c == '+') { + for (;;) { + char d = cheof(input); + buffer.push_back(d); + if (std::isdigit(d)) { + continue; + } else if (d == 'f') { + deduced = tag_id::tag_float; + } else if (d == 'd') { + deduced = tag_id::tag_double; + } else { + deduced = tag_id::tag_double; + } + break; + } + } else { + deduced = tag_id::tag_int; + } } else if (b == '.') { for (;;) { char c = cheof(input); @@ -291,7 +307,7 @@ tag_id deduce_tag(std::istream & input) { } else if (c == 'e' || c == 'E') { char d = cheof(input); buffer.push_back(d); - if (std::isdigit(a) || a == '-' || a == '+') + if (std::isdigit(d) || d == '-' || d == '+') continue; } else if (c == 'f') { deduced = tag_id::tag_float; @@ -379,8 +395,8 @@ std::string read_string_text(std::istream & input) { } 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::uint32_t size = (ctxt.format == context::formats::zigzag || ctxt.format == context::formats::zint) ? + load_varint(input) : load_flat(input, ctxt.order); std::string result; result.resize(size); input.read(result.data(), size); @@ -423,11 +439,12 @@ void write_string(std::ostream & output, const std::string & string, const conte 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; + case context::formats::zigzag: + case context::formats::zint: + default: + dump_varint(output, static_cast(string.size())); break; } output << string; } @@ -681,34 +698,43 @@ std::unique_ptr compound_tag::read(std::istream & input) { return ptr; } -void compound_tag::write(std::ostream & output) const { - const context & ctxt = context::get(output); - if (ctxt.format == context::formats::mojangson) - output << '{'; +void compound_write_text(std::ostream & output, const compound_tag & compound, const context & ctxt) { + const auto & value = compound.value; + 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 << ':'; + output << ':'; entry.second->write(output); }; action(*iter); for (++iter; iter != end; ++iter) { - if (ctxt.format == context::formats::mojangson) - output << ','; + output << ','; action(*iter); } } - if (ctxt.format == context::formats::mojangson) { - output << '}'; - } else { - if (!is_root) - tags::end.write(output); + output << '}'; +} + +void compound_write_bin(std::ostream & output, const compound_tag & compound, const context & ctxt) { + const auto & value = compound.value; + for (const auto & entry : value) { + output.put(static_cast(entry.second->id())); + write_string(output, entry.first, ctxt); + entry.second->write(output); } + if (!compound.is_root) + tags::end.write(output); +} + +void compound_tag::write(std::ostream & output) const { + const context & ctxt = context::get(output); + if (ctxt.format == context::formats::mojangson) + compound_write_text(output, *this, ctxt); + else + compound_write_bin(output, *this, ctxt); } std::unique_ptr compound_tag::copy() const { @@ -782,6 +808,9 @@ void read_compound_text(std::istream & input, tags::compound_tag & compound, con tag_id key_type = deduce_tag(input); switch (key_type) { case tag_id::tag_end: + skip_space(input); + if (cheof(input) != '}') + throw std::runtime_error("failed to close compound tag"); return; case tag_id::tag_string: break; @@ -866,4 +895,11 @@ string to_string(nbt::tag_id tid) { # undef TAG_ID_CASE } +string to_string(const nbt::tags::tag & tag) { + using namespace nbt; + std::stringstream result; + tag.write(result << contexts::mojangson); + return result.str(); +} + } // namespace std diff --git a/src/nbt_internal.hpp b/src/nbt_internal.hpp new file mode 100644 index 0000000..8fd2828 --- /dev/null +++ b/src/nbt_internal.hpp @@ -0,0 +1,101 @@ +#ifndef NBT_INTERNAL_HEAD_BFGUDYHGVWCEARSTYJGHI +#define NBT_INTERNAL_HEAD_BFGUDYHGVWCEARSTYJGHI + +#include + +namespace nbt { + +template +constexpr std::size_t varnum_max() { + return sizeof(number_t) * 8 / 7 + 1; +} + +template +constexpr std::enable_if_t, std::size_t> zint_max() { + return varnum_max(); +} + +template +constexpr std::enable_if_t, std::size_t> zint_max() { + return (sizeof(numeric) * 8 + 1) / 7 + ((sizeof(numeric) * 8 + 1) % 7 > 0); +} + +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); +} + +template +std::enable_if_t, numeric> load_zint(std::istream & input) { + return load_varnum(input); +} + +template +std::enable_if_t, numeric> load_zint(std::istream & input) { + std::uint8_t read = cheof(input); + bool sign = read & 1; + numeric value = (read >> 1) & 0b00111111; + unsigned numRead = 0; + while ((read & 0b10000000) != 0) { + read = cheof(input); + numeric tmp = (read & 0b01111111); + value |= (tmp << (7 * numRead + 6)); + ++numRead; + if (numRead > zint_max() - 1) + throw std::runtime_error("szint is too big"); + } + if (sign) + return -value; + else + return value; +} + +template +std::enable_if_t> dump_zint(std::ostream & output, numeric value) { + dump_varnum(output, value); +} + +template +std::enable_if_t> dump_zint(std::ostream & output, numeric value) { + std::uint8_t sign = value < 0; + std::make_unsigned_t uval = sign ? -value : value; + std::uint8_t temp = (static_cast(uval & 0b00111111) << 1) | sign; + uval >>= 6; + if (uval != 0) + temp |= 0b10000000; + output.put(temp); + while (uval != 0) { + temp = static_cast(uval & 0b01111111); + uval >>= 7; + if (uval != 0) + temp |= 0b10000000; + output.put(temp); + } +} + +} // namespace nbt + +#endif // NBT_INTERNAL_HEAD_BFGUDYHGVWCEARSTYJGHI diff --git a/src/nbtc.cpp b/src/nbtc.cpp index 1726baa..70cf6e2 100644 --- a/src/nbtc.cpp +++ b/src/nbtc.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -53,12 +55,13 @@ int main(int argc, char ** argv) { const context & from = iter_from->second; const context & to = iter_to->second; std::unique_ptr root; - std::cin >> from; + auto & input = std::cin; + input >> 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); + tag_id id = deduce_tag(input); + std::unique_ptr tag = tags::read(id, input); if (tag->id() == tag_id::tag_compound) { root = std::move(tag); } else { @@ -67,16 +70,18 @@ int main(int argc, char ** argv) { root = std::move(compound); } } else { - root = tags::read(std::cin); + root = tags::read(input); } } catch (const std::exception & e) { std::cerr << "failed to read NBT (stream position " - << std::cin.tellg() << "): " << e.what() << std::endl; + << input.tellg() << "): " << e.what() << std::endl; return EXIT_FAILURE; } try { - std::cout << *root << std::endl; + std::cout << *root; + if (context::formats::mojangson == to.format) + std::cout << std::endl; } catch (const std::exception & e) { std::cerr << "failed to write NBT (stream position "