228 lines
6.3 KiB
Rust
228 lines
6.3 KiB
Rust
|
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))
|
||
|
}
|