cge_nes 0.1.2

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
//! Parser for the iNES ROM file header format.
//!
//! The iNES ROM format begins with a 16-byte header containing the following information:
//!
//! ```text
//! Offset   Size    Description
//! ------------------------------------
//! 0-3      4      Constant $4E $45 $53 $1A ("NES" followed by MS-DOS EOF)
//! 4        1      Size of PRG ROM in 16 KB units
//! 5        1      Size of CHR ROM in 8 KB units (0 means the board uses CHR RAM)
//! 6        1      Flags 6
//!                 - Bit 0: Mirroring (0: horizontal; 1: vertical)
//!                 - Bit 1: Battery RAM present
//!                 - Bit 2: 512-byte trainer present
//!                 - Bit 3: Four-screen VRAM layout
//!                 - Bits 4-7: Low ROM mapper number
//! 7        1      Flags 7
//!                 - Bits 0-3: High ROM mapper number
//!                 - Bit 0: VS Unisystem
//!                 - Bit 1: PlayChoice-10
//!                 - Bits 2-3: NES 2.0 format (when both are 2)
//! 8-15     8      Reserved, should be zero in iNES format
//! ```
//!
//! This module provides types and functions for parsing this header data into
//! a structured format that can be used to configure cartridge emulation.

use devices6502::size_const::*;

/// Represents the name table mirroring modes available in NES games.
///
/// Name table mirroring determines how the NES PPU maps VRAM addresses beyond
/// its physical memory. This affects how background graphics are rendered.
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
pub enum Mirroring {
    /// Horizontal mirroring (vertical arrangement):
    /// - $2000 equals $2400
    /// - $2800 equals $2C00
    #[default]
    Horizontal,

    /// Vertical mirroring (horizontal arrangement):
    /// - $2000 equals $2800
    /// - $2400 equals $2C00
    Vertical,

    /// Four-screen mirroring:
    /// Uses additional VRAM on the cartridge to provide four unique name tables
    /// with no mirroring
    FourScreen,
}

/// Parsed iNES ROM header data containing cartridge configuration.
///
/// This structure holds all the relevant information extracted from a ROM's 16-byte
/// iNES header, including ROM sizes, mapper numbers, and various capability flags.
#[derive(Default, Debug)]
pub struct HeaderData {
    /// Size of PRG ROM in bytes (16KB units in header)
    pub prg_rom_size: u64,
    /// Size of CHR ROM in bytes (8KB units in header)
    pub chr_rom_size: u64,
    /// Name table mirroring mode
    pub mirroring: Mirroring,
    /// Whether the cartridge contains battery-backed RAM
    pub has_persistent_ram: bool,
    /// Whether a 512-byte trainer is present
    pub has_trainer: bool,
    /// Mapper number (0-255)
    pub mapper: u8,
    /// Whether this is a VS System game
    pub vs_unisystem: bool,
    /// Whether this is a PlayChoice-10 game
    pub playchoice: bool,
    /// Whether this ROM uses the NES 2.0 format
    pub ines2: bool,
    /// Size of PRG RAM in bytes
    pub prg_ram_size: u16,
    // tv system?
}

impl HeaderData {
    /// Creates a new HeaderData instance by parsing a 16-byte iNES header.
    ///
    /// # Arguments
    ///
    /// * `header` - A 16-byte array containing the iNES header data
    ///
    /// # Panics
    ///
    /// Panics if the header slice is not exactly 16 bytes long
    pub fn new(header: &[u8]) -> Self {
        assert_eq!(header.len(), 16);
        let prg_rom_size = header[4] as u64 * SIZE_16K as u64;
        let chr_rom_size = header[5] as u64 * SIZE_8K as u64;
        let mirroring = if (header[6] & 8) != 0 {
            Mirroring::FourScreen
        } else if (header[6] & 1) == 0 {
            Mirroring::Horizontal
        } else {
            Mirroring::Vertical
        };
        let has_persistent_ram = (header[6] & 2) != 0;
        let has_trainer = (header[6] & 4) != 0;
        let mapper_low = header[6] >> 4;
        let mapper_high = header[7] & 0xF0;
        let mapper = mapper_high | mapper_low;

        // TO DO: Others

        Self {
            prg_rom_size,
            chr_rom_size,
            mirroring,
            has_persistent_ram,
            has_trainer,
            mapper,
            ..Self::default()
        }
    }
}