a bit more

This commit is contained in:
2025-05-19 14:43:14 +02:00
parent 9d495ba239
commit a580e421f0
3 changed files with 503 additions and 152 deletions

255
src/io.rs Normal file
View 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)
}
}

View File

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

View File

@@ -1,9 +1,9 @@
// src/test.rs
#[cfg(test)]
mod tests {
use crate::Tag;
#[test]
fn new_byte_tag() {
use crate::{Tag, read_nbt, write_nbt};
#[test]
fn new_byte_tag() {
let tag = Tag::new_byte("health", 20);
match tag {
Tag::Byte { name, value } => {
@@ -12,10 +12,10 @@ mod tests {
}
_ => panic!("Expected Byte tag"),
}
}
}
#[test]
fn new_short_tag() {
#[test]
fn new_short_tag() {
let tag = Tag::new_short("optLevel", 3);
match tag {
Tag::Short { name, value } => {
@@ -24,10 +24,10 @@ mod tests {
}
_ => panic!("Expected Short tag"),
}
}
}
#[test]
fn new_int_tag() {
#[test]
fn new_int_tag() {
let tag = Tag::new_int("xPos", 100);
match tag {
Tag::Int { name, value } => {
@@ -36,10 +36,10 @@ mod tests {
}
_ => panic!("Expected Int tag"),
}
}
}
#[test]
fn new_long_tag() {
#[test]
fn new_long_tag() {
let tag = Tag::new_long("timestamp", 1_620_000_000_000);
match tag {
Tag::Long { name, value } => {
@@ -48,10 +48,10 @@ mod tests {
}
_ => panic!("Expected Long tag"),
}
}
}
#[test]
fn new_float_tag() {
#[test]
fn new_float_tag() {
let tag = Tag::new_float("speed", 0.5);
match tag {
Tag::Float { name, value } => {
@@ -60,10 +60,10 @@ mod tests {
}
_ => panic!("Expected Float tag"),
}
}
}
#[test]
fn new_double_tag() {
#[test]
fn new_double_tag() {
let tag = Tag::new_double("gravity", 9.81);
match tag {
Tag::Double { name, value } => {
@@ -72,12 +72,12 @@ mod tests {
}
_ => panic!("Expected Double tag"),
}
}
}
#[test]
fn new_byte_array_tag() {
#[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");
@@ -85,10 +85,10 @@ mod tests {
}
_ => panic!("Expected ByteArray tag"),
}
}
}
#[test]
fn new_string_tag() {
#[test]
fn new_string_tag() {
let tag = Tag::new_string("name", "Steve");
match tag {
Tag::String { name, value } => {
@@ -97,24 +97,28 @@ mod tests {
}
_ => panic!("Expected String tag"),
}
}
}
#[test]
fn new_list_tag() {
#[test]
fn new_list_tag() {
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);
}
_ => panic!("Expected List tag"),
}
}
}
#[test]
fn new_compound_tag() {
#[test]
fn new_compound_tag() {
let tag = Tag::new_compound("Level");
match tag {
Tag::Compound { name, entries } => {
@@ -123,10 +127,10 @@ mod tests {
}
_ => panic!("Expected Compound tag"),
}
}
}
#[test]
fn new_int_array_tag() {
#[test]
fn new_int_array_tag() {
let data = vec![1i32, 2, 3];
let tag = Tag::new_int_array("palette", data.clone());
match tag {
@@ -136,10 +140,10 @@ mod tests {
}
_ => panic!("Expected IntArray tag"),
}
}
}
#[test]
fn new_long_array_tag() {
#[test]
fn new_long_array_tag() {
let data = vec![100i64, 200];
let tag = Tag::new_long_array("timestamps", data.clone());
match tag {
@@ -149,5 +153,16 @@ 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);
}