nescore/mapper/unrom.rs
1//
2// mapper/unrom.rs
3//
4// @author Natesh Narain <nnaraindev@gmail.com>
5// @date Dec 27 2019
6//
7
8// +-----------------+
9// ¦ Mapper 2: UNROM ¦
10// +-----------------+
11//
12// +---------------+ +------------------------------------------+
13// ¦ $8000 - $FFFF +---------¦ PPPPPPPP ¦
14// +---------------+ ¦ +------+ ¦
15// ¦ ¦ ¦
16// ¦ ¦ ¦
17// ¦ +------- Select 16K ROM bank at $8000 ¦
18// +------------------------------------------+
19//
20// Notes: - When the cart is first started, the first 16K ROM bank in the cart
21// is loaded into $8000, and the LAST 16K ROM bank is loaded into
22// $C000. This last 16K bank is permanently "hard-wired" to $C000,
23// and it cannot be swapped.
24// - This mapper has no provisions for VROM; therefore, all carts
25// using it have 8K of VRAM at PPU $0000.
26// - Most carts with this mapper are 128K. A few, mostly Japanese
27// carts, such as Final Fantasy 2 and Dragon Quest 3, are 256K.
28// - Overall, this is one of the easiest mappers to implement in
29// a NES emulator.
30// http://tuxnes.sourceforge.net/mappers-0.80.txt
31
32use super::MapperControl;
33use super::mem::Memory;
34use crate::cart::{Cartridge, PRG_ROM_BANK_SIZE};
35
36const CHR_RAM_SIZE: usize = kb!(8);
37
38/// UNROM Mapper
39pub struct Unrom {
40 prg_rom: Memory,
41 chr_ram: [u8; CHR_RAM_SIZE],
42 rom_bank_selection: usize, // Select ROM bank
43}
44
45
46impl From<Cartridge> for Unrom {
47 fn from(cart: Cartridge) -> Self {
48 // Extract info and ROM data, VROM is unused
49 let (_, prg_rom, _, _) = cart.into_parts();
50
51 Unrom{
52 prg_rom: Memory::new(prg_rom, PRG_ROM_BANK_SIZE),
53 chr_ram: [0; CHR_RAM_SIZE],
54 rom_bank_selection: 0,
55 }
56 }
57}
58
59impl MapperControl for Unrom {
60 fn read(&self, addr: u16) -> u8 {
61 match addr {
62 0x8000..=0xBFFF => {
63 self.prg_rom.read(self.rom_bank_selection, (addr - 0x8000) as usize)
64 },
65 0xC000..=0xFFFF => {
66 self.prg_rom.read_last((addr - 0xC000) as usize)
67 }
68 _ => { 0 }
69 }
70 }
71
72 fn write(&mut self, addr: u16, data: u8) {
73 if let 0x8000..=0xFFFF = addr {
74 self.rom_bank_selection = (data & 0x0F) as usize;
75 }
76 }
77
78 fn read_chr(&self, addr: u16) -> u8 {
79 self.chr_ram[addr as usize]
80 }
81
82 fn write_chr(&mut self, addr: u16, value: u8) {
83 self.chr_ram[addr as usize] = value;
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn bank_select() {
93 let mut data = vec![0; 20];
94 data[10] = 0xDE;
95
96 let mut unrom = init_unrom(data, 10);
97 // Select ROM bank 1
98 unrom.write(0x8000, 1);
99
100 assert_eq!(unrom.read(0x8000), 0xDE);
101 }
102
103 #[test]
104 fn irq() {
105 let mut prg = vec![0; PRG_ROM_BANK_SIZE * 2];
106 prg[(PRG_ROM_BANK_SIZE * 2)-2] = 0x01;
107 prg[(PRG_ROM_BANK_SIZE * 2)-1] = 0x60;
108
109 let unrom = init_unrom(prg, PRG_ROM_BANK_SIZE);
110 let irq_lo = unrom.read(0xFFFE) as u16;
111 let irq_hi = unrom.read(0xFFFF) as u16;
112
113 let irq = (irq_hi << 8) | irq_lo;
114
115 assert_eq!(irq, 0x6001);
116 }
117
118 fn init_unrom(data: Vec<u8>, bank_size: usize) -> Unrom {
119 Unrom {
120 prg_rom: Memory::new(data, bank_size),
121 chr_ram: [0; CHR_RAM_SIZE],
122 rom_bank_selection: 0,
123 }
124 }
125}