Skip to content

Commit

Permalink
feat: jalecoss88006
Browse files Browse the repository at this point in the history
  • Loading branch information
lukexor committed Nov 11, 2024
1 parent 89d7fb4 commit 406777a
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 7 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ Support for the following mappers is currently implemented or in development:
| 010 | FxROM/MMC4 | Fire Emblem Gaiden | 3 | <0.01% |
| 011 | Color Dreams | Crystal Mines, Metal Fighter | 15 | ~1% |
| 016 | Bandai FCG | Dragon Ball: Daimaou Fukkatsu | 14 | ~1% |
| 018 | Jaleco SS 88006 | Magic John | 15 | ~1% |
| 019 | Namco163 | Battle Fleet, Dragon Ninja | 20 | ~1% |
| 024 | VRC6a | Akumajou Densetsu | 1 | <0.01% |
| 026 | VRC6b | Madara, Esper Dream 2 | 2 | <0.01% |
| 034 | BNROM/NINA-001 | Deadly Towers, Impossible Mission II | 3 | <0.01% |
Expand All @@ -239,7 +241,8 @@ Support for the following mappers is currently implemented or in development:
| 155 | SxROM/MMC1A | Tatakae!! Ramen Man: Sakuretsu Choujin | 2 | <0.01% |
| 159 | Bandai FCG | Dragon Ball Z: Kyoushuu! Saiya-jin | 4 | <0.01% |
| 206 | DxROM/Namco 108 | Fantasy Zone, Gauntlet | 45 | ~2% |
| | | | ~2217 / 2447 | ~91.0% |
| 210 | Namco175/340 | Dream Master, Family Circuit '91 | 4 | <0.01% |
| | | | ~2256 / 2447 | ~92.2% |

<!-- markdownlint-enable line-length -->

