cge_nes 0.1.2

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
//! A command-line tool for inspecting iNES ROM headers.
//!
//! This binary scans a directory for .nes files and generates a report containing
//! header information for each valid iNES ROM file found. The report includes details
//! such as ROM sizes, mapper numbers, and various cartridge capabilities.
//!
//! The tool writes its output to a file named "header_info.txt" in the current directory.
//! Invalid ROM files or files with errors are logged to stderr.

use cge_nes::rom_loader::ines::HeaderData;
use std::io::{Read, Write};
use std::path::Path;
use std::{fs, io};

/// Writes formatted header data to the specified writer.
///
/// # Arguments
///
/// * `w` - Any type that implements `Write` trait
/// * `header_data` - Reference to the parsed header data
///
/// # Errors
///
/// Returns `io::Error` if writing to the output fails
fn print_header_data(mut w: impl Write, header_data: &HeaderData) -> Result<(), std::io::Error> {
    writeln!(w, "\tProgram Rom size: {} KiB", header_data.prg_rom_size)?;
    writeln!(w, "\tCharacter ROM size: {} KiB", header_data.chr_rom_size)?;
    writeln!(w, "\tProgram RAM size: {} KiB", header_data.prg_ram_size)?;
    writeln!(w, "\tScreen mirroring: {:?}", header_data.mirroring)?;
    writeln!(
        w,
        "\tHas persistent RAM: {}",
        header_data.has_persistent_ram
    )?;
    writeln!(w, "\tHas trainer: {}", header_data.has_trainer)?;
    writeln!(w, "\tMapper: {:03}", header_data.mapper)?;
    writeln!(w, "\tVS Unisystem: {}", header_data.vs_unisystem)?;
    writeln!(w, "\tPlaychoice: {}", header_data.playchoice)?;
    writeln!(w, "\tiNES 2: {}", header_data.ines2)?;
    write!(w, "\n\n")?;
    Ok(())
}

fn main() -> Result<(), io::Error> {
    let output_file = fs::File::create("header_info.txt")?;
    let mut file_write = io::BufWriter::new(output_file);

    let files: Vec<_> = fs::read_dir("test_assets")?
        // Filter out error entries
        .filter_map(|entry| entry.ok())
        // Filter out non-file entries
        .filter(|entry| entry.file_type().map_or(false, |x| x.is_file()))
        // Get path
        .map(|entry| entry.path())
        // filter out files without extension
        .filter(|path| path.extension().is_some())
        // filter out files with extension different than .nes
        .filter(|path| path.extension().is_some_and(|e| e == "nes"))
        .collect();

    for file_path in &files {
        if let Err(err) = write_rom(&mut file_write, file_path) {
            eprintln!("File: {}\tError: {}", file_path.display(), err);
        }
    }
    Ok(())
}

/// Processes a single ROM file and writes its header information.
///
/// # Arguments
///
/// * `file_write` - The output writer to receive the formatted header information
/// * `file_path` - Path to the ROM file to process
///
/// # Errors
///
/// Returns `io::Error` if:
/// * The file cannot be opened or read
/// * The file name is invalid UTF-8
/// * Writing to the output fails
fn write_rom(mut file_write: impl Write, file_path: &Path) -> Result<(), io::Error> {
    let file = fs::File::open(file_path)?;
    let mut file_read = io::BufReader::new(file);
    let mut header = [0u8; 16];
    file_read.read_exact(&mut header)?;

    if &header[0..4] == b"NES\x1A".as_slice() {
        let header_data = HeaderData::new(&header);
        writeln!(
            file_write,
            "{}\n",
            file_path
                .file_stem()
                // TO DO: This should be changed to io::ErrorKind::InvalidFilename once it's stabilized
                .ok_or(io::ErrorKind::Other)?
                .to_str()
                .ok_or(io::ErrorKind::Other)?
        )?;
        print_header_data(&mut file_write, &header_data)?;
    } else {
        writeln!(
            file_write,
            "{} is not a valid .nes file\n",
            file_path.display()
        )?;
        eprintln!("{} is not a valid .nes file\n", file_path.display());
    }
    Ok(())
}