nds_cart 0.4.1

NDS 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: 2026 Gabriel Marcano <gabemarcano@yahoo.com>

use nds_cart::Cart;

use std::fs::File;
use std::io::BufWriter;
use std::path::PathBuf;

use png::BitDepth;
use png::ColorType;
use png::Encoder;

use clap::Parser;

#[derive(Parser)]
struct Cli {
    #[arg(short, long)]
    fetch_icon: bool,
    files: Vec<PathBuf>,
}

pub fn main() {
    let args = Cli::parse();

    if args.files.is_empty() {
        return;
    }

    println!("[");

    for (i, path) in args.files.iter().enumerate() {
        let f = File::open(path).unwrap();
        let cart = Cart::try_from(f);
        if let Err(ref err) = cart {
            eprintln!("Skipping {}, error: {err}", path.to_string_lossy());
            continue;
        }
        let mut cart = cart.unwrap();

        let header_checksum = cart.header_checksum().unwrap();
        let header = &cart.metadata;

        println!("\t{{");
        println!("\t\t\"title\": \"{}\",", header.title);
        println!("\t\t\"game code\": \"{}\",", header.game_code);
        println!("\t\t\"manufacturer\": \"{}\",", header.manufacturer);
        println!("\t\t\"unit code\": \"{}\",", header.unit_code);
        println!(
            "\t\t\"encryption seed select\": {},",
            header.encryption_seed_select
        );
        println!(
            "\t\t\"device capacity\": \"{} KiB\",",
            header.device_capacity
        );
        println!("\t\t\"language\": \"{}\",", header.language);
        println!("\t\t\"region\": \"{}\",", header.region);
        println!("\t\t\"version\": {},", header.version);
        println!("\t\t\"autostart\": {},", header.autostart);
        println!("\t\t\"ARM9 rom offset\": {},", header.arm9_rom_offset);
        println!(
            "\t\t\"ARM9 entry address\": \"0x{:08X}\",",
            header.arm9_entry_address
        );
        println!(
            "\t\t\"ARM9 RAM address\": \"0x{:08X}\",",
            header.arm9_ram_address
        );
        println!("\t\t\"ARM9 size\": {},", header.arm9_size);
        println!("\t\t\"ARM7 rom offset\": {},", header.arm7_rom_offset);
        println!(
            "\t\t\"ARM7 entry address\": \"0x{:08X}\",",
            header.arm7_entry_address
        );
        println!(
            "\t\t\"ARM7 RAM address\": \"0x{:08X}\",",
            header.arm7_ram_address
        );
        println!("\t\t\"ARM7 size\": {},", header.arm7_size);
        println!(
            "\t\t\"file name table offset\": {},",
            header.file_name_table_offset
        );
        println!(
            "\t\t\"file name table size\": {},",
            header.file_name_table_size
        );
        println!(
            "\t\t\"file allocation table offset\": {},",
            header.file_allocation_table_offset
        );
        println!(
            "\t\t\"file allocation table size\": {},",
            header.file_allocation_table_size
        );
        println!(
            "\t\t\"ARM9 overlay offset\": {},",
            header.arm9_overlay_offset
        );
        println!("\t\t\"ARM9 overlay size\": {},", header.arm9_overlay_size);
        println!(
            "\t\t\"ARM7 overlay offset\": {},",
            header.arm7_overlay_offset
        );
        println!("\t\t\"ARM7 overlay size\": {},", header.arm7_overlay_size);
        println!(
            "\t\t\"ROMCTL setting normal\": \"0x{:08X}\",",
            header.romctl_setting_normal
        );
        println!(
            "\t\t\"ROMCTL setting key1\": \"0x{:08X}\",",
            header.romctl_setting_key1
        );
        println!("\t\t\"icon offset\": {},", header.icon_offset);
        println!(
            "\t\t\"secure area checksum\": \"0x{:04X}\",",
            header.secure_area_checksum
        );
        println!(
            "\t\t\"secure area delay\": \"{} ms\",",
            u32::from(header.secure_area_delay) / 131
        );
        println!("\t\t\"ARM9 autoload\": \"0x{:08X}\",", header.arm9_autoload);
        println!("\t\t\"ARM7 autoload\": \"0x{:08X}\",", header.arm7_autoload);
        println!(
            "\t\t\"secure area disable\": \"0x{:016X}\",",
            header.secure_area_disable
        );
        println!("\t\t\"used ROM size\": {},", header.used_rom_size);
        println!("\t\t\"ROM header size\": {},", header.rom_header_size);
        println!(
            "\t\t\"NAND end of ROM\": \"0x{:08X}\",",
            u32::from(header.nand_end_of_rom) * 0x20000
        );
        println!(
            "\t\t\"NAND start of RW\": \"0x{:08X}\",",
            u32::from(header.nand_start_of_rw) * 0x20000
        );
        println!(
            "\t\t\"Nintendo logo checksum\": \"0x{:04X}\",",
            header.nintendo_logo_checksum
        );
        println!(
            "\t\t\"header checksum\": \"0x{:04X}\",",
            header.header_checksum
        );
        println!("\t\t\"computed header checksum\": \"0x{header_checksum:04X}\",");
        println!(
            "\t\t\"debug ROM offset\": \"0x{:08X}\",",
            header.debug_rom_offset
        );
        println!("\t\t\"debug size\": \"0x{:08X}\",", header.debug_size);
        println!(
            "\t\t\"debug RAM address\": \"0x{:08X}\"",
            header.debug_ram_address
        );
        if i == (args.files.len() - 1) {
            println!("\t}}");
        } else {
            println!("\t}},");
        }

        if args.fetch_icon {
            let icon = cart.extract_rgba_icon().unwrap();
            let f = File::create(cart.metadata.game_code + ".png").unwrap();
            let w = BufWriter::new(f);
            let mut encoder = Encoder::new(w, 32, 32);
            encoder.set_color(ColorType::Rgba);
            encoder.set_depth(BitDepth::Eight);
            let mut writer = encoder.write_header().unwrap();
            writer.write_image_data(&icon).unwrap();
        }
    }

    println!("]");
}