Compare commits

..

4 Commits

6 changed files with 169 additions and 24 deletions

View File

@ -1,4 +1,4 @@
# nbt-rust # shen-nbt5
一个 "全功能" 的 "快速" NBT 解析器 一个 "全功能" 的 "快速" NBT 解析器
@ -64,9 +64,11 @@ writen in rust!
## 感谢 ## 感谢
感谢 @langyo@InfyniteHeap 感谢 [@langyo](https://github.com/langyo) 和 [@InfyniteHeap](https://github.com/InfyniteHeap)
在编写过程中的帮助( 在编写过程中的帮助(
感谢 [mat](https://github.com/mat-1) 的 simd-nbt 中 [`mutf8.rs`](https://github.com/azalea-rs/simdnbt/blob/master/simdnbt/benches/mutf8.rs) 的实现
感谢 [wiki.vg](https://wiki.vg/NBT) 存储的 NBT 格式的详细信息 感谢 [wiki.vg](https://wiki.vg/NBT) 存储的 NBT 格式的详细信息
## 概况 ## 概况
@ -137,3 +139,9 @@ speed: 2483288579.985664 (bytes/s)
2368.2485389572753 (MB/s) 2368.2485389572753 (MB/s)
2.312742713825464 (GB/s) 2.312742713825464 (GB/s)
``` ```
shen-nbt5 通过了作者电脑上 所有 .nbt 格式的文件的读取测试
```text
total: 6063, open failed: 25, parse failed: 0, gzip parse: 6013, normal parse: 25
```

View File

@ -1,6 +1,6 @@
[package] [package]
name = "shen-nbt5" name = "shen-nbt5"
version = "0.4.3" version = "0.4.4"
edition = "2021" edition = "2021"
description = "Just A FASSST NBT parser/writer" description = "Just A FASSST NBT parser/writer"
homepage = "https://github.com/shenjackyuanjie/nbt-rust" homepage = "https://github.com/shenjackyuanjie/nbt-rust"
@ -20,3 +20,9 @@ serde = { version = "1.0", features = ["derive"], optional = true }
[features] [features]
default = [] default = []
serde = ["dep:serde"] serde = ["dep:serde"]
test = []
# test dep
[dev-dependencies]
# gzip
flate2 = "1.0"

View File

@ -721,6 +721,26 @@ impl NbtReader<'_> {
self.cursor += len; self.cursor += len;
value value
} }
/// 读取指定长度的 i16 数组
///
/// # 安全性
///
/// 长度溢出会导致 UB
#[inline]
pub unsafe fn read_be_i16_array_unsafe(&mut self, len: usize) -> Vec<i16> {
let mut value: Vec<i16> = Vec::with_capacity(len);
std::ptr::copy_nonoverlapping(
self.data[self.cursor..].as_ptr() as *const u8,
value.as_ptr() as *mut u8,
len * 2,
);
value.set_len(len);
for n in &mut value {
*n = n.to_be();
}
self.cursor += len * 2;
value
}
/// 读取指定长度的 i32 数组 /// 读取指定长度的 i32 数组
/// ///
/// # 安全性 /// # 安全性
@ -728,15 +748,39 @@ impl NbtReader<'_> {
/// 长度溢出会导致 UB /// 长度溢出会导致 UB
#[inline] #[inline]
pub unsafe fn read_be_i32_array_unsafe(&mut self, len: usize) -> Vec<i32> { pub unsafe fn read_be_i32_array_unsafe(&mut self, len: usize) -> Vec<i32> {
let value = let mut value: Vec<i32> = Vec::with_capacity(len);
std::slice::from_raw_parts(self.data[self.cursor..].as_ptr() as *const i32, len); std::ptr::copy_nonoverlapping(
let mut value = value.to_vec(); self.data[self.cursor..].as_ptr() as *const u8,
value.as_ptr() as *mut u8,
len * 4,
);
value.set_len(len);
for n in &mut value { for n in &mut value {
*n = n.to_be(); *n = n.to_be();
} }
self.cursor += len * 4; self.cursor += len * 4;
value value
} }
/// 读取指定长度的 i64 数组
///
/// # 安全性
///
/// 长度溢出会导致 UB
#[inline]
pub unsafe fn read_be_i64_array_unsafe(&mut self, len: usize) -> Vec<i64> {
let mut value: Vec<i64> = Vec::with_capacity(len);
std::ptr::copy_nonoverlapping(
self.data[self.cursor..].as_ptr() as *const u8,
value.as_ptr() as *mut u8,
len * 8,
);
value.set_len(len);
for n in &mut value {
*n = n.to_be();
}
self.cursor += len * 8;
value
}
/// 读取指定长度的 i32 数组 /// 读取指定长度的 i32 数组
/// ///
/// # 安全性 /// # 安全性
@ -755,22 +799,6 @@ impl NbtReader<'_> {
/// ///
/// # 安全性 /// # 安全性
/// ///
/// 长度溢出会导致 UB
#[inline]
pub unsafe fn read_be_i64_array_unsafe(&mut self, len: usize) -> Vec<i64> {
let value =
std::slice::from_raw_parts(self.data[self.cursor..].as_ptr() as *const i64, len);
let mut value = value.to_vec();
for n in &mut value {
*n = n.to_be();
}
self.cursor += len * 8;
value
}
/// 读取指定长度的 i64 数组
///
/// # 安全性
///
/// 长度溢出会导致 panic /// 长度溢出会导致 panic
#[inline] #[inline]
pub fn read_be_i64_array(&mut self, len: usize) -> Vec<i64> { pub fn read_be_i64_array(&mut self, len: usize) -> Vec<i64> {

View File

@ -280,9 +280,51 @@ mod unsafe_test {
assert_eq!(reader.cursor, 100 * 8); assert_eq!(reader.cursor, 100 * 8);
} }
} }
/// 未对齐的地址
#[test]
fn unaligned_read_u16_array() {
let mut value = vec![0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04];
let mut reader = NbtReader::new(&mut value);
let value = reader.read_u8();
assert_eq!(value, 0x01);
assert_eq!(reader.cursor, 1);
unsafe {
// 读取 u16 数组
let array = reader.read_be_i16_array_unsafe(3);
assert_eq!(array, vec![0x0203, 0x0401, 0x0203]);
assert_eq!(reader.cursor, 7);
let value = reader.read_u8();
assert_eq!(value, 0x04);
assert_eq!(reader.cursor, 8);
}
}
/// 依然是未对齐
/// 只不过是 u32/i32
#[test]
fn unaligned_read_x32_array() {
let mut value = gen_datas(202);
let mut reader = NbtReader::new(&mut value);
let value = reader.read_u8();
assert_eq!(value, 0x00);
assert_eq!(reader.cursor, 1);
unsafe {
let array = reader.read_be_i32_array_unsafe(50);
reader.roll_back(50 * 4);
let safe_array = reader.read_be_i32_array(50);
assert_eq!(array, safe_array);
assert_eq!(reader.cursor, 201);
}
let value = reader.read_u8();
assert_eq!(value, 201);
assert_eq!(reader.cursor, 202);
}
} }
mod nbt { mod nbt {
use std::io::Read;
use super::*; use super::*;
#[test] #[test]
@ -471,4 +513,65 @@ mod nbt {
assert!(value.is_ok()); assert!(value.is_ok());
// 其他版本 // 其他版本
} }
#[test]
#[cfg(feature = "test")]
fn file_sys_test() {
// 测试所有能直接找到的 .nbt 文件
// es -r .*\.nbt
// command
// 总计数, 文件打开失败计数, 解析失败计数, gzip 解析计数, 普通解析计数
let mut counter: Vec<i32> = vec![0; 5];
let find_paths = std::process::Command::new("es")
.arg("-r")
.arg(r".*\.nbt$")
.output()
.expect("failed to execute process");
let find_paths = String::from_utf8_lossy(find_paths.stdout.as_slice());
let find_paths = find_paths.split("\n").collect::<Vec<&str>>();
for path in find_paths {
if path.len() == 0 {
continue;
}
counter[0] += 1;
let path = path.trim();
// println!("path: {}", path);
let file = std::fs::File::open(path);
if file.is_err() {
println!("open file failed: {}", path);
counter[1] += 1;
continue;
}
let file = file.unwrap();
let mut data = file.bytes().collect::<Result<Vec<u8>, _>>().unwrap();
// 检查一下是否是 gzip
if data[0] == 0x1F && data[1] == 0x8B {
let mut decoder = flate2::read::GzDecoder::new(data.as_slice());
let mut data = Vec::with_capacity(data.len());
decoder.read_to_end(&mut data).unwrap();
let value = NbtValue::from_binary::<nbt_version::Java>(&mut data);
if !value.is_ok() {
counter[2] += 1;
println!("failed: {} {:?}", path, value.as_ref());
assert!(value.is_ok());
}
counter[3] += 1;
} else {
let value = NbtValue::from_binary::<nbt_version::Java>(&mut data);
if !value.is_ok() {
counter[2] += 1;
println!("failed: {} {:?}", path, value.as_ref());
assert!(value.is_ok());
}
counter[4] += 1;
}
}
// 输出统计结果
println!(
"total: {}, open failed: {}, parse failed: {}, gzip parse: {}, normal parse: {}",
counter[0], counter[1], counter[2], counter[3], counter[4]
);
}
} }

View File

@ -187,7 +187,7 @@ impl NbtWriteTrait for JavaNetAfter1_20_2 {
buff.push(0); buff.push(0);
Ok(()) Ok(())
} }
x => return Err(NbtError::WrongRootType(x.tag())), x => Err(NbtError::WrongRootType(x.tag())),
} }
} }
#[inline] #[inline]