Expand Down
5 changes: 3 additions & 2 deletions tetanes-core/src/cart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::{
mapper::{
self, m024_m026_vrc6::Revision as Vrc6Revision, m034_nina001::Nina001, Axrom, BandaiFCG,
Bf909x, Bnrom, Cnrom, ColorDreams, Dxrom154, Dxrom206, Dxrom76, Dxrom88, Dxrom95, Exrom,
Fxrom, Gxrom, Mapper, Mmc1Revision, Namco163, Nina003006, Nrom, Pxrom, SunsoftFme7, Sxrom,
Txrom, Uxrom, Vrc6,
Fxrom, Gxrom, JalecoSs88006, Mapper, Mmc1Revision, Namco163, Nina003006, Nrom, Pxrom,
SunsoftFme7, Sxrom, Txrom, Uxrom, Vrc6,
},
mem::{Memory, RamState},
ppu::Mirroring,
Expand Down Expand Up @@ -220,6 +220,7 @@ impl Cart {
10 => Fxrom::load(&mut cart)?,
11 => ColorDreams::load(&mut cart)?,
16 | 153 | 157 | 159 => BandaiFCG::load(&mut cart)?,
18 => JalecoSs88006::load(&mut cart)?,
19 | 210 => Namco163::load(&mut cart)?,
24 => Vrc6::load(&mut cart, Vrc6Revision::A)?,
26 => Vrc6::load(&mut cart, Vrc6Revision::B)?,
Expand Down
3 changes: 2 additions & 1 deletion tetanes-core/src/control_deck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ impl ControlDeck {
| Mapper::Fxrom(_)
| Mapper::ColorDreams(_)
| Mapper::BandaiFCG(_)
| Mapper::JalecoSs88006(_)
| Mapper::Namco163(_)
| Mapper::Vrc6(_)
| Mapper::Bnrom(_)
Expand All @@ -407,7 +408,7 @@ impl ControlDeck {
| Mapper::Dxrom88(_)
| Mapper::Dxrom95(_)
| Mapper::Dxrom154(_)
| Mapper::Dxrom206(_) => tracing::warn!("unhandled mapper revision variant"),
| Mapper::Dxrom206(_) => (),
}
}

Expand Down
3 changes: 3 additions & 0 deletions tetanes-core/src/mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub use m007_axrom::Axrom;
pub use m009_pxrom::Pxrom;
pub use m010_fxrom::Fxrom;
pub use m011_color_dreams::ColorDreams;
pub use m018_jalecoss88006::JalecoSs88006;
pub use m019_namco163::Namco163;
pub use m024_m026_vrc6::Vrc6;
pub use m034_bnrom::Bnrom;
Expand All @@ -46,6 +47,7 @@ pub mod m007_axrom;
pub mod m009_pxrom;
pub mod m010_fxrom;
pub mod m011_color_dreams;
pub mod m018_jalecoss88006;
pub mod m019_namco163;
pub mod m024_m026_vrc6;
pub mod m034_bnrom;
Expand Down Expand Up @@ -113,6 +115,7 @@ pub enum Mapper {
Fxrom,
ColorDreams,
BandaiFCG,
JalecoSs88006,
Namco163,
Vrc6,
Bnrom,
Expand Down
230 changes: 230 additions & 0 deletions tetanes-core/src/mapper/m018_jalecoss88006.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
//! `Jaleco SS88006` (Mapper 018)
//!
//! <https://www.nesdev.org/wiki/INES_Mapper_018>
use crate::{
cart::Cart,
common::{Clock, Regional, Reset, ResetKind, Sram},
cpu::{Cpu, Irq},
mapper::{self, Mapped, MappedRead, MappedWrite, Mapper, MemMap},
mem::{BankAccess, Banks},
ppu::Mirroring,
};
use serde::{Deserialize, Serialize};

#[derive(Debug)]
#[must_use]
enum PageBits {
Low,
High,
}

impl PageBits {
const fn page(&self, page: usize, val: u8) -> usize {
let val = (val as usize) & 0x0F;
match self {
PageBits::Low => (page & 0xF0) | val,
PageBits::High => (val << 4) | (page & 0x0F),
}
}
}

impl From<u16> for PageBits {
fn from(addr: u16) -> Self {
if addr & 0x01 == 0x01 {
Self::High
} else {
Self::Low
}
}
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Regs {
pub irq_enabled: bool,
pub irq_reload: [u8; 4],
pub irq_counter_size: u8,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct JalecoSs88006 {
pub regs: Regs,
pub irq_counter: u16,
pub mirroring: Mirroring,
pub chr_banks: Banks,
pub prg_ram_banks: Banks,
pub prg_rom_banks: Banks,
}

impl JalecoSs88006 {
const PRG_WINDOW: usize = 8 * 1024;
const PRG_RAM_SIZE: usize = 8 * 1024;
const CHR_WINDOW: usize = 1024;

const IRQ_MASKS: [u16; 4] = [0xFFFF, 0x0FFF, 0x00FF, 0x000F];

pub fn load(cart: &mut Cart) -> Result<Mapper, mapper::Error> {
if !cart.has_prg_ram() {
cart.add_prg_ram(Self::PRG_RAM_SIZE);
}
let mut jalecoss88006 = Self {
regs: Regs::default(),
irq_counter: 0,
mirroring: cart.mirroring(),
chr_banks: Banks::new(0x0000, 0x1FFF, cart.chr_rom.len(), Self::CHR_WINDOW)?,
prg_ram_banks: Banks::new(0x6000, 0x7FFF, cart.prg_ram.len(), Self::PRG_WINDOW)?,
prg_rom_banks: Banks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_WINDOW)?,
};
jalecoss88006
.prg_rom_banks
.set(3, jalecoss88006.prg_rom_banks.last());
Ok(jalecoss88006.into())
}

fn update_prg_bank(&mut self, bank: usize, val: u8, bits: PageBits) {
self.prg_rom_banks
.set(bank, bits.page(self.prg_rom_banks.page(bank), val));
}

fn update_chr_bank(&mut self, bank: usize, val: u8, bits: PageBits) {
self.chr_banks
.set(bank, bits.page(self.chr_banks.page(bank), val));
}
}

impl Mapped for JalecoSs88006 {
fn mirroring(&self) -> Mirroring {
self.mirroring
}

fn set_mirroring(&mut self, mirroring: Mirroring) {
self.mirroring = mirroring;
}
}

impl MemMap for JalecoSs88006 {
// PPU $0000..=$03FF: 1K CHR Bank 1 Switchable
// PPU $0400..=$07FF: 1K CHR Bank 2 Switchable
// PPU $0800..=$0BFF: 1K CHR Bank 3 Switchable
// PPU $0C00..=$0FFF: 1K CHR Bank 4 Switchable
// PPU $1000..=$13FF: 1K CHR Bank 5 Switchable
// PPU $1400..=$17FF: 1K CHR Bank 6 Switchable
// PPU $1800..=$1BFF: 1K CHR Bank 7 Switchable
// PPU $1C00..=$1FFF: 1K CHR Bank 8 Switchable
//
// CPU $6000..=$7FFF: 8K PRG-RAM Bank, if WRAM is present
// CPU $8000..=$9FFF: 8K PRG-ROM Bank 1 Switchable
// CPU $A000..=$BFFF: 8K PRG-ROM Bank 2 Switchable
// CPU $C000..=$DFFF: 8K PRG-ROM Bank 3 Switchable
// CPU $E000..=$FFFF: 8K PRG-ROM Bank 4 Fixed to last

fn map_peek(&self, addr: u16) -> MappedRead {
match addr {
0x0000..=0x1FFF => MappedRead::Chr(self.chr_banks.translate(addr)),
0x6000..=0x7FFF if self.prg_ram_banks.readable(addr) => {
MappedRead::PrgRam(self.prg_ram_banks.translate(addr))
}
0x8000..=0xFFFF => MappedRead::PrgRom(self.prg_rom_banks.translate(addr)),
_ => MappedRead::Bus,
}
}

fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite {
match addr {
0x6000..=0x7FFF => {
if self.prg_ram_banks.writable(addr) {
return MappedWrite::PrgRam(self.prg_ram_banks.translate(addr), val);
}
}
_ => match addr & 0xF003 {
0x8000 | 0x8001 => self.update_prg_bank(0, val, PageBits::from(addr)),
0x8002 | 0x8003 => self.update_prg_bank(1, val, PageBits::from(addr)),
0x9000 | 0x9001 => self.update_prg_bank(2, val, PageBits::from(addr)),
0x9002 => {
let prg_ram_access = if val & 0x01 == 0x01 {
if val & 0x02 == 0x02 {
BankAccess::ReadWrite
} else {
BankAccess::Read
}
} else {
BankAccess::None
};
self.prg_ram_banks.set_access(0, prg_ram_access);
}
0xA000 | 0xA001 => self.update_chr_bank(0, val, PageBits::from(addr)),
0xA002 | 0xA003 => self.update_chr_bank(1, val, PageBits::from(addr)),
0xB000 | 0xB001 => self.update_chr_bank(2, val, PageBits::from(addr)),
0xB002 | 0xB003 => self.update_chr_bank(3, val, PageBits::from(addr)),
0xC000 | 0xC001 => self.update_chr_bank(4, val, PageBits::from(addr)),
0xC002 | 0xC003 => self.update_chr_bank(5, val, PageBits::from(addr)),
0xD000 | 0xD001 => self.update_chr_bank(6, val, PageBits::from(addr)),
0xD002 | 0xD003 => self.update_chr_bank(7, val, PageBits::from(addr)),
0xE000..=0xE003 => self.regs.irq_reload[(addr & 0x03) as usize] = val,
0xF000 => {
Cpu::clear_irq(Irq::MAPPER);
self.irq_counter = u16::from(self.regs.irq_reload[0])
| (u16::from(self.regs.irq_reload[1]) << 4)
| (u16::from(self.regs.irq_reload[2]) << 8)
| (u16::from(self.regs.irq_reload[3]) << 12);
}
0xF001 => {
Cpu::clear_irq(Irq::MAPPER);
self.regs.irq_enabled = val & 0x01 == 0x01;
if val & 0x08 == 0x08 {
self.regs.irq_counter_size = 3;
} else if val & 0x04 == 0x04 {
self.regs.irq_counter_size = 2;
} else if val & 0x02 == 0x02 {
self.regs.irq_counter_size = 1;
} else {
self.regs.irq_counter_size = 0;
}
}
0xF002 => self.set_mirroring(match val & 0x03 {
0 => Mirroring::Horizontal,
1 => Mirroring::Vertical,
2 => Mirroring::SingleScreenA,
3 => Mirroring::SingleScreenB,
_ => unreachable!("invalid mirroring mode: ${val:02X}"),
}),
0xF003 => {
// TODO: Expansion audio
}
_ => (),
},
}
MappedWrite::Bus
}
}

impl Reset for JalecoSs88006 {
fn reset(&mut self, kind: ResetKind) {
self.regs = Regs::default();
if kind == ResetKind::Hard {
self.prg_rom_banks.set(3, self.prg_rom_banks.last());
}
}
}

impl Clock for JalecoSs88006 {
fn clock(&mut self) -> usize {
if self.regs.irq_enabled {
let irq_mask = Self::IRQ_MASKS[self.regs.irq_counter_size as usize];
let counter = self.irq_counter & irq_mask;
if counter == 0 {
Cpu::set_irq(Irq::MAPPER);
}
self.irq_counter =
(self.irq_counter & !irq_mask) | (counter.wrapping_sub(1) & irq_mask);
1
} else {
0
}
}
}

impl Regional for JalecoSs88006 {}
impl Sram for JalecoSs88006 {}
3 changes: 2 additions & 1 deletion tetanes-core/src/mapper/m019_namco163.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ impl MemMap for Namco163 {
0 => Mirroring::SingleScreenA,
1 => Mirroring::Vertical,
2 => Mirroring::Horizontal,
_ => Mirroring::SingleScreenB,
3 => Mirroring::SingleScreenB,
_ => unreachable!("invalid mirroring mode: ${val:02X}"),
});
}
Board::Namco163 => self.audio.write_register(addr, val),
Expand Down
4 changes: 2 additions & 2 deletions tetanes-core/src/mapper/mapper.template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crate::{
cart::Cart,
common::{Clock, Regional, Reset, Sram},
mapper::{self, Mapped, Mapper, MemMap},
mapper::{self, Mapped, MappedRead, MappedWrite, Mapper, MemMap},
mem::Banks,
};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -39,7 +39,7 @@ impl MapperName {
}

impl Mapped for MapperName {
// Implement Mapped methods
// Optional, Mapped methods
}

impl MemMap for MapperName {
Expand Down
5 changes: 5 additions & 0 deletions tetanes-core/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ impl Banks {
page_offset | (addr as usize) & (self.window.get() - 1)
}

#[must_use]
pub fn page(&self, bank: usize) -> usize {
self.banks[bank] >> self.shift
}

#[must_use]
pub fn page_offset(&self, bank: usize) -> usize {
self.banks[bank]
Expand Down

0 comments on commit 406777a

Please sign in to comment.