use std::{
borrow::Borrow,
error::Error,
fs::{self, File},
io::{self, Read, Seek, SeekFrom},
path,
};
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
#[derive(Debug)]
pub struct PakHeader {
pub id: String,
pub offset: u32,
pub size: u32,
}
impl Default for PakHeader {
fn default() -> Self {
Self::new()
}
}
impl PakHeader {
#[must_use]
pub fn new() -> PakHeader {
PakHeader {
id: "PACK".to_string(),
offset: 0,
size: 0,
}
}
#[must_use = "the result should be used, as it may contain parsing errors"]
pub fn from_u8(buf: &[u8]) -> Result<PakHeader, Box<dyn Error>> {
Ok(PakHeader {
id: String::from_utf8(buf[0..4].to_vec())?,
offset: LittleEndian::read_u32(&buf[4..8]),
size: LittleEndian::read_u32(&buf[8..12]),
})
}
pub fn write_to<W: io::Write>(&self, mut writer: W) -> Result<(), Box<dyn Error>> {
writer.write_all(self.id.as_bytes())?;
writer.write_u32::<LittleEndian>(self.offset)?;
writer.write_u32::<LittleEndian>(self.size)?;
Ok(())
}
}
#[derive(Debug)]
pub struct PakFileEntry {
pub name: String,
pub offset: u32,
pub size: u32,
data: Vec<u8>,
}
impl PakFileEntry {
#[must_use = "the result should be used, as it may contain parsing errors"]
pub fn from_u8(header_buf: &[u8], file_buf: &[u8]) -> Result<PakFileEntry, Box<dyn Error>> {
let namebuf = header_buf[0..56].to_vec();
let nul_range_end = namebuf
.iter()
.position(|&c| c == b'\0')
.unwrap_or(namebuf.len());
let offset = LittleEndian::read_u32(&header_buf[56..60]);
let size = LittleEndian::read_u32(&header_buf[60..64]);
Ok(PakFileEntry {
name: String::from_utf8(header_buf[0..nul_range_end].to_vec())?
.trim()
.to_string(),
offset,
size,
data: (file_buf[offset as usize..(offset + size) as usize]).to_vec(),
})
}
pub fn save_to(&self, path: &str, with_full_path: bool) -> Result<String, std::io::Error> {
let data: &Vec<u8> = self.data.borrow();
let mut path = path::Path::new(&path);
if with_full_path {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
} else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Path has no parent directory",
));
}
} else if let Some(file_name) = path.file_name() {
if let Some(file_str) = file_name.to_str() {
path = path::Path::new(file_str);
} else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Filename contains invalid UTF-8 characters",
));
}
} else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Path has no filename",
));
}
std::fs::write(path, data)?;
Ok(path
.to_str()
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Path contains invalid UTF-8 characters",
)
})?
.to_string())
}
#[must_use]
pub fn get_data(&self) -> &Vec<u8> {
&self.data
}
#[must_use]
pub fn new(name: String, offset: u32, data: &[u8]) -> PakFileEntry {
PakFileEntry {
name,
offset,
size: u32::try_from(data.len()).expect("file size exceeds u32::MAX"),
data: data.to_vec(),
}
}
pub fn write_to<W: io::Write>(&self, mut writer: W) -> Result<(), Box<dyn Error>> {
let mut buf = self.name.as_bytes().to_vec();
while buf.len() < 56 {
buf.push(0_u8);
}
writer.write_all(buf.as_slice())?;
writer.write_u32::<LittleEndian>(self.offset)?;
writer.write_u32::<LittleEndian>(self.size)?;
Ok(())
}
}
#[derive(Debug)]
pub struct Pak {
pub pak_path: String,
pub header: PakHeader,
pub files: Vec<PakFileEntry>,
}
impl Default for Pak {
fn default() -> Self {
Self::new()
}
}
impl Pak {
#[must_use]
pub fn new() -> Pak {
Pak {
pak_path: String::new(),
header: PakHeader::new(),
files: Vec::new(),
}
}
pub fn from_file(path: String) -> Result<Pak, Box<dyn Error>> {
let bytes = std::fs::read(&path)?;
let pakheader = PakHeader::from_u8(&bytes)?;
let num_files = pakheader.size / 64;
let file_table_offset = pakheader.offset;
let mut my_offset: u32 = 0;
let mut pakfiles: Vec<PakFileEntry> = Vec::new();
for _i in 0..num_files {
let file_entry = PakFileEntry::from_u8(
&bytes[(file_table_offset + my_offset) as usize
..(file_table_offset + my_offset + 64) as usize],
&bytes,
)?;
pakfiles.push(file_entry);
my_offset += 64;
}
Ok(Pak {
pak_path: path,
header: pakheader,
files: pakfiles,
})
}
pub fn add_file(&mut self, file: PakFileEntry) -> Result<&mut Pak, Box<dyn Error>> {
if self.files.iter().any(|f| f.name.eq(&file.name)) {
Err(Box::new(PakFileError {
msg: "File already exists".to_string(),
}))
} else {
self.files.push(file);
Ok(self)
}
}
pub fn remove_file(&mut self, filename: &str) -> Result<(), Box<dyn Error>> {
if let Some(p) = self.files.iter().position(|p| p.name.eq(&filename)) {
self.files.remove(p);
Ok(())
} else {
Err(Box::new(PakFileError {
msg: "file entry not found".to_string(),
}))
}
}
pub fn save(&self, filename: String) -> Result<(), Box<dyn Error>> {
let mut hdr = PakHeader::new();
hdr.offset = 12;
hdr.size = u32::try_from(self.files.len() * 64).expect("directory size exceeds u32::MAX");
let mut f = File::create(filename)?;
hdr.write_to(&f)?;
for file in &self.files {
file.write_to(&f)?;
}
for file in &self.files {
f.seek(SeekFrom::Start(u64::from(file.offset)))?;
io::Write::write(&mut f, file.data.as_slice())?;
}
Ok(())
}
pub fn append_file(
&mut self,
infilepath: String,
pakfilepath: &str,
) -> Result<(), Box<dyn Error>> {
fn get_last_offset(path: String) -> Result<u32, Box<dyn Error>> {
let f = File::open(path)?;
let metadata = f.metadata()?;
let len = metadata.len();
u32::try_from(len).map_err(|_| {
Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"file size exceeds u32::MAX",
)) as Box<dyn Error>
})
}
fn get_file_data(path: String) -> Result<Vec<u8>, Box<dyn Error>> {
let mut f = File::open(path)?;
let mut vec: Vec<u8> = Vec::new();
f.read_to_end(&mut vec)?;
Ok(vec)
}
let newfilepath = path::Path::new(&infilepath);
if !newfilepath.exists() {
return Err(Box::new(PakFileError {
msg: "File does not exist!".to_string(),
}));
}
let last_offset = get_last_offset(self.pak_path.clone())?;
let data = get_file_data(infilepath)?;
let fe = PakFileEntry::new(pakfilepath.to_string(), last_offset, &data);
self.add_file(fe)?;
Ok(())
}
}
impl std::fmt::Display for Pak {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"<Pak structure from file {} with {} files>",
self.pak_path,
self.files.len()
)
}
}
#[derive(Debug, Clone)]
pub struct PakFileError {
pub msg: String,
}
impl std::fmt::Display for PakFileError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.msg)
}
}
impl Error for PakFileError {}