use gcn_disk::Banner;
use gcn_disk::Disc;
use std::fs::File;
use std::io;
use std::io::BufWriter;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
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>,
}
enum Io {
File(File),
Rvz(Box<Rvz<File>>),
}
impl Read for Io {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::File(file) => file.read(buf),
Self::Rvz(file) => file.read(buf),
}
}
}
impl Seek for Io {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match self {
Self::File(file) => file.seek(pos),
Self::Rvz(file) => file.seek(pos),
}
}
}
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 io = if f.has_rvz_magic() {
let io = Rvz::new(f);
if let Err(err) = io {
eprintln!("Error: {err}");
continue;
}
Io::Rvz(Box::new(io.unwrap()))
} else {
Io::File(f)
};
let disk = Disc::try_from(io);
if let Err(ref err) = disk {
eprintln!("Error: {err}");
continue;
}
let mut disk = disk.unwrap();
let header = &disk.metadata.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.metadata.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!("]");
}