Skip to content

Commit

Permalink
Refactor cartridge to its own module and add ext. RAM
Browse files Browse the repository at this point in the history
  • Loading branch information
twvd committed Nov 16, 2023
1 parent 4869dbe commit e0ad740
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 22 deletions.
13 changes: 4 additions & 9 deletions src/bin/siena/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use siena::frontend::sdl::{SDLEventPump, SDLRenderer};
use siena::frontend::Renderer;
use siena::snes::bus::mainbus::{BusTrace, Mainbus};
use siena::snes::bus::Bus;
use siena::snes::cartridge::Cartridge;
use siena::snes::cpu_65816::cpu::Cpu65816;
use siena::snes::joypad::{Button, Joypad, JoypadEvent};
use siena::snes::ppu::{SCREEN_HEIGHT, SCREEN_WIDTH};
Expand Down Expand Up @@ -69,22 +70,16 @@ fn main() -> Result<()> {
let mut args = Args::parse();

let f = fs::read(args.filename)?;
let load_offset = match f.len() % 1024 {
0 => 0,
0x200 => {
println!("Cartridge contains 0x200 bytes of weird header");
0x200
}
_ => panic!("Illogical cartridge file size: 0x{:08X}", f.len()),
};

let (mut joypads, joypad_senders) = Joypad::new_channel_all();
for j in joypads.iter_mut() {
j.sticky_enabled = args.sticky;
}
let display = SDLRenderer::new(SCREEN_WIDTH, SCREEN_HEIGHT)?;
let eventpump = SDLEventPump::new();
let bus = Mainbus::<SDLRenderer>::new(&f[load_offset..], args.bustrace, display, joypads);
let cart = Cartridge::load(&f);
println!("Cartridge: {}", &cart);
let bus = Mainbus::<SDLRenderer>::new(cart, args.bustrace, display, joypads);

let reset = bus.read16(0xFFFC);
let mut cpu = Cpu65816::<Mainbus<SDLRenderer>>::new(bus, reset);
Expand Down
21 changes: 9 additions & 12 deletions src/snes/bus/mainbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use dbg_hex::dbg_hex;

use crate::frontend::Renderer;
use crate::snes::bus::{Address, Bus, BusMember};
use crate::snes::cartridge::Cartridge;
use crate::snes::joypad::{Joypad, JOYPAD_COUNT};
use crate::snes::ppu::PPU;
use crate::tickable::{Tickable, Ticks};
Expand All @@ -28,7 +29,7 @@ pub struct Mainbus<TRenderer>
where
TRenderer: Renderer,
{
cartridge: Vec<u8>,
cartridge: Cartridge,
wram: Vec<u8>,
pub trace: BusTrace,

Expand Down Expand Up @@ -220,13 +221,13 @@ where
TRenderer: Renderer,
{
pub fn new(
cartridge: &[u8],
cartridge: Cartridge,
trace: BusTrace,
renderer: TRenderer,
joypads: [Joypad; JOYPAD_COUNT],
) -> Self {
Self {
cartridge: cartridge.to_owned(),
cartridge,
wram: vec![0; WRAM_SIZE],
trace,
dma: [DMAChannel::new(); DMA_CHANNELS],
Expand Down Expand Up @@ -549,19 +550,14 @@ where
_ => None,
}
}
// WS1/2 LoROM
0x8000..=0xFFFF => Some(self.cartridge[addr - 0x8000 + (bank & !0x80) * 0x8000]),
// LoROM
0x8000..=0xFFFF => self.cartridge.read(fulladdr),

_ => None,
},
// WS1 HiROM
0x40..=0x7D => {
Some(self.cartridge[(addr + ((bank - 0x40) * 0x10000)) % self.cartridge.len()])
}
0x40..=0x7D => self.cartridge.read(fulladdr),
// Full WRAM area
0x7E..=0x7F => Some(self.wram[((bank - 0x7E) * WRAM_BANK_SIZE) + addr]),
// WS2 HiROM
0xC0..=0xFF => Some(self.cartridge[addr + ((bank - 0xC0) * 0x10000)]),
_ => None,
};

Expand Down Expand Up @@ -735,6 +731,7 @@ where

_ => None,
},
0x70..=0x7D => self.cartridge.write(fulladdr, val),
// Full WRAM area
0x7E..=0x7F => Some(self.wram[((bank - 0x7E) * WRAM_BANK_SIZE) + addr] = val),

