use std::collections::HashMap; use clap::Parser; use destiny_pkg::{package::PackagePlatform, GameVersion, PackageManager}; use rustc_hash::FxHashMap; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None, disable_version_flag(true))] struct Args { /// Path to packages directory packages_path: String, /// Version of the package to extract #[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: FxHashMap<u32, (usize, usize)> = Default::default(); for (_, entries) in package_manager.package_entry_index { for entry in entries { 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; } let e = totals .entry((entry.file_type, entry.file_subtype)) .or_default(); e.0 += entry.file_size as usize; e.1 += 1; } } 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 { // 12345 => 54321 let s: Vec<char> = v.to_string().chars().rev().collect(); // 54321 => 543 21 let c: Vec<String> = s.chunks(3).map(|s| s.iter().collect::<String>()).collect(); // 543 21 => 12 345 => 12'345 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) } }