mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
reading nbt
This commit is contained in:
parent
428d0ee0e6
commit
76e1985fc4
12 changed files with 324 additions and 5 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -80,6 +80,15 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "azalea-nbt"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "azalea-protocol"
|
name = "azalea-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -417,6 +426,26 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
@ -673,11 +702,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.14.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
|
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -690,9 +718,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.6.0"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
|
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -7,4 +7,5 @@ members = [
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
"azalea-auth",
|
"azalea-auth",
|
||||||
|
"azalea-nbt",
|
||||||
]
|
]
|
||||||
|
|
11
azalea-nbt/Cargo.toml
Normal file
11
azalea-nbt/Cargo.toml
Normal 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
3
azalea-nbt/README.md
Normal 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
111
azalea-nbt/src/decode.rs
Normal 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
117
azalea-nbt/src/encode.rs
Normal 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
5
azalea-nbt/src/error.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
InvalidTagType(u8),
|
||||||
|
InvalidTag,
|
||||||
|
}
|
6
azalea-nbt/src/lib.rs
Normal file
6
azalea-nbt/src/lib.rs
Normal 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
18
azalea-nbt/src/tag.rs
Normal 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
|
||||||
|
}
|
BIN
azalea-nbt/tests/bigtest.nbt
Normal file
BIN
azalea-nbt/tests/bigtest.nbt
Normal file
Binary file not shown.
19
azalea-nbt/tests/decode.rs
Normal file
19
azalea-nbt/tests/decode.rs
Normal 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()),
|
||||||
|
)]))
|
||||||
|
)]))
|
||||||
|
);
|
||||||
|
}
|
BIN
azalea-nbt/tests/hello_world.nbt
Normal file
BIN
azalea-nbt/tests/hello_world.nbt
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue