neser 0.1.1

NESER - NES Emulator in Rust - is a NES emulator written in Rust. It aims to be a high-quality, hardware-accurate emulator that is also easy to use and extend. It supports a wide range of NES games and features, including various mappers, audio processing, and input handling. NESER is designed to be modular and extensible, allowing developers to easily add new features or support for additional hardware. It can be run using one of two frontends: a native desktop application using SDL2, or a web application using WebAssembly. The desktop application provides a high-performance, feature-rich experience with support for various input devices and display options, while the web application allows users to play NES games directly in their browsers without needing to install any software in a BYOR manner (Bring Your Own Roms).
Documentation
//! Mapper 057 - BMC GK-192 multicart
//!
//! Specifications:
//! - Main: <https://www.nesdev.org/wiki/INES_Mapper_057>
//!
//! Known Limitations:
//! - No known gameplay-blocking functional limitations are currently documented.

use crate::cartridge::base_mapper::BaseMapper;
use crate::cartridge::mapper::{Mapper, MapperCapabilities};

/// Mapper 057 - BMC GK-192 multicart
///
/// Hardware: GK-192 PCB
///
/// Specifications:
/// - Main: <https://www.nesdev.org/wiki/INES_Mapper_057>
/// - PRG-ROM: Up to 128 KiB (8 x 16 KiB banks)
/// - CHR: Up to 128 KiB (16 x 8 KiB banks)
/// - Mirroring: Programmable (H/V)
///
/// Two registers, selected by address bit 11:
/// - $8000 (bit 11 = 0): [CH.. ..AA]
///   - C = CHR Mode (0=CNROM/8KB from $8000, 1=NROM/8KB from $8800)
///   - H = CHR A16
///   - A = CHR A13-A14 in CNROM mode
/// - $8800 (bit 11 = 1): [PPPO MBbb]
///   - P = PRG bank (3 bits, bits 7:5)
///   - O = PRG Mode (0=16KB mirrored, 1=32KB)
///   - M = Mirroring (0=Vert, 1=Horz)
///   - B = CHR A15
///   - b = CHR A13-A14 in NROM mode (bits 1:0)
///
/// CHR bank (8KB):
///   C=0: bank = (H<<3) | (B<<2) | AA
///   C=1: bank = (H<<3) | (B<<2) | bb
///
/// PRG bank (16KB or 32KB):
///   O=0: 16KB bank PPP at both $8000-$BFFF and $C000-$FFFF
///   O=1: 32KB at $8000-$FFFF = banks (PPP & 6) and (PPP | 1)
pub struct Mapper57 {
    base: BaseMapper,
    // $8000 register: [CH.. ..AA]
    reg0: u8,
    // $8800 register: [PPPO MBbb]
    reg1: u8,
}

impl Mapper57 {
    pub fn new(ctx: super::mapper::MapperContext) -> Self {
        let capabilities = MapperCapabilities {
            has_dynamic_mirroring: true,
            has_chr_banking: true,
            max_prg_ram_kb: 0,
            prg_bank_size_kb: 16,
            chr_bank_size_kb: 8,
            ..Default::default()
        };
        let mut base = BaseMapper::new(&ctx, capabilities);
        base.configure_prg_banking(16 * 1024);
        base.configure_chr_banking(8 * 1024);
        // Default: NROM-128, bank 0 → both slots same
        base.select_prg_page(1, 0);
        let mut s = Self {
            base,
            reg0: 0,
            reg1: 0,
        };
        s.update_banks();
        s
    }

    fn prg_16k_bank(&self) -> usize {
        ((self.reg1 >> 5) & 0x07) as usize
    }

    fn prg_mode_32k(&self) -> bool {
        (self.reg1 & 0x10) != 0
    }

    fn chr_bank(&self) -> usize {
        let h = ((self.reg0 >> 6) & 0x01) as usize;
        let b = ((self.reg1 >> 2) & 0x01) as usize;
        let chr_mode = (self.reg0 & 0x80) != 0;
        let low = if chr_mode {
            // NROM mode: use bb from $8800
            (self.reg1 & 0x03) as usize
        } else {
            // CNROM mode: use AA from $8000
            (self.reg0 & 0x03) as usize
        };
        (h << 3) | (b << 2) | low
    }

    fn update_banks(&mut self) {
        let prg_bank = self.prg_16k_bank();
        if self.prg_mode_32k() {
            let base = prg_bank & !1;
            self.base.select_prg_page(0, base as i16);
            self.base.select_prg_page(1, (base | 1) as i16);
        } else {
            self.base.select_prg_page(0, prg_bank as i16);
            self.base.select_prg_page(1, prg_bank as i16);
        }
        self.base.select_chr_page(0, self.chr_bank() as i16);

        self.base.set_mirroring_hv((self.reg1 & 0x08) != 0);
    }
}

impl Mapper for Mapper57 {
    fn base(&self) -> &BaseMapper {
        &self.base
    }

    fn base_mut(&mut self) -> &mut BaseMapper {
        &mut self.base
    }

    fn write_prg(&mut self, addr: u16, value: u8) {
        if let 0x8000..=0xFFFF = addr {
            if (addr & 0x0800) != 0 {
                self.reg1 = value;
            } else {
                self.reg0 = value;
            }
            self.update_banks();
        }
    }

    fn registers_snapshot(&self) -> Vec<u8> {
        vec![self.reg0, self.reg1]
    }

    fn restore_registers(&mut self, data: &[u8]) {
        if data.len() >= 2 {
            self.reg0 = data[0];
            self.reg1 = data[1];
            self.update_banks();
        }
    }

    fn reset(&mut self) {
        self.reg0 = 0;
        self.reg1 = 0;
        self.update_banks();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cartridge::NametableLayout;
    use crate::cartridge::mapper::{Mapper, MapperContext, create_mapper};
    use crate::cartridge::test_helpers::banked_data;

    fn make_mapper() -> Mapper57 {
        let prg = banked_data(16 * 1024, 8);
        let chr = banked_data(8 * 1024, 16);
        Mapper57::new(MapperContext::new_for_test(
            57,
            prg,
            chr,
            NametableLayout::Vertical,
        ))
    }

    #[test]
    fn mapper_57_is_registered() {
        let result = create_mapper(MapperContext::new_for_test(
            57,
            banked_data(16 * 1024, 8),
            banked_data(8 * 1024, 16),
            NametableLayout::Vertical,
        ));
        assert!(result.is_ok(), "Mapper 57 must be registered");
    }

    #[test]
    fn default_prg_reads_bank_0_at_8000_and_c000() {
        let mapper = make_mapper();
        assert_eq!(mapper.read_prg(0x8000), 0);
        assert_eq!(mapper.read_prg(0xC000), 0, "16KB mode mirrors same bank");
    }

    #[test]
    fn prg_bank_select_via_reg1() {
        let mut mapper = make_mapper();
        // Set PPP = 3 (bits 7:5 of reg1 = 0110_0000 = 0x60)
        mapper.write_prg(0x8800, 0x60); // PPP=3, O=0 (16KB mode)
        assert_eq!(mapper.read_prg(0x8000), 3);
        assert_eq!(mapper.read_prg(0xC000), 3, "16KB mode: same bank at C000");
    }

    #[test]
    fn prg_32kb_mode() {
        let mut mapper = make_mapper();
        // PPP=2 (0b010), O=1 → 32KB: base = 2 (even), $8000=bank2, $C000=bank3
        mapper.write_prg(0x8800, 0b0100_0001 << 1 | 0x10); // PPP=4, O=1
        // PPP=4 → base = 4 (even), $8000=4, $C000=5
        mapper.write_prg(0x8800, 0b1000_0000 | 0x10); // reg1: PPP=4, O=1 → 0x90
        // 0x90 = 1001_0000: PPP = bits[7:5] = 100 = 4, O = bit4 = 1
        assert_eq!(mapper.read_prg(0x8000), 4);
        assert_eq!(mapper.read_prg(0xC000), 5);
    }

    #[test]
    fn chr_cnrom_mode_uses_aa_from_reg0() {
        let mut mapper = make_mapper();
        // C=0 (CNROM), H=0, A=2 → chr bank = (0<<3)|(0<<2)|2 = 2
        mapper.write_prg(0x8000, 0b0000_0010); // reg0: C=0, A=2
        assert_eq!(mapper.read_chr(0x0000), 2);
    }

    #[test]
    fn chr_nrom_mode_uses_bb_from_reg1() {
        let mut mapper = make_mapper();
        // C=1 (NROM), H=0; bb=3 (reg1 bits 1:0)
        mapper.write_prg(0x8000, 0b1000_0000); // reg0: C=1
        mapper.write_prg(0x8800, 0b0000_0011); // reg1: bb=3
        assert_eq!(mapper.read_chr(0x0000), 3);
    }

    #[test]
    fn mirroring_bit3_of_reg1() {
        let mut mapper = make_mapper();
        mapper.write_prg(0x8800, 0x00);
        assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
        mapper.write_prg(0x8800, 0x08);
        assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
    }

    #[test]
    fn reset_restores_defaults() {
        let mut mapper = make_mapper();
        mapper.write_prg(0x8000, 0xFF);
        mapper.write_prg(0x8800, 0xFF);
        mapper.reset();
        assert_eq!(mapper.read_prg(0x8000), 0);
        assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
    }

    #[test]
    fn registers_snapshot_restore() {
        let mut mapper = make_mapper();
        mapper.write_prg(0x8000, 0x42);
        mapper.write_prg(0x8800, 0x65);
        let snap = mapper.registers_snapshot();
        let mut r = make_mapper();
        r.restore_registers(&snap);
        assert_eq!(r.read_prg(0x8000), mapper.read_prg(0x8000));
        assert_eq!(r.get_mirroring(), mapper.get_mirroring());
    }
}