Expand Down Expand Up @@ -819,7 +816,7 @@ mod tests {
fn mainbus() -> Mainbus<NullRenderer> {
let (joypads, _) = Joypad::new_channel_all();
Mainbus::<NullRenderer>::new(
&[],
Cartridge::new_empty(),
BusTrace::All,
NullRenderer::new(0, 0).unwrap(),
joypads,
Expand Down
213 changes: 213 additions & 0 deletions src/snes/cartridge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
use std::fmt;

use num_derive::FromPrimitive;
use num_traits::FromPrimitive;

use crate::snes::bus::{Address, BusMember};

const HDR_TITLE_OFFSET: usize = 0x00;
const HDR_TITLE_SIZE: usize = 21;
const HDR_MAPMODE_OFFSET: usize = 0x15;
const HDR_CHIPSET_OFFSET: usize = 0x16;
const HDR_ROMSIZE_OFFSET: usize = 0x17;
const HDR_RAMSIZE_OFFSET: usize = 0x18;
const HDR_CHECKSUM_OFFSET: usize = 0x1C;
const HDR_ICHECKSUM_OFFSET: usize = 0x1E;
const HDR_LEN: usize = 0x1F;

#[derive(Debug, Clone, Copy, Eq, PartialEq, FromPrimitive)]
pub enum Chipset {
RomOnly = 0,
RomRam = 1,
RomRamBat = 2,
RomCo = 3,
RomRamCo = 4,
RomRamCoBat = 5,
RomCoBat = 6,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, FromPrimitive)]
pub enum MapMode {
LoROM = 0,
HiROM = 1,
ExHiROM = 5,
}

/// A mounted SNES cartridge
pub struct Cartridge {
rom: Vec<u8>,
ram: Vec<u8>,
header_offset: usize,

/// True if HiROM. Cache this for performance reasons.
hirom: bool,
}

impl Cartridge {
/// Returns the title of the cartridge as set in the header.
pub fn get_title(&self) -> String {
String::from_utf8(
self.rom[(self.header_offset + HDR_TITLE_OFFSET)
..(self.header_offset + HDR_TITLE_OFFSET + HDR_TITLE_SIZE)]
.into_iter()
.take_while(|&&c| c != 0)
.copied()
.collect(),
)
.unwrap_or("INVALID".to_string())
.trim()
.to_owned()
}

fn get_map(&self) -> MapMode {
MapMode::from_u8(self.rom[self.header_offset + HDR_MAPMODE_OFFSET] & 0x0F).unwrap()
}

fn get_chipset(&self) -> Chipset {
Chipset::from_u8(self.rom[self.header_offset + HDR_CHIPSET_OFFSET] & 0x0F).unwrap()
}

fn get_rom_size(&self) -> usize {
(1 << self.rom[self.header_offset + HDR_ROMSIZE_OFFSET]) * 1024
}

fn get_ram_size(&self) -> usize {
(1 << self.rom[self.header_offset + HDR_RAMSIZE_OFFSET]) * 1024
}

fn probe_header(hdr: &[u8]) -> bool {
let csum1: u16 =
(hdr[HDR_CHECKSUM_OFFSET + 0] as u16) | (hdr[HDR_CHECKSUM_OFFSET + 1] as u16) << 8;
let csum2: u16 =
(hdr[HDR_ICHECKSUM_OFFSET + 0] as u16) | (hdr[HDR_ICHECKSUM_OFFSET + 1] as u16) << 8;
return csum1 == (csum2 ^ 0xFFFF);
}

/// Loads a cartridge.
/// Fails if it cannot find the cartridge header.
pub fn load(rom: &[u8]) -> Self {
Self::load_with_save(rom, &[])
}

/// Loads a cartridge and a save.
/// Fails if it cannot find the cartridge header.
pub fn load_with_save(rom: &[u8], _save: &[u8]) -> Self {
let load_offset = match rom.len() % 1024 {
0 => 0,
0x200 => {
println!("Cartridge contains 0x200 bytes of weird header");
0x200
}
_ => panic!("Illogical cartridge file size: 0x{:08X}", rom.len()),
};
let rom = &rom[load_offset..];

let mut header_offset = None;
for possible_offset in [0x7FC0, 0xFFC0] {
if (possible_offset + HDR_LEN) > rom.len() {
continue;
}
if Self::probe_header(&rom[possible_offset..]) {
println!("Cartridge header at 0x{:06X}", possible_offset);
header_offset = Some(possible_offset);
break;
}
}

let mut c = Self {
rom: Vec::from(rom),
ram: vec![0; 512 * 1024],
hirom: false,
header_offset: header_offset.expect("Could not locate header"),
};
c.hirom = match c.get_map() {
MapMode::HiROM => true,
_ => false,
};
c
}

/// Loads a cartridge but does not do header detection
pub fn load_nohdr(rom: &[u8], hirom: bool) -> Self {
Self {
rom: Vec::from(rom),
ram: vec![0; 512 * 1024],
hirom,
header_offset: 0,
}
}

/// Creates an empty new cartridge (for tests)
/// Does not do header detection
pub fn new_empty() -> Self {
Self {
rom: vec![],
ram: vec![0; 512 * 1024],
hirom: false,
header_offset: 0,
}
}
}

impl fmt::Display for Cartridge {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\"{}\" - {:?} {:?} - {} KB ROM, {} KB RAM",
self.get_title(),
self.get_chipset(),
self.get_map(),
self.get_rom_size() / 1024,
self.get_ram_size() / 1024,
)
}
}

