tetanes_core/mapper/
m018_jalecoss88006.rs

1//! `Jaleco SS88006` (Mapper 018).
2//!
3//! <https://www.nesdev.org/wiki/INES_Mapper_018>
4
5use crate::{
6    cart::Cart,
7    common::{Clock, Regional, Reset, ResetKind, Sram},
8    cpu::{Cpu, Irq},
9    mapper::{
10        self, MapRead, MapWrite, MappedRead, MappedWrite, Mapper, Mirrored, OnBusRead, OnBusWrite,
11    },
12    mem::{BankAccess, Banks},
13    ppu::Mirroring,
14};
15use serde::{Deserialize, Serialize};
16
17/// `Jaleco SS88006` page bit.
18#[derive(Debug)]
19#[must_use]
20enum PageBit {
21    Low,
22    High,
23}
24
25impl PageBit {
26    const fn page(&self, page: usize, val: u8) -> usize {
27        let val = (val as usize) & 0x0F;
28        match self {
29            PageBit::Low => (page & 0xF0) | val,
30            PageBit::High => (val << 4) | (page & 0x0F),
31        }
32    }
33}
34
35impl From<u16> for PageBit {
36    fn from(addr: u16) -> Self {
37        if addr & 0x01 == 0x01 {
38            Self::High
39        } else {
40            Self::Low
41        }
42    }
43}
44
45/// `Jaleco SS88006` registers.
46#[derive(Default, Debug, Clone, Serialize, Deserialize)]
47#[must_use]
48pub struct Regs {
49    pub irq_enabled: bool,
50    pub irq_reload: [u8; 4],
51    pub irq_counter_size: u8,
52}
53
54/// `Jaleco SS88006` (Mapper 018).
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[must_use]
57pub struct JalecoSs88006 {
58    pub regs: Regs,
59    pub irq_counter: u16,
60    pub mirroring: Mirroring,
61    pub chr_banks: Banks,
62    pub prg_ram_banks: Banks,
63    pub prg_rom_banks: Banks,
64}
65
66impl JalecoSs88006 {
67    const PRG_WINDOW: usize = 8 * 1024;
68    const PRG_RAM_SIZE: usize = 8 * 1024;
69    const CHR_WINDOW: usize = 1024;
70
71    const IRQ_MASKS: [u16; 4] = [0xFFFF, 0x0FFF, 0x00FF, 0x000F];
72
73    pub fn load(cart: &mut Cart) -> Result<Mapper, mapper::Error> {
74        if !cart.has_prg_ram() {
75            cart.add_prg_ram(Self::PRG_RAM_SIZE);
76        }
77        let mut jalecoss88006 = Self {
78            regs: Regs::default(),
79            irq_counter: 0,
80            mirroring: cart.mirroring(),
81            chr_banks: Banks::new(0x0000, 0x1FFF, cart.chr_rom.len(), Self::CHR_WINDOW)?,
82            prg_ram_banks: Banks::new(0x6000, 0x7FFF, cart.prg_ram.len(), Self::PRG_WINDOW)?,
83            prg_rom_banks: Banks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_WINDOW)?,
84        };
85        jalecoss88006
86            .prg_rom_banks
87            .set(3, jalecoss88006.prg_rom_banks.last());
88        Ok(jalecoss88006.into())
89    }
90
91    fn update_prg_bank(&mut self, bank: usize, val: u8, bits: PageBit) {
92        self.prg_rom_banks
93            .set(bank, bits.page(self.prg_rom_banks.page(bank), val));
94    }
95
96    fn update_chr_bank(&mut self, bank: usize, val: u8, bits: PageBit) {
97        self.chr_banks
98            .set(bank, bits.page(self.chr_banks.page(bank), val));
99    }
100}
101
102impl Mirrored for JalecoSs88006 {
103    fn mirroring(&self) -> Mirroring {
104        self.mirroring
105    }
106
107    fn set_mirroring(&mut self, mirroring: Mirroring) {
108        self.mirroring = mirroring;
109    }
110}
111
112impl MapRead for JalecoSs88006 {
113    // PPU $0000..=$03FF: 1K CHR Bank 1 Switchable
114    // PPU $0400..=$07FF: 1K CHR Bank 2 Switchable
115    // PPU $0800..=$0BFF: 1K CHR Bank 3 Switchable
116    // PPU $0C00..=$0FFF: 1K CHR Bank 4 Switchable
117    // PPU $1000..=$13FF: 1K CHR Bank 5 Switchable
118    // PPU $1400..=$17FF: 1K CHR Bank 6 Switchable
119    // PPU $1800..=$1BFF: 1K CHR Bank 7 Switchable
120    // PPU $1C00..=$1FFF: 1K CHR Bank 8 Switchable
121    //
122    // CPU $6000..=$7FFF: 8K PRG-RAM Bank, if WRAM is present
123    // CPU $8000..=$9FFF: 8K PRG-ROM Bank 1 Switchable
124    // CPU $A000..=$BFFF: 8K PRG-ROM Bank 2 Switchable
125    // CPU $C000..=$DFFF: 8K PRG-ROM Bank 3 Switchable
126    // CPU $E000..=$FFFF: 8K PRG-ROM Bank 4 Fixed to last
127
128    fn map_peek(&self, addr: u16) -> MappedRead {
129        match addr {
130            0x0000..=0x1FFF => MappedRead::Chr(self.chr_banks.translate(addr)),
131            0x6000..=0x7FFF if self.prg_ram_banks.readable(addr) => {
132                MappedRead::PrgRam(self.prg_ram_banks.translate(addr))
133            }
134            0x8000..=0xFFFF => MappedRead::PrgRom(self.prg_rom_banks.translate(addr)),
135            _ => MappedRead::Bus,
136        }
137    }
138}
139
140impl MapWrite for JalecoSs88006 {
141    fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite {
142        match addr {
143            0x6000..=0x7FFF => {
144                if self.prg_ram_banks.writable(addr) {
145                    return MappedWrite::PrgRam(self.prg_ram_banks.translate(addr), val);
146                }
147            }
148            _ => match addr & 0xF003 {
149                0x8000 | 0x8001 => self.update_prg_bank(0, val, PageBit::from(addr)),
150                0x8002 | 0x8003 => self.update_prg_bank(1, val, PageBit::from(addr)),
151                0x9000 | 0x9001 => self.update_prg_bank(2, val, PageBit::from(addr)),
152                0x9002 => {
153                    let prg_ram_access = if val & 0x01 == 0x01 {
154                        if val & 0x02 == 0x02 {
155                            BankAccess::ReadWrite
156                        } else {
157                            BankAccess::Read
158                        }
159                    } else {
160                        BankAccess::None
161                    };
162                    self.prg_ram_banks.set_access(0, prg_ram_access);
163                }
164                0xA000 | 0xA001 => self.update_chr_bank(0, val, PageBit::from(addr)),
165                0xA002 | 0xA003 => self.update_chr_bank(1, val, PageBit::from(addr)),
166                0xB000 | 0xB001 => self.update_chr_bank(2, val, PageBit::from(addr)),
167                0xB002 | 0xB003 => self.update_chr_bank(3, val, PageBit::from(addr)),
168                0xC000 | 0xC001 => self.update_chr_bank(4, val, PageBit::from(addr)),
169                0xC002 | 0xC003 => self.update_chr_bank(5, val, PageBit::from(addr)),
170                0xD000 | 0xD001 => self.update_chr_bank(6, val, PageBit::from(addr)),
171                0xD002 | 0xD003 => self.update_chr_bank(7, val, PageBit::from(addr)),
172                0xE000..=0xE003 => self.regs.irq_reload[(addr & 0x03) as usize] = val,
173                0xF000 => {
174                    Cpu::clear_irq(Irq::MAPPER);
175                    self.irq_counter = u16::from(self.regs.irq_reload[0])
176                        | (u16::from(self.regs.irq_reload[1]) << 4)
177                        | (u16::from(self.regs.irq_reload[2]) << 8)
178                        | (u16::from(self.regs.irq_reload[3]) << 12);
179                }
180                0xF001 => {
181                    Cpu::clear_irq(Irq::MAPPER);
182                    self.regs.irq_enabled = val & 0x01 == 0x01;
183                    if val & 0x08 == 0x08 {
184                        self.regs.irq_counter_size = 3;
185                    } else if val & 0x04 == 0x04 {
186                        self.regs.irq_counter_size = 2;
187                    } else if val & 0x02 == 0x02 {
188                        self.regs.irq_counter_size = 1;
189                    } else {
190                        self.regs.irq_counter_size = 0;
191                    }
192                }
193                0xF002 => self.set_mirroring(match val & 0x03 {
194                    0 => Mirroring::Horizontal,
195                    1 => Mirroring::Vertical,
196                    2 => Mirroring::SingleScreenA,
197                    3 => Mirroring::SingleScreenB,
198                    _ => unreachable!("invalid mirroring mode: ${val:02X}"),
199                }),
200                0xF003 => {
201                    // TODO: Expansion audio
202                }
203                _ => (),
204            },
205        }
206        MappedWrite::Bus
207    }
208}
209
210impl Reset for JalecoSs88006 {
211    fn reset(&mut self, kind: ResetKind) {
212        self.regs = Regs::default();
213        if kind == ResetKind::Hard {
214            self.prg_rom_banks.set(3, self.prg_rom_banks.last());
215        }
216    }
217}
218
219impl Clock for JalecoSs88006 {
220    fn clock(&mut self) -> u64 {
221        if self.regs.irq_enabled {
222            let irq_mask = Self::IRQ_MASKS[self.regs.irq_counter_size as usize];
223            let counter = self.irq_counter & irq_mask;
224            if counter == 0 {
225                Cpu::set_irq(Irq::MAPPER);
226            }
227            self.irq_counter =
228                (self.irq_counter & !irq_mask) | (counter.wrapping_sub(1) & irq_mask);
229            1
230        } else {
231            0
232        }
233    }
234}
235
236impl OnBusRead for JalecoSs88006 {}
237impl OnBusWrite for JalecoSs88006 {}
238impl Regional for JalecoSs88006 {}
239impl Sram for JalecoSs88006 {}