#![doc = include_str!("../README.md")]
mod class_name;
mod error;
mod header;
mod object;
mod value;
pub use crate::{class_name::*, error::*, object::*, value::*};
use header::*;
use std::{
fs::File,
io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write},
};
const MAGIC_BYTES: &[u8; 10] = b"NIBArchive";
const DEFAULT_FORMAT_VERSION: u32 = 1;
const DEFAULT_CODER_VERSION: u32 = 9;
type VarInt = i32;
macro_rules! check_position {
($reader:ident, $offset:expr, $err:literal) => {
if $reader.stream_position()? != $offset as u64 {
return Err(Error::FormatError(format!(
"Expected {} offset at {} - got {}",
$err,
$reader.stream_position()?,
$offset
)));
}
};
}
#[derive(Debug, Clone, PartialEq)]
pub struct NIBArchive {
objects: Vec<Object>,
keys: Vec<String>,
values: Vec<Value>,
class_names: Vec<ClassName>,
format_version: u32,
coder_version: u32,
}
impl NIBArchive {
pub fn new(
objects: Vec<Object>,
keys: Vec<String>,
values: Vec<Value>,
class_names: Vec<ClassName>,
) -> Result<Self, Error> {
for obj in &objects {
Self::check_object(obj, values.len() as u32, class_names.len() as u32)?;
}
for val in &values {
Self::check_value(val, keys.len() as u32)?;
}
for cls in &class_names {
Self::check_class_name(cls, class_names.len() as u32)?;
}
Ok(Self {
objects,
keys,
values,
class_names,
format_version: DEFAULT_FORMAT_VERSION,
coder_version: DEFAULT_CODER_VERSION,
})
}
pub fn new_unchecked(
objects: Vec<Object>,
keys: Vec<String>,
values: Vec<Value>,
class_names: Vec<ClassName>,
) -> Self {
Self {
objects,
keys,
values,
class_names,
format_version: DEFAULT_FORMAT_VERSION,
coder_version: DEFAULT_CODER_VERSION,
}
}
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
Self::from_reader(&mut reader)
}
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self, Error> {
let mut cursor = Cursor::new(bytes);
Self::from_reader(&mut cursor)
}
pub fn from_reader<T: Read + Seek>(mut reader: &mut T) -> Result<Self, Error> {
reader.seek(SeekFrom::Start(0))?;
let mut magic_bytes = [0; 10];
reader.read_exact(&mut magic_bytes)?;
if &magic_bytes != MAGIC_BYTES {
return Err(Error::FormatError("Magic bytes don't match".into()));
}
let header = Header::try_from_reader(&mut reader)?;
check_position!(reader, header.offset_objects, "object");
let mut objects = Vec::with_capacity(header.object_count as usize);
for _ in 0..header.object_count {
let obj = Object::try_from_reader(&mut reader)?;
Self::check_object(&obj, header.value_count, header.class_name_count)?;
objects.push(obj);
}
check_position!(reader, header.offset_keys, "keys");
let mut keys = Vec::with_capacity(header.key_count as usize);
for _ in 0..header.key_count {
let length = decode_var_int(&mut reader)?;
let mut name_bytes = vec![0; length as usize];
reader.read_exact(&mut name_bytes)?;
let name = String::from_utf8(name_bytes)?;
keys.push(name);
}
check_position!(reader, header.offset_values, "values");
let mut values = Vec::with_capacity(header.value_count as usize);
for _ in 0..header.value_count {
let val = Value::try_from_reader(&mut reader)?;
Self::check_value(&val, header.key_count)?;
values.push(val);
}
check_position!(reader, header.offset_class_names, "class names'");
let mut class_names = Vec::with_capacity(header.class_name_count as usize);
for _ in 0..header.class_name_count {
let cls = ClassName::try_from_reader(&mut reader)?;
Self::check_class_name(&cls, header.class_name_count)?;
class_names.push(cls);
}
Ok(Self {
objects,
keys,
values,
class_names,
format_version: header.format_version,
coder_version: header.coder_version,
})
}
fn check_object(obj: &Object, value_count: u32, class_name_count: u32) -> Result<(), Error> {
if (obj.values_index() + obj.value_count()) as u32 > value_count {
return Err(Error::FormatError("Value index out of bounds".into()));
}
if obj.class_name_index() as u32 > class_name_count {
return Err(Error::FormatError("Class name index out of bounds".into()));
}
Ok(())
}
fn check_value(val: &Value, key_count: u32) -> Result<(), Error> {
if val.key_index() as u32 > key_count {
return Err(Error::FormatError("Key index out of bounds".into()));
}
Ok(())
}
fn check_class_name(cls: &ClassName, class_name_count: u32) -> Result<(), Error> {
for index in cls.fallback_classes_indeces() {
if *index as u32 > class_name_count {
return Err(Error::FormatError(
"Class name (fallback class) index out of bounds".into(),
));
}
}
Ok(())
}
pub fn to_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), Error> {
let file = File::create(path)?;
let mut reader = BufWriter::new(file);
self.to_writer(&mut reader)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut cursor = Cursor::new(Vec::with_capacity(1024));
self.to_writer(&mut cursor).unwrap(); cursor.into_inner()
}
pub fn to_writer<T: Write>(&self, writer: &mut T) -> Result<(), Error> {
let mut objects_bytes = Vec::with_capacity(self.objects.len() * 3 * 2);
for obj in &self.objects {
objects_bytes.append(&mut obj.to_bytes());
}
let mut keys_bytes = Vec::with_capacity(self.keys.len() * (16 + 2));
for key in &self.keys {
keys_bytes.append(&mut encode_var_int(key.len() as i32));
keys_bytes.extend(key.as_bytes());
}
let mut values_bytes = Vec::with_capacity(self.values.len() * (8 + 2));
for val in &self.values {
values_bytes.append(&mut val.to_bytes());
}
let mut classes_bytes = Vec::with_capacity(self.class_names.len() * 16);
for cls in &self.class_names {
classes_bytes.append(&mut cls.to_bytes());
}
let header = Header {
format_version: self.format_version,
coder_version: self.coder_version,
object_count: self.objects.len() as u32,
offset_objects: 50,
key_count: self.keys.len() as u32,
offset_keys: (50 + objects_bytes.len()) as u32,
value_count: self.values.len() as u32,
offset_values: (50 + objects_bytes.len() + keys_bytes.len()) as u32,
class_name_count: self.class_names.len() as u32,
offset_class_names: (50 + objects_bytes.len() + keys_bytes.len() + values_bytes.len())
as u32,
};
writer.write_all(MAGIC_BYTES)?;
writer.write_all(&header.to_bytes())?;
writer.write_all(&objects_bytes)?;
writer.write_all(&keys_bytes)?;
writer.write_all(&values_bytes)?;
writer.write_all(&classes_bytes)?;
writer.flush()?;
Ok(())
}
pub fn format_version(&self) -> u32 {
self.format_version
}
pub fn set_format_version(&mut self, value: u32) {
self.format_version = value;
}
pub fn coder_version(&self) -> u32 {
self.coder_version
}
pub fn set_coder_version(&mut self, value: u32) {
self.coder_version = value;
}
pub fn objects(&self) -> &[Object] {
&self.objects
}
pub fn set_objects(&mut self, objects: Vec<Object>) -> Result<(), Error> {
for obj in &objects {
Self::check_object(obj, self.values.len() as u32, self.class_names.len() as u32)?;
}
self.objects = objects;
Ok(())
}
pub fn keys(&self) -> &[String] {
&self.keys
}
pub fn set_keys(&mut self, keys: Vec<String>) {
self.keys = keys;
}
pub fn values(&self) -> &[Value] {
&self.values
}
pub fn set_values(&mut self, values: Vec<Value>) -> Result<(), Error> {
for val in &values {
Self::check_value(val, self.keys.len() as u32)?;
}
self.values = values;
Ok(())
}
pub fn class_names(&self) -> &[ClassName] {
&self.class_names
}
pub fn set_class_names(&mut self, class_names: Vec<ClassName>) -> Result<(), Error> {
for cls in &class_names {
Self::check_class_name(cls, class_names.len() as u32)?;
}
self.class_names = class_names;
Ok(())
}
pub fn into_inner(self) -> (Vec<Object>, Vec<String>, Vec<Value>, Vec<ClassName>) {
(self.objects, self.keys, self.values, self.class_names)
}
}
fn decode_var_int<T: Read + Seek>(reader: &mut T) -> Result<VarInt, Error> {
let mut result = 0;
let mut shift = 0;
loop {
let mut current_byte = [0; 1];
reader.read_exact(&mut current_byte)?;
let current_byte = current_byte[0];
result |= (current_byte as VarInt & 0x7F) << shift;
shift += 7;
if (current_byte & 128) != 0 {
break;
}
}
Ok(result)
}
fn encode_var_int(mut value: VarInt) -> Vec<u8> {
let mut number_of_bytes = 0;
let mut _v = value;
loop {
number_of_bytes += 1;
_v >>= 7;
if _v == 0 {
break;
}
}
let mut offset = 0;
let mut bytes = vec![0; number_of_bytes];
while offset < number_of_bytes {
let digit: u8 = (0x7f & value) as u8;
value >>= 7;
let is_last_digit = value == 0;
bytes[offset] = digit | if is_last_digit { 0x80 } else { 0 };
offset += 1;
if is_last_digit {
break;
}
}
bytes
}