summaryrefslogtreecommitdiff
path: root/libnres/src
diff options
context:
space:
mode:
Diffstat (limited to 'libnres/src')
-rw-r--r--libnres/src/converter.rs33
-rw-r--r--libnres/src/error.rs45
-rw-r--r--libnres/src/lib.rs24
-rw-r--r--libnres/src/reader.rs227
4 files changed, 329 insertions, 0 deletions
diff --git a/libnres/src/converter.rs b/libnres/src/converter.rs
new file mode 100644
index 0000000..bbf0535
--- /dev/null
+++ b/libnres/src/converter.rs
@@ -0,0 +1,33 @@
+use crate::error::ConverterError;
+
+/// Method for converting u32 to u64.
+pub fn u32_to_u64(value: u32) -> Result<u64, ConverterError> {
+ match u64::try_from(value) {
+ Err(error) => Err(ConverterError::Infallible(error)),
+ Ok(result) => Ok(result),
+ }
+}
+
+/// Method for converting u32 to usize.
+pub fn u32_to_usize(value: u32) -> Result<usize, ConverterError> {
+ match usize::try_from(value) {
+ Err(error) => Err(ConverterError::TryFromIntError(error)),
+ Ok(result) => Ok(result),
+ }
+}
+
+/// Method for converting u64 to u32.
+pub fn u64_to_u32(value: u64) -> Result<u32, ConverterError> {
+ match u32::try_from(value) {
+ Err(error) => Err(ConverterError::TryFromIntError(error)),
+ Ok(result) => Ok(result),
+ }
+}
+
+/// Method for converting usize to u32.
+pub fn usize_to_u32(value: usize) -> Result<u32, ConverterError> {
+ match u32::try_from(value) {
+ Err(error) => Err(ConverterError::TryFromIntError(error)),
+ Ok(result) => Ok(result),
+ }
+}
diff --git a/libnres/src/error.rs b/libnres/src/error.rs
new file mode 100644
index 0000000..440ab06
--- /dev/null
+++ b/libnres/src/error.rs
@@ -0,0 +1,45 @@
+extern crate miette;
+extern crate thiserror;
+
+use miette::Diagnostic;
+use thiserror::Error;
+
+#[derive(Error, Diagnostic, Debug)]
+pub enum ConverterError {
+ #[error("error converting an value")]
+ #[diagnostic(code(libnres::infallible))]
+ Infallible(#[from] std::convert::Infallible),
+
+ #[error("error converting an value")]
+ #[diagnostic(code(libnres::try_from_int_error))]
+ TryFromIntError(#[from] std::num::TryFromIntError),
+}
+
+#[derive(Error, Diagnostic, Debug)]
+pub enum ReaderError {
+ #[error(transparent)]
+ #[diagnostic(code(libnres::convert_error))]
+ ConvertValue(#[from] ConverterError),
+
+ #[error("incorrect header format")]
+ #[diagnostic(code(libnres::list_type_error))]
+ IncorrectHeader,
+
+ #[error("incorrect file size (expected {expected:?} bytes, received {received:?} bytes)")]
+ #[diagnostic(code(libnres::file_size_error))]
+ IncorrectSizeFile { expected: u32, received: u32 },
+
+ #[error(
+ "incorrect size of the file list (not a multiple of {expected:?}, received {received:?})"
+ )]
+ #[diagnostic(code(libnres::list_size_error))]
+ IncorrectSizeList { expected: u32, received: u32 },
+
+ #[error("resource file reading error")]
+ #[diagnostic(code(libnres::io_error))]
+ ReadFile(#[from] std::io::Error),
+
+ #[error("file is too small (must be at least {expected:?} bytes, received {received:?} byte)")]
+ #[diagnostic(code(libnres::file_size_error))]
+ SmallFile { expected: u32, received: u32 },
+}
diff --git a/libnres/src/lib.rs b/libnres/src/lib.rs
new file mode 100644
index 0000000..40c0b32
--- /dev/null
+++ b/libnres/src/lib.rs
@@ -0,0 +1,24 @@
+/// First constant value of the NRes file ("NRes" characters in numeric)
+pub const FILE_TYPE_1: u32 = 1936020046;
+/// Second constant value of the NRes file
+pub const FILE_TYPE_2: u32 = 256;
+/// Size of the element item (in bytes)
+pub const LIST_ELEMENT_SIZE: u32 = 64;
+/// Minimum allowed file size (in bytes)
+pub const MINIMUM_FILE_SIZE: u32 = 16;
+
+static DEBUG: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
+
+mod converter;
+mod error;
+pub mod reader;
+
+/// Get debug status value
+pub fn get_debug() -> bool {
+ DEBUG.load(std::sync::atomic::Ordering::Relaxed)
+}
+
+/// Change debug status value
+pub fn set_debug(value: bool) {
+ DEBUG.store(value, std::sync::atomic::Ordering::Relaxed)
+}
diff --git a/libnres/src/reader.rs b/libnres/src/reader.rs
new file mode 100644
index 0000000..2a450ee
--- /dev/null
+++ b/libnres/src/reader.rs
@@ -0,0 +1,227 @@
+use std::io::{Read, Seek};
+
+use byteorder::ByteOrder;
+
+use crate::error::ReaderError;
+use crate::{converter, FILE_TYPE_1, FILE_TYPE_2, LIST_ELEMENT_SIZE, MINIMUM_FILE_SIZE};
+
+#[derive(Debug)]
+pub struct ListElement {
+ /// Unknown parameter
+ _unknown0: i32,
+ /// Unknown parameter
+ _unknown1: i32,
+ /// Unknown parameter
+ _unknown2: i32,
+ /// File extension
+ pub extension: String,
+ /// Identifier or sequence number
+ pub index: u32,
+ /// File name
+ pub name: String,
+ /// Position in the file
+ pub position: u32,
+ /// File size (in bytes)
+ pub size: u32,
+}
+
+impl ListElement {
+ /// Get full name of the file
+ pub fn get_filename(&self) -> String {
+ format!("{}.{}", self.name, self.extension)
+ }
+}
+
+#[derive(Debug)]
+pub struct FileHeader {
+ /// File size
+ size: u32,
+ /// Number of files
+ total: u32,
+ /// First constant value
+ type1: u32,
+ /// Second constant value
+ type2: u32,
+}
+
+/// Get a packed file data
+pub fn get_file(file: &std::fs::File, element: &ListElement) -> Result<Vec<u8>, ReaderError> {
+ let size = get_file_size(file)?;
+ check_file_size(size)?;
+
+ let header = get_file_header(file)?;
+ check_file_header(&header, size)?;
+
+ let data = get_element_data(file, element)?;
+ Ok(data)
+}
+
+/// Get a list of packed files
+pub fn get_list(file: &std::fs::File) -> Result<Vec<ListElement>, ReaderError> {
+ let mut list: Vec<ListElement> = Vec::new();
+
+ let size = get_file_size(file)?;
+ check_file_size(size)?;
+
+ let header = get_file_header(file)?;
+ check_file_header(&header, size)?;
+
+ get_file_list(file, &header, &mut list)?;
+
+ Ok(list)
+}
+
+fn check_file_header(header: &FileHeader, size: u32) -> Result<(), ReaderError> {
+ if header.type1 != FILE_TYPE_1 || header.type2 != FILE_TYPE_2 {
+ return Err(ReaderError::IncorrectHeader);
+ }
+
+ if header.size != size {
+ return Err(ReaderError::IncorrectSizeFile {
+ expected: size,
+ received: header.size,
+ });
+ }
+
+ Ok(())
+}
+
+fn check_file_size(size: u32) -> Result<(), ReaderError> {
+ if size < MINIMUM_FILE_SIZE {
+ return Err(ReaderError::SmallFile {
+ expected: MINIMUM_FILE_SIZE,
+ received: size,
+ });
+ }
+
+ Ok(())
+}
+
+fn get_element_data(file: &std::fs::File, element: &ListElement) -> Result<Vec<u8>, ReaderError> {
+ let position = converter::u32_to_u64(element.position)?;
+ let size = converter::u32_to_usize(element.size)?;
+
+ let mut reader = std::io::BufReader::new(file);
+ let mut buffer = vec![0u8; size];
+
+ if let Err(error) = reader.seek(std::io::SeekFrom::Start(position)) {
+ return Err(ReaderError::ReadFile(error));
+ };
+
+ if let Err(error) = reader.read_exact(&mut buffer) {
+ return Err(ReaderError::ReadFile(error));
+ };
+
+ Ok(buffer)
+}
+
+fn get_element_position(index: u32) -> Result<(usize, usize), ReaderError> {
+ let from = converter::u32_to_usize(index * LIST_ELEMENT_SIZE)?;
+ let to = converter::u32_to_usize((index * LIST_ELEMENT_SIZE) + LIST_ELEMENT_SIZE)?;
+ Ok((from, to))
+}
+
+fn get_file_header(file: &std::fs::File) -> Result<FileHeader, ReaderError> {
+ let mut reader = std::io::BufReader::new(file);
+ let mut buffer = vec![0u8; MINIMUM_FILE_SIZE as usize];
+
+ if let Err(error) = reader.seek(std::io::SeekFrom::Start(0)) {
+ return Err(ReaderError::ReadFile(error));
+ };
+
+ if let Err(error) = reader.read_exact(&mut buffer) {
+ return Err(ReaderError::ReadFile(error));
+ };
+
+ let header = FileHeader {
+ size: byteorder::LittleEndian::read_u32(&buffer[12..16]),
+ total: byteorder::LittleEndian::read_u32(&buffer[8..12]),
+ type1: byteorder::LittleEndian::read_u32(&buffer[0..4]),
+ type2: byteorder::LittleEndian::read_u32(&buffer[4..8]),
+ };
+
+ buffer.clear();
+ Ok(header)
+}
+
+fn get_file_list(
+ file: &std::fs::File,
+ header: &FileHeader,
+ list: &mut Vec<ListElement>,
+) -> Result<(), ReaderError> {
+ let (start_position, list_size) = get_list_position(header)?;
+ let mut reader = std::io::BufReader::new(file);
+ let mut buffer = vec![0u8; list_size];
+
+ if let Err(error) = reader.seek(std::io::SeekFrom::Start(start_position)) {
+ return Err(ReaderError::ReadFile(error));
+ };
+
+ if let Err(error) = reader.read_exact(&mut buffer) {
+ return Err(ReaderError::ReadFile(error));
+ }
+
+ let buffer_size = converter::usize_to_u32(buffer.len())?;
+
+ if buffer_size % LIST_ELEMENT_SIZE != 0 {
+ return Err(ReaderError::IncorrectSizeList {
+ expected: LIST_ELEMENT_SIZE,
+ received: buffer_size,
+ });
+ }
+
+ for i in 0..(buffer_size / LIST_ELEMENT_SIZE) {
+ let (from, to) = get_element_position(i)?;
+ let chunk: &[u8] = &buffer[from..to];
+
+ let element = get_list_element(chunk)?;
+ list.push(element);
+ }
+
+ buffer.clear();
+ Ok(())
+}
+
+fn get_file_size(file: &std::fs::File) -> Result<u32, ReaderError> {
+ let metadata = match file.metadata() {
+ Err(error) => return Err(ReaderError::ReadFile(error)),
+ Ok(value) => value,
+ };
+
+ let result = converter::u64_to_u32(metadata.len())?;
+ Ok(result)
+}
+
+fn get_list_element(buffer: &[u8]) -> Result<ListElement, ReaderError> {
+ let index = byteorder::LittleEndian::read_u32(&buffer[60..64]);
+ let position = byteorder::LittleEndian::read_u32(&buffer[56..60]);
+ let size = byteorder::LittleEndian::read_u32(&buffer[12..16]);
+ let unknown0 = byteorder::LittleEndian::read_i32(&buffer[4..8]);
+ let unknown1 = byteorder::LittleEndian::read_i32(&buffer[8..12]);
+ let unknown2 = byteorder::LittleEndian::read_i32(&buffer[16..20]);
+
+ let extension = String::from_utf8_lossy(&buffer[0..4])
+ .trim_matches(char::from(0))
+ .to_string();
+
+ let name = String::from_utf8_lossy(&buffer[20..56])
+ .trim_matches(char::from(0))
+ .to_string();
+
+ Ok(ListElement {
+ _unknown0: unknown0,
+ _unknown1: unknown1,
+ _unknown2: unknown2,
+ extension,
+ index,
+ name,
+ position,
+ size,
+ })
+}
+
+fn get_list_position(header: &FileHeader) -> Result<(u64, usize), ReaderError> {
+ let position = converter::u32_to_u64(header.size - (header.total * LIST_ELEMENT_SIZE))?;
+ let size = converter::u32_to_usize(header.total * LIST_ELEMENT_SIZE)?;
+ Ok((position, size))
+}