feat(sram): Implement SRAM save type
Tested to work on Kirby. Fixes #8 Former-commit-id: 90aa60b901a4ef790592c34c2350a7349939d612
This commit is contained in:
parent
70c19ea343
commit
23a6bf1637
118
src/core/backup/mod.rs
Normal file
118
src/core/backup/mod.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::SeekFrom;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::de::{self, Deserialize, Deserializer, SeqAccess, Visitor};
|
||||||
|
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||||
|
|
||||||
|
use crate::util::write_bin_file;
|
||||||
|
|
||||||
|
pub mod flash;
|
||||||
|
|
||||||
|
pub const BACKUP_FILE_EXT: &'static str = "sav";
|
||||||
|
|
||||||
|
#[derive(Debug, Primitive, Serialize, Deserialize, Clone)]
|
||||||
|
pub enum BackupType {
|
||||||
|
Eeprom = 0,
|
||||||
|
Sram = 1,
|
||||||
|
Flash = 2,
|
||||||
|
Flash512 = 3,
|
||||||
|
Flash1M = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BackupMemory {
|
||||||
|
size: usize,
|
||||||
|
path: PathBuf,
|
||||||
|
file: File,
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for BackupMemory {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
BackupMemory::new(self.size, self.path.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for BackupMemory {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut state = serializer.serialize_struct("BackupMemory", 2)?;
|
||||||
|
state.serialize_field("size", &self.size)?;
|
||||||
|
state.serialize_field("path", &self.path)?;
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for BackupMemory {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct BackupMemoryVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for BackupMemoryVisitor {
|
||||||
|
type Value = BackupMemory;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("struct BackupMemory")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<V>(self, mut seq: V) -> Result<BackupMemory, V::Error>
|
||||||
|
where
|
||||||
|
V: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let size = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||||
|
let path: String = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
|
||||||
|
Ok(BackupMemory::new(size, PathBuf::from(path)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIELDS: &'static [&'static str] = &["size", "path"];
|
||||||
|
deserializer.deserialize_struct("BackupMemory", FIELDS, BackupMemoryVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackupMemory {
|
||||||
|
pub fn new(size: usize, path: PathBuf) -> BackupMemory {
|
||||||
|
// TODO handle errors without unwrap
|
||||||
|
if !path.is_file() {
|
||||||
|
write_bin_file(&path, &vec![0xff; size]).unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(&path)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
file.read_to_end(&mut buffer).unwrap();
|
||||||
|
buffer.resize(size, 0xff);
|
||||||
|
|
||||||
|
BackupMemory {
|
||||||
|
size,
|
||||||
|
path,
|
||||||
|
file: file,
|
||||||
|
buffer: buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, offset: usize, value: u8) {
|
||||||
|
self.buffer[offset] = value;
|
||||||
|
self.file.seek(SeekFrom::Start(offset as u64)).unwrap();
|
||||||
|
self.file.write_all(&[value]).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self, offset: usize) -> u8 {
|
||||||
|
self.buffer[offset]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,19 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use memmem::{Searcher, TwoWaySearcher};
|
use memmem::{Searcher, TwoWaySearcher};
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
use super::super::util::read_bin_file;
|
use super::super::util::read_bin_file;
|
||||||
use super::{Addr, Bus, GBAResult};
|
use super::{Addr, Bus, GBAResult};
|
||||||
|
|
||||||
|
use super::backup::flash::*;
|
||||||
|
use super::backup::{BackupMemory, BackupType, BACKUP_FILE_EXT};
|
||||||
|
|
||||||
/// From GBATEK
|
/// From GBATEK
|
||||||
///
|
///
|
||||||
/// The first 192 bytes at 8000000h-80000BFh in ROM are used as cartridge header. The same header is also used for Multiboot images at 2000000h-20000BFh (plus some additional multiboot entries at 20000C0h and up).
|
/// The first 192 bytes at 8000000h-80000BFh in ROM are used as cartridge header. The same header is also used for Multiboot images at 2000000h-20000BFh (plus some additional multiboot entries at 20000C0h and up).
|
||||||
|
@ -70,13 +73,24 @@ impl CartridgeHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Primitive, Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub enum BackupType {
|
pub enum BackupMedia {
|
||||||
Eeprom = 0,
|
Sram(BackupMemory),
|
||||||
Sram = 1,
|
Flash,
|
||||||
Flash = 2,
|
Eeprom,
|
||||||
Flash512 = 3,
|
Undetected,
|
||||||
Flash1M = 4,
|
}
|
||||||
|
|
||||||
|
impl BackupMedia {
|
||||||
|
pub fn type_string(&self) -> &'static str {
|
||||||
|
use BackupMedia::*;
|
||||||
|
match self {
|
||||||
|
Sram(..) => "SRAM",
|
||||||
|
Flash => "FLASH",
|
||||||
|
Eeprom => "EEPROM",
|
||||||
|
Undetected => "Undetected",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
@ -84,6 +98,7 @@ pub struct Cartridge {
|
||||||
pub header: CartridgeHeader,
|
pub header: CartridgeHeader,
|
||||||
bytes: Box<[u8]>,
|
bytes: Box<[u8]>,
|
||||||
size: usize,
|
size: usize,
|
||||||
|
backup: BackupMedia,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
|
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
|
||||||
|
@ -117,48 +132,90 @@ fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
pub fn from_path(rom_path: &Path) -> GBAResult<Cartridge> {
|
pub fn from_path(rom_path: &Path) -> GBAResult<Cartridge> {
|
||||||
let rom_bin = load_rom(rom_path)?;
|
let rom_bin = load_rom(rom_path)?;
|
||||||
Ok(Cartridge::from_bytes(&rom_bin))
|
Ok(Cartridge::from_bytes(
|
||||||
|
&rom_bin,
|
||||||
|
Some(rom_path.to_path_buf()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Cartridge {
|
pub fn from_bytes(bytes: &[u8], rom_path: Option<PathBuf>) -> Cartridge {
|
||||||
let backup = Cartridge::detect_backup_type(&bytes);
|
|
||||||
println!("Backup detected: {:?}", backup);
|
|
||||||
|
|
||||||
let size = bytes.len();
|
let size = bytes.len();
|
||||||
let header = CartridgeHeader::parse(&bytes);
|
let header = CartridgeHeader::parse(&bytes);
|
||||||
|
|
||||||
|
let backup = if let Some(path) = rom_path {
|
||||||
|
create_backup(bytes, &path)
|
||||||
|
} else {
|
||||||
|
BackupMedia::Undetected
|
||||||
|
};
|
||||||
|
println!("Backup: {}", backup.type_string());
|
||||||
|
|
||||||
Cartridge {
|
Cartridge {
|
||||||
header: header,
|
header: header,
|
||||||
bytes: bytes.into(),
|
bytes: bytes.into(),
|
||||||
size: size,
|
size: size,
|
||||||
|
backup: backup,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_backup_type(bin: &[u8]) -> Option<BackupType> {
|
fn create_backup(bytes: &[u8], rom_path: &Path) -> BackupMedia {
|
||||||
|
let backup_path = rom_path.with_extension(BACKUP_FILE_EXT);
|
||||||
|
if let Some(backup_type) = detect_backup_type(bytes) {
|
||||||
|
match backup_type {
|
||||||
|
BackupType::Flash | BackupType::Flash512 | BackupType::Flash1M => {
|
||||||
|
BackupMedia::Flash
|
||||||
|
}
|
||||||
|
BackupType::Sram => BackupMedia::Sram(BackupMemory::new(0x8000, backup_path)),
|
||||||
|
BackupType::Eeprom => BackupMedia::Eeprom,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BackupMedia::Undetected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_backup_type(bytes: &[u8]) -> Option<BackupType> {
|
||||||
const ID_STRINGS: &'static [&'static str] =
|
const ID_STRINGS: &'static [&'static str] =
|
||||||
&["EEPROM_V", "SRAM_V", "FLASH_V", "FLASH512_V", "FLASH1M_V"];
|
&["EEPROM", "SRAM", "FLASH_", "FLASH512_", "FLASH1M_"];
|
||||||
|
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
let search = TwoWaySearcher::new(ID_STRINGS[i].as_bytes());
|
let search = TwoWaySearcher::new(ID_STRINGS[i].as_bytes());
|
||||||
match search.search_in(bin) {
|
match search.search_in(bytes) {
|
||||||
Some(_) => return Some(BackupType::from_u8(i as u8).unwrap()),
|
Some(_) => return Some(BackupType::from_u8(i as u8).unwrap()),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
println!("Could not detect backup type");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
use super::sysbus::{SRAM_HI, SRAM_LO};
|
||||||
|
|
||||||
impl Bus for Cartridge {
|
impl Bus for Cartridge {
|
||||||
fn read_8(&self, addr: Addr) -> u8 {
|
fn read_8(&self, addr: Addr) -> u8 {
|
||||||
if addr >= (self.size as u32) {
|
let offset = (addr & 0x01ff_ffff) as usize;
|
||||||
|
match addr & 0xff000000 {
|
||||||
|
SRAM_LO | SRAM_HI => match &self.backup {
|
||||||
|
BackupMedia::Sram(memory) => memory.read((addr & 0x7FFF) as usize),
|
||||||
|
_ => 0,
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
if offset >= self.size {
|
||||||
0xDD // TODO - open bus implementation
|
0xDD // TODO - open bus implementation
|
||||||
} else {
|
} else {
|
||||||
self.bytes[addr as usize]
|
self.bytes[offset as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_8(&mut self, addr: Addr, value: u8) {
|
fn write_8(&mut self, addr: u32, value: u8) {
|
||||||
self.bytes[addr as usize] = value;
|
let offset = (addr & 0x01ff_ffff) as usize;
|
||||||
|
match addr & 0xff000000 {
|
||||||
|
SRAM_LO | SRAM_HI => match &mut self.backup {
|
||||||
|
BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => self.bytes[offset] = value,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,11 +189,10 @@ mod tests {
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::super::bus::Bus;
|
|
||||||
use super::super::arm7tdmi;
|
use super::super::arm7tdmi;
|
||||||
|
use super::super::bus::Bus;
|
||||||
use super::super::cartridge::Cartridge;
|
use super::super::cartridge::Cartridge;
|
||||||
|
|
||||||
|
|
||||||
struct DummyInterface {}
|
struct DummyInterface {}
|
||||||
|
|
||||||
impl DummyInterface {
|
impl DummyInterface {
|
||||||
|
@ -209,7 +208,7 @@ mod tests {
|
||||||
fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance {
|
fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance {
|
||||||
let bios = vec![0; 0x4000];
|
let bios = vec![0; 0x4000];
|
||||||
let cpu = arm7tdmi::Core::new();
|
let cpu = arm7tdmi::Core::new();
|
||||||
let cartridge = Cartridge::from_bytes(rom);
|
let cartridge = Cartridge::from_bytes(rom, None);
|
||||||
let dummy = Rc::new(RefCell::new(DummyInterface::new()));
|
let dummy = Rc::new(RefCell::new(DummyInterface::new()));
|
||||||
let mut gba = GameBoyAdvance::new(
|
let mut gba = GameBoyAdvance::new(
|
||||||
cpu,
|
cpu,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod arm7tdmi;
|
pub mod arm7tdmi;
|
||||||
|
mod backup;
|
||||||
pub mod cartridge;
|
pub mod cartridge;
|
||||||
pub mod gpu;
|
pub mod gpu;
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
|
|
|
@ -18,10 +18,14 @@ pub const IOMEM_ADDR: u32 = 0x0400_0000;
|
||||||
pub const PALRAM_ADDR: u32 = 0x0500_0000;
|
pub const PALRAM_ADDR: u32 = 0x0500_0000;
|
||||||
pub const VRAM_ADDR: u32 = 0x0600_0000;
|
pub const VRAM_ADDR: u32 = 0x0600_0000;
|
||||||
pub const OAM_ADDR: u32 = 0x0700_0000;
|
pub const OAM_ADDR: u32 = 0x0700_0000;
|
||||||
pub const GAMEPAK_WS0_ADDR: u32 = 0x0800_0000;
|
pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000;
|
||||||
pub const GAMEPAK_MIRROR_WS0_ADDR: u32 = 0x0900_0000;
|
pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000;
|
||||||
pub const GAMEPAK_WS1_ADDR: u32 = 0x0A00_0000;
|
pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000;
|
||||||
pub const GAMEPAK_WS2_ADDR: u32 = 0x0C00_0000;
|
pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000;
|
||||||
|
pub const GAMEPAK_WS2_LO: u32 = 0x0C00_0000;
|
||||||
|
pub const GAMEPAK_WS2_HI: u32 = 0x0D00_0000;
|
||||||
|
pub const SRAM_LO: u32 = 0x0E00_0000;
|
||||||
|
pub const SRAM_HI: u32 = 0x0F00_0000;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum MemoryAccessType {
|
pub enum MemoryAccessType {
|
||||||
|
@ -107,21 +111,23 @@ pub struct SysBus {
|
||||||
bios: BoxedMemory,
|
bios: BoxedMemory,
|
||||||
onboard_work_ram: BoxedMemory,
|
onboard_work_ram: BoxedMemory,
|
||||||
internal_work_ram: BoxedMemory,
|
internal_work_ram: BoxedMemory,
|
||||||
gamepak: Cartridge,
|
cartridge: Cartridge,
|
||||||
dummy: DummyBus,
|
dummy: DummyBus,
|
||||||
|
|
||||||
pub trace_access: bool,
|
pub trace_access: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use ansi_term::Colour;
|
||||||
|
|
||||||
impl SysBus {
|
impl SysBus {
|
||||||
pub fn new(io: IoDevices, bios_rom: Vec<u8>, gamepak: Cartridge) -> SysBus {
|
pub fn new(io: IoDevices, bios_rom: Vec<u8>, cartridge: Cartridge) -> SysBus {
|
||||||
SysBus {
|
SysBus {
|
||||||
io: io,
|
io: io,
|
||||||
|
|
||||||
bios: BoxedMemory::new(bios_rom.into_boxed_slice()),
|
bios: BoxedMemory::new(bios_rom.into_boxed_slice()),
|
||||||
onboard_work_ram: BoxedMemory::new(vec![0; WORK_RAM_SIZE].into_boxed_slice()),
|
onboard_work_ram: BoxedMemory::new(vec![0; WORK_RAM_SIZE].into_boxed_slice()),
|
||||||
internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM_SIZE].into_boxed_slice()),
|
internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM_SIZE].into_boxed_slice()),
|
||||||
gamepak: gamepak,
|
cartridge: cartridge,
|
||||||
dummy: DummyBus([0; 4]),
|
dummy: DummyBus([0; 4]),
|
||||||
|
|
||||||
trace_access: false,
|
trace_access: false,
|
||||||
|
@ -129,67 +135,93 @@ impl SysBus {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map(&self, addr: Addr) -> (&dyn Bus, Addr) {
|
fn map(&self, addr: Addr) -> (&dyn Bus, Addr) {
|
||||||
let ofs = addr & 0x00ff_ffff;
|
|
||||||
match addr & 0xff000000 {
|
match addr & 0xff000000 {
|
||||||
BIOS_ADDR => {
|
BIOS_ADDR => {
|
||||||
if ofs >= 0x4000 {
|
if addr >= 0x4000 {
|
||||||
(&self.dummy, ofs) // TODO return last fetched opcode
|
(&self.dummy, addr) // TODO return last fetched opcode
|
||||||
} else {
|
} else {
|
||||||
(&self.bios, ofs)
|
(&self.bios, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EWRAM_ADDR => (&self.onboard_work_ram, ofs & 0x3_ffff),
|
EWRAM_ADDR => (&self.onboard_work_ram, addr & 0x3_ffff),
|
||||||
IWRAM_ADDR => (&self.internal_work_ram, ofs & 0x7fff),
|
IWRAM_ADDR => (&self.internal_work_ram, addr & 0x7fff),
|
||||||
IOMEM_ADDR => (&self.io, {
|
IOMEM_ADDR => (&self.io, {
|
||||||
if ofs & 0xffff == 0x8000 {
|
if addr & 0xffff == 0x8000 {
|
||||||
0x800
|
0x800
|
||||||
} else {
|
} else {
|
||||||
ofs & 0x7ff
|
addr & 0x7ff
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
PALRAM_ADDR => (&self.io.gpu.palette_ram, ofs & 0x3ff),
|
PALRAM_ADDR => (&self.io.gpu.palette_ram, addr & 0x3ff),
|
||||||
VRAM_ADDR => (&self.io.gpu.vram, {
|
VRAM_ADDR => (&self.io.gpu.vram, {
|
||||||
let mut ofs = ofs & ((VIDEO_RAM_SIZE as u32) - 1);
|
let mut ofs = addr & ((VIDEO_RAM_SIZE as u32) - 1);
|
||||||
if ofs > 0x18000 {
|
if ofs > 0x18000 {
|
||||||
ofs -= 0x8000;
|
ofs -= 0x8000;
|
||||||
}
|
}
|
||||||
ofs
|
ofs
|
||||||
}),
|
}),
|
||||||
OAM_ADDR => (&self.io.gpu.oam, ofs & 0x3ff),
|
OAM_ADDR => (&self.io.gpu.oam, addr & 0x3ff),
|
||||||
GAMEPAK_WS0_ADDR | GAMEPAK_MIRROR_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => {
|
GAMEPAK_WS0_LO | GAMEPAK_WS0_HI | GAMEPAK_WS1_LO | GAMEPAK_WS1_HI | GAMEPAK_WS2_LO => {
|
||||||
(&self.gamepak, addr & 0x01ff_ffff)
|
(&self.cartridge, addr)
|
||||||
|
}
|
||||||
|
GAMEPAK_WS2_HI => {
|
||||||
|
// println!(
|
||||||
|
// "[{}] Possible read form EEPROM",
|
||||||
|
// Colour::Yellow.bold().paint("warn")
|
||||||
|
// );
|
||||||
|
(&self.cartridge, addr)
|
||||||
|
}
|
||||||
|
SRAM_LO | SRAM_HI => (&self.cartridge, addr),
|
||||||
|
_ => {
|
||||||
|
println!(
|
||||||
|
"[{}] Trying to read address {:#x}",
|
||||||
|
Colour::Yellow.bold().paint("warn"),
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
(&self.dummy, addr)
|
||||||
}
|
}
|
||||||
_ => (&self.dummy, ofs),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO proc-macro for generating this function
|
/// TODO proc-macro for generating this function
|
||||||
fn map_mut(&mut self, addr: Addr) -> (&mut dyn Bus, Addr) {
|
fn map_mut(&mut self, addr: Addr) -> (&mut dyn Bus, Addr) {
|
||||||
let ofs = addr & 0x00ff_ffff;
|
|
||||||
match addr & 0xff000000 {
|
match addr & 0xff000000 {
|
||||||
BIOS_ADDR => (&mut self.dummy, ofs),
|
BIOS_ADDR => (&mut self.dummy, addr),
|
||||||
EWRAM_ADDR => (&mut self.onboard_work_ram, ofs & 0x3_ffff),
|
EWRAM_ADDR => (&mut self.onboard_work_ram, addr & 0x3_ffff),
|
||||||
IWRAM_ADDR => (&mut self.internal_work_ram, ofs & 0x7fff),
|
IWRAM_ADDR => (&mut self.internal_work_ram, addr & 0x7fff),
|
||||||
IOMEM_ADDR => (&mut self.io, {
|
IOMEM_ADDR => (&mut self.io, {
|
||||||
if ofs & 0xffff == 0x8000 {
|
if addr & 0xffff == 0x8000 {
|
||||||
0x800
|
0x800
|
||||||
} else {
|
} else {
|
||||||
ofs & 0x7ff
|
addr & 0x7ff
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
PALRAM_ADDR => (&mut self.io.gpu.palette_ram, ofs & 0x3ff),
|
PALRAM_ADDR => (&mut self.io.gpu.palette_ram, addr & 0x3ff),
|
||||||
VRAM_ADDR => (&mut self.io.gpu.vram, {
|
VRAM_ADDR => (&mut self.io.gpu.vram, {
|
||||||
let mut ofs = ofs & ((VIDEO_RAM_SIZE as u32) - 1);
|
let mut ofs = addr & ((VIDEO_RAM_SIZE as u32) - 1);
|
||||||
if ofs > 0x18000 {
|
if ofs > 0x18000 {
|
||||||
ofs -= 0x8000;
|
ofs -= 0x8000;
|
||||||
}
|
}
|
||||||
ofs
|
ofs
|
||||||
}),
|
}),
|
||||||
OAM_ADDR => (&mut self.io.gpu.oam, ofs & 0x3ff),
|
OAM_ADDR => (&mut self.io.gpu.oam, addr & 0x3ff),
|
||||||
GAMEPAK_WS0_ADDR | GAMEPAK_MIRROR_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => {
|
GAMEPAK_WS0_LO | GAMEPAK_WS0_HI => (&mut self.dummy, addr),
|
||||||
(&mut self.gamepak, addr & 0x01ff_ffff)
|
GAMEPAK_WS2_HI => {
|
||||||
|
// println!(
|
||||||
|
// "[{}] Possible write to EEPROM",
|
||||||
|
// Colour::Yellow.bold().paint("warn")
|
||||||
|
// );
|
||||||
|
(&mut self.dummy, addr)
|
||||||
|
}
|
||||||
|
SRAM_LO | SRAM_HI => (&mut self.cartridge, addr),
|
||||||
|
_ => {
|
||||||
|
println!(
|
||||||
|
"[{}] Trying to write {:#x}",
|
||||||
|
Colour::Yellow.bold().paint("warn"),
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
(&mut self.dummy, addr)
|
||||||
}
|
}
|
||||||
_ => (&mut self.dummy, ofs),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +246,7 @@ impl SysBus {
|
||||||
cycles += 1;
|
cycles += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GAMEPAK_WS0_ADDR | GAMEPAK_MIRROR_WS0_ADDR => match access.0 {
|
GAMEPAK_WS0_LO | GAMEPAK_WS0_HI => match access.0 {
|
||||||
MemoryAccessType::NonSeq => match access.1 {
|
MemoryAccessType::NonSeq => match access.1 {
|
||||||
MemoryAccessWidth::MemoryAccess32 => {
|
MemoryAccessWidth::MemoryAccess32 => {
|
||||||
cycles += nonseq_cycles[self.io.waitcnt.ws0_first_access() as usize];
|
cycles += nonseq_cycles[self.io.waitcnt.ws0_first_access() as usize];
|
||||||
|
@ -231,9 +263,40 @@ impl SysBus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => {
|
GAMEPAK_WS1_LO | GAMEPAK_WS1_HI => match access.0 {
|
||||||
panic!("unimplemented - need to refactor code with a nice macro :(")
|
MemoryAccessType::NonSeq => match access.1 {
|
||||||
|
MemoryAccessWidth::MemoryAccess32 => {
|
||||||
|
cycles += nonseq_cycles[self.io.waitcnt.ws1_first_access() as usize];
|
||||||
|
cycles += seq_cycles[self.io.waitcnt.ws1_second_access() as usize];
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
cycles += nonseq_cycles[self.io.waitcnt.ws1_first_access() as usize];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MemoryAccessType::Seq => {
|
||||||
|
cycles += seq_cycles[self.io.waitcnt.ws1_second_access() as usize];
|
||||||
|
if access.1 == MemoryAccessWidth::MemoryAccess32 {
|
||||||
|
cycles += seq_cycles[self.io.waitcnt.ws1_second_access() as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GAMEPAK_WS2_LO | GAMEPAK_WS2_HI => match access.0 {
|
||||||
|
MemoryAccessType::NonSeq => match access.1 {
|
||||||
|
MemoryAccessWidth::MemoryAccess32 => {
|
||||||
|
cycles += nonseq_cycles[self.io.waitcnt.ws2_first_access() as usize];
|
||||||
|
cycles += seq_cycles[self.io.waitcnt.ws2_second_access() as usize];
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cycles += nonseq_cycles[self.io.waitcnt.ws2_first_access() as usize];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MemoryAccessType::Seq => {
|
||||||
|
cycles += seq_cycles[self.io.waitcnt.ws2_second_access() as usize];
|
||||||
|
if access.1 == MemoryAccessWidth::MemoryAccess32 {
|
||||||
|
cycles += seq_cycles[self.io.waitcnt.ws2_second_access() as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,32 +306,44 @@ impl SysBus {
|
||||||
|
|
||||||
impl Bus for SysBus {
|
impl Bus for SysBus {
|
||||||
fn read_32(&self, addr: Addr) -> u32 {
|
fn read_32(&self, addr: Addr) -> u32 {
|
||||||
let (dev, addr) = self.map(addr);
|
if addr & 3 != 0 {
|
||||||
dev.read_32(addr & 0x1ff_fffc)
|
println!("warn: Unaligned read32 at {:#X}", addr);
|
||||||
|
}
|
||||||
|
let (dev, addr) = self.map(addr & !3);
|
||||||
|
dev.read_32(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_16(&self, addr: Addr) -> u16 {
|
fn read_16(&self, addr: Addr) -> u16 {
|
||||||
let (dev, addr) = self.map(addr);
|
if addr & 1 != 0 {
|
||||||
dev.read_16(addr & 0x1ff_fffe)
|
println!("warn: Unaligned read16 at {:#X}", addr);
|
||||||
|
}
|
||||||
|
let (dev, addr) = self.map(addr & !1);
|
||||||
|
dev.read_16(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_8(&self, addr: Addr) -> u8 {
|
fn read_8(&self, addr: Addr) -> u8 {
|
||||||
let (dev, addr) = self.map(addr);
|
let (dev, addr) = self.map(addr);
|
||||||
dev.read_8(addr & 0x1ff_ffff)
|
dev.read_8(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_32(&mut self, addr: Addr, value: u32) {
|
fn write_32(&mut self, addr: Addr, value: u32) {
|
||||||
let (dev, addr) = self.map_mut(addr);
|
if addr & 3 != 0 {
|
||||||
dev.write_32(addr & 0x1ff_fffc, value);
|
println!("warn: Unaligned write32 at {:#X} (value={:#X}", addr, value);
|
||||||
|
}
|
||||||
|
let (dev, addr) = self.map_mut(addr & !3);
|
||||||
|
dev.write_32(addr, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_16(&mut self, addr: Addr, value: u16) {
|
fn write_16(&mut self, addr: Addr, value: u16) {
|
||||||
let (dev, addr) = self.map_mut(addr);
|
if addr & 1 != 0 {
|
||||||
dev.write_16(addr & 0x1ff_fffe, value);
|
println!("warn: Unaligned write16 at {:#X} (value={:#X}", addr, value);
|
||||||
|
}
|
||||||
|
let (dev, addr) = self.map_mut(addr & !1);
|
||||||
|
dev.write_16(addr, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_8(&mut self, addr: Addr, value: u8) {
|
fn write_8(&mut self, addr: Addr, value: u8) {
|
||||||
let (dev, addr) = self.map_mut(addr);
|
let (dev, addr) = self.map_mut(addr);
|
||||||
dev.write_8(addr & 0x1ff_ffff, value);
|
dev.write_8(addr, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue