Initial commit

This commit is contained in:
Luke Street
2023-02-09 13:04:39 -05:00
commit c1d2a921c9
10 changed files with 926 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
/.idea
/out
/target
Generated
+415
View File
@@ -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
View File
@@ -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"
+8
View File
@@ -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
+63
View File
@@ -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 processs `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
}
+1
View File
@@ -0,0 +1 @@
pub mod pak;
+326
View File
@@ -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
View File
@@ -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);
}
}
+57
View File
@@ -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)
}
+1
View File
@@ -0,0 +1 @@
pub mod file;