mirror of
https://github.com/PrimeDecomp/retrotool.git
synced 2026-07-12 18:18:58 -07:00
Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
/.idea
|
||||
/out
|
||||
/target
|
||||
Generated
+415
@@ -0,0 +1,415 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
|
||||
[[package]]
|
||||
name = "argh"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab257697eb9496bf75526f0217b5ed64636a9cfafa78b8365c71bd283fcef93e"
|
||||
dependencies = [
|
||||
"argh_derive",
|
||||
"argh_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argh_derive"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6"
|
||||
dependencies = [
|
||||
"argh_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argh_shared"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f"
|
||||
|
||||
[[package]]
|
||||
name = "array-init"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
|
||||
|
||||
[[package]]
|
||||
name = "binrw"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "272caaf6e0bfb7d508c0606e541e2c68f85c0d6352b62d0b299924eed59fe384"
|
||||
dependencies = [
|
||||
"array-init",
|
||||
"binrw_derive",
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binrw_derive"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb4b28c1e534d96213c8966bb9240095757aa0909128985f97d16afd2e7257a8"
|
||||
dependencies = [
|
||||
"either",
|
||||
"owo-colors",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||
|
||||
[[package]]
|
||||
name = "paktool-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argh",
|
||||
"binrw",
|
||||
"binrw_derive",
|
||||
"byteorder",
|
||||
"env_logger",
|
||||
"log",
|
||||
"memmap2",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "paktool-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.69"
|
||||
argh = "0.1.10"
|
||||
binrw = "0.11.1"
|
||||
binrw_derive = "0.11.1"
|
||||
byteorder = "1.4.3"
|
||||
env_logger = "0.10.0"
|
||||
log = "0.4.17"
|
||||
memmap2 = "0.5.8"
|
||||
uuid = "1.3.0"
|
||||
@@ -0,0 +1,8 @@
|
||||
fn_single_line = true
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
overflow_delimited_expr = true
|
||||
reorder_impl_items = true
|
||||
use_field_init_shorthand = true
|
||||
use_small_heuristics = "Max"
|
||||
where_single_line = true
|
||||
@@ -0,0 +1,63 @@
|
||||
// From https://gist.github.com/suluke/e0c672492126be0a4f3b4f0e1115d77c
|
||||
//! Extend `argh` to be better integrated with the `cargo` ecosystem
|
||||
//!
|
||||
//! For now, this only adds a --version/-V option which causes early-exit.
|
||||
use argh::{FromArgs, TopLevelCommand};
|
||||
|
||||
struct ArgsOrVersion<T: FromArgs>(T);
|
||||
impl<T> TopLevelCommand for ArgsOrVersion<T> where T: FromArgs {}
|
||||
impl<T> FromArgs for ArgsOrVersion<T>
|
||||
where T: FromArgs
|
||||
{
|
||||
fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, argh::EarlyExit> {
|
||||
/// Also use argh for catching `--version`-only invocations
|
||||
#[derive(FromArgs)]
|
||||
struct Version {
|
||||
/// print version information and exit
|
||||
#[argh(switch, short = 'V')]
|
||||
pub version: bool,
|
||||
}
|
||||
match Version::from_args(command_name, args) {
|
||||
Ok(v) => {
|
||||
if v.version {
|
||||
Err(argh::EarlyExit {
|
||||
output: format!(
|
||||
"{} {}",
|
||||
command_name.first().unwrap_or(&""),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
),
|
||||
status: Ok(()),
|
||||
})
|
||||
} else {
|
||||
// seems args are empty
|
||||
T::from_args(command_name, args).map(Self)
|
||||
}
|
||||
}
|
||||
Err(exit) => match exit.status {
|
||||
Ok(()) => {
|
||||
// must have been --help
|
||||
let help = match T::from_args(command_name, &["--help"]) {
|
||||
Ok(_) => unreachable!(),
|
||||
Err(exit) => exit.output,
|
||||
};
|
||||
Err(argh::EarlyExit {
|
||||
output: format!(
|
||||
"{help} -V, --version print version information and exit"
|
||||
),
|
||||
status: Ok(()),
|
||||
})
|
||||
}
|
||||
Err(()) => T::from_args(command_name, args).map(Self),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `FromArgs` type from the current process’s `env::args`.
|
||||
///
|
||||
/// This function will exit early from the current process if argument parsing was unsuccessful or if information like `--help` was requested.
|
||||
/// Error messages will be printed to stderr, and `--help` output to stdout.
|
||||
pub fn from_env<T>() -> T
|
||||
where T: TopLevelCommand {
|
||||
argh::from_env::<ArgsOrVersion<T>>().0
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod pak;
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter, Write},
|
||||
fs,
|
||||
fs::File,
|
||||
io::{Cursor, Read, Seek, SeekFrom},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use argh::FromArgs;
|
||||
use binrw::{BinRead, BinReaderExt, BinResult, Endian, NullString};
|
||||
use binrw_derive::{binrw, BinWrite};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::util::file::map_file;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// process PAK files
|
||||
#[argh(subcommand, name = "pak")]
|
||||
pub struct Args {
|
||||
#[argh(subcommand)]
|
||||
command: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
#[argh(subcommand)]
|
||||
enum SubCommand {
|
||||
Extract(ExtractArgs),
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// extract a PAK file
|
||||
#[argh(subcommand, name = "extract")]
|
||||
pub struct ExtractArgs {
|
||||
#[argh(positional)]
|
||||
/// input file
|
||||
input: PathBuf,
|
||||
#[argh(positional)]
|
||||
/// output directory
|
||||
output: PathBuf,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct FourCC(pub [u8; 4]);
|
||||
|
||||
impl FourCC {
|
||||
#[inline]
|
||||
const fn swap(self) -> Self { Self([self.0[3], self.0[2], self.0[1], self.0[0]]) }
|
||||
}
|
||||
|
||||
impl Display for FourCC {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for c in self.0 {
|
||||
f.write_char(c as char)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FourCC {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_char('"')?;
|
||||
for c in self.0 {
|
||||
f.write_char(c as char)?;
|
||||
}
|
||||
f.write_char('"')?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<[u8; 4]> for FourCC {
|
||||
fn eq(&self, other: &[u8; 4]) -> bool { &self.0 == other }
|
||||
}
|
||||
|
||||
// PAK file
|
||||
const K_FORM_PAK: FourCC = FourCC(*b"PACK");
|
||||
// Table of contents
|
||||
const K_FORM_TOC: FourCC = FourCC(*b"TOCC");
|
||||
|
||||
#[binrw]
|
||||
#[brw(magic = b"RFRM")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FormDescriptor {
|
||||
pub size: u64,
|
||||
pub unk: u64,
|
||||
pub id: FourCC,
|
||||
pub version: u32,
|
||||
pub other_version: u32, // ?
|
||||
}
|
||||
|
||||
impl FormDescriptor {
|
||||
#[inline]
|
||||
fn read<R: Read + Seek>(reader: &mut R, e: Endian) -> BinResult<Self> { reader.read_type(e) }
|
||||
|
||||
#[inline]
|
||||
fn slice(data: &[u8], e: Endian) -> BinResult<(Self, &[u8], &[u8])> {
|
||||
let mut reader = Cursor::new(data);
|
||||
let header = Self::read(&mut reader, e)?;
|
||||
let start = reader.position();
|
||||
let slice = &data[start as usize..(start + header.size) as usize];
|
||||
let remain = &data[(start + header.size) as usize..];
|
||||
Ok((header, slice, remain))
|
||||
}
|
||||
}
|
||||
|
||||
// Asset directory
|
||||
const K_CHUNK_ADIR: FourCC = FourCC(*b"ADIR");
|
||||
// Metadata
|
||||
const K_CHUNK_META: FourCC = FourCC(*b"META");
|
||||
// String table
|
||||
const K_CHUNK_STRG: FourCC = FourCC(*b"STRG");
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ChunkDescriptor {
|
||||
pub id: FourCC,
|
||||
pub size: u64,
|
||||
pub unk: u32,
|
||||
// game skips this amount of bytes before continuing
|
||||
// but always 0?
|
||||
pub skip: u64,
|
||||
}
|
||||
|
||||
impl ChunkDescriptor {
|
||||
#[inline]
|
||||
fn read<R: Read + Seek>(reader: &mut R, e: Endian) -> BinResult<Self> {
|
||||
let desc: ChunkDescriptor = reader.read_type(e)?;
|
||||
reader.seek(SeekFrom::Current(desc.skip as i64))?;
|
||||
Ok(desc)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn slice(data: &[u8], e: Endian) -> BinResult<(Self, &[u8], &[u8])> {
|
||||
let mut reader = Cursor::new(data);
|
||||
let header = Self::read(&mut reader, e)?;
|
||||
let start = reader.position();
|
||||
let slice = &data[start as usize..(start + header.size) as usize];
|
||||
let remain = &data[(start + header.size) as usize..];
|
||||
Ok((header, slice, remain))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum ChunkType {
|
||||
AssetDirectory(AssetDirectory),
|
||||
Metadata(Metadata),
|
||||
StringTable(StringTable),
|
||||
}
|
||||
|
||||
impl ChunkType {
|
||||
#[inline]
|
||||
fn read(data: &[u8], kind: FourCC, e: Endian) -> Result<Self> {
|
||||
let mut reader = Cursor::new(data);
|
||||
match kind {
|
||||
K_CHUNK_ADIR => Ok(Self::AssetDirectory(reader.read_type(e)?)),
|
||||
K_CHUNK_META => Ok(Self::Metadata(reader.read_type(e)?)),
|
||||
K_CHUNK_STRG => Ok(Self::StringTable(reader.read_type(e)?)),
|
||||
_ => Err(anyhow!("Unknown chunk type {:?}", kind)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
struct AssetDirectory {
|
||||
#[bw(try_calc = entries.len().try_into())]
|
||||
entry_count: u32,
|
||||
#[br(count = entry_count)]
|
||||
entries: Vec<AssetDirectoryEntry>,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssetDirectoryEntry {
|
||||
pub asset_type: FourCC,
|
||||
#[br(map = Uuid::from_u128)]
|
||||
#[bw(map = Uuid::as_u128)]
|
||||
pub asset_id: Uuid,
|
||||
pub unk1: u32,
|
||||
pub unk2: u32,
|
||||
pub offset: u64,
|
||||
pub size: u64,
|
||||
pub unk3: u64,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Metadata {
|
||||
#[bw(try_calc = entries.len().try_into())]
|
||||
entry_count: u32,
|
||||
#[br(count = entry_count)]
|
||||
entries: Vec<MetadataEntry>,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MetadataEntry {
|
||||
#[br(map = Uuid::from_u128)]
|
||||
#[bw(map = Uuid::as_u128)]
|
||||
pub asset_id: Uuid,
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StringTable {
|
||||
#[bw(try_calc = entries.len().try_into())]
|
||||
entry_count: u32,
|
||||
#[br(count = entry_count)]
|
||||
entries: Vec<StringTableEntry>,
|
||||
}
|
||||
|
||||
impl StringTable {
|
||||
fn name_for_uuid(&self, id: Uuid) -> Option<String> {
|
||||
self.entries
|
||||
.iter()
|
||||
.find(|e| e.asset_id == id)
|
||||
.map(|e| String::from_utf8(e.name.clone()).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StringTableEntry {
|
||||
#[br(map = FourCC::swap)]
|
||||
#[bw(map = |&f| f.swap())]
|
||||
pub kind: FourCC,
|
||||
#[br(map = Uuid::from_u128)]
|
||||
#[bw(map = Uuid::as_u128)]
|
||||
pub asset_id: Uuid,
|
||||
#[bw(try_calc = name.len().try_into())]
|
||||
pub name_length: u32,
|
||||
#[br(count = name_length)]
|
||||
pub name: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
match args.command {
|
||||
SubCommand::Extract(c_args) => extract(c_args),
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively dump an RFRM + contained chunks
|
||||
fn dump_rfrm<'a, W: std::io::Write>(w: &mut W, data: &'a [u8], indent: usize) -> Result<&'a [u8]> {
|
||||
let (rfrm, mut rfrm_data, remain) = FormDescriptor::slice(data, Endian::Little)?;
|
||||
let indstr = " ".repeat(indent);
|
||||
writeln!(w, "{indstr}{rfrm:?}")?;
|
||||
while !rfrm_data.is_empty() {
|
||||
if rfrm_data[0..4] == *b"RFRM" {
|
||||
rfrm_data = dump_rfrm(w, rfrm_data, indent + 1)?;
|
||||
} else {
|
||||
let (desc, chunk_data, remain) = ChunkDescriptor::slice(rfrm_data, Endian::Little)?;
|
||||
writeln!(w, "{indstr}- {desc:?}")?;
|
||||
rfrm_data = remain;
|
||||
}
|
||||
}
|
||||
Ok(remain)
|
||||
}
|
||||
|
||||
fn extract(args: ExtractArgs) -> Result<()> {
|
||||
let data = map_file(args.input)?;
|
||||
let (pack, pack_data, _) = FormDescriptor::slice(&data, Endian::Little)?;
|
||||
ensure!(pack.id == K_FORM_PAK);
|
||||
ensure!(pack.version == 1);
|
||||
log::info!("PACK: {:?}", pack);
|
||||
let (tocc, mut tocc_data, pack_remain) = FormDescriptor::slice(pack_data, Endian::Little)?;
|
||||
ensure!(tocc.id == K_FORM_TOC);
|
||||
ensure!(tocc.version == 3);
|
||||
log::info!("TOCC: {:?}", tocc);
|
||||
let mut adir: Option<AssetDirectory> = None;
|
||||
let mut meta: Option<Metadata> = None;
|
||||
let mut strg: Option<StringTable> = None;
|
||||
while !tocc_data.is_empty() {
|
||||
let (desc, chunk_data, remain) = ChunkDescriptor::slice(tocc_data, Endian::Little)?;
|
||||
log::info!("{:?} data size {}", desc, chunk_data.len());
|
||||
let header = ChunkType::read(chunk_data, desc.id, Endian::Little)?;
|
||||
match header {
|
||||
ChunkType::AssetDirectory(chunk) => {
|
||||
for entry in &chunk.entries {
|
||||
log::info!("- {:?}", entry);
|
||||
}
|
||||
adir = Some(chunk);
|
||||
}
|
||||
ChunkType::Metadata(chunk) => {
|
||||
for entry in &chunk.entries {
|
||||
log::info!("- {:?}", entry);
|
||||
}
|
||||
meta = Some(chunk);
|
||||
}
|
||||
ChunkType::StringTable(chunk) => {
|
||||
for entry in &chunk.entries {
|
||||
log::info!("- {:?}", entry);
|
||||
}
|
||||
strg = Some(chunk);
|
||||
}
|
||||
}
|
||||
tocc_data = remain;
|
||||
}
|
||||
log::info!("Remaining PACK data: {:#X}", pack_remain.len());
|
||||
if let Some(adir) = adir {
|
||||
for asset_entry in adir.entries {
|
||||
let name = strg
|
||||
.as_ref()
|
||||
.and_then(|table| table.name_for_uuid(asset_entry.asset_id))
|
||||
.unwrap_or_else(|| format!("{}", asset_entry.asset_id));
|
||||
let filename = format!("{}.{}", name, asset_entry.asset_type);
|
||||
let path = args.output.join(filename);
|
||||
// log::info!("Extracting {}", path.display());
|
||||
let mut data = &data
|
||||
[asset_entry.offset as usize..(asset_entry.offset + asset_entry.size) as usize];
|
||||
if asset_entry.asset_type == *b"FMV0" {
|
||||
let (hdr, fmv_data, _) = FormDescriptor::slice(data, Endian::Little)?;
|
||||
// log::info!("{:?}", hdr);
|
||||
data = fmv_data;
|
||||
} else if asset_entry.asset_type == *b"ROOM" {
|
||||
let mut file = File::create("ROOM-format.txt")?;
|
||||
dump_rfrm(&mut file, data, 0)?;
|
||||
}
|
||||
fs::write(path, data)?;
|
||||
}
|
||||
} else {
|
||||
bail!("Failed to locate ADIR chunk");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
mod argh_version;
|
||||
mod cmd;
|
||||
mod util;
|
||||
|
||||
use argh::FromArgs;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// GameCube/Wii decompilation project tools.
|
||||
struct TopLevel {
|
||||
#[argh(subcommand)]
|
||||
command: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
#[argh(subcommand)]
|
||||
enum SubCommand {
|
||||
Pak(cmd::pak::Args),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
|
||||
.format_timestamp(None)
|
||||
.format_target(false)
|
||||
.format_level(false)
|
||||
.init();
|
||||
|
||||
let args: TopLevel = argh_version::from_env();
|
||||
let result = match args.command {
|
||||
SubCommand::Pak(args) => cmd::pak::run(args),
|
||||
};
|
||||
if let Err(e) = result {
|
||||
eprintln!("Failed: {e:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, Cursor, Read},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use byteorder::ReadBytesExt;
|
||||
use memmap2::{Mmap, MmapOptions};
|
||||
|
||||
/// Opens a memory mapped file.
|
||||
pub fn map_file<P: AsRef<Path>>(path: P) -> Result<Mmap> {
|
||||
let file = File::open(&path)
|
||||
.with_context(|| format!("Failed to open file '{}'", path.as_ref().display()))?;
|
||||
let map = unsafe { MmapOptions::new().map(&file) }
|
||||
.with_context(|| format!("Failed to mmap file: '{}'", path.as_ref().display()))?;
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
pub type Reader<'a> = Cursor<&'a [u8]>;
|
||||
|
||||
/// Creates a reader for the memory mapped file.
|
||||
#[inline]
|
||||
pub fn map_reader(mmap: &Mmap) -> Reader { Cursor::new(&*mmap) }
|
||||
|
||||
/// Creates a buffered reader around a file (not memory mapped).
|
||||
pub fn buf_reader<P: AsRef<Path>>(path: P) -> Result<BufReader<File>> {
|
||||
let file = File::open(&path)
|
||||
.with_context(|| format!("Failed to open file '{}'", path.as_ref().display()))?;
|
||||
Ok(BufReader::new(file))
|
||||
}
|
||||
|
||||
/// Reads a string with known size at the specified offset.
|
||||
pub fn read_string(reader: &mut Reader, off: u64, size: usize) -> Result<String> {
|
||||
let mut data = vec![0u8; size];
|
||||
let pos = reader.position();
|
||||
reader.set_position(off);
|
||||
reader.read_exact(&mut data)?;
|
||||
reader.set_position(pos);
|
||||
Ok(String::from_utf8(data)?)
|
||||
}
|
||||
|
||||
/// Reads a zero-terminated string at the specified offset.
|
||||
pub fn read_c_string(reader: &mut Reader, off: u64) -> Result<String> {
|
||||
let pos = reader.position();
|
||||
reader.set_position(off);
|
||||
let mut s = String::new();
|
||||
loop {
|
||||
let b = reader.read_u8()?;
|
||||
if b == 0 {
|
||||
break;
|
||||
}
|
||||
s.push(b as char);
|
||||
}
|
||||
reader.set_position(pos);
|
||||
Ok(s)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod file;
|
||||
Reference in New Issue
Block a user