diff options
Diffstat (limited to 'libnres')
-rw-r--r-- | libnres/Cargo.toml | 16 | ||||
-rw-r--r-- | libnres/README.md | 25 | ||||
-rw-r--r-- | libnres/src/converter.rs | 33 | ||||
-rw-r--r-- | libnres/src/error.rs | 45 | ||||
-rw-r--r-- | libnres/src/lib.rs | 24 | ||||
-rw-r--r-- | libnres/src/reader.rs | 227 |
6 files changed, 370 insertions, 0 deletions
diff --git a/libnres/Cargo.toml b/libnres/Cargo.toml new file mode 100644 index 0000000..85597bd --- /dev/null +++ b/libnres/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libnres" +version = "0.1.4" +description = "Library for NRes files" +authors = ["Valentin Popov <valentin@popov.link>"] +homepage = "https://git.popov.link/valentineus/fparkan" +repository = "https://git.popov.link/valentineus/fparkan.git" +license = "GPL-2.0" +edition = "2021" +keywords = ["gamedev", "library", "nres"] + +[dependencies] +byteorder = "1.4" +log = "0.4" +miette = "5.6" +thiserror = "1.0" diff --git a/libnres/README.md b/libnres/README.md new file mode 100644 index 0000000..065bd40 --- /dev/null +++ b/libnres/README.md @@ -0,0 +1,25 @@ +# Library for NRes files (Deprecated) + +Library for viewing and retrieving game resources of the game **"Parkan: Iron Strategy"**. +All versions of the game are supported: Demo, IS, IS: Part 1, IS: Part 2. +Supports files with `lib`, `trf`, `rlb` extensions. + +The files `gamefont.rlb` and `sprites.lib` are not supported. +This files have an unknown signature. + +## Example + +Example of extracting game resources: + +```rust +fn main() { + let file = std::fs::File::open("./voices.lib").unwrap(); + // Extracting the list of files + let list = libnres::reader::get_list(&file).unwrap(); + + for element in list { + // Extracting the contents of the file + let data = libnres::reader::get_file(&file, &element).unwrap(); + } +} +``` 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)) +} |