mirror of
https://github.com/xavo95/repak.git
synced 2025-01-18 19:04:07 +00:00
Better error handling
This commit is contained in:
parent
27e69115a3
commit
f1fe922c2d
4 changed files with 114 additions and 48 deletions
|
@ -368,17 +368,12 @@ impl Entry {
|
||||||
Some(Compression::Gzip) => decompress!(flate2::read::GzDecoder<&[u8]>),
|
Some(Compression::Gzip) => decompress!(flate2::read::GzDecoder<&[u8]>),
|
||||||
Some(Compression::Oodle) => {
|
Some(Compression::Oodle) => {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
return Err(super::Error::Other(
|
return Err(super::Error::Oodle());
|
||||||
"Oodle compression only supported on Windows (or WINE)",
|
|
||||||
));
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let lib = libloading::Library::new("oo2core_9_win64.dll").map_err(|_| {
|
let lib = libloading::Library::new("oo2core_9_win64.dll")
|
||||||
super::Error::Other(
|
.map_err(|_| super::Error::OodleMissing())?;
|
||||||
"Could not find oo2core_9_win64.dll for Oodle compression",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
let set_printf: libloading::Symbol<
|
let set_printf: libloading::Symbol<
|
||||||
|
@ -451,7 +446,7 @@ impl Entry {
|
||||||
3,
|
3,
|
||||||
);
|
);
|
||||||
if out == 0 {
|
if out == 0 {
|
||||||
return Err(super::Error::Other("decompression failed"));
|
return Err(super::Error::DecompressionFailed(Compression::Oodle));
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out as u64, self.uncompressed,
|
out as u64, self.uncompressed,
|
||||||
|
|
|
@ -1,35 +1,79 @@
|
||||||
#[derive(thiserror::Error, Debug)]
|
use crate::Compression;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
// dependency errors
|
// dependency errors
|
||||||
#[error("enum conversion: {0}")]
|
#[error("enum conversion: {0}")]
|
||||||
Strum(#[from] strum::ParseError),
|
Strum(#[from] strum::ParseError),
|
||||||
|
|
||||||
#[error("key hash is an incorrect length")]
|
#[error("key hash is an incorrect length")]
|
||||||
Aes,
|
Aes,
|
||||||
|
|
||||||
#[error("malformed base64")]
|
#[error("malformed base64")]
|
||||||
Base64,
|
Base64,
|
||||||
|
|
||||||
// std errors
|
// std errors
|
||||||
#[error("io error: {0}")]
|
#[error("io error: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("utf8 conversion: {0}")]
|
#[error("utf8 conversion: {0}")]
|
||||||
Utf8(#[from] std::string::FromUtf8Error),
|
Utf8(#[from] std::string::FromUtf8Error),
|
||||||
|
|
||||||
#[error("utf16 conversion: {0}")]
|
#[error("utf16 conversion: {0}")]
|
||||||
Utf16(#[from] std::string::FromUtf16Error),
|
Utf16(#[from] std::string::FromUtf16Error),
|
||||||
|
|
||||||
#[error("bufwriter dereference: {0}")]
|
#[error("bufwriter dereference: {0}")]
|
||||||
IntoInner(#[from] std::io::IntoInnerError<std::io::BufWriter<Vec<u8>>>),
|
IntoInner(#[from] std::io::IntoInnerError<std::io::BufWriter<Vec<u8>>>),
|
||||||
|
|
||||||
// crate errors
|
// crate errors
|
||||||
#[error("got {0}, which is not a boolean")]
|
#[error("got {0}, which is not a boolean")]
|
||||||
Bool(u8),
|
Bool(u8),
|
||||||
|
|
||||||
#[error("found magic of {0:#x} instead of {:#x}", super::MAGIC)]
|
#[error("found magic of {0:#x} instead of {:#x}", super::MAGIC)]
|
||||||
Magic(u32),
|
Magic(u32),
|
||||||
|
|
||||||
|
#[error("Oodle compression only supported on Windows (or WINE)")]
|
||||||
|
Oodle(),
|
||||||
|
|
||||||
|
#[error("Could not find oo2core_9_win64.dll for Oodle compression")]
|
||||||
|
OodleMissing(),
|
||||||
|
|
||||||
|
#[error("No entry found at {0}")]
|
||||||
|
MissingEntry(String),
|
||||||
|
|
||||||
|
#[error("Prefix \"{prefix}\" does not match path \"{path}\"")]
|
||||||
|
PrefixMismatch { prefix: String, path: String },
|
||||||
|
|
||||||
|
#[error("Attempted to write to \"{0}\" which outside of output directory")]
|
||||||
|
WriteOutsideOutput(String),
|
||||||
|
|
||||||
|
#[error("Output directory is not empty: \"{0}\"")]
|
||||||
|
OutputNotEmpty(String),
|
||||||
|
|
||||||
|
#[error("Input is not a directory: \"{0}\"")]
|
||||||
|
InputNotADirectory(String),
|
||||||
|
|
||||||
|
#[error("{0} decompression failed")]
|
||||||
|
DecompressionFailed(Compression),
|
||||||
|
|
||||||
#[error("used version {used} but pak is version {version}")]
|
#[error("used version {used} but pak is version {version}")]
|
||||||
Version {
|
Version {
|
||||||
used: super::VersionMajor,
|
used: super::VersionMajor,
|
||||||
version: super::VersionMajor,
|
version: super::VersionMajor,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("pak is encrypted but no key was provided")]
|
#[error("pak is encrypted but no key was provided")]
|
||||||
Encrypted,
|
Encrypted,
|
||||||
|
|
||||||
#[error("error with OsString")]
|
#[error("error with OsString")]
|
||||||
OsString(std::ffi::OsString),
|
OsString(std::ffi::OsString),
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(&'static str),
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
std::fmt::Display::fmt(self, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ impl PakReader {
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(super::Error::Other("version unsupported"))
|
Err(super::Error::Other("version unsupported".to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> super::Version {
|
pub fn version(&self) -> super::Version {
|
||||||
|
@ -122,7 +122,7 @@ impl PakReader {
|
||||||
self.key.as_ref(),
|
self.key.as_ref(),
|
||||||
writer,
|
writer,
|
||||||
),
|
),
|
||||||
None => Err(super::Error::Other("no file found at given path")),
|
None => Err(super::Error::MissingEntry(path.to_owned())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,10 @@ struct ActionUnpack {
|
||||||
#[arg(short, long, default_value = "false")]
|
#[arg(short, long, default_value = "false")]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
|
||||||
|
/// Force overwrite existing files/directories.
|
||||||
|
#[arg(short, long, default_value = "false")]
|
||||||
|
force: bool,
|
||||||
|
|
||||||
/// Files or directories to include. Can be specified multiple times. If not specified, everything is extracted.
|
/// Files or directories to include. Can be specified multiple times. If not specified, everything is extracted.
|
||||||
#[arg(action = clap::ArgAction::Append, short, long)]
|
#[arg(action = clap::ArgAction::Append, short, long)]
|
||||||
include: Vec<String>,
|
include: Vec<String>,
|
||||||
|
@ -132,9 +136,6 @@ struct Args {
|
||||||
fn main() -> Result<(), repak::Error> {
|
fn main() -> Result<(), repak::Error> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
//let aasdf = repak::Version::iter().map(|v| format!("{v}"));
|
|
||||||
//clap::builder::PossibleValuesParser::new(aasdf.map(|a| a.as_str()));
|
|
||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
Action::Info(args) => info(args),
|
Action::Info(args) => info(args),
|
||||||
Action::List(args) => list(args),
|
Action::List(args) => list(args),
|
||||||
|
@ -191,8 +192,10 @@ fn unpack(args: ActionUnpack) -> Result<(), repak::Error> {
|
||||||
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()),
|
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}?;
|
}?;
|
||||||
if output.read_dir()?.next().is_some() {
|
if !args.force && output.read_dir()?.next().is_some() {
|
||||||
return Err(repak::Error::Other("output directory not empty"));
|
return Err(repak::Error::OutputNotEmpty(
|
||||||
|
output.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let mount_point = PathBuf::from(pak.mount_point());
|
let mount_point = PathBuf::from(pak.mount_point());
|
||||||
let prefix = Path::new(&args.strip_prefix);
|
let prefix = Path::new(&args.strip_prefix);
|
||||||
|
@ -203,43 +206,62 @@ fn unpack(args: ActionUnpack) -> Result<(), repak::Error> {
|
||||||
.map(|i| prefix.join(Path::new(i)))
|
.map(|i| prefix.join(Path::new(i)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
|
struct UnpackEntry {
|
||||||
|
entry_path: String,
|
||||||
|
out_path: PathBuf,
|
||||||
|
out_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
pak.files().into_par_iter().try_for_each_init(
|
let entries =
|
||||||
|| (File::open(&args.input), includes.clone(), counter.clone()),
|
pak.files()
|
||||||
|(file, includes, counter), path| -> Result<(), repak::Error> {
|
.into_iter()
|
||||||
let full_path = mount_point.join(&path);
|
.map(|entry_path| {
|
||||||
if !includes.is_empty() && !includes.iter().any(|i| full_path.starts_with(i)) {
|
let full_path = mount_point.join(&entry_path);
|
||||||
return Ok(());
|
if !includes.is_empty() && !includes.iter().any(|i| full_path.starts_with(i)) {
|
||||||
}
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let out_path = output
|
||||||
|
.join(full_path.strip_prefix(prefix).map_err(|_| {
|
||||||
|
repak::Error::PrefixMismatch {
|
||||||
|
path: full_path.to_string_lossy().to_string(),
|
||||||
|
prefix: prefix.to_string_lossy().to_string(),
|
||||||
|
}
|
||||||
|
})?)
|
||||||
|
.clean();
|
||||||
|
|
||||||
|
if !out_path.starts_with(&output) {
|
||||||
|
return Err(repak::Error::WriteOutsideOutput(
|
||||||
|
out_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_dir = out_path.parent().expect("will be a file").to_path_buf();
|
||||||
|
|
||||||
|
Ok(Some(UnpackEntry {
|
||||||
|
entry_path,
|
||||||
|
out_path,
|
||||||
|
out_dir,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.filter_map(|e| e.transpose())
|
||||||
|
.collect::<Result<Vec<_>, repak::Error>>()?;
|
||||||
|
|
||||||
|
entries.par_iter().try_for_each_init(
|
||||||
|
|| File::open(&args.input),
|
||||||
|
|file, entry| -> Result<(), repak::Error> {
|
||||||
if args.verbose {
|
if args.verbose {
|
||||||
println!("extracting {path}");
|
println!("extracting {}", entry.entry_path);
|
||||||
}
|
}
|
||||||
let file_path = output.join(
|
fs::create_dir_all(&entry.out_dir)?;
|
||||||
full_path
|
|
||||||
.strip_prefix(prefix)
|
|
||||||
.map_err(|_| repak::Error::Other("prefix does not match"))?,
|
|
||||||
);
|
|
||||||
if !file_path.clean().starts_with(&output) {
|
|
||||||
return Err(repak::Error::Other(
|
|
||||||
"tried to write file outside of output directory",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
fs::create_dir_all(file_path.parent().expect("will be a file"))?;
|
|
||||||
counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
pak.read_file(
|
pak.read_file(
|
||||||
&path,
|
&entry.entry_path,
|
||||||
&mut BufReader::new(file.as_ref().unwrap()), // TODO: avoid this unwrap
|
&mut BufReader::new(file.as_ref().unwrap()), // TODO: avoid this unwrap
|
||||||
&mut fs::File::create(file_path)?,
|
&mut fs::File::create(&entry.out_path)?,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!(
|
println!("Unpacked {} files to {}", entries.len(), output.display());
|
||||||
"Unpacked {} files to {}",
|
|
||||||
counter.load(std::sync::atomic::Ordering::Relaxed),
|
|
||||||
output.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -264,7 +286,9 @@ fn pack(args: ActionPack) -> Result<(), repak::Error> {
|
||||||
}
|
}
|
||||||
let input_path = Path::new(&args.input);
|
let input_path = Path::new(&args.input);
|
||||||
if !input_path.is_dir() {
|
if !input_path.is_dir() {
|
||||||
return Err(repak::Error::Other("input is not a directory"));
|
return Err(repak::Error::InputNotADirectory(
|
||||||
|
input_path.to_string_lossy().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
collect_files(&mut paths, input_path)?;
|
collect_files(&mut paths, input_path)?;
|
||||||
|
@ -309,7 +333,10 @@ fn get(args: ActionGet) -> Result<(), repak::Error> {
|
||||||
let full_path = mount_point.join(args.file);
|
let full_path = mount_point.join(args.file);
|
||||||
let file = full_path
|
let file = full_path
|
||||||
.strip_prefix(prefix)
|
.strip_prefix(prefix)
|
||||||
.map_err(|_| repak::Error::Other("prefix does not match"))?;
|
.map_err(|_| repak::Error::PrefixMismatch {
|
||||||
|
path: full_path.to_string_lossy().to_string(),
|
||||||
|
prefix: prefix.to_string_lossy().to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
std::io::stdout().write_all(&pak.get(&file.to_string_lossy(), &mut reader)?)?;
|
std::io::stdout().write_all(&pak.get(&file.to_string_lossy(), &mut reader)?)?;
|
||||||
|
|
Loading…
Reference in a new issue