use std::path::PathBuf;
use clap::Parser;
use colored::{ColoredString, Colorize};
use picori::{gcm, Gcm};
extern crate picori;
#[derive(Parser, Debug)]
#[command(
name = "gcm_dump",
bin_name = "gcm_dump",
author="Julgodis <self@julgodis.xyz>",
version=env!("CARGO_PKG_VERSION"),
about="Example program to dump .gcm/.iso files using picori",
long_about = None)]
struct Args {
#[arg()]
path: PathBuf,
#[arg(short, long)]
boot: bool,
#[arg(short = '2', long)]
bi2: bool,
#[arg(short = 'l', long)]
apploader: bool,
#[arg(short, long)]
fst: bool,
#[arg(short, long)]
data: bool,
#[arg(short, long)]
all: bool,
#[arg(short, long, default_value = "32")]
width: usize,
}
fn hex2(value: u8) -> ColoredString { format!("{:#04x}", value).cyan() }
fn hex8(value: u32) -> ColoredString { format!("{:#010x}", value).cyan() }
fn num(value: u32) -> ColoredString { format!("{}", value).cyan() }
fn output_boot(boot: &gcm::Boot) {
println!("boot.bin:");
println!(" console: {}", match boot.console {
gcm::ConsoleType::GameCube => "GameCube",
});
println!(
" game code: {} {}",
hex2(boot.game_code[0]),
hex2(boot.game_code[1])
);
println!(" country code: {}", hex2(boot.country_code));
println!(
" maker code: {} {}",
hex2(boot.maker_code[0]),
hex2(boot.maker_code[1])
);
println!(" disc id: {}", hex2(boot.disc_id));
println!(" version: {}", hex2(boot.version));
println!(" audio streaming: {}", hex2(boot.audio_streaming));
println!(
" streaming buffer size: {}",
hex2(boot.streaming_buffer_size)
);
println!(
" debug_monitor_offset: {}",
hex8(boot.debug_monitor_offset)
);
println!(
" debug_monitor_address: {}",
hex8(boot.debug_monitor_address)
);
println!(
" main_executable_offset: {}",
hex8(boot.main_executable_offset)
);
println!(" fst_offset: {}", hex8(boot.fst_offset));
println!(" fst_size: {}", hex8(boot.fst_size));
println!(" fst_max_size: {}", hex8(boot.fst_max_size));
println!(" user_position: {}", hex8(boot.user_position));
println!(" user_length: {}", hex8(boot.user_length));
}
fn output_bi2(bi2: &gcm::Bi2) {
println!("bi2.bin:");
let mut options = bi2
.options()
.iter()
.map(|x| (*x.0, *x.1))
.collect::<Vec<_>>();
options.sort_by(|a, b| a.0.cmp(&b.0));
for (i, value) in options {
println!(" [{:04x}]: {} ({})", i.index(), hex8(value), num(value));
}
}
fn output_data(data: &Vec<u8>, width: usize) {
for (j, line) in data.chunks(width).enumerate() {
print!("{:06x}: ", j * 32);
for byte in line {
print!("{:02x} ", byte);
}
println!();
}
}
fn output_apploader(apploader: &gcm::Apploader, data: bool, width: usize) {
println!("apploader.img:");
println!(" entry point: {}", hex8(apploader.entry_point));
println!(" size: {}", hex8(apploader.size));
println!(" trailer size: {}", hex8(apploader.trailer_size));
println!(" unknown: {}", hex8(apploader.unknown));
if data {
println!(" data:");
output_data(&apploader.data, width);
}
}
fn output_fst(fst: &gcm::Fst) {
println!("fst.bin:");
for (path, entry) in fst.files() {
let indent = path.ancestors().count() - 1;
match entry {
gcm::fst::Entry::Root => {},
gcm::fst::Entry::File {
name, offset, size, ..
} => {
println!(
"{:indent$}{} offset: {} size: {}",
"",
name.green(),
hex8(offset),
hex8(size),
indent = indent * 2
);
},
gcm::fst::Entry::Directory { name, .. } => {
println!("{:indent$}{}", "", name.red(), indent = indent * 2);
},
}
}
}
fn main() {
let args = Args::parse();
let mut dump_boot = args.boot;
let mut dump_bi2 = args.bi2;
let mut dump_apploader = args.apploader;
let mut dump_fst = args.fst;
let data = args.data;
let width = match args.width {
0 => 1,
_ => args.width,
};
if args.all {
dump_boot = true;
dump_bi2 = true;
dump_apploader = true;
dump_fst = true;
}
if !dump_boot && !dump_bi2 && !dump_apploader && !dump_fst {
println!("nothing to dump :(");
return;
}
let file = std::fs::File::open(args.path).unwrap();
let mut file = std::io::BufReader::new(file);
let gcm = Gcm::from_binary(&mut file).unwrap();
if dump_boot {
output_boot(&gcm.boot());
}
if dump_bi2 {
output_bi2(&gcm.bi2());
}
if dump_apploader {
output_apploader(&gcm.apploader(), data, width);
}
if dump_fst {
output_fst(&gcm.fst());
}
}