Skip to content

Commit

Permalink
Support unnamed bnbt tags (#89)
Browse files Browse the repository at this point in the history
* Support writing unnamed bnbt tags

* Support reading unnamed tags

* Remove redundant `.none(true)` on `name` parameters

* Add tests for unnamed tag

* Put the `named` parameter before `read_offset`

* Add docs and typehint for unnamed tags

* Remove redundant `std::optional` casts

* Make read functions return `NamedTag` with empty name when reading unnamed tags.

* oops, remove unused `#include <optional>`

* Fix typehint

* Reformatted

---------

Co-authored-by: gentlegiantJGC <[email protected]>
  • Loading branch information
shBLOCK and gentlegiantJGC authored Oct 14, 2024
1 parent 7f40e69 commit 0d921f5
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 87 deletions.
16 changes: 12 additions & 4 deletions src/amulet_nbt/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ 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.
:param preset: A class containing endianness and encoding presets.
: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.
"""

Expand All @@ -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.
Expand All @@ -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.
"""

Expand Down Expand Up @@ -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, 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.
"""
Expand All @@ -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.
Expand All @@ -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, 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.
"""
Expand All @@ -1627,13 +1631,15 @@ 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.
: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, 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.
"""
Expand All @@ -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.
Expand All @@ -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, 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.
"""
Expand Down
31 changes: 18 additions & 13 deletions src/amulet_nbt/cpp/nbt_encoding/binary/read_binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,41 +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::uint8_t>();
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);
}

// 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){
// 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(reader);
return read_nbt(reader, named);
}

// 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, named, offset);
}

// 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);
return read_nbt(raw, endianness, string_decode, true);
}

// Read count named tags from the string at position offset.
std::vector<AmuletNBT::NamedTag> 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<AmuletNBT::NamedTag> 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<AmuletNBT::NamedTag> out;
for (size_t i = 0; i < count; i++){
out.push_back(read_nbt(reader));
out.push_back(read_nbt(reader, named));
}
return out;
}

// Read all named tags from the string at position offset.
std::vector<AmuletNBT::NamedTag> 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<AmuletNBT::NamedTag> 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<AmuletNBT::NamedTag> out;
while (reader.has_more_data()){
out.push_back(read_nbt(reader));
out.push_back(read_nbt(reader, named));
}
return out;
}
Expand Down
59 changes: 30 additions & 29 deletions src/amulet_nbt/cpp/nbt_encoding/binary/write_binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cstdint>
#include <memory>
#include <vector>
#include <optional>

#include <amulet_nbt/common.hpp>
#include <amulet_nbt/tag/int.hpp>
Expand Down Expand Up @@ -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<std::string>& name, const T& tag){
writer.writeNumeric<std::uint8_t>(AmuletNBT::tag_id_v<T>);
write_string(writer, name);
if (name) write_string(writer, *name);
write_payload(writer, tag);
}

Expand All @@ -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<std::string>& name, const T tag) {
write_name_and_tag<typename T::element_type>(writer, name, *tag);
}

Expand All @@ -193,7 +194,7 @@ template <
typename T,
std::enable_if_t<std::is_same_v<T, AmuletNBT::TagNode>, 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<std::string>& name, const AmuletNBT::TagNode& node){
std::visit([&writer, &name](auto&& tag) {
using tagT = std::decay_t<decltype(tag)>;
write_name_and_tag<tagT>(writer, name, tag);
Expand All @@ -211,47 +212,47 @@ inline void write_payload<AmuletNBT::CompoundTag>(AmuletNBT::BinaryWriter& write


template <typename T>
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<std::string>& name, const T& tag, std::endian endianness, AmuletNBT::StringEncode string_encode){
AmuletNBT::BinaryWriter writer(endianness, string_encode);
write_name_and_tag<T>(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<std::string>& name, const ByteTag& tag) {
write_name_and_tag<ByteTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const ShortTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const ShortTag& tag) {
write_name_and_tag<ShortTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const IntTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const IntTag& tag) {
write_name_and_tag<IntTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const LongTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const LongTag& tag) {
write_name_and_tag<LongTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const FloatTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const FloatTag& tag) {
write_name_and_tag<FloatTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const DoubleTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const DoubleTag& tag) {
write_name_and_tag<DoubleTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const ByteArrayTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const ByteArrayTag& tag) {
write_name_and_tag<ByteArrayTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const StringTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const StringTag& tag) {
write_name_and_tag<StringTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const ListTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const ListTag& tag) {
write_name_and_tag<ListTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const CompoundTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const CompoundTag& tag) {
write_name_and_tag<CompoundTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const IntArrayTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const IntArrayTag& tag) {
write_name_and_tag<IntArrayTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const LongArrayTag& tag) {
void write_nbt(BinaryWriter& writer, const std::optional<std::string>& name, const LongArrayTag& tag) {
write_name_and_tag<LongArrayTag>(writer, name, tag);
}
void write_nbt(BinaryWriter& writer, const std::string& name, const TagNode& tag) {
Expand All @@ -261,40 +262,40 @@ namespace AmuletNBT {
write_name_and_tag<TagNode>(writer, 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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){
Expand Down
Loading

0 comments on commit 0d921f5

Please sign in to comment.