neser 1.2.0

NESER - Nintendo Emulation Systems Engine (Rust). Desktop and WebAssembly frontends.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
//! Mapper 172 – Super Mega P-4070 (JV001 IC)
//!
//! # Specifications
//! - Primary source: NESdev Wiki <https://www.nesdev.org/wiki/INES_Mapper_172>
//!
//! ## Overview
//!
//! Used by three games published by Super Mega / TXC:
//! - *1991 賭馬 Racing* ("Enjoyable Horse Racing")
//! - *麻将方块* (Mahjong Block) — original release
//! - *Venice Beach Volley* (Super Mega release)
//!
//! The board uses a custom JV001 IC that implements a latch, adder, and
//! inverter for copy-protection purposes.  Only CHR banking is switchable;
//! PRG is fixed at 32 KiB.
//!
//! ## Memory Map
//!
//! - `CPU $8000–$FFFF`: 32 KiB PRG-ROM, fixed
//! - `PPU $0000–$1FFF`: 8 KiB switchable CHR-ROM bank
//!
//! ## JV001 IC Registers
//!
//! The IC contains five internal values:
//! - **Input** (6 bits): loaded via writes to `$4102`
//! - **Register** (6 bits): computed from Input/Mode/Invert
//! - **Output** (6 bits): latched from Register on `$8000–$FFFF` writes
//! - **Mode** (1 bit): set via writes to `$4103` bit 5
//! - **Invert** (1 bit): set via writes to `$4101` bit 5
//!
//! ### Address mask: `$E103`
//!
//! Reads from `$4100–$4103` (any address where `addr & $E103` matches):
//! ```text
//! D~7654 3210
//!   ..RR RRRR  Register bits [5:0], with bits [5:4] inverted when Invert=1.
//!              Bits [7:6] return open bus.
//!              Note: bit order D0–D5 is reversed on the data bus.
//! ```
//!
//! Writes decoded by `addr & 3`:
//! - `$4100` (`addr & 3 == 0`, `addr & $E100 == $4100`):
//!   - `Mode == 0`: `Register[5:0] := Input`, bits [3:0] inverted when `Invert=1`
//!   - `Mode == 1`: `Register[3:0] += 1` (increment lower nibble; bits [5:4] unchanged)
//! - `$4101` (`addr & 3 == 1`): `Invert := value bit 5`
//! - `$4102` (`addr & 3 == 2`): `Input := value bits [5:0]`  
//!   (bit order `D0–D5` is reversed on the data bus)
//! - `$4103` (`addr & 3 == 3`): `Mode := value bit 5`
//! - `$8000–$FFFF` (any, value ignored):
//!   - `Output := Register`
//!   - Mirroring → Horizontal if `Invert == 0`; Vertical if `Invert == 1`
//!
//! ## CHR Bank Selection
//!
//! ```text
//! CHR bank = Output & 3   (bits [1:0] of Output)
//! ```
//!
//! ## Power-on State
//!
//! All IC registers (`Input`, `Register`, `Output`, `Mode`, `Invert`) are zero.
//! Mirroring is Horizontal (Invert==0).

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

const MAPPER_NUMBER: u16 = 172;
const PRG_BANK_SIZE: usize = 32 * 1024;
const CHR_BANK_SIZE: usize = 8 * 1024;

/// Mapper 172 – Super Mega P-4070 / JV001 IC.
///
/// See the module-level documentation for hardware details.
pub struct Mapper172 {
    base: BaseMapper,
    /// JV001 Input register (6 bits).  Written via $4102 (bit order reversed).
    input: u8,
    /// JV001 internal Register (6 bits).  Computed from Input/Mode/Invert.
    register: u8,
    /// JV001 Output register (6 bits).  Latched from Register on $8000 writes.
    output: u8,
    /// JV001 Mode flag (1 bit): 0 = latch mode, 1 = increment mode.
    mode: bool,
    /// JV001 Invert flag (1 bit): gates bit inversions.
    invert: bool,
}

impl Mapper172 {
    pub fn new(ctx: MapperContext) -> Self {
        let capabilities = MapperCapabilities {
            has_dynamic_mirroring: true,
            prg_bank_size_kb: PRG_BANK_SIZE / 1024,
            chr_bank_size_kb: CHR_BANK_SIZE / 1024,
            ..Default::default()
        };
        let mut base = BaseMapper::new(&ctx, capabilities);
        base.configure_prg_banking(PRG_BANK_SIZE);
        base.configure_chr_banking(CHR_BANK_SIZE);
        // Hardwired to Horizontal at power-on (Invert == 0).
        base.set_mirroring(NametableLayout::Horizontal);

        let mut mapper = Self {
            base,
            input: 0,
            register: 0,
            output: 0,
            mode: false,
            invert: false,
        };
        mapper.apply_banks();
        mapper
    }

    fn apply_banks(&mut self) {
        self.base.select_prg_page(0, 0);
        // CHR bank = Output & 3
        self.base.select_chr_page(0, (self.output & 3) as i16);
    }

    /// Returns true when the address falls in the JV001 IC range ($4100–$41FF).
    fn is_jv001_addr(addr: u16) -> bool {
        (addr & 0xE100) == 0x4100
    }

    /// Reverse the low 6 bits of a byte (D5..D0 → D0..D5).
    ///
    /// The JV001 IC physically reverses data-bus bits when latching via $4102.
    fn reverse_bits_6(val: u8) -> u8 {
        // reverse_bits() reverses all 8 bits; the 6-bit result ends up in [7:2].
        (val & 0x3F).reverse_bits() >> 2
    }
}

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

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

    fn mapper_number(&self) -> u16 {
        MAPPER_NUMBER
    }

    fn read_prg(&self, addr: u16) -> u8 {
        if Self::is_jv001_addr(addr) {
            // Return Register[5:0]; bits [5:4] inverted when Invert=1.
            // Bits [7:6] are open bus (0 when there is no prior open-bus value).
            let mut reg = self.register & 0x3F;
            if self.invert {
                reg ^= 0x30; // invert bits [5:4]
            }
            return reg;
        }

        if addr >= 0x8000 {
            return self.base.read_prg_banked(addr);
        }

        0
    }

    fn read_prg_open_bus(&self, addr: u16, open_bus: u8) -> u8 {
        if Self::is_jv001_addr(addr) {
            // Bits [7:6] are open bus; merge them from the bus capacitor value.
            let mut reg = self.register & 0x3F;
            if self.invert {
                reg ^= 0x30;
            }
            return (open_bus & 0xC0) | reg;
        }
        if addr >= 0x8000 {
            return self.base.read_prg_banked(addr);
        }
        open_bus
    }

    fn write_prg(&mut self, addr: u16, value: u8) {
        if Self::is_jv001_addr(addr) {
            match addr & 3 {
                0 => {
                    // $4100: latch or increment
                    if !self.mode {
                        // Mode 0: Register := Input; bits [3:0] inverted when Invert=1
                        let mut new_reg = self.input & 0x3F;
                        if self.invert {
                            new_reg ^= 0x0F; // invert bits [3:0]
                        }
                        self.register = new_reg;
                    } else {
                        // Mode 1: Register[3:0] += 1; bits [5:4] unchanged
                        let hi = self.register & 0x30;
                        let lo = (self.register.wrapping_add(1)) & 0x0F;
                        self.register = hi | lo;
                    }
                }
                1 => {
                    // $4101: Invert := bit 5
                    self.invert = (value & 0x20) != 0;
                }
                2 => {
                    // $4102: Input := bit-reversed value[5:0]
                    // The JV001 IC physically reverses data-bus bit order on this latch.
                    self.input = Self::reverse_bits_6(value & 0x3F);
                }
                3 => {
                    // $4103: Mode := bit 5
                    self.mode = (value & 0x20) != 0;
                }
                _ => unreachable!(),
            }
        } else if addr >= 0x8000 {
            // $8000-$FFFF: Output := Register; update mirroring
            self.output = self.register;
            if self.invert {
                self.base.set_mirroring(NametableLayout::Vertical);
            } else {
                self.base.set_mirroring(NametableLayout::Horizontal);
            }
            self.apply_banks();
        }
    }

    fn registers_snapshot(&self) -> Vec<u8> {
        let flags = (self.mode as u8) | ((self.invert as u8) << 1);
        vec![self.input, self.register, self.output, flags]
    }

    fn restore_registers(&mut self, data: &[u8]) {
        if data.len() >= 4 {
            self.input = data[0] & 0x3F;
            self.register = data[1] & 0x3F;
            self.output = data[2] & 0x3F;
            self.mode = (data[3] & 1) != 0;
            self.invert = (data[3] & 2) != 0;
            // Restore mirroring from invert flag.
            if self.invert {
                self.base.set_mirroring(NametableLayout::Vertical);
            } else {
                self.base.set_mirroring(NametableLayout::Horizontal);
            }
            self.apply_banks();
        }
    }

    fn reset(&mut self) {
        self.input = 0;
        self.register = 0;
        self.output = 0;
        self.mode = false;
        self.invert = false;
        self.base.set_mirroring(NametableLayout::Horizontal);
        self.apply_banks();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::nes::cartridge::mapper::create_mapper;
    use crate::nes::cartridge::test_helpers::banked_data;

    fn make_mapper() -> Mapper172 {
        Mapper172::new(MapperContext::new_for_test(
            MAPPER_NUMBER,
            banked_data(PRG_BANK_SIZE, 1),
            banked_data(CHR_BANK_SIZE, 4),
            NametableLayout::Horizontal,
        ))
    }

    #[test]
    fn mapper_172_is_registered_in_factory() {
        let result = create_mapper(MapperContext::new_for_test(
            MAPPER_NUMBER,
            banked_data(PRG_BANK_SIZE, 1),
            banked_data(CHR_BANK_SIZE, 4),
            NametableLayout::Horizontal,
        ));
        assert!(result.is_ok(), "Mapper 172 must be creatable via factory");
    }

    #[test]
    fn power_on_state_is_zero() {
        let m = make_mapper();
        assert_eq!(m.input, 0);
        assert_eq!(m.register, 0);
        assert_eq!(m.output, 0);
        assert!(!m.mode);
        assert!(!m.invert);
    }

    #[test]
    fn power_on_mirroring_is_horizontal() {
        let m = make_mapper();
        assert_eq!(m.base.mirroring(), NametableLayout::Horizontal);
    }

    #[test]
    fn write_4101_sets_invert_from_bit5() {
        let mut m = make_mapper();
        m.write_prg(0x4101, 0x20);
        assert!(m.invert);
        m.write_prg(0x4101, 0x00);
        assert!(!m.invert);
    }

    #[test]
    fn write_4102_sets_input_bits0_5() {
        let mut m = make_mapper();
        // 0xFF & 0x3F = 0x3F which is a bit-palindrome; reversal leaves it unchanged.
        m.write_prg(0x4102, 0xFF);
        assert_eq!(m.input, 0x3F);
    }

    #[test]
    fn write_4102_bit_reverses_6bit_input() {
        let mut m = make_mapper();
        // The JV001 IC physically reverses data-bus bits D0..D5 when latching.
        // Writing 0x01 (bit 0 set) should place that bit at position 5 in the input reg.
        m.write_prg(0x4102, 0x01);
        assert_eq!(
            m.input, 0x20,
            "bit 0 of written value must map to bit 5 of input"
        );
        // Writing 0x20 (bit 5 set) should place that bit at position 0.
        m.write_prg(0x4102, 0x20);
        assert_eq!(
            m.input, 0x01,
            "bit 5 of written value must map to bit 0 of input"
        );
    }

    #[test]
    fn write_4103_sets_mode_from_bit5() {
        let mut m = make_mapper();
        m.write_prg(0x4103, 0x20);
        assert!(m.mode);
        m.write_prg(0x4103, 0x00);
        assert!(!m.mode);
    }

    #[test]
    fn write_4100_mode0_latches_input_to_register() {
        let mut m = make_mapper();
        // Write 0x2A to $4102; after 6-bit reversal: input = 0x15 = 0b010101.
        m.write_prg(0x4102, 0x2A);
        m.write_prg(0x4100, 0); // Mode=0: register := input
        assert_eq!(m.register, 0x15);
    }

    #[test]
    fn write_4100_mode0_inverts_lower_nibble_when_invert_set() {
        let mut m = make_mapper();
        m.write_prg(0x4101, 0x20); // invert = 1
        // Write 0x2A → input = 0x15 = 0b010101 after bit-reversal.
        m.write_prg(0x4102, 0x2A);
        m.write_prg(0x4100, 0); // register := input XOR 0x0F on lower nibble
        // lower nibble: 0x5 XOR 0xF = 0xA; upper nibble unchanged: 0x1
        assert_eq!(m.register, 0x1A);
    }

    #[test]
    fn write_4100_mode1_increments_lower_nibble() {
        let mut m = make_mapper();
        // Write 0x2A → input = 0x15 after bit-reversal.
        m.write_prg(0x4102, 0x2A);
        m.write_prg(0x4100, 0); // latch input: register = 0x15
        m.write_prg(0x4103, 0x20); // mode = 1
        m.write_prg(0x4100, 0); // increment lower nibble: 0x15 + 1 = 0x16
        assert_eq!(m.register, 0x16);
    }

    #[test]
    fn write_4100_mode1_lower_nibble_wraps() {
        let mut m = make_mapper();
        // Write 0x3C → input = 0x0F after bit-reversal (lower nibble = 0xF).
        m.write_prg(0x4102, 0x3C);
        m.write_prg(0x4100, 0); // register = 0x0F
        m.write_prg(0x4103, 0x20); // mode = 1
        m.write_prg(0x4100, 0); // lower nibble wraps: 0xF + 1 = 0x0; upper nibble stays 0
        assert_eq!(m.register & 0x0F, 0x00);
        assert_eq!(m.register & 0x30, 0x00);
    }

    #[test]
    fn write_8000_latches_register_to_output_and_selects_chr_bank() {
        let mut m = make_mapper();
        // Write 0x30 to $4102; after 6-bit reversal: input = 0x03.
        m.write_prg(0x4102, 0x30);
        m.write_prg(0x4100, 0); // register = 0x03
        m.write_prg(0x8000, 0); // output = register = 0x03; chr bank = 0x03 & 3 = 3
        assert_eq!(m.output, 3);
    }

    #[test]
    fn write_8000_updates_mirroring_horizontal_when_invert_clear() {
        let mut m = make_mapper();
        m.write_prg(0x4101, 0x00); // invert = 0
        m.write_prg(0x8000, 0);
        assert_eq!(m.base.mirroring(), NametableLayout::Horizontal);
    }

    #[test]
    fn write_8000_updates_mirroring_vertical_when_invert_set() {
        let mut m = make_mapper();
        m.write_prg(0x4101, 0x20); // invert = 1
        m.write_prg(0x8000, 0);
        assert_eq!(m.base.mirroring(), NametableLayout::Vertical);
    }

    #[test]
    fn read_4100_returns_register_bits() {
        let mut m = make_mapper();
        // Write 0x2A → input = 0x15 after bit-reversal; latch to register.
        m.write_prg(0x4102, 0x2A);
        m.write_prg(0x4100, 0); // register = 0x15
        let val = m.read_prg(0x4100);
        assert_eq!(val & 0x3F, 0x15);
    }

    #[test]
    fn read_4100_inverts_bits4_5_when_invert_set() {
        let mut m = make_mapper();
        // Write 0x2B → input = 0x35 = 0b110101 after bit-reversal; latch to register.
        m.write_prg(0x4102, 0x2B);
        m.write_prg(0x4100, 0); // register = 0x35
        m.write_prg(0x4101, 0x20); // invert = 1
        let val = m.read_prg(0x4100);
        // bits [5:4] = 0b11 XOR 0b11 = 0b00; bits [3:0] = 0b0101 unchanged
        assert_eq!(val & 0x3F, 0x05);
    }

    #[test]
    fn read_4100_open_bus_merges_high_bits() {
        let m = make_mapper();
        // Bits [7:6] at $4100 are open bus — they must come from the open_bus parameter.
        let val = m.read_prg_open_bus(0x4100, 0xC0);
        assert_eq!(val & 0xC0, 0xC0, "bits [7:6] must be taken from open_bus");
        let val2 = m.read_prg_open_bus(0x4100, 0x00);
        assert_eq!(
            val2 & 0xC0,
            0x00,
            "bits [7:6] must be 0 when open_bus bits are 0"
        );
    }

    #[test]
    fn jv001_mirror_addr_4108_also_hits_register_0() {
        // Mask $E103: any addr where (addr & $E100) == $4100 hits register 0..3
        let mut m = make_mapper();
        m.write_prg(0x4108, 0); // addr & $E100 == $4100, addr & 3 == 0 → write $4100
        // Should not panic; register behavior same as $4100
    }

    #[test]
    fn reset_clears_all_ic_state() {
        let mut m = make_mapper();
        m.write_prg(0x4102, 0x3F);
        m.write_prg(0x4100, 0);
        m.write_prg(0x4101, 0x20);
        m.write_prg(0x8000, 0);
        m.reset();
        assert_eq!(m.input, 0);
        assert_eq!(m.register, 0);
        assert_eq!(m.output, 0);
        assert!(!m.mode);
        assert!(!m.invert);
        assert_eq!(m.base.mirroring(), NametableLayout::Horizontal);
    }

    #[test]
    fn snapshot_restore_round_trips() {
        let mut m = make_mapper();
        m.write_prg(0x4101, 0x20); // invert = 1
        m.write_prg(0x4103, 0x20); // mode = 1
        m.write_prg(0x4102, 0x2A); // after 6-bit reversal: input = 0x15
        m.write_prg(0x4100, 0); // mode=1: increment lower nibble of register (which is 0 → 1)
        m.write_prg(0x8000, 0); // output = register

        let snap = m.registers_snapshot();
        let mut m2 = make_mapper();
        m2.restore_registers(&snap);

        assert_eq!(m2.input, m.input);
        assert_eq!(m2.register, m.register);
        assert_eq!(m2.output, m.output);
        assert_eq!(m2.mode, m.mode);
        assert_eq!(m2.invert, m.invert);
    }
}