reading nbt

This commit is contained in:
mat 2021-12-18 17:02:23 -06:00
parent 428d0ee0e6
commit 76e1985fc4
12 changed files with 324 additions and 5 deletions

38
Cargo.lock generated
View file

@ -80,6 +80,15 @@ dependencies = [
"uuid",
]
[[package]]
name = "azalea-nbt"
version = "0.1.0"
dependencies = [
"byteorder",
"num-derive",
"num-traits",
]
[[package]]
name = "azalea-protocol"
version = "0.1.0"
@ -417,6 +426,26 @@ dependencies = [
"winapi",
]
[[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"
@ -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",

View file

@ -7,4 +7,5 @@ members = [
"azalea-chat",
"azalea-core",
"azalea-auth",
"azalea-nbt",
]

11
azalea-nbt/Cargo.toml Normal file
View file

@ -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"

3
azalea-nbt/README.md Normal file
View file

@ -0,0 +1,3 @@
Deserialize Minecraft NBT. This is somewhat based on [Hermatite NBT](https://github.com/PistonDevelopers/hematite_nbt).

111
azalea-nbt/src/decode.rs Normal file
View file

@ -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)
}
}

117
azalea-nbt/src/encode.rs Normal file
View file

@ -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)
}
}

5
azalea-nbt/src/error.rs Normal file
View file

@ -0,0 +1,5 @@
#[derive(Debug)]
pub enum Error {
InvalidTagType(u8),
InvalidTag,
}

6
azalea-nbt/src/lib.rs Normal file
View file

@ -0,0 +1,6 @@
mod decode;
mod error;
mod tag;
pub use error::Error;
pub use tag::Tag;

18
azalea-nbt/src/tag.rs Normal file
View file

@ -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
}

Binary file not shown.

View file

@ -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()),
)]))
)]))
);
}

Binary file not shown.