aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock38
-rw-r--r--Cargo.toml1
-rw-r--r--azalea-nbt/Cargo.toml11
-rw-r--r--azalea-nbt/README.md3
-rw-r--r--azalea-nbt/src/decode.rs111
-rw-r--r--azalea-nbt/src/encode.rs117
-rw-r--r--azalea-nbt/src/error.rs5
-rw-r--r--azalea-nbt/src/lib.rs6
-rw-r--r--azalea-nbt/src/tag.rs18
-rw-r--r--azalea-nbt/tests/bigtest.nbtbin0 -> 507 bytes
-rw-r--r--azalea-nbt/tests/decode.rs19
-rw-r--r--azalea-nbt/tests/hello_world.nbtbin0 -> 33 bytes
12 files changed, 324 insertions, 5 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c4ecf771..6c46763e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -81,6 +81,15 @@ dependencies = [
]
[[package]]
+name = "azalea-nbt"
+version = "0.1.0"
+dependencies = [
+ "byteorder",
+ "num-derive",
+ "num-traits",
+]
+
+[[package]]
name = "azalea-protocol"
version = "0.1.0"
dependencies = [
@@ -418,6 +427,26 @@ dependencies = [
]
[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -673,11 +702,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
-version = "1.14.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
+checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
dependencies = [
- "autocfg",
"bytes",
"libc",
"memchr",
@@ -690,9 +718,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "1.6.0"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
+checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 0cf441eb..d22c326b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,4 +7,5 @@ members = [
"azalea-chat",
"azalea-core",
"azalea-auth",
+ "azalea-nbt",
]
diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml
new file mode 100644
index 00000000..38fa4123
--- /dev/null
+++ b/azalea-nbt/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+edition = "2021"
+name = "azalea-nbt"
+version = "0.1.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+byteorder = "^1.4.3"
+num-derive = "^0.3.3"
+num-traits = "^0.2.14"
diff --git a/azalea-nbt/README.md b/azalea-nbt/README.md
new file mode 100644
index 00000000..448c306b
--- /dev/null
+++ b/azalea-nbt/README.md
@@ -0,0 +1,3 @@
+Deserialize Minecraft NBT. This is somewhat based on [Hermatite NBT](https://github.com/PistonDevelopers/hematite_nbt).
+
+
diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs
new file mode 100644
index 00000000..704c8e2a
--- /dev/null
+++ b/azalea-nbt/src/decode.rs
@@ -0,0 +1,111 @@
+use crate::Error;
+use crate::Tag;
+use byteorder::{ReadBytesExt, BE};
+use std::{collections::HashMap, io::Read};
+
+impl Tag {
+ fn read_known(stream: &mut impl Read, id: u8) -> Result<Tag, Error> {
+ let tag = match id {
+ // Signifies the end of a TAG_Compound. It is only ever used inside
+ // a TAG_Compound, and is not named despite being in a TAG_Compound
+ 0 => Tag::End,
+ // A single signed byte
+ 1 => Tag::Byte(stream.read_i8().map_err(|_| Error::InvalidTag)?),
+ // A single signed, big endian 16 bit integer
+ 2 => Tag::Short(stream.read_i16::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single signed, big endian 32 bit integer
+ 3 => Tag::Int(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single signed, big endian 64 bit integer
+ 4 => Tag::Long(stream.read_i64::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single, big endian IEEE-754 single-precision floating point
+ // number (NaN possible)
+ 5 => Tag::Float(stream.read_f32::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single, big endian IEEE-754 double-precision floating point
+ // number (NaN possible)
+ 6 => Tag::Double(stream.read_f64::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A length-prefixed array of signed bytes. The prefix is a signed
+ // integer (thus 4 bytes)
+ 7 => {
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut bytes = Vec::new();
+ for _ in 0..length {
+ bytes.push(stream.read_i8().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::ByteArray(bytes)
+ }
+ // A length-prefixed modified UTF-8 string. The prefix is an
+ // unsigned short (thus 2 bytes) signifying the length of the
+ // string in bytes
+ 8 => {
+ let length = stream.read_u16::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut bytes = Vec::new();
+ for _ in 0..length {
+ bytes.push(stream.read_u8().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::String(String::from_utf8(bytes).map_err(|_| Error::InvalidTag)?)
+ }
+ // A list of nameless tags, all of the same type. The list is
+ // prefixed with the Type ID of the items it contains (thus 1
+ // byte), and the length of the list as a signed integer (a further
+ // 4 bytes). If the length of the list is 0 or negative, the type
+ // may be 0 (TAG_End) but otherwise it must be any other type. (The
+ // notchian implementation uses TAG_End in that situation, but
+ // another reference implementation by Mojang uses 1 instead;
+ // parsers should accept any type if the length is <= 0).
+ 9 => {
+ let type_id = stream.read_u8().map_err(|_| Error::InvalidTag)?;
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut list = Vec::new();
+ for _ in 0..length {
+ list.push(Tag::read_known(stream, type_id)?);
+ }
+ Tag::List(list)
+ }
+ // Effectively a list of a named tags. Order is not guaranteed.
+ 10 => {
+ let mut map = HashMap::new();
+ loop {
+ let tag_id = stream.read_u8().unwrap_or(0);
+ if tag_id == 0 {
+ break;
+ }
+ let name = match Tag::read_known(stream, 8)? {
+ Tag::String(name) => name,
+ _ => panic!("Expected a string tag"),
+ };
+ let tag = Tag::read_known(stream, tag_id).map_err(|_| Error::InvalidTag)?;
+ map.insert(name, tag);
+ }
+ Tag::Compound(map)
+ }
+ // A length-prefixed array of signed integers. The prefix is a
+ // signed integer (thus 4 bytes) and indicates the number of 4 byte
+ // integers.
+ 11 => {
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut ints = Vec::new();
+ for _ in 0..length {
+ ints.push(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::IntArray(ints)
+ }
+ // A length-prefixed array of signed longs. The prefix is a signed
+ // integer (thus 4 bytes) and indicates the number of 8 byte longs.
+ 12 => {
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut longs = Vec::new();
+ for _ in 0..length {
+ longs.push(stream.read_i64::<BE>().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::LongArray(longs)
+ }
+ _ => return Err(Error::InvalidTagType(id)),
+ };
+ Ok(tag)
+ }
+
+ pub fn read(stream: &mut impl Read) -> Result<Tag, Error> {
+ // default to compound tag
+ Tag::read_known(stream, 10)
+ }
+}
diff --git a/azalea-nbt/src/encode.rs b/azalea-nbt/src/encode.rs
new file mode 100644
index 00000000..86c0d868
--- /dev/null
+++ b/azalea-nbt/src/encode.rs
@@ -0,0 +1,117 @@
+use byteorder::{ReadBytesExt, BE};
+use error::Error;
+use std::{collections::HashMap, io::Read};
+use tag::Tag;
+
+impl Tag {
+ fn write(&self, stream: &mut impl Read) -> Result<(), Error> {
+ println!("read_known: id={}", id);
+ let tag = match id {
+ // Signifies the end of a TAG_Compound. It is only ever used inside
+ // a TAG_Compound, and is not named despite being in a TAG_Compound
+ 0 => Tag::End,
+ // A single signed byte
+ 1 => Tag::Byte(stream.read_i8().map_err(|_| Error::InvalidTag)?),
+ // A single signed, big endian 16 bit integer
+ 2 => Tag::Short(stream.read_i16::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single signed, big endian 32 bit integer
+ 3 => Tag::Int(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single signed, big endian 64 bit integer
+ 4 => Tag::Long(stream.read_i64::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single, big endian IEEE-754 single-precision floating point
+ // number (NaN possible)
+ 5 => Tag::Float(stream.read_f32::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A single, big endian IEEE-754 double-precision floating point
+ // number (NaN possible)
+ 6 => Tag::Double(stream.read_f64::<BE>().map_err(|_| Error::InvalidTag)?),
+ // A length-prefixed array of signed bytes. The prefix is a signed
+ // integer (thus 4 bytes)
+ 7 => {
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut bytes = Vec::new();
+ for _ in 0..length {
+ bytes.push(stream.read_i8().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::ByteArray(bytes)
+ }
+ // A length-prefixed modified UTF-8 string. The prefix is an
+ // unsigned short (thus 2 bytes) signifying the length of the
+ // string in bytes
+ 8 => {
+ let length = stream.read_u16::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut bytes = Vec::new();
+ for _ in 0..length {
+ bytes.push(stream.read_u8().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::String(String::from_utf8(bytes).map_err(|_| Error::InvalidTag)?)
+ }
+ // A list of nameless tags, all of the same type. The list is
+ // prefixed with the Type ID of the items it contains (thus 1
+ // byte), and the length of the list as a signed integer (a further
+ // 4 bytes). If the length of the list is 0 or negative, the type
+ // may be 0 (TAG_End) but otherwise it must be any other type. (The
+ // notchian implementation uses TAG_End in that situation, but
+ // another reference implementation by Mojang uses 1 instead;
+ // parsers should accept any type if the length is <= 0).
+ 9 => {
+ let type_id = stream.read_u8().map_err(|_| Error::InvalidTag)?;
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut list = Vec::new();
+ for _ in 0..length {
+ list.push(Tag::read_known(stream, type_id)?);
+ }
+ Tag::List(list)
+ }
+ // Effectively a list of a named tags. Order is not guaranteed.
+ 10 => {
+ println!("reading compound {{");
+ let mut map = HashMap::new();
+ loop {
+ let tag_id = stream.read_u8().unwrap_or(0);
+ println!("compound tag id: {}", tag_id);
+ if tag_id == 0 {
+ break;
+ }
+ let name = match Tag::read_known(stream, 8)? {
+ Tag::String(name) => name,
+ _ => panic!("Expected a string tag"),
+ };
+ println!("compound name: {}", name);
+ let tag = Tag::read_known(stream, tag_id).map_err(|_| Error::InvalidTag)?;
+ println!("aight read tag: {:?}", tag);
+ map.insert(name, tag);
+ }
+ println!("}} compound map: {:?}", map);
+ Tag::Compound(map)
+ }
+ // A length-prefixed array of signed integers. The prefix is a
+ // signed integer (thus 4 bytes) and indicates the number of 4 byte
+ // integers.
+ 11 => {
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut ints = Vec::new();
+ for _ in 0..length {
+ ints.push(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::IntArray(ints)
+ }
+ // A length-prefixed array of signed longs. The prefix is a signed
+ // integer (thus 4 bytes) and indicates the number of 8 byte longs.
+ 12 => {
+ let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
+ let mut longs = Vec::new();
+ for _ in 0..length {
+ longs.push(stream.read_i64::<BE>().map_err(|_| Error::InvalidTag)?);
+ }
+ Tag::LongArray(longs)
+ }
+ _ => return Err(Error::InvalidTagType(id)),
+ };
+ Ok(tag)
+ }
+
+ pub fn read(stream: &mut impl Read) -> Result<Tag, Error> {
+ // default to compound tag
+ Tag::read_known(stream, 10)
+ }
+}
diff --git a/azalea-nbt/src/error.rs b/azalea-nbt/src/error.rs
new file mode 100644
index 00000000..149a00e9
--- /dev/null
+++ b/azalea-nbt/src/error.rs
@@ -0,0 +1,5 @@
+#[derive(Debug)]
+pub enum Error {
+ InvalidTagType(u8),
+ InvalidTag,
+}
diff --git a/azalea-nbt/src/lib.rs b/azalea-nbt/src/lib.rs
new file mode 100644
index 00000000..adeb6e56
--- /dev/null
+++ b/azalea-nbt/src/lib.rs
@@ -0,0 +1,6 @@
+mod decode;
+mod error;
+mod tag;
+
+pub use error::Error;
+pub use tag::Tag;
diff --git a/azalea-nbt/src/tag.rs b/azalea-nbt/src/tag.rs
new file mode 100644
index 00000000..1f496c9d
--- /dev/null
+++ b/azalea-nbt/src/tag.rs
@@ -0,0 +1,18 @@
+use std::collections::HashMap;
+
+#[derive(Debug, PartialEq)]
+pub enum Tag {
+ End, // 0
+ Byte(i8), // 1
+ Short(i16), // 2
+ Int(i32), // 3
+ Long(i64), // 4
+ Float(f32), // 5
+ Double(f64), // 6
+ ByteArray(Vec<i8>), // 7
+ String(String), // 8
+ List(Vec<Tag>), // 9
+ Compound(HashMap<String, Tag>), // 10
+ IntArray(Vec<i32>), // 11
+ LongArray(Vec<i64>), // 12
+}
diff --git a/azalea-nbt/tests/bigtest.nbt b/azalea-nbt/tests/bigtest.nbt
new file mode 100644
index 00000000..dc3769bc
--- /dev/null
+++ b/azalea-nbt/tests/bigtest.nbt
Binary files differ
diff --git a/azalea-nbt/tests/decode.rs b/azalea-nbt/tests/decode.rs
new file mode 100644
index 00000000..f4060512
--- /dev/null
+++ b/azalea-nbt/tests/decode.rs
@@ -0,0 +1,19 @@
+use azalea_nbt::Tag;
+use std::collections::HashMap;
+
+#[test]
+fn test_hello_world() {
+ // read hello_world.nbt
+ let mut file = std::fs::File::open("tests/hello_world.nbt").unwrap();
+ let tag = Tag::read(&mut file).unwrap();
+ assert_eq!(
+ tag,
+ Tag::Compound(HashMap::from_iter(vec![(
+ "hello world".to_string(),
+ Tag::Compound(HashMap::from_iter(vec![(
+ "name".to_string(),
+ Tag::String("Bananrama".to_string()),
+ )]))
+ )]))
+ );
+}
diff --git a/azalea-nbt/tests/hello_world.nbt b/azalea-nbt/tests/hello_world.nbt
new file mode 100644
index 00000000..f3f5e21a
--- /dev/null
+++ b/azalea-nbt/tests/hello_world.nbt
Binary files differ