gcn_disk 0.1.3

Gamecube file header library and utilities.
Documentation
// SPDX-License-Identifier: LGPL-2.1-or-later OR GPL-2.0-or-later OR MPL-2.0
// SPDX-FileCopyrightText: 2024 Gabriel Marcano <gabemarcano@yahoo.com>

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!("]");
}