mirror of
https://github.com/xavo95/repak.git
synced 2025-01-18 10:54:38 +00:00
make proper api + example + README
This commit is contained in:
parent
07f97f6c98
commit
0959394241
15 changed files with 118 additions and 63 deletions
|
@ -6,7 +6,7 @@ description = "a no-nonsense unreal pak parsing crate"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["modding", "parsing", "compression"]
|
keywords = ["modding", "parsing", "compression"]
|
||||||
categories = ["filesystem", "parser-implementations"]
|
categories = ["filesystem", "parser-implementations"]
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# unpak
|
||||||
|
## a no-nonsense unreal pak parser
|
||||||
|
- doesn't force files to be extracted
|
||||||
|
- only converts entries to bytes when requested
|
||||||
|
- supports up to frozen index (4.25) paks (planned support for higher)
|
||||||
|
- supports compressed and encrypted paks
|
||||||
|
- supports iteration over entries
|
||||||
|
## [click for example code](https://github.com/bananaturtlesandwich/unpak/blob/master/examples/unpak.rs)
|
||||||
|
## the problem
|
||||||
|
looking at the libraries for pak reading, they were never not quite right for what i wanted to do:
|
||||||
|
- [rust-u4pak](https://github.com/panzi/rust-u4pak) - excellent support but very limited api
|
||||||
|
- [ue4pak](https://github.com/Speedy37/ue4pak-rs) - excellent api but no support for extraction
|
||||||
|
- [unrealpak](https://github.com/AstroTechies/unrealmodding/tree/main/unreal_pak) - excellent api but only supports version 8
|
||||||
|
- [rust-unreal-unpak](https://crates.io/crates/rust-unreal-unpak) - is async only supports version 10
|
||||||
|
|
||||||
|
so i just though *fuck it i'll do it myself* and did it myself
|
||||||
|
|
||||||
|
## references
|
||||||
|
although the api of [rust-u4pak](https://github.com/panzi/rust-u4pak) wasn't very friendly, the [`README`](https://github.com/panzi/rust-u4pak#readme) went into beautiful detail into the intricacies of the file format and when the readme had incorrect info *cough cough* `encryption uuid` *cough cough* the source code also had the answers as long as you looked hard enough
|
|
@ -1,11 +0,0 @@
|
||||||
fn main() -> Result<(), unpak::Error> {
|
|
||||||
let pak = unpak::Pak::new(
|
|
||||||
std::io::BufReader::new(std::io::Cursor::new(include_bytes!("rando_p.pak"))),
|
|
||||||
unpak::Version::CompressionEncryption,
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
for file in pak.files() {
|
|
||||||
println!("{file}");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
6
examples/subcommands/list.rs
Normal file
6
examples/subcommands/list.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
pub fn list(path: String, key: String) -> Result<(), unpak::Error> {
|
||||||
|
for file in super::load_pak(path, key)?.files() {
|
||||||
|
println!("{file}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
26
examples/subcommands/mod.rs
Normal file
26
examples/subcommands/mod.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
mod list;
|
||||||
|
mod unpack;
|
||||||
|
mod version;
|
||||||
|
pub use {list::list, unpack::unpack, version::version};
|
||||||
|
|
||||||
|
fn load_pak(
|
||||||
|
path: String,
|
||||||
|
key: String,
|
||||||
|
) -> Result<unpak::Pak<std::io::BufReader<std::fs::File>>, unpak::Error> {
|
||||||
|
for ver in unpak::Version::iter() {
|
||||||
|
match unpak::Pak::new(
|
||||||
|
std::io::BufReader::new(std::fs::OpenOptions::new().read(true).open(&path)?),
|
||||||
|
ver,
|
||||||
|
match key.as_bytes() {
|
||||||
|
&[] => None,
|
||||||
|
key => Some(key),
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(pak) => {
|
||||||
|
return Ok(pak);
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(unpak::Error::Other("version unsupported"))
|
||||||
|
}
|
17
examples/subcommands/unpack.rs
Normal file
17
examples/subcommands/unpack.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
pub fn unpack(path: String, key: String) -> Result<(), unpak::Error> {
|
||||||
|
let folder = std::path::Path::new(
|
||||||
|
std::path::Path::new(&path)
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
let mut pak = super::load_pak(path.clone(), key)?;
|
||||||
|
for file in pak.files() {
|
||||||
|
std::fs::create_dir_all(folder.join(&file).parent().expect("will be a file"))?;
|
||||||
|
match pak.get(&file).expect("file should be in pak") {
|
||||||
|
Ok(data) => std::fs::write(folder.join(&file), data)?,
|
||||||
|
Err(e) => eprintln!("{e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
4
examples/subcommands/version.rs
Normal file
4
examples/subcommands/version.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub fn version(path: String, key: String) -> Result<(), unpak::Error> {
|
||||||
|
println!("{}", super::load_pak(path, key)?.version());
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
fn main() -> Result<(), unpak::Error> {
|
|
||||||
let mut pak = unpak::Pak::new(
|
|
||||||
std::io::BufReader::new(std::io::Cursor::new(include_bytes!("rando_p.pak"))),
|
|
||||||
unpak::Version::CompressionEncryption,
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
for file in pak.files() {
|
|
||||||
std::fs::create_dir_all(
|
|
||||||
std::path::Path::new(&file)
|
|
||||||
.parent()
|
|
||||||
.expect("will be a file"),
|
|
||||||
)?;
|
|
||||||
match pak.get(&file).expect("file should be in pak") {
|
|
||||||
Ok(data) => std::fs::write(&file, data)?,
|
|
||||||
Err(e) => eprintln!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
37
examples/unpak.rs
Normal file
37
examples/unpak.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
mod subcommands;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = std::env::args();
|
||||||
|
args.next();
|
||||||
|
let Some(path) = args.next() else {
|
||||||
|
help()
|
||||||
|
};
|
||||||
|
// can't map key to &[u8] because refers to owned data
|
||||||
|
match match args.next().unwrap_or_default().as_str() {
|
||||||
|
"version" => subcommands::version(path, args.next().unwrap_or_default()),
|
||||||
|
"list" => subcommands::list(path, args.next().unwrap_or_default()),
|
||||||
|
"unpack" | "" => subcommands::unpack(path, args.next().unwrap_or_default()),
|
||||||
|
"help" | _ => help(),
|
||||||
|
} {
|
||||||
|
Ok(_) => println!("success!"),
|
||||||
|
Err(e) => eprintln!("{e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help() -> ! {
|
||||||
|
println!("{HELP}");
|
||||||
|
std::process::exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const HELP: &str = r"
|
||||||
|
usage:
|
||||||
|
unpak <file> <subcommand> <optional AES key>
|
||||||
|
OR
|
||||||
|
drag the file onto the executable
|
||||||
|
|
||||||
|
subcommands:
|
||||||
|
help - show this message
|
||||||
|
unpack - decompress the pak
|
||||||
|
list - print the files in the pak
|
||||||
|
version - print the version of the pak
|
||||||
|
";
|
|
@ -1,24 +0,0 @@
|
||||||
fn main() -> Result<(), unpak::Error> {
|
|
||||||
// drag onto or open any pak with the example
|
|
||||||
let path = std::env::args().nth(1).unwrap_or_default();
|
|
||||||
for ver in unpak::Version::iter() {
|
|
||||||
match unpak::Pak::new(
|
|
||||||
std::io::BufReader::new(std::fs::OpenOptions::new().read(true).open(&path)?),
|
|
||||||
ver,
|
|
||||||
None,
|
|
||||||
) {
|
|
||||||
Ok(pak) => {
|
|
||||||
println!("{}", pak.version());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(unpak::Error::Version { version, .. }) => {
|
|
||||||
println!("{version}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// so you can read the results
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{Compression, ReadExt, Version};
|
use super::{ext::ReadExt, Compression, Version};
|
||||||
use byteorder::{ReadBytesExt, LE};
|
use byteorder::{ReadBytesExt, LE};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
|
|
@ -27,5 +27,5 @@ pub enum Error {
|
||||||
#[error("pak is encrypted but no key was provided")]
|
#[error("pak is encrypted but no key was provided")]
|
||||||
Encrypted,
|
Encrypted,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(String),
|
Other(&'static str),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{Compression, ReadExt, Version};
|
use super::{ext::ReadExt, Compression, Version};
|
||||||
use byteorder::{ReadBytesExt, LE};
|
use byteorder::{ReadBytesExt, LE};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ mod ext;
|
||||||
mod footer;
|
mod footer;
|
||||||
mod pak;
|
mod pak;
|
||||||
|
|
||||||
pub use {entry::*, error::*, ext::*, footer::*, pak::*};
|
pub use {error::*, pak::*};
|
||||||
|
|
||||||
pub const MAGIC: u32 = 0x5A6F12E1;
|
pub const MAGIC: u32 = 0x5A6F12E1;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub struct Pak<R: io::Read + io::Seek> {
|
||||||
version: Version,
|
version: Version,
|
||||||
mount_point: String,
|
mount_point: String,
|
||||||
key: Option<aes::Aes256Dec>,
|
key: Option<aes::Aes256Dec>,
|
||||||
entries: hashbrown::HashMap<String, super::Entry>,
|
entries: hashbrown::HashMap<String, super::entry::Entry>,
|
||||||
reader: R,
|
reader: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,11 +16,11 @@ impl<R: io::Read + io::Seek> Pak<R> {
|
||||||
version: super::Version,
|
version: super::Version,
|
||||||
key_hash: Option<&[u8]>,
|
key_hash: Option<&[u8]>,
|
||||||
) -> Result<Self, super::Error> {
|
) -> Result<Self, super::Error> {
|
||||||
use super::ReadExt;
|
use super::ext::ReadExt;
|
||||||
use byteorder::{ReadBytesExt, LE};
|
use byteorder::{ReadBytesExt, LE};
|
||||||
// read footer to get index, encryption & compression info
|
// read footer to get index, encryption & compression info
|
||||||
reader.seek(io::SeekFrom::End(-version.size()))?;
|
reader.seek(io::SeekFrom::End(-version.size()))?;
|
||||||
let footer = super::Footer::new(&mut reader, version)?;
|
let footer = super::footer::Footer::new(&mut reader, version)?;
|
||||||
// read index to get all the entry info
|
// read index to get all the entry info
|
||||||
reader.seek(io::SeekFrom::Start(footer.index_offset))?;
|
reader.seek(io::SeekFrom::Start(footer.index_offset))?;
|
||||||
let mut index = reader.read_len(footer.index_size as usize)?;
|
let mut index = reader.read_len(footer.index_size as usize)?;
|
||||||
|
@ -46,7 +46,7 @@ impl<R: io::Read + io::Seek> Pak<R> {
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
entries.insert(
|
entries.insert(
|
||||||
index.read_string()?,
|
index.read_string()?,
|
||||||
super::Entry::new(&mut index, version)?,
|
super::entry::Entry::new(&mut index, version)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
Loading…
Reference in a new issue