impl BusMember for Cartridge {
fn read(&self, fulladdr: Address) -> Option<u8> {
let (bank, addr) = ((fulladdr >> 16) as usize, (fulladdr & 0xFFFF) as usize);

match (bank, addr) {
// LoROM
(0x00..=0x3F | 0x80..=0xBF, 0x8000..=0xFFFF) => {
Some(self.rom[addr - 0x8000 + (bank & !0x80) * 0x8000])
}

// HiROM SRAM
(0x30..=0x3F, 0x6000..=0x6FFF) if self.hirom => {
Some(self.ram[(bank - 0x30) * 0x1000 + (addr - 0x6000)])
}

// HiROM
(0x40..=0x6F, _) => Some(self.rom[(addr + ((bank - 0x40) * 0x10000)) % self.rom.len()]),

// LoROM SRAM
(0x70..=0x7D, 0x0000..=0x7FFF) if !self.hirom => {
Some(self.ram[(bank - 0x70) * 0x8000 + addr])
}

// HiROM
(0xC0..=0xFF, _) => Some(self.rom[addr + ((bank - 0xC0) * 0x10000)]),

_ => None,
}
}

fn write(&mut self, fulladdr: Address, val: u8) -> Option<()> {
let (bank, addr) = ((fulladdr >> 16) as usize, (fulladdr & 0xFFFF) as usize);

match (bank, addr) {
// HiROM SRAM
(0x30..=0x3F, 0x6000..=0x6FFF) if self.hirom => {
Some(self.ram[(bank - 0x30) * 0x1000 + (addr - 0x6000)] = val)
}

// LoROM SRAM
(0x70..=0x7D, 0x0000..=0x7FFF) if !self.hirom => {
Some(self.ram[(bank - 0x70) * 0x8000 + addr] = val)
}

_ => None,
}
}
}
1 change: 1 addition & 0 deletions src/snes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod bus;
pub mod cartridge;
pub mod cpu_65816;
pub mod joypad;
pub mod ppu;
4 changes: 3 additions & 1 deletion src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ use std::time::Instant;
use crate::frontend::test::TestRenderer;
use crate::snes::bus::mainbus::{BusTrace, Mainbus};
use crate::snes::bus::Bus;
use crate::snes::cartridge::Cartridge;
use crate::snes::cpu_65816::cpu::Cpu65816;
use crate::snes::joypad::Joypad;
use crate::snes::ppu::{SCREEN_HEIGHT, SCREEN_WIDTH};

fn test_display(rom: &[u8], pass_hash: &[u8], time_limit: u128, stable: bool) {
let (display, dispstatus) = TestRenderer::new_test(SCREEN_WIDTH, SCREEN_HEIGHT);
let (joypads, _) = Joypad::new_channel_all();
let bus = Mainbus::<TestRenderer>::new(rom, BusTrace::None, display, joypads);
let cart = Cartridge::load_nohdr(rom, false);
let bus = Mainbus::<TestRenderer>::new(cart, BusTrace::None, display, joypads);
let reset = bus.read16(0xFFFC);
let mut cpu = Cpu65816::<Mainbus<TestRenderer>>::new(bus, reset);

Expand Down

0 comments on commit e0ad740

Please sign in to comment.