From 8703528495fe58f002c8d89b41231ade03401a81 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 06:27:03 +0800 Subject: [PATCH 01/11] Support writing unnamed bnbt tags --- .../cpp/nbt_encoding/binary/write_binary.cpp | 67 ++++++++++--------- .../amulet_nbt/nbt_encoding/binary.hpp | 49 +++++++------- .../amulet_nbt/pybind/serialisation.hpp | 16 ++--- src/amulet_nbt/pybind/tag/py_abc_tag.cpp | 12 ++-- src/amulet_nbt/pybind/tag/py_array_tag.cpp | 3 +- 5 files changed, 75 insertions(+), 72 deletions(-) diff --git a/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp b/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp index 62451bda..8d59f07b 100644 --- a/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp +++ b/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -167,9 +168,9 @@ template < bool > = true > -inline void write_name_and_tag(AmuletNBT::BinaryWriter& writer, const std::string& name, const T& tag){ +inline void write_name_and_tag(AmuletNBT::BinaryWriter& writer, const std::optional& name, const T& tag){ writer.writeNumeric(AmuletNBT::tag_id_v); - write_string(writer, name); + if (name) write_string(writer, *name); write_payload(writer, tag); } @@ -184,7 +185,7 @@ template < bool > = true > -inline void write_name_and_tag(AmuletNBT::BinaryWriter & writer, const std::string & name, const T tag) { +inline void write_name_and_tag(AmuletNBT::BinaryWriter & writer, const std::optional& name, const T tag) { write_name_and_tag(writer, name, *tag); } @@ -193,7 +194,7 @@ template < typename T, std::enable_if_t, bool> = true > -inline void write_name_and_tag(AmuletNBT::BinaryWriter& writer, const std::string& name, const AmuletNBT::TagNode& node){ +inline void write_name_and_tag(AmuletNBT::BinaryWriter& writer, const std::optional& name, const AmuletNBT::TagNode& node){ std::visit([&writer, &name](auto&& tag) { using tagT = std::decay_t; write_name_and_tag(writer, name, tag); @@ -204,101 +205,101 @@ inline void write_name_and_tag(AmuletNBT::BinaryWriter& writer, const std::strin template <> inline void write_payload(AmuletNBT::BinaryWriter& writer, const AmuletNBT::CompoundTag& value){ for (auto it = value.begin(); it != value.end(); it++){ - write_name_and_tag(writer, it->first, it->second); + write_name_and_tag(writer, std::optional(it->first), it->second); } writer.writeNumeric(0); }; template -inline std::string _write_nbt(const std::string& name, const T& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ +inline std::string _write_nbt(const std::optional& name, const T& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ AmuletNBT::BinaryWriter writer(endianness, string_encode); write_name_and_tag(writer, name, tag); return writer.getBuffer(); } namespace AmuletNBT { - void write_nbt(BinaryWriter& writer, const std::string& name, const ByteTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const ByteTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const ShortTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const ShortTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const IntTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const IntTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const LongTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const LongTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const FloatTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const FloatTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const DoubleTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const DoubleTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const ByteArrayTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const ByteArrayTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const StringTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const StringTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const ListTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const ListTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const CompoundTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const CompoundTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const IntArrayTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const IntArrayTag& tag) { write_name_and_tag(writer, name, tag); } - void write_nbt(BinaryWriter& writer, const std::string& name, const LongArrayTag& tag) { + void write_nbt(BinaryWriter& writer, const std::optional& name, const LongArrayTag& tag) { write_name_and_tag(writer, name, tag); } void write_nbt(BinaryWriter& writer, const std::string& name, const TagNode& tag) { - write_name_and_tag(writer, name, tag); + write_name_and_tag(writer, std::optional(name), tag); } void write_nbt(BinaryWriter& writer, const NamedTag& tag) { - write_name_and_tag(writer, tag.name, tag.tag_node); + write_name_and_tag(writer, std::optional(tag.name), tag.tag_node); } - std::string write_nbt(const std::string& name, const AmuletNBT::ByteTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::ByteTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::ShortTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::ShortTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::IntTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::IntTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::LongTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::LongTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::FloatTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::FloatTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::DoubleTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::DoubleTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::ByteArrayTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::ByteArrayTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::StringTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::StringTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::ListTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::ListTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::CompoundTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::CompoundTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::IntArrayTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::IntArrayTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; - std::string write_nbt(const std::string& name, const AmuletNBT::LongArrayTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ + std::string write_nbt(const std::optional& name, const AmuletNBT::LongArrayTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return _write_nbt(name, tag, endianness, string_encode); }; std::string write_nbt(const std::string& name, const AmuletNBT::TagNode& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ - return _write_nbt(name, tag, endianness, string_encode); + return _write_nbt(std::optional(name), tag, endianness, string_encode); }; std::string write_nbt(const AmuletNBT::NamedTag& named_tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return write_nbt(named_tag.name, named_tag.tag_node, endianness, string_encode); diff --git a/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp b/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp index d804adbe..034ce1fd 100644 --- a/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp +++ b/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -22,33 +23,33 @@ namespace AmuletNBT { std::vector read_nbt_array(const std::string&, std::endian, StringDecode, size_t& offset); std::vector read_nbt_array(const std::string&, std::endian, StringDecode, size_t& offset, size_t count); - void write_nbt(BinaryWriter&, const std::string& name, const ByteTag&); - void write_nbt(BinaryWriter&, const std::string& name, const ShortTag&); - void write_nbt(BinaryWriter&, const std::string& name, const IntTag&); - void write_nbt(BinaryWriter&, const std::string& name, const LongTag&); - void write_nbt(BinaryWriter&, const std::string& name, const FloatTag&); - void write_nbt(BinaryWriter&, const std::string& name, const DoubleTag&); - void write_nbt(BinaryWriter&, const std::string& name, const ByteArrayTag&); - void write_nbt(BinaryWriter&, const std::string& name, const StringTag&); - void write_nbt(BinaryWriter&, const std::string& name, const ListTag&); - void write_nbt(BinaryWriter&, const std::string& name, const CompoundTag&); - void write_nbt(BinaryWriter&, const std::string& name, const IntArrayTag&); - void write_nbt(BinaryWriter&, const std::string& name, const LongArrayTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const ByteTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const ShortTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const IntTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const LongTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const FloatTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const DoubleTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const ByteArrayTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const StringTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const ListTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const CompoundTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const IntArrayTag&); + void write_nbt(BinaryWriter&, const std::optional& name, const LongArrayTag&); void write_nbt(BinaryWriter&, const std::string& name, const TagNode&); void write_nbt(BinaryWriter&, const NamedTag& tag); - std::string write_nbt(const std::string& name, const ByteTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const ShortTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const IntTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const LongTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const FloatTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const DoubleTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const ByteArrayTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const StringTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const ListTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const CompoundTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const IntArrayTag&, std::endian, StringEncode); - std::string write_nbt(const std::string& name, const LongArrayTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const ByteTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const ShortTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const IntTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const LongTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const FloatTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const DoubleTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const ByteArrayTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const StringTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const ListTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const CompoundTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const IntArrayTag&, std::endian, StringEncode); + std::string write_nbt(const std::optional& name, const LongArrayTag&, std::endian, StringEncode); std::string write_nbt(const std::string& name, const TagNode&, std::endian, StringEncode); std::string write_nbt(const NamedTag& tag, std::endian, StringEncode); } diff --git a/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp b/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp index 12421365..c4713f72 100644 --- a/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp +++ b/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp @@ -1,7 +1,7 @@ #define SerialiseTag(CLSNAME)\ auto to_nbt_##CLSNAME = [compress](\ const AmuletNBT::CLSNAME& self,\ - std::string name,\ + std::optional name,\ bool compressed,\ std::endian endianness,\ AmuletNBT::StringEncode string_encoder\ @@ -17,7 +17,7 @@ [to_nbt_##CLSNAME](\ const AmuletNBT::CLSNAME& self,\ AmuletNBT::EncodingPreset preset,\ - std::string name\ + std::optional name\ ){\ return to_nbt_##CLSNAME(\ self,\ @@ -29,7 +29,7 @@ },\ py::kw_only(),\ py::arg("preset") = java_encoding,\ - py::arg("name") = ""\ + py::arg("name").none(true) = ""\ );\ CLSNAME.def(\ "to_nbt",\ @@ -38,7 +38,7 @@ bool compressed,\ bool little_endian,\ AmuletNBT::StringEncoding string_encoding,\ - std::string name\ + std::optional name\ ){\ return to_nbt_##CLSNAME(\ self,\ @@ -52,12 +52,12 @@ py::arg("compressed") = true,\ py::arg("little_endian") = false,\ py::arg("string_encoding") = mutf8_encoding,\ - py::arg("name") = ""\ + py::arg("name").none(true) = ""\ );\ auto save_to_##CLSNAME = [to_nbt_##CLSNAME](\ const AmuletNBT::CLSNAME& self,\ py::object filepath_or_writable,\ - std::string name,\ + std::optional name,\ bool compressed,\ std::endian endianness,\ AmuletNBT::StringEncode string_encoder\ @@ -80,7 +80,7 @@ const AmuletNBT::CLSNAME& self,\ py::object filepath_or_writable,\ AmuletNBT::EncodingPreset preset,\ - std::string name\ + std::optional name\ ){\ return save_to_##CLSNAME(\ self,\ @@ -105,7 +105,7 @@ bool compressed,\ bool little_endian,\ AmuletNBT::StringEncoding string_encoding,\ - std::string name\ + std::optional name\ ){\ return save_to_##CLSNAME(\ self,\ diff --git a/src/amulet_nbt/pybind/tag/py_abc_tag.cpp b/src/amulet_nbt/pybind/tag/py_abc_tag.cpp index 070f512c..d67cf3a1 100644 --- a/src/amulet_nbt/pybind/tag/py_abc_tag.cpp +++ b/src/amulet_nbt/pybind/tag/py_abc_tag.cpp @@ -44,14 +44,14 @@ void init_abc(py::module& m) { []( const AmuletNBT::AbstractBaseTag& self, AmuletNBT::EncodingPreset preset, - std::string name + std::optional name ){ PyErr_SetString(PyExc_NotImplementedError, ""); throw py::error_already_set(); }, py::kw_only(), py::arg("preset") = java_encoding, - py::arg("name") = "" + py::arg("name").none(true) = "" ); AbstractBaseTag.def( "to_nbt", @@ -60,7 +60,7 @@ void init_abc(py::module& m) { bool compressed, bool little_endian, AmuletNBT::StringEncoding string_encoding, - std::string name + std::optional name ){ PyErr_SetString(PyExc_NotImplementedError, ""); throw py::error_already_set(); @@ -69,7 +69,7 @@ void init_abc(py::module& m) { py::arg("compressed") = true, py::arg("little_endian") = false, py::arg("string_encoding") = mutf8_encoding, - py::arg("name") = "" + py::arg("name").none(true) = "" ); AbstractBaseTag.def( "save_to", @@ -77,7 +77,7 @@ void init_abc(py::module& m) { const AmuletNBT::AbstractBaseTag& self, py::object filepath_or_writable, AmuletNBT::EncodingPreset preset, - std::string name + std::optional name ){ PyErr_SetString(PyExc_NotImplementedError, ""); throw py::error_already_set(); @@ -96,7 +96,7 @@ void init_abc(py::module& m) { bool compressed, bool little_endian, AmuletNBT::StringEncoding string_encoding, - std::string name + std::optional name ){ PyErr_SetString(PyExc_NotImplementedError, ""); throw py::error_already_set(); diff --git a/src/amulet_nbt/pybind/tag/py_array_tag.cpp b/src/amulet_nbt/pybind/tag/py_array_tag.cpp index 7edd0cb7..3928087d 100644 --- a/src/amulet_nbt/pybind/tag/py_array_tag.cpp +++ b/src/amulet_nbt/pybind/tag/py_array_tag.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -105,7 +106,7 @@ namespace py = pybind11; CLSNAME.def(\ py::pickle(\ [](const AmuletNBT::CLSNAME& self){\ - return py::bytes(AmuletNBT::write_nbt("", self, std::endian::big, AmuletNBT::utf8_to_mutf8));\ + return py::bytes(AmuletNBT::write_nbt(std::optional(""), self, std::endian::big, AmuletNBT::utf8_to_mutf8));\ },\ [](py::bytes state){\ return std::get(\ From 13ae0a41da25ed30c103494d9425c5e29a28c08e Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 13:06:50 +0800 Subject: [PATCH 02/11] Support reading unnamed tags --- .../cpp/nbt_encoding/binary/read_binary.cpp | 37 +++++++ .../amulet_nbt/nbt_encoding/binary.hpp | 6 ++ src/amulet_nbt/pybind/bnbt.cpp | 96 ++++++++++++++----- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp b/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp index 6557b444..7957de5d 100644 --- a/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp +++ b/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp @@ -170,18 +170,35 @@ namespace AmuletNBT { return AmuletNBT::NamedTag(name, node); } + AmuletNBT::TagNode read_nbt_unnamed(AmuletNBT::BinaryReader& reader){ + std::uint8_t tag_id = reader.readNumeric(); + return read_node(reader, tag_id); + } + // Read one named tag from the string at position offset. AmuletNBT::NamedTag read_nbt(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); return read_nbt(reader); } + // Read one named tag from the string at position offset. + AmuletNBT::TagNode read_nbt_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ + AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); + return read_nbt_unnamed(reader); + } + // Read one named tag from the string. AmuletNBT::NamedTag read_nbt(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode){ size_t offset = 0; return read_nbt(raw, endianness, string_decode, offset); } + // Read one named tag from the string. + AmuletNBT::TagNode read_nbt_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode){ + size_t offset = 0; + return read_nbt_unnamed(raw, endianness, string_decode, offset); + } + // Read count named tags from the string at position offset. std::vector read_nbt_array(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset, size_t count){ AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); @@ -192,6 +209,16 @@ namespace AmuletNBT { return out; } + // Read count named tags from the string at position offset. + std::vector read_nbt_array_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset, size_t count){ + AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); + std::vector out; + for (size_t i = 0; i < count; i++){ + out.push_back(read_nbt_unnamed(reader)); + } + return out; + } + // Read all named tags from the string at position offset. std::vector read_nbt_array(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); @@ -201,4 +228,14 @@ namespace AmuletNBT { } return out; } + + // Read all named tags from the string at position offset. + std::vector read_nbt_array_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ + AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); + std::vector out; + while (reader.has_more_data()){ + out.push_back(read_nbt_unnamed(reader)); + } + return out; + } } diff --git a/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp b/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp index 034ce1fd..1250f7b4 100644 --- a/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp +++ b/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp @@ -23,6 +23,12 @@ namespace AmuletNBT { std::vector read_nbt_array(const std::string&, std::endian, StringDecode, size_t& offset); std::vector read_nbt_array(const std::string&, std::endian, StringDecode, size_t& offset, size_t count); + TagNode read_nbt_unnamed(BinaryReader& reader); + TagNode read_nbt_unnamed(const std::string&, std::endian, StringDecode, size_t& offset); + TagNode read_nbt_unnamed(const std::string&, std::endian, StringDecode); + std::vector read_nbt_array_unnamed(const std::string&, std::endian, StringDecode, size_t& offset); + std::vector read_nbt_array_unnamed(const std::string&, std::endian, StringDecode, size_t& offset, size_t count); + void write_nbt(BinaryWriter&, const std::optional& name, const ByteTag&); void write_nbt(BinaryWriter&, const std::optional& name, const ShortTag&); void write_nbt(BinaryWriter&, const std::optional& name, const IntTag&); diff --git a/src/amulet_nbt/pybind/bnbt.cpp b/src/amulet_nbt/pybind/bnbt.cpp index 0c219b58..bc9f37cc 100644 --- a/src/amulet_nbt/pybind/bnbt.cpp +++ b/src/amulet_nbt/pybind/bnbt.cpp @@ -82,28 +82,41 @@ void init_bnbt(py::module& m) { return data; }; + typedef std::variant NodeOrNamedTag; + typedef std::variant, std::vector> NodeOrNamedTagVector; + auto read_nbt = [get_buffer]( py::object filepath_or_buffer, bool compressed, std::endian endianness, AmuletNBT::StringDecode string_decoder, - py::object read_offset_py - ){ + py::object read_offset_py, + bool named + ) -> NodeOrNamedTag { std::string buffer = get_buffer(filepath_or_buffer, compressed); if (py::isinstance(read_offset_py)){ AmuletNBT::ReadOffset& read_offset = read_offset_py.cast(); - return AmuletNBT::read_nbt( + return named ? NodeOrNamedTag(AmuletNBT::read_nbt( buffer, endianness, string_decoder, read_offset.offset - ); + )) : NodeOrNamedTag(AmuletNBT::read_nbt_unnamed( + buffer, + endianness, + string_decoder, + read_offset.offset + )); } else if (read_offset_py.is(py::none())){ - return AmuletNBT::read_nbt( + return named ? NodeOrNamedTag(AmuletNBT::read_nbt( buffer, endianness, string_decoder - ); + )) : NodeOrNamedTag(AmuletNBT::read_nbt_unnamed( + buffer, + endianness, + string_decoder + )); } else { throw std::invalid_argument("read_offset must be ReadOffset or None"); } @@ -114,20 +127,23 @@ void init_bnbt(py::module& m) { [read_nbt]( py::object filepath_or_buffer, AmuletNBT::EncodingPreset preset, - py::object read_offset + py::object read_offset, + bool named ){ return read_nbt( filepath_or_buffer, preset.compressed, preset.endianness, preset.string_encoding.decode, - read_offset + read_offset, + named ); }, py::arg("filepath_or_buffer"), py::kw_only(), py::arg("preset") = java_encoding, py::arg("read_offset") = py::none(), + py::arg("named") = true, py::doc( "Load one binary NBT object.\n" "\n" @@ -144,14 +160,16 @@ void init_bnbt(py::module& m) { bool compressed, bool little_endian, AmuletNBT::StringEncoding string_encoding, - py::object read_offset + py::object read_offset, + bool named ){ return read_nbt( filepath_or_buffer, compressed, little_endian ? std::endian::little : std::endian::big, string_encoding.decode, - read_offset + read_offset, + named ); }, py::arg("filepath_or_buffer"), @@ -160,6 +178,7 @@ void init_bnbt(py::module& m) { py::arg("little_endian") = false, py::arg("string_encoding") = mutf8_encoding, py::arg("read_offset") = py::none(), + py::arg("named") = true, py::doc( "Load one binary NBT object.\n" "\n" @@ -178,8 +197,9 @@ void init_bnbt(py::module& m) { bool compressed, std::endian endianness, AmuletNBT::StringDecode string_decoder, - py::object read_offset_py - ){ + py::object read_offset_py, + bool named + ) -> NodeOrNamedTagVector { if (count < -1){ throw std::invalid_argument("count must be -1 or higher"); } @@ -187,38 +207,60 @@ void init_bnbt(py::module& m) { if (py::isinstance(read_offset_py)){ AmuletNBT::ReadOffset& read_offset = read_offset_py.cast(); if (count == -1){ - return AmuletNBT::read_nbt_array( + return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( + buffer, + endianness, + string_decoder, + read_offset.offset + )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array( buffer, endianness, string_decoder, read_offset.offset - ); + )); } else { - return AmuletNBT::read_nbt_array( + return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( buffer, endianness, string_decoder, read_offset.offset, count - ); + )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array_unnamed( + buffer, + endianness, + string_decoder, + read_offset.offset, + count + )); } } else if (read_offset_py.is(py::none())){ size_t offset = 0; if (count == -1){ - return AmuletNBT::read_nbt_array( + return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( + buffer, + endianness, + string_decoder, + offset + )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array_unnamed( buffer, endianness, string_decoder, offset - ); + )); } else { - return AmuletNBT::read_nbt_array( + return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( + buffer, + endianness, + string_decoder, + offset, + count + )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array_unnamed( buffer, endianness, string_decoder, offset, count - ); + )); } } else { throw std::invalid_argument("read_offset must be ReadOffset or None"); @@ -230,7 +272,8 @@ void init_bnbt(py::module& m) { py::object filepath_or_buffer, Py_ssize_t count, AmuletNBT::EncodingPreset preset, - py::object read_offset + py::object read_offset, + bool named ){ return read_nbt_array( filepath_or_buffer, @@ -238,7 +281,8 @@ void init_bnbt(py::module& m) { preset.compressed, preset.endianness, preset.string_encoding.decode, - read_offset + read_offset, + named ); }, py::arg("filepath_or_buffer"), @@ -246,6 +290,7 @@ void init_bnbt(py::module& m) { py::arg("count") = 1, py::arg("preset") = java_encoding, py::arg("read_offset") = py::none(), + py::arg("named") = true, py::doc( "Load an array of binary NBT objects from a contiguous buffer.\n" "\n" @@ -265,7 +310,8 @@ void init_bnbt(py::module& m) { bool compressed, bool little_endian, AmuletNBT::StringEncoding string_encoding, - py::object read_offset + py::object read_offset, + bool named ){ return read_nbt_array( filepath_or_buffer, @@ -273,7 +319,8 @@ void init_bnbt(py::module& m) { compressed, little_endian ? std::endian::little : std::endian::big, string_encoding.decode, - read_offset + read_offset, + named ); }, py::arg("filepath_or_buffer"), @@ -283,6 +330,7 @@ void init_bnbt(py::module& m) { py::arg("little_endian") = false, py::arg("string_encoding") = mutf8_encoding, py::arg("read_offset") = py::none(), + py::arg("named") = true, py::doc( "Load an array of binary NBT objects from a contiguous buffer.\n" "\n" From 10265b0f33bc84a655d4689bc88f1c1aca4f2571 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 13:09:15 +0800 Subject: [PATCH 03/11] Remove redundant `.none(true)` on `name` parameters --- src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp | 4 ++-- src/amulet_nbt/pybind/tag/py_abc_tag.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp b/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp index c4713f72..e5a0abf6 100644 --- a/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp +++ b/src/amulet_nbt/include/amulet_nbt/pybind/serialisation.hpp @@ -29,7 +29,7 @@ },\ py::kw_only(),\ py::arg("preset") = java_encoding,\ - py::arg("name").none(true) = ""\ + py::arg("name") = ""\ );\ CLSNAME.def(\ "to_nbt",\ @@ -52,7 +52,7 @@ py::arg("compressed") = true,\ py::arg("little_endian") = false,\ py::arg("string_encoding") = mutf8_encoding,\ - py::arg("name").none(true) = ""\ + py::arg("name") = ""\ );\ auto save_to_##CLSNAME = [to_nbt_##CLSNAME](\ const AmuletNBT::CLSNAME& self,\ diff --git a/src/amulet_nbt/pybind/tag/py_abc_tag.cpp b/src/amulet_nbt/pybind/tag/py_abc_tag.cpp index d67cf3a1..3d37f351 100644 --- a/src/amulet_nbt/pybind/tag/py_abc_tag.cpp +++ b/src/amulet_nbt/pybind/tag/py_abc_tag.cpp @@ -51,7 +51,7 @@ void init_abc(py::module& m) { }, py::kw_only(), py::arg("preset") = java_encoding, - py::arg("name").none(true) = "" + py::arg("name") = "" ); AbstractBaseTag.def( "to_nbt", @@ -69,7 +69,7 @@ void init_abc(py::module& m) { py::arg("compressed") = true, py::arg("little_endian") = false, py::arg("string_encoding") = mutf8_encoding, - py::arg("name").none(true) = "" + py::arg("name") = "" ); AbstractBaseTag.def( "save_to", From d951edf84127ba752b2cf2431010f86d932ad590 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 17:25:36 +0800 Subject: [PATCH 04/11] Add tests for unnamed tag --- tests/test_nbt.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_nbt.py b/tests/test_nbt.py index 841cd77a..3d6ea102 100644 --- a/tests/test_nbt.py +++ b/tests/test_nbt.py @@ -61,6 +61,24 @@ def test_write_little_endian(self) -> None: msg=str(data.named_tag), ) + def test_unnamed(self) -> None: + # Only one case is tested as the implementation of this is shared among all tag types and thus behaves the same + self.assertEqual( + amulet_nbt.read_nbt(b"\x01\x05", named=False, compressed=False, little_endian=False), + amulet_nbt.ByteTag(5), + "reading unnamed tag" + ) + self.assertEqual( + amulet_nbt.read_nbt_array(b"\x01\x05\x01\x06\x01\x07", named=False, count=-1, compressed=False, little_endian=False), + [amulet_nbt.ByteTag(i) for i in (5, 6, 7)], + "reading unnamed tag array" + ) + self.assertEqual( + amulet_nbt.ByteTag(5).to_nbt(name=None, compressed=False, little_endian=False), + b"\x01\x05", + msg="writing unnamed tag" + ) + if __name__ == "__main__": unittest.main() From f5254ec5ed0e6c3405fcf4fe30d3dfd7e773d834 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 17:32:45 +0800 Subject: [PATCH 05/11] Put the `named` parameter before `read_offset` --- src/amulet_nbt/pybind/bnbt.cpp | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/amulet_nbt/pybind/bnbt.cpp b/src/amulet_nbt/pybind/bnbt.cpp index bc9f37cc..0e4416bb 100644 --- a/src/amulet_nbt/pybind/bnbt.cpp +++ b/src/amulet_nbt/pybind/bnbt.cpp @@ -90,8 +90,8 @@ void init_bnbt(py::module& m) { bool compressed, std::endian endianness, AmuletNBT::StringDecode string_decoder, - py::object read_offset_py, - bool named + bool named, + py::object read_offset_py ) -> NodeOrNamedTag { std::string buffer = get_buffer(filepath_or_buffer, compressed); if (py::isinstance(read_offset_py)){ @@ -127,23 +127,23 @@ void init_bnbt(py::module& m) { [read_nbt]( py::object filepath_or_buffer, AmuletNBT::EncodingPreset preset, - py::object read_offset, - bool named + bool named, + py::object read_offset ){ return read_nbt( filepath_or_buffer, preset.compressed, preset.endianness, preset.string_encoding.decode, - read_offset, - named + named, + read_offset ); }, py::arg("filepath_or_buffer"), py::kw_only(), py::arg("preset") = java_encoding, - py::arg("read_offset") = py::none(), py::arg("named") = true, + py::arg("read_offset") = py::none(), py::doc( "Load one binary NBT object.\n" "\n" @@ -160,16 +160,16 @@ void init_bnbt(py::module& m) { bool compressed, bool little_endian, AmuletNBT::StringEncoding string_encoding, - py::object read_offset, - bool named + bool named, + py::object read_offset ){ return read_nbt( filepath_or_buffer, compressed, little_endian ? std::endian::little : std::endian::big, string_encoding.decode, - read_offset, - named + named, + read_offset ); }, py::arg("filepath_or_buffer"), @@ -177,8 +177,8 @@ void init_bnbt(py::module& m) { py::arg("compressed") = true, py::arg("little_endian") = false, py::arg("string_encoding") = mutf8_encoding, - py::arg("read_offset") = py::none(), py::arg("named") = true, + py::arg("read_offset") = py::none(), py::doc( "Load one binary NBT object.\n" "\n" @@ -197,8 +197,8 @@ void init_bnbt(py::module& m) { bool compressed, std::endian endianness, AmuletNBT::StringDecode string_decoder, - py::object read_offset_py, - bool named + bool named, + py::object read_offset_py ) -> NodeOrNamedTagVector { if (count < -1){ throw std::invalid_argument("count must be -1 or higher"); @@ -272,8 +272,8 @@ void init_bnbt(py::module& m) { py::object filepath_or_buffer, Py_ssize_t count, AmuletNBT::EncodingPreset preset, - py::object read_offset, - bool named + bool named, + py::object read_offset ){ return read_nbt_array( filepath_or_buffer, @@ -281,16 +281,16 @@ void init_bnbt(py::module& m) { preset.compressed, preset.endianness, preset.string_encoding.decode, - read_offset, - named + named, + read_offset ); }, py::arg("filepath_or_buffer"), py::kw_only(), py::arg("count") = 1, py::arg("preset") = java_encoding, - py::arg("read_offset") = py::none(), py::arg("named") = true, + py::arg("read_offset") = py::none(), py::doc( "Load an array of binary NBT objects from a contiguous buffer.\n" "\n" @@ -310,8 +310,8 @@ void init_bnbt(py::module& m) { bool compressed, bool little_endian, AmuletNBT::StringEncoding string_encoding, - py::object read_offset, - bool named + bool named, + py::object read_offset ){ return read_nbt_array( filepath_or_buffer, @@ -319,8 +319,8 @@ void init_bnbt(py::module& m) { compressed, little_endian ? std::endian::little : std::endian::big, string_encoding.decode, - read_offset, - named + named, + read_offset ); }, py::arg("filepath_or_buffer"), @@ -329,8 +329,8 @@ void init_bnbt(py::module& m) { py::arg("compressed") = true, py::arg("little_endian") = false, py::arg("string_encoding") = mutf8_encoding, - py::arg("read_offset") = py::none(), py::arg("named") = true, + py::arg("read_offset") = py::none(), py::doc( "Load an array of binary NBT objects from a contiguous buffer.\n" "\n" From d54ee9f4c028d6aafa0d14d909329ad03a3e1ec2 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 17:39:41 +0800 Subject: [PATCH 06/11] Add docs and typehint for unnamed tags --- src/amulet_nbt/__init__.pyi | 14 +++++++++++--- src/amulet_nbt/pybind/bnbt.cpp | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/amulet_nbt/__init__.pyi b/src/amulet_nbt/__init__.pyi index 7d82ebc2..85a65ea8 100644 --- a/src/amulet_nbt/__init__.pyi +++ b/src/amulet_nbt/__init__.pyi @@ -122,7 +122,7 @@ class AbstractBaseTag: compressed: bool = True, little_endian: bool = False, string_encoding: StringEncoding = mutf8_encoding, - name: str | bytes = b"", + name: str | bytes | None = b"", ) -> bytes: """Get the data in binary NBT format. @@ -130,7 +130,7 @@ class AbstractBaseTag: :param compressed: Should the bytes be compressed with gzip. :param little_endian: Should the bytes be saved in little endian format. Ignored if preset is defined. :param string_encoding: The StringEncoding to use. Ignored if preset is defined. - :param name: The root tag name. + :param name: The root tag name, or `None` for unnamed tag. :return: The binary NBT representation of the class. """ @@ -155,7 +155,7 @@ class AbstractBaseTag: :param compressed: Should the bytes be compressed with gzip. :param little_endian: Should the bytes be saved in little endian format. Ignored if preset is defined. :param string_encoding: The StringEncoding to use. Ignored if preset is defined. - :param name: The root tag name. + :param name: The root tag name, or `None` for unnamed tag. :return: The binary NBT representation of the class. """ @@ -1592,12 +1592,14 @@ def read_nbt( filepath_or_buffer: str | bytes | memoryview | _Readable | None, *, preset: EncodingPreset | None = None, + named: bool = True, read_offset: ReadOffset | None = None, ) -> NamedTag: """Load one binary NBT object. :param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from. :param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect. + :param named: If the tag to read is named. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ @@ -1609,6 +1611,7 @@ def read_nbt( compressed: bool = True, little_endian: bool = False, string_encoding: StringEncoding = mutf8_encoding, + named: bool = True, read_offset: ReadOffset | None = None, ) -> NamedTag: """Load one binary NBT object. @@ -1617,6 +1620,7 @@ def read_nbt( :param compressed: Is the binary data gzip compressed. :param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java. :param string_encoding: The bytes decoder function to parse strings. mutf8_encoding for Java, utf8_escape_encoding for Bedrock. + :param named: If the tag to read is named. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ @@ -1627,6 +1631,7 @@ def read_nbt_array( *, count: int = 1, preset: EncodingPreset | None = None, + named: bool = True, read_offset: ReadOffset | None = None, ) -> list[NamedTag]: """Load an array of binary NBT objects from a contiguous buffer. @@ -1634,6 +1639,7 @@ def read_nbt_array( :param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from. :param count: The number of binary NBT objects to read. Use -1 to exhaust the buffer. :param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect. + :param named: If the tags to read are named. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ @@ -1646,6 +1652,7 @@ def read_nbt_array( compressed: bool = True, little_endian: bool = False, string_encoding: StringEncoding = mutf8_encoding, + named: bool = True, read_offset: ReadOffset | None = None, ) -> list[NamedTag]: """Load an array of binary NBT objects from a contiguous buffer. @@ -1655,6 +1662,7 @@ def read_nbt_array( :param compressed: Is the binary data gzip compressed. This only supports the whole buffer compressed as one. :param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java. :param string_encoding: The bytes decoder function to parse strings. mutf8.decode_modified_utf8 for Java, amulet_nbt.utf8_escape_decoder for Bedrock. + :param named: If the tags to read are named. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ diff --git a/src/amulet_nbt/pybind/bnbt.cpp b/src/amulet_nbt/pybind/bnbt.cpp index 0e4416bb..a36c324f 100644 --- a/src/amulet_nbt/pybind/bnbt.cpp +++ b/src/amulet_nbt/pybind/bnbt.cpp @@ -149,6 +149,7 @@ void init_bnbt(py::module& m) { "\n" ":param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from.\n" ":param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect.\n" + ":param named: If the tag to read is named.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) @@ -186,6 +187,7 @@ void init_bnbt(py::module& m) { ":param compressed: Is the binary data gzip compressed.\n" ":param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java.\n" ":param string_encoding: The bytes decoder function to parse strings. mutf8_encoding for Java, utf8_escape_encoding for Bedrock.\n" + ":param named: If the tag to read is named.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) @@ -297,6 +299,7 @@ void init_bnbt(py::module& m) { ":param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from.\n" ":param count: The number of binary NBT objects to read. Use -1 to exhaust the buffer.\n" ":param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect.\n" + ":param named: If the tags to read are named.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) @@ -339,6 +342,7 @@ void init_bnbt(py::module& m) { ":param compressed: Is the binary data gzip compressed. This only supports the whole buffer compressed as one.\n" ":param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java.\n" ":param string_encoding: The bytes decoder function to parse strings. mutf8.decode_modified_utf8 for Java, amulet_nbt.utf8_escape_decoder for Bedrock.\n" + ":param named: If the tags to read are named.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) From 1f091fc3dcf6c9be6c27db422289fd83c26b1257 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 21:17:10 +0800 Subject: [PATCH 07/11] Remove redundant `std::optional` casts --- src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp | 8 ++++---- src/amulet_nbt/pybind/tag/py_array_tag.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp b/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp index 8d59f07b..292d4325 100644 --- a/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp +++ b/src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp @@ -205,7 +205,7 @@ inline void write_name_and_tag(AmuletNBT::BinaryWriter& writer, const std::optio template <> inline void write_payload(AmuletNBT::BinaryWriter& writer, const AmuletNBT::CompoundTag& value){ for (auto it = value.begin(); it != value.end(); it++){ - write_name_and_tag(writer, std::optional(it->first), it->second); + write_name_and_tag(writer, it->first, it->second); } writer.writeNumeric(0); }; @@ -256,10 +256,10 @@ namespace AmuletNBT { write_name_and_tag(writer, name, tag); } void write_nbt(BinaryWriter& writer, const std::string& name, const TagNode& tag) { - write_name_and_tag(writer, std::optional(name), tag); + write_name_and_tag(writer, name, tag); } void write_nbt(BinaryWriter& writer, const NamedTag& tag) { - write_name_and_tag(writer, std::optional(tag.name), tag.tag_node); + write_name_and_tag(writer, tag.name, tag.tag_node); } std::string write_nbt(const std::optional& name, const AmuletNBT::ByteTag& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ @@ -299,7 +299,7 @@ namespace AmuletNBT { return _write_nbt(name, tag, endianness, string_encode); }; std::string write_nbt(const std::string& name, const AmuletNBT::TagNode& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ - return _write_nbt(std::optional(name), tag, endianness, string_encode); + return _write_nbt(name, tag, endianness, string_encode); }; std::string write_nbt(const AmuletNBT::NamedTag& named_tag, std::endian endianness, AmuletNBT::StringEncode string_encode){ return write_nbt(named_tag.name, named_tag.tag_node, endianness, string_encode); diff --git a/src/amulet_nbt/pybind/tag/py_array_tag.cpp b/src/amulet_nbt/pybind/tag/py_array_tag.cpp index 3928087d..92ce4b8b 100644 --- a/src/amulet_nbt/pybind/tag/py_array_tag.cpp +++ b/src/amulet_nbt/pybind/tag/py_array_tag.cpp @@ -106,7 +106,7 @@ namespace py = pybind11; CLSNAME.def(\ py::pickle(\ [](const AmuletNBT::CLSNAME& self){\ - return py::bytes(AmuletNBT::write_nbt(std::optional(""), self, std::endian::big, AmuletNBT::utf8_to_mutf8));\ + return py::bytes(AmuletNBT::write_nbt("", self, std::endian::big, AmuletNBT::utf8_to_mutf8));\ },\ [](py::bytes state){\ return std::get(\ From 72c734b19ed4ed557c46ee1def977050526bdfd3 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 21:59:00 +0800 Subject: [PATCH 08/11] Make read functions return `NamedTag` with empty name when reading unnamed tags. --- src/amulet_nbt/__init__.pyi | 8 +- .../cpp/nbt_encoding/binary/read_binary.cpp | 64 ++++----------- .../amulet_nbt/nbt_encoding/binary.hpp | 15 ++-- src/amulet_nbt/pybind/bnbt.cpp | 78 ++++++------------- tests/test_nbt.py | 4 +- 5 files changed, 52 insertions(+), 117 deletions(-) diff --git a/src/amulet_nbt/__init__.pyi b/src/amulet_nbt/__init__.pyi index 85a65ea8..f88b54a2 100644 --- a/src/amulet_nbt/__init__.pyi +++ b/src/amulet_nbt/__init__.pyi @@ -1599,7 +1599,7 @@ def read_nbt( :param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from. :param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect. - :param named: If the tag to read is named. + :param named: If the tag to read is named, if not, return NamedTag with empty name. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ @@ -1620,7 +1620,7 @@ def read_nbt( :param compressed: Is the binary data gzip compressed. :param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java. :param string_encoding: The bytes decoder function to parse strings. mutf8_encoding for Java, utf8_escape_encoding for Bedrock. - :param named: If the tag to read is named. + :param named: If the tag to read is named, if not, return NamedTag with empty name. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ @@ -1639,7 +1639,7 @@ def read_nbt_array( :param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from. :param count: The number of binary NBT objects to read. Use -1 to exhaust the buffer. :param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect. - :param named: If the tags to read are named. + :param named: If the tags to read are named, if not, return NamedTags with empty name. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ @@ -1662,7 +1662,7 @@ def read_nbt_array( :param compressed: Is the binary data gzip compressed. This only supports the whole buffer compressed as one. :param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java. :param string_encoding: The bytes decoder function to parse strings. mutf8.decode_modified_utf8 for Java, amulet_nbt.utf8_escape_decoder for Bedrock. - :param named: If the tags to read are named. + :param named: If the tags to read are named, if not, return NamedTags with empty name. :param read_offset: Optional ReadOffset object to get read end offset. :raises: IndexError if the data is not long enough. """ diff --git a/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp b/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp index 7957de5d..0db0f0e8 100644 --- a/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp +++ b/src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp @@ -163,78 +163,46 @@ inline AmuletNBT::TagNode read_node(AmuletNBT::BinaryReader& reader, std::uint8_ namespace AmuletNBT { - AmuletNBT::NamedTag read_nbt(AmuletNBT::BinaryReader& reader){ + AmuletNBT::NamedTag read_nbt(AmuletNBT::BinaryReader& reader, bool named){ std::uint8_t tag_id = reader.readNumeric(); - std::string name = read_string_tag(reader); + std::string name = named ? read_string_tag(reader) : ""; AmuletNBT::TagNode node = read_node(reader, tag_id); return AmuletNBT::NamedTag(name, node); } - AmuletNBT::TagNode read_nbt_unnamed(AmuletNBT::BinaryReader& reader){ - std::uint8_t tag_id = reader.readNumeric(); - return read_node(reader, tag_id); - } - - // Read one named tag from the string at position offset. - AmuletNBT::NamedTag read_nbt(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ - AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); - return read_nbt(reader); - } - - // Read one named tag from the string at position offset. - AmuletNBT::TagNode read_nbt_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ + // Read one (un)named tag from the string at position offset. + AmuletNBT::NamedTag read_nbt(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, bool named, size_t& offset){ AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); - return read_nbt_unnamed(reader); + return read_nbt(reader, named); } - // Read one named tag from the string. - AmuletNBT::NamedTag read_nbt(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode){ + // Read one (un)named tag from the string. + AmuletNBT::NamedTag read_nbt(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, bool named){ size_t offset = 0; - return read_nbt(raw, endianness, string_decode, offset); + return read_nbt(raw, endianness, string_decode, named, offset); } // Read one named tag from the string. - AmuletNBT::TagNode read_nbt_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode){ - size_t offset = 0; - return read_nbt_unnamed(raw, endianness, string_decode, offset); + AmuletNBT::NamedTag read_nbt(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode){ + return read_nbt(raw, endianness, string_decode, true); } - // Read count named tags from the string at position offset. - std::vector read_nbt_array(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset, size_t count){ + // Read count (un)named tags from the string at position offset. + std::vector read_nbt_array(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, bool named, size_t& offset, size_t count){ AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); std::vector out; for (size_t i = 0; i < count; i++){ - out.push_back(read_nbt(reader)); - } - return out; - } - - // Read count named tags from the string at position offset. - std::vector read_nbt_array_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset, size_t count){ - AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); - std::vector out; - for (size_t i = 0; i < count; i++){ - out.push_back(read_nbt_unnamed(reader)); + out.push_back(read_nbt(reader, named)); } return out; } - // Read all named tags from the string at position offset. - std::vector read_nbt_array(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ + // Read all (un)named tags from the string at position offset. + std::vector read_nbt_array(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, bool named, size_t& offset){ AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); std::vector out; while (reader.has_more_data()){ - out.push_back(read_nbt(reader)); - } - return out; - } - - // Read all named tags from the string at position offset. - std::vector read_nbt_array_unnamed(const std::string& raw, std::endian endianness, AmuletNBT::StringDecode string_decode, size_t& offset){ - AmuletNBT::BinaryReader reader(raw, offset, endianness, string_decode); - std::vector out; - while (reader.has_more_data()){ - out.push_back(read_nbt_unnamed(reader)); + out.push_back(read_nbt(reader, named)); } return out; } diff --git a/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp b/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp index 1250f7b4..90ae2498 100644 --- a/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp +++ b/src/amulet_nbt/include/amulet_nbt/nbt_encoding/binary.hpp @@ -17,17 +17,12 @@ #include namespace AmuletNBT { - NamedTag read_nbt(BinaryReader& reader); - NamedTag read_nbt(const std::string&, std::endian, StringDecode, size_t& offset); + NamedTag read_nbt(BinaryReader& reader, bool named); + NamedTag read_nbt(const std::string&, std::endian, StringDecode, bool named, size_t& offset); + NamedTag read_nbt(const std::string&, std::endian, StringDecode, bool named); NamedTag read_nbt(const std::string&, std::endian, StringDecode); - std::vector read_nbt_array(const std::string&, std::endian, StringDecode, size_t& offset); - std::vector read_nbt_array(const std::string&, std::endian, StringDecode, size_t& offset, size_t count); - - TagNode read_nbt_unnamed(BinaryReader& reader); - TagNode read_nbt_unnamed(const std::string&, std::endian, StringDecode, size_t& offset); - TagNode read_nbt_unnamed(const std::string&, std::endian, StringDecode); - std::vector read_nbt_array_unnamed(const std::string&, std::endian, StringDecode, size_t& offset); - std::vector read_nbt_array_unnamed(const std::string&, std::endian, StringDecode, size_t& offset, size_t count); + std::vector read_nbt_array(const std::string&, std::endian, StringDecode, bool named, size_t& offset); + std::vector read_nbt_array(const std::string&, std::endian, StringDecode, bool named, size_t& offset, size_t count); void write_nbt(BinaryWriter&, const std::optional& name, const ByteTag&); void write_nbt(BinaryWriter&, const std::optional& name, const ShortTag&); diff --git a/src/amulet_nbt/pybind/bnbt.cpp b/src/amulet_nbt/pybind/bnbt.cpp index a36c324f..85f872ff 100644 --- a/src/amulet_nbt/pybind/bnbt.cpp +++ b/src/amulet_nbt/pybind/bnbt.cpp @@ -82,9 +82,6 @@ void init_bnbt(py::module& m) { return data; }; - typedef std::variant NodeOrNamedTag; - typedef std::variant, std::vector> NodeOrNamedTagVector; - auto read_nbt = [get_buffer]( py::object filepath_or_buffer, bool compressed, @@ -92,31 +89,24 @@ void init_bnbt(py::module& m) { AmuletNBT::StringDecode string_decoder, bool named, py::object read_offset_py - ) -> NodeOrNamedTag { + ) { std::string buffer = get_buffer(filepath_or_buffer, compressed); if (py::isinstance(read_offset_py)){ AmuletNBT::ReadOffset& read_offset = read_offset_py.cast(); - return named ? NodeOrNamedTag(AmuletNBT::read_nbt( - buffer, - endianness, - string_decoder, - read_offset.offset - )) : NodeOrNamedTag(AmuletNBT::read_nbt_unnamed( + return AmuletNBT::read_nbt( buffer, endianness, string_decoder, + named, read_offset.offset - )); + ); } else if (read_offset_py.is(py::none())){ - return named ? NodeOrNamedTag(AmuletNBT::read_nbt( + return AmuletNBT::read_nbt( buffer, endianness, - string_decoder - )) : NodeOrNamedTag(AmuletNBT::read_nbt_unnamed( - buffer, - endianness, - string_decoder - )); + string_decoder, + named + ); } else { throw std::invalid_argument("read_offset must be ReadOffset or None"); } @@ -149,7 +139,7 @@ void init_bnbt(py::module& m) { "\n" ":param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from.\n" ":param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect.\n" - ":param named: If the tag to read is named.\n" + ":param named: If the tag to read is named, if not, return NamedTag with empty name.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) @@ -187,7 +177,7 @@ void init_bnbt(py::module& m) { ":param compressed: Is the binary data gzip compressed.\n" ":param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java.\n" ":param string_encoding: The bytes decoder function to parse strings. mutf8_encoding for Java, utf8_escape_encoding for Bedrock.\n" - ":param named: If the tag to read is named.\n" + ":param named: If the tag to read is named, if not, return NamedTag with empty name.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) @@ -201,7 +191,7 @@ void init_bnbt(py::module& m) { AmuletNBT::StringDecode string_decoder, bool named, py::object read_offset_py - ) -> NodeOrNamedTagVector { + ) { if (count < -1){ throw std::invalid_argument("count must be -1 or higher"); } @@ -209,60 +199,42 @@ void init_bnbt(py::module& m) { if (py::isinstance(read_offset_py)){ AmuletNBT::ReadOffset& read_offset = read_offset_py.cast(); if (count == -1){ - return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( - buffer, - endianness, - string_decoder, - read_offset.offset - )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array( + return AmuletNBT::read_nbt_array( buffer, endianness, string_decoder, + named, read_offset.offset - )); + ); } else { - return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( + return AmuletNBT::read_nbt_array( buffer, endianness, string_decoder, + named, read_offset.offset, count - )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array_unnamed( - buffer, - endianness, - string_decoder, - read_offset.offset, - count - )); + ); } } else if (read_offset_py.is(py::none())){ size_t offset = 0; if (count == -1){ - return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( - buffer, - endianness, - string_decoder, - offset - )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array_unnamed( + return AmuletNBT::read_nbt_array( buffer, endianness, string_decoder, + named, offset - )); + ); } else { - return named ? NodeOrNamedTagVector(AmuletNBT::read_nbt_array( - buffer, - endianness, - string_decoder, - offset, - count - )) : NodeOrNamedTagVector(AmuletNBT::read_nbt_array_unnamed( + return AmuletNBT::read_nbt_array( buffer, endianness, string_decoder, + named, offset, count - )); + ); } } else { throw std::invalid_argument("read_offset must be ReadOffset or None"); @@ -299,7 +271,7 @@ void init_bnbt(py::module& m) { ":param filepath_or_buffer: A string path to a file on disk, a bytes or memory view object containing the binary NBT or a file-like object to read the binary data from.\n" ":param count: The number of binary NBT objects to read. Use -1 to exhaust the buffer.\n" ":param preset: The encoding preset. If this is defined little_endian and string_encoding have no effect.\n" - ":param named: If the tags to read are named.\n" + ":param named: If the tags to read are named, if not, return NamedTags with empty name.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) @@ -342,7 +314,7 @@ void init_bnbt(py::module& m) { ":param compressed: Is the binary data gzip compressed. This only supports the whole buffer compressed as one.\n" ":param little_endian: Are the numerical values stored as little endian. True for Bedrock, False for Java.\n" ":param string_encoding: The bytes decoder function to parse strings. mutf8.decode_modified_utf8 for Java, amulet_nbt.utf8_escape_decoder for Bedrock.\n" - ":param named: If the tags to read are named.\n" + ":param named: If the tags to read are named, if not, return NamedTags with empty name.\n" ":param read_offset: Optional ReadOffset object to get read end offset.\n" ":raises: IndexError if the data is not long enough." ) diff --git a/tests/test_nbt.py b/tests/test_nbt.py index 3d6ea102..37236f07 100644 --- a/tests/test_nbt.py +++ b/tests/test_nbt.py @@ -65,12 +65,12 @@ def test_unnamed(self) -> None: # Only one case is tested as the implementation of this is shared among all tag types and thus behaves the same self.assertEqual( amulet_nbt.read_nbt(b"\x01\x05", named=False, compressed=False, little_endian=False), - amulet_nbt.ByteTag(5), + amulet_nbt.NamedTag(amulet_nbt.ByteTag(5), ""), "reading unnamed tag" ) self.assertEqual( amulet_nbt.read_nbt_array(b"\x01\x05\x01\x06\x01\x07", named=False, count=-1, compressed=False, little_endian=False), - [amulet_nbt.ByteTag(i) for i in (5, 6, 7)], + [amulet_nbt.NamedTag(amulet_nbt.ByteTag(i), "") for i in (5, 6, 7)], "reading unnamed tag array" ) self.assertEqual( From dc9bbdc9891f888cbc38fffb9f83930c0c5b6372 Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Wed, 9 Oct 2024 23:02:03 +0800 Subject: [PATCH 09/11] oops, remove unused `#include ` --- src/amulet_nbt/pybind/tag/py_array_tag.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/amulet_nbt/pybind/tag/py_array_tag.cpp b/src/amulet_nbt/pybind/tag/py_array_tag.cpp index 92ce4b8b..7edd0cb7 100644 --- a/src/amulet_nbt/pybind/tag/py_array_tag.cpp +++ b/src/amulet_nbt/pybind/tag/py_array_tag.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include From e6bb9068e5da4dcf10af6256016c7f85f5eced7d Mon Sep 17 00:00:00 2001 From: shBLOCK Date: Thu, 10 Oct 2024 06:10:52 +0800 Subject: [PATCH 10/11] Fix typehint --- src/amulet_nbt/__init__.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amulet_nbt/__init__.pyi b/src/amulet_nbt/__init__.pyi index f88b54a2..24194061 100644 --- a/src/amulet_nbt/__init__.pyi +++ b/src/amulet_nbt/__init__.pyi @@ -142,7 +142,7 @@ class AbstractBaseTag: compressed: bool = True, little_endian: bool = False, string_encoding: StringEncoding = mutf8_encoding, - name: str | bytes = b"", + name: str | bytes | None = b"", ) -> bytes: """Convert the data to the binary NBT format. Optionally write to a file. From 115d99095754e5db98173e5e279c970da023cfb8 Mon Sep 17 00:00:00 2001 From: gentlegiantJGC Date: Mon, 14 Oct 2024 11:58:14 +0100 Subject: [PATCH 11/11] Reformatted --- tests/test_nbt.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/test_nbt.py b/tests/test_nbt.py index 37236f07..ef07d5d0 100644 --- a/tests/test_nbt.py +++ b/tests/test_nbt.py @@ -64,19 +64,29 @@ def test_write_little_endian(self) -> None: def test_unnamed(self) -> None: # Only one case is tested as the implementation of this is shared among all tag types and thus behaves the same self.assertEqual( - amulet_nbt.read_nbt(b"\x01\x05", named=False, compressed=False, little_endian=False), + amulet_nbt.read_nbt( + b"\x01\x05", named=False, compressed=False, little_endian=False + ), amulet_nbt.NamedTag(amulet_nbt.ByteTag(5), ""), - "reading unnamed tag" + "reading unnamed tag", ) self.assertEqual( - amulet_nbt.read_nbt_array(b"\x01\x05\x01\x06\x01\x07", named=False, count=-1, compressed=False, little_endian=False), + amulet_nbt.read_nbt_array( + b"\x01\x05\x01\x06\x01\x07", + named=False, + count=-1, + compressed=False, + little_endian=False, + ), [amulet_nbt.NamedTag(amulet_nbt.ByteTag(i), "") for i in (5, 6, 7)], - "reading unnamed tag array" + "reading unnamed tag array", ) self.assertEqual( - amulet_nbt.ByteTag(5).to_nbt(name=None, compressed=False, little_endian=False), + amulet_nbt.ByteTag(5).to_nbt( + name=None, compressed=False, little_endian=False + ), b"\x01\x05", - msg="writing unnamed tag" + msg="writing unnamed tag", )