mirror of
https://github.com/Cactus-minecraft-server/nbt.git
synced 2025-12-07 10:40:37 +00:00
a bit more
This commit is contained in:
255
src/io.rs
Normal file
255
src/io.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
// src/io.rs
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Error, ErrorKind, Read, Result, Write},
|
||||
};
|
||||
|
||||
use crate::{Tag, TagId};
|
||||
|
||||
/// Binary reader for NBT format
|
||||
pub struct Reader<R: Read> {
|
||||
inner: R,
|
||||
}
|
||||
|
||||
impl<R: Read> Reader<R> {
|
||||
pub fn new(inner: R) -> Self {
|
||||
Reader { inner }
|
||||
}
|
||||
|
||||
/// Read a full tag (ID + name + payload)
|
||||
pub fn read_tag(&mut self) -> Result<Tag> {
|
||||
let id = self.read_u8()?;
|
||||
if id == 0 {
|
||||
return Ok(Tag::End);
|
||||
}
|
||||
let name = Some(self.read_string()?);
|
||||
self.read_payload(id, name)
|
||||
}
|
||||
|
||||
fn read_payload(&mut self, id: TagId, name: Option<String>) -> Result<Tag> {
|
||||
match id {
|
||||
1 => Ok(Tag::Byte {
|
||||
name,
|
||||
value: self.read_i8()?,
|
||||
}),
|
||||
2 => Ok(Tag::Short {
|
||||
name,
|
||||
value: self.read_i16()?,
|
||||
}),
|
||||
3 => Ok(Tag::Int {
|
||||
name,
|
||||
value: self.read_i32()?,
|
||||
}),
|
||||
4 => Ok(Tag::Long {
|
||||
name,
|
||||
value: self.read_i64()?,
|
||||
}),
|
||||
5 => Ok(Tag::Float {
|
||||
name,
|
||||
value: self.read_f32()?,
|
||||
}),
|
||||
6 => Ok(Tag::Double {
|
||||
name,
|
||||
value: self.read_f64()?,
|
||||
}),
|
||||
7 => {
|
||||
let len = self.read_i32()? as usize;
|
||||
let mut buf = vec![0u8; len];
|
||||
self.inner.read_exact(&mut buf)?;
|
||||
Ok(Tag::ByteArray { name, value: buf })
|
||||
}
|
||||
8 => {
|
||||
let s = self.read_string()?;
|
||||
Ok(Tag::String { name, value: s })
|
||||
}
|
||||
9 => {
|
||||
let elem_id = self.read_u8()?;
|
||||
let len = self.read_i32()? as usize;
|
||||
let mut elements = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
elements.push(self.read_payload(elem_id, None)?);
|
||||
}
|
||||
Ok(Tag::List {
|
||||
name,
|
||||
element_id: elem_id,
|
||||
elements,
|
||||
})
|
||||
}
|
||||
10 => {
|
||||
let mut entries = HashMap::new();
|
||||
loop {
|
||||
let id = self.read_u8()?;
|
||||
if id == 0 {
|
||||
break;
|
||||
}
|
||||
let key = self.read_string()?;
|
||||
let tag = self.read_payload(id, Some(key.clone()))?;
|
||||
entries.insert(key, tag);
|
||||
}
|
||||
Ok(Tag::Compound { name, entries })
|
||||
}
|
||||
11 => {
|
||||
let len = self.read_i32()? as usize;
|
||||
let mut v = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
v.push(self.read_i32()?);
|
||||
}
|
||||
Ok(Tag::IntArray { name, value: v })
|
||||
}
|
||||
12 => {
|
||||
let len = self.read_i32()? as usize;
|
||||
let mut v = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
v.push(self.read_i64()?);
|
||||
}
|
||||
Ok(Tag::LongArray { name, value: v })
|
||||
}
|
||||
other => Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("Unknown tag id {}", other),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> Result<u8> {
|
||||
let mut buf = [0u8; 1];
|
||||
self.inner.read_exact(&mut buf)?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
fn read_i8(&mut self) -> Result<i8> {
|
||||
Ok(self.read_u8()? as i8)
|
||||
}
|
||||
fn read_i16(&mut self) -> Result<i16> {
|
||||
let mut buf = [0u8; 2];
|
||||
self.inner.read_exact(&mut buf)?;
|
||||
Ok(i16::from_be_bytes(buf))
|
||||
}
|
||||
fn read_i32(&mut self) -> Result<i32> {
|
||||
let mut buf = [0u8; 4];
|
||||
self.inner.read_exact(&mut buf)?;
|
||||
Ok(i32::from_be_bytes(buf))
|
||||
}
|
||||
fn read_i64(&mut self) -> Result<i64> {
|
||||
let mut buf = [0u8; 8];
|
||||
self.inner.read_exact(&mut buf)?;
|
||||
Ok(i64::from_be_bytes(buf))
|
||||
}
|
||||
fn read_f32(&mut self) -> Result<f32> {
|
||||
Ok(f32::from_bits(self.read_i32()? as u32))
|
||||
}
|
||||
fn read_f64(&mut self) -> Result<f64> {
|
||||
Ok(f64::from_bits(self.read_i64()? as u64))
|
||||
}
|
||||
fn read_string(&mut self) -> Result<String> {
|
||||
let len = self.read_i16()? as usize;
|
||||
let mut buf = vec![0u8; len];
|
||||
self.inner.read_exact(&mut buf)?;
|
||||
String::from_utf8(buf).map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Binary writer for NBT format
|
||||
pub struct Writer<W: Write> {
|
||||
inner: W,
|
||||
}
|
||||
|
||||
impl<W: Write> Writer<W> {
|
||||
pub fn new(inner: W) -> Self {
|
||||
Writer { inner }
|
||||
}
|
||||
|
||||
/// Write a full tag (ID + name + payload)
|
||||
pub fn write_tag(&mut self, tag: &Tag) -> Result<()> {
|
||||
let id = tag.id();
|
||||
self.write_u8(id)?;
|
||||
if id != 0 {
|
||||
if let Some(name) = tag.name() {
|
||||
self.write_string(name)?;
|
||||
} else {
|
||||
self.write_string("")?;
|
||||
}
|
||||
self.write_payload(tag)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_payload(&mut self, tag: &Tag) -> Result<()> {
|
||||
match tag {
|
||||
Tag::End => {}
|
||||
Tag::Byte { value, .. } => self.write_i8(*value)?,
|
||||
Tag::Short { value, .. } => self.write_i16(*value)?,
|
||||
Tag::Int { value, .. } => self.write_i32(*value)?,
|
||||
Tag::Long { value, .. } => self.write_i64(*value)?,
|
||||
Tag::Float { value, .. } => self.write_f32(*value)?,
|
||||
Tag::Double { value, .. } => self.write_f64(*value)?,
|
||||
Tag::ByteArray { value, .. } => {
|
||||
self.write_i32(value.len() as i32)?;
|
||||
self.inner.write_all(value)?;
|
||||
}
|
||||
Tag::String { value, .. } => {
|
||||
self.write_string(value)?;
|
||||
}
|
||||
Tag::List {
|
||||
element_id,
|
||||
elements,
|
||||
..
|
||||
} => {
|
||||
self.write_u8(*element_id)?;
|
||||
self.write_i32(elements.len() as i32)?;
|
||||
for elem in elements {
|
||||
self.write_payload(elem)?; // lists omit names
|
||||
}
|
||||
}
|
||||
Tag::Compound { entries, .. } => {
|
||||
for (key, entry) in entries {
|
||||
let id = entry.id();
|
||||
self.write_u8(id)?;
|
||||
self.write_string(key)?;
|
||||
self.write_payload(entry)?;
|
||||
}
|
||||
self.write_u8(0)?; // TAG_End
|
||||
}
|
||||
Tag::IntArray { value, .. } => {
|
||||
self.write_i32(value.len() as i32)?;
|
||||
for &i in value {
|
||||
self.write_i32(i)?;
|
||||
}
|
||||
}
|
||||
Tag::LongArray { value, .. } => {
|
||||
self.write_i32(value.len() as i32)?;
|
||||
for &l in value {
|
||||
self.write_i64(l)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_u8(&mut self, v: u8) -> Result<()> {
|
||||
self.inner.write_all(&[v])
|
||||
}
|
||||
fn write_i8(&mut self, v: i8) -> Result<()> {
|
||||
self.write_u8(v as u8)
|
||||
}
|
||||
fn write_i16(&mut self, v: i16) -> Result<()> {
|
||||
self.inner.write_all(&v.to_be_bytes())
|
||||
}
|
||||
fn write_i32(&mut self, v: i32) -> Result<()> {
|
||||
self.inner.write_all(&v.to_be_bytes())
|
||||
}
|
||||
fn write_i64(&mut self, v: i64) -> Result<()> {
|
||||
self.inner.write_all(&v.to_be_bytes())
|
||||
}
|
||||
fn write_f32(&mut self, v: f32) -> Result<()> {
|
||||
self.write_i32(v.to_bits() as i32)
|
||||
}
|
||||
fn write_f64(&mut self, v: f64) -> Result<()> {
|
||||
self.write_i64(v.to_bits() as i64)
|
||||
}
|
||||
fn write_string(&mut self, s: &str) -> Result<()> {
|
||||
let bytes = s.as_bytes();
|
||||
self.write_i16(bytes.len() as i16)?;
|
||||
self.inner.write_all(bytes)
|
||||
}
|
||||
}
|
||||
89
src/lib.rs
89
src/lib.rs
@@ -1,10 +1,15 @@
|
||||
// src/lib.rs
|
||||
|
||||
use std::collections::HashMap;
|
||||
pub mod io;
|
||||
mod test;
|
||||
// Id of nbt
|
||||
pub use io::{Reader, Writer};
|
||||
|
||||
/// Identifier for an NBT tag type
|
||||
pub type TagId = u8;
|
||||
|
||||
// All the types of nbt
|
||||
#[derive(PartialEq,Clone,Debug,)]
|
||||
/// All NBT tag types
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum Tag {
|
||||
End,
|
||||
Byte {
|
||||
@@ -95,7 +100,7 @@ impl Tag {
|
||||
value: v,
|
||||
}
|
||||
}
|
||||
pub fn new_ByteArray(name: impl Into<String>, v: Vec<u8>) -> Tag {
|
||||
pub fn new_byte_array(name: impl Into<String>, v: Vec<u8>) -> Tag {
|
||||
Tag::ByteArray {
|
||||
name: Some(name.into()),
|
||||
value: v,
|
||||
@@ -134,3 +139,79 @@ impl Tag {
|
||||
}
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
/// Insert a sub-tag into a Compound
|
||||
pub fn insert(&mut self, key: String, tag: Tag) {
|
||||
if let Tag::Compound { entries, .. } = self {
|
||||
entries.insert(key, tag);
|
||||
} else {
|
||||
panic!("insert() called on non-Compound");
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve a sub-tag from a Compound
|
||||
pub fn get(&self, key: &str) -> Option<&Tag> {
|
||||
if let Tag::Compound { entries, .. } = self {
|
||||
entries.get(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an element to a List
|
||||
pub fn push(&mut self, tag: Tag) {
|
||||
if let Tag::List { elements, .. } = self {
|
||||
elements.push(tag);
|
||||
} else {
|
||||
panic!("push() called on non-List");
|
||||
}
|
||||
}
|
||||
|
||||
/// Numeric ID of this tag (0 = End, 1 = Byte, …, 12 = LongArray)
|
||||
pub fn id(&self) -> TagId {
|
||||
match self {
|
||||
Tag::End => 0,
|
||||
Tag::Byte { .. } => 1,
|
||||
Tag::Short { .. } => 2,
|
||||
Tag::Int { .. } => 3,
|
||||
Tag::Long { .. } => 4,
|
||||
Tag::Float { .. } => 5,
|
||||
Tag::Double { .. } => 6,
|
||||
Tag::ByteArray { .. } => 7,
|
||||
Tag::String { .. } => 8,
|
||||
Tag::List { .. } => 9,
|
||||
Tag::Compound { .. } => 10,
|
||||
Tag::IntArray { .. } => 11,
|
||||
Tag::LongArray { .. } => 12,
|
||||
}
|
||||
}
|
||||
|
||||
/// Name of this tag (None for End or list elements)
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
match self {
|
||||
Tag::End => None,
|
||||
Tag::Byte { name, .. }
|
||||
| Tag::Short { name, .. }
|
||||
| Tag::Int { name, .. }
|
||||
| Tag::Long { name, .. }
|
||||
| Tag::Float { name, .. }
|
||||
| Tag::Double { name, .. }
|
||||
| Tag::ByteArray { name, .. }
|
||||
| Tag::String { name, .. }
|
||||
| Tag::List { name, .. }
|
||||
| Tag::Compound { name, .. }
|
||||
| Tag::IntArray { name, .. }
|
||||
| Tag::LongArray { name, .. } => name.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an NBT Tag from any reader
|
||||
pub fn read_nbt<R: std::io::Read>(reader: R) -> std::io::Result<Tag> {
|
||||
Reader::new(reader).read_tag()
|
||||
}
|
||||
|
||||
/// Write an NBT Tag to any writer
|
||||
pub fn write_nbt<W: std::io::Write>(tag: &Tag, writer: W) -> std::io::Result<()> {
|
||||
Writer::new(writer).write_tag(tag)
|
||||
}
|
||||
|
||||
25
src/test.rs
25
src/test.rs
@@ -1,7 +1,7 @@
|
||||
// src/test.rs
|
||||
|
||||
use crate::{Tag, read_nbt, write_nbt};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Tag;
|
||||
#[test]
|
||||
fn new_byte_tag() {
|
||||
let tag = Tag::new_byte("health", 20);
|
||||
@@ -77,7 +77,7 @@ mod tests {
|
||||
#[test]
|
||||
fn new_byte_array_tag() {
|
||||
let data = vec![1u8, 2, 3, 4];
|
||||
let tag = Tag::new_ByteArray("blocks", data.clone());
|
||||
let tag = Tag::new_byte_array("blocks", data.clone());
|
||||
match tag {
|
||||
Tag::ByteArray { name, value } => {
|
||||
assert_eq!(name.unwrap(), "blocks");
|
||||
@@ -104,7 +104,11 @@ mod tests {
|
||||
let elements = vec![Tag::new_int("", 1), Tag::new_int("", 2)];
|
||||
let tag = Tag::new_list("nums", 3, elements.clone());
|
||||
match tag {
|
||||
Tag::List { name, element_id, elements: elems } => {
|
||||
Tag::List {
|
||||
name,
|
||||
element_id,
|
||||
elements: elems,
|
||||
} => {
|
||||
assert_eq!(name.unwrap(), "nums");
|
||||
assert_eq!(element_id, 3);
|
||||
assert_eq!(elems, elements);
|
||||
@@ -150,4 +154,15 @@ mod tests {
|
||||
_ => panic!("Expected LongArray tag"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_compound() {
|
||||
let mut root = Tag::new_compound("Test");
|
||||
root.insert("value".to_string(), Tag::new_int("value", 42));
|
||||
|
||||
let mut buf = Vec::new();
|
||||
write_nbt(&root, &mut buf).unwrap();
|
||||
|
||||
let decoded = read_nbt(&buf[..]).unwrap();
|
||||
assert_eq!(decoded, root);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user