use std::collections::HashMap;
use ahash::AHashMap;
use clap::Parser;
use tiger_pkg::{package::PackagePlatform, GameVersion, PackageManager, TagHash};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, disable_version_flag(true))]
struct Args {
packages_path: String,
#[arg(short, value_enum)]
version: GameVersion,
#[arg(short, value_enum)]
platform: Option<PackagePlatform>,
}
fn main() -> anyhow::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let args = Args::parse();
let package_manager = PackageManager::new(args.packages_path, args.version, args.platform)?;
let mut totals: HashMap<(u8, u8), (usize, usize)> = Default::default();
let mut references: AHashMap<u32, (usize, usize)> = Default::default();
let mut biggest_of_type: HashMap<(u8, u8), (usize, TagHash)> = Default::default();
for (pkg_id, entries) in package_manager.lookup.tag32_entries_by_pkg {
for (i, entry) in entries.iter().enumerate() {
if entry.file_type == 8 || entry.file_type == 16 {
let e = references.entry(entry.reference).or_default();
e.0 += 1;
e.1 += entry.file_size as usize;
}
match biggest_of_type
.entry((entry.file_type, entry.file_subtype))
.or_default()
{
(size, hash) if *size < entry.file_size as usize => {
*size = entry.file_size as usize;
*hash = TagHash::new(pkg_id, i as u16);
}
_ => {}
}
let e = totals
.entry((entry.file_type, entry.file_subtype))
.or_default();
e.0 += entry.file_size as usize;
e.1 += 1;
}
}
for ((ftype, fsubtype), (size, tag)) in &biggest_of_type {
println!(
"biggest of type {ftype}.{fsubtype} - {tag} ({})",
format_file_size(*size),
);
}
let mut resorted_totals: Vec<((u8, u8), (usize, usize))> = totals.into_iter().collect();
resorted_totals.sort_by_key(|((t, s), _)| ((*t as u32) << 16) | *s as u32);
for ((ftype, fsubtype), (size, count)) in resorted_totals {
println!(
"{ftype}.{fsubtype} - {} ({} files, {} per file on average)",
format_file_size(size),
split_thousands(count, '\''),
format_file_size(size / count)
);
}
println!();
println!("Tag reference types ({} unique):", references.len());
let mut resorted_references: Vec<(u32, (usize, usize))> = references.into_iter().collect();
resorted_references.sort_by_key(|(r, _)| *r);
for (reference, (count, size)) in resorted_references {
println!(
" {:08X} {} \t({}, {} per file on average)",
reference.to_be(),
split_thousands(count, '\''),
format_file_size(size),
format_file_size(size / count)
);
}
Ok(())
}
fn split_thousands(v: usize, separator: char) -> String {
let s: Vec<char> = v.to_string().chars().rev().collect();
let c: Vec<String> = s.chunks(3).map(|s| s.iter().collect::<String>()).collect();
c.into_iter()
.collect::<Vec<String>>()
.join(&separator.to_string())
.chars()
.rev()
.collect()
}
fn format_file_size(size: usize) -> String {
const KB: usize = 1024;
const MB: usize = KB * 1024;
const GB: usize = MB * 1024;
const TB: usize = GB * 1024;
if size < KB {
format!("{} B", size)
} else if size < MB {
format!("{:.2} KB", size as f64 / KB as f64)
} else if size < GB {
format!("{:.2} MB", size as f64 / MB as f64)
} else if size < TB {
format!("{:.2} GB", size as f64 / GB as f64)
} else {
format!("{:.2} TB", size as f64 / TB as f64)
}
}