use gcn_disk::Banner;
use gcn_disk::Disk;
use std::fs::File;
use std::io::BufWriter;
use std::io::Read;
use std::io::Seek;
use std::path::PathBuf;
use png::BitDepth;
use png::ColorType;
use png::Encoder;
use clap::Parser;
use rvz::HeaderRead;
use rvz::Rvz;
#[derive(Parser)]
struct Cli {
#[arg(short, long)]
fetch_icon: bool,
files: Vec<PathBuf>,
}
trait ReadSeek: std::io::Read + Seek {}
impl<T: Seek + Read> ReadSeek for T {}
pub fn main() {
let args = Cli::parse();
if args.files.is_empty() {
return;
}
println!("[");
for (i, path) in args.files.iter().enumerate() {
let mut f = File::open(path).unwrap();
let disk: &mut (dyn ReadSeek) = if f.has_rvz_magic() {
let io = Rvz::new(f);
if let Err(err) = io {
eprintln!("Error: {err}");
continue;
}
&mut io.unwrap()
} else {
&mut f
};
let disk = Disk::new(disk);
if let Err(ref err) = disk {
eprintln!("Error: {err}");
continue;
}
let mut disk = disk.unwrap();
let header = &disk.header;
println!("\t{{");
println!("\t\t\"filename\": \"{}\",", path.display());
println!("\t\t\"console ID\": \"{}\",", header.console_id);
println!("\t\t\"game code\": \"{}\",", header.game_code);
println!("\t\t\"country code\": \"{}\",", header.country_code);
println!("\t\t\"maker code\": \"{}\",", header.maker_code);
println!("\t\t\"disk ID\": \"0x{:02X}\",", header.disk_id);
println!("\t\t\"version\": \"0x{:02X}\",", header.version);
println!(
"\t\t\"audio streaming\": \"0x{:02X}\",",
header.audio_streaming
);
println!(
"\t\t\"stream buffer size\": \"0x{:02X}\",",
header.stream_buffer_size
);
println!("\t\t\"magic\": \"0x{:08X}\",", header.magic);
println!("\t\t\"game name\": \"{}\",", header.game_name);
println!(
"\t\t\"debug monitor offset\": \"0x{:08X}\",",
header.debug_monitor_offset
);
println!(
"\t\t\"debug monitor address\": \"0x{:08X}\",",
header.debug_monitor_address
);
println!("\t\t\"executable offset\": {},", header.executable_offset);
println!("\t\t\"FST offset\": \"0x{:08X}\",", header.fst_offset);
println!("\t\t\"FST size\": {},", header.fst_size);
println!("\t\t\"FST max size\": {},", header.fst_max_size);
println!("\t\t\"user position\": {},", header.user_position);
if args.fetch_icon {
println!("\t\t\"user length\": {},", header.user_length);
let data = disk.get_file("opening.bnr").unwrap();
let banner: Banner = data[..].try_into().unwrap();
println!("\t\t\"banner\": {{");
println!("\t\t\t\"game name\": \"{}\",", banner.game_name);
println!("\t\t\t\"developer\": \"{}\",", banner.developer);
println!("\t\t\t\"full title\": \"{}\",", banner.full_title);
println!("\t\t\t\"full developer\": \"{}\",", banner.full_developer);
println!(
"\t\t\t\"description\": \"{}\"",
banner.description.replace('\r', "").replace('\n', " "),
);
println!("\t\t}}");
let icon = banner.extract_rgba_banner_image();
let f = File::create(disk.header.game_code + ".png").unwrap();
let w = BufWriter::new(f);
let mut encoder = Encoder::new(w, 96, 32);
encoder.set_color(ColorType::Rgba);
encoder.set_depth(BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&icon).unwrap();
} else {
println!("\t\t\"user length\": {}", header.user_length);
}
if i == (args.files.len() - 1) {
println!("\t}}");
} else {
println!("\t}},");
}
}
println!("]");
}