From 23a6bf16370fe9a255328717cc0647f555b9ce01 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sun, 26 Jan 2020 02:06:44 +0200 Subject: [PATCH] feat(sram): Implement SRAM save type Tested to work on Kirby. Fixes #8 Former-commit-id: 90aa60b901a4ef790592c34c2350a7349939d612 --- src/core/backup/mod.rs | 118 ++++++++++++++++++++++++++++ src/core/cartridge.rs | 125 +++++++++++++++++++++--------- src/core/gba.rs | 5 +- src/core/mod.rs | 1 + src/core/sysbus.rs | 169 +++++++++++++++++++++++++++++------------ 5 files changed, 334 insertions(+), 84 deletions(-) create mode 100644 src/core/backup/mod.rs diff --git a/src/core/backup/mod.rs b/src/core/backup/mod.rs new file mode 100644 index 0000000..fc545e1 --- /dev/null +++ b/src/core/backup/mod.rs @@ -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, +} + +impl Clone for BackupMemory { + fn clone(&self) -> Self { + BackupMemory::new(self.size, self.path.clone()) + } +} + +impl Serialize for BackupMemory { + fn serialize(&self, serializer: S) -> Result + 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(deserializer: D) -> Result + 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(self, mut seq: V) -> Result + 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] + } +} diff --git a/src/core/cartridge.rs b/src/core/cartridge.rs index 18da6cc..0b1ee36 100644 --- a/src/core/cartridge.rs +++ b/src/core/cartridge.rs @@ -1,16 +1,19 @@ use std::fs::File; use std::io::prelude::*; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str::from_utf8; -use serde::{Deserialize, Serialize}; use memmem::{Searcher, TwoWaySearcher}; use num::FromPrimitive; +use serde::{Deserialize, Serialize}; use zip::ZipArchive; use super::super::util::read_bin_file; use super::{Addr, Bus, GBAResult}; +use super::backup::flash::*; +use super::backup::{BackupMemory, BackupType, BACKUP_FILE_EXT}; + /// 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). @@ -70,13 +73,24 @@ impl CartridgeHeader { } } -#[derive(Debug, Primitive, Serialize, Deserialize, Clone)] -pub enum BackupType { - Eeprom = 0, - Sram = 1, - Flash = 2, - Flash512 = 3, - Flash1M = 4, +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum BackupMedia { + Sram(BackupMemory), + Flash, + Eeprom, + Undetected, +} + +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)] @@ -84,6 +98,7 @@ pub struct Cartridge { pub header: CartridgeHeader, bytes: Box<[u8]>, size: usize, + backup: BackupMedia, } fn load_rom(path: &Path) -> GBAResult> { @@ -117,48 +132,90 @@ fn load_rom(path: &Path) -> GBAResult> { impl Cartridge { pub fn from_path(rom_path: &Path) -> GBAResult { 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 { - let backup = Cartridge::detect_backup_type(&bytes); - println!("Backup detected: {:?}", backup); - + pub fn from_bytes(bytes: &[u8], rom_path: Option) -> Cartridge { let size = bytes.len(); 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 { header: header, bytes: bytes.into(), size: size, + backup: backup, } } - - fn detect_backup_type(bin: &[u8]) -> Option { - const ID_STRINGS: &'static [&'static str] = - &["EEPROM_V", "SRAM_V", "FLASH_V", "FLASH512_V", "FLASH1M_V"]; - - for i in 0..5 { - let search = TwoWaySearcher::new(ID_STRINGS[i].as_bytes()); - match search.search_in(bin) { - Some(_) => return Some(BackupType::from_u8(i as u8).unwrap()), - _ => {} - } - } - return None; - } } +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 { + const ID_STRINGS: &'static [&'static str] = + &["EEPROM", "SRAM", "FLASH_", "FLASH512_", "FLASH1M_"]; + + for i in 0..5 { + let search = TwoWaySearcher::new(ID_STRINGS[i].as_bytes()); + match search.search_in(bytes) { + Some(_) => return Some(BackupType::from_u8(i as u8).unwrap()), + _ => {} + } + } + println!("Could not detect backup type"); + return None; +} + +use super::sysbus::{SRAM_HI, SRAM_LO}; + impl Bus for Cartridge { fn read_8(&self, addr: Addr) -> u8 { - if addr >= (self.size as u32) { - 0xDD // TODO - open bus implementation - } else { - self.bytes[addr as usize] + 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 + } else { + self.bytes[offset as usize] + } + } } } - fn write_8(&mut self, addr: Addr, value: u8) { - self.bytes[addr as usize] = value; + fn write_8(&mut self, addr: u32, value: u8) { + 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, + }; } } diff --git a/src/core/gba.rs b/src/core/gba.rs index 7e474d6..9f52c4c 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -189,11 +189,10 @@ mod tests { use std::cell::RefCell; use std::rc::Rc; - use super::super::bus::Bus; use super::super::arm7tdmi; + use super::super::bus::Bus; use super::super::cartridge::Cartridge; - struct DummyInterface {} impl DummyInterface { @@ -209,7 +208,7 @@ mod tests { fn make_mock_gba(rom: &[u8]) -> GameBoyAdvance { let bios = vec![0; 0x4000]; 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 mut gba = GameBoyAdvance::new( cpu, diff --git a/src/core/mod.rs b/src/core/mod.rs index 21d89f3..8826ec8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ pub mod arm7tdmi; +mod backup; pub mod cartridge; pub mod gpu; pub mod sound; diff --git a/src/core/sysbus.rs b/src/core/sysbus.rs index 8a5de1d..28bb31b 100644 --- a/src/core/sysbus.rs +++ b/src/core/sysbus.rs @@ -18,10 +18,14 @@ pub const IOMEM_ADDR: u32 = 0x0400_0000; pub const PALRAM_ADDR: u32 = 0x0500_0000; pub const VRAM_ADDR: u32 = 0x0600_0000; pub const OAM_ADDR: u32 = 0x0700_0000; -pub const GAMEPAK_WS0_ADDR: u32 = 0x0800_0000; -pub const GAMEPAK_MIRROR_WS0_ADDR: u32 = 0x0900_0000; -pub const GAMEPAK_WS1_ADDR: u32 = 0x0A00_0000; -pub const GAMEPAK_WS2_ADDR: u32 = 0x0C00_0000; +pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000; +pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000; +pub const GAMEPAK_WS1_LO: u32 = 0x0A00_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)] pub enum MemoryAccessType { @@ -107,21 +111,23 @@ pub struct SysBus { bios: BoxedMemory, onboard_work_ram: BoxedMemory, internal_work_ram: BoxedMemory, - gamepak: Cartridge, + cartridge: Cartridge, dummy: DummyBus, pub trace_access: bool, } +use ansi_term::Colour; + impl SysBus { - pub fn new(io: IoDevices, bios_rom: Vec, gamepak: Cartridge) -> SysBus { + pub fn new(io: IoDevices, bios_rom: Vec, cartridge: Cartridge) -> SysBus { SysBus { io: io, bios: BoxedMemory::new(bios_rom.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()), - gamepak: gamepak, + cartridge: cartridge, dummy: DummyBus([0; 4]), trace_access: false, @@ -129,67 +135,93 @@ impl SysBus { } fn map(&self, addr: Addr) -> (&dyn Bus, Addr) { - let ofs = addr & 0x00ff_ffff; match addr & 0xff000000 { BIOS_ADDR => { - if ofs >= 0x4000 { - (&self.dummy, ofs) // TODO return last fetched opcode + if addr >= 0x4000 { + (&self.dummy, addr) // TODO return last fetched opcode } else { - (&self.bios, ofs) + (&self.bios, addr) } } - EWRAM_ADDR => (&self.onboard_work_ram, ofs & 0x3_ffff), - IWRAM_ADDR => (&self.internal_work_ram, ofs & 0x7fff), + EWRAM_ADDR => (&self.onboard_work_ram, addr & 0x3_ffff), + IWRAM_ADDR => (&self.internal_work_ram, addr & 0x7fff), IOMEM_ADDR => (&self.io, { - if ofs & 0xffff == 0x8000 { + if addr & 0xffff == 0x8000 { 0x800 } 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, { - let mut ofs = ofs & ((VIDEO_RAM_SIZE as u32) - 1); + let mut ofs = addr & ((VIDEO_RAM_SIZE as u32) - 1); if ofs > 0x18000 { ofs -= 0x8000; } ofs }), - OAM_ADDR => (&self.io.gpu.oam, ofs & 0x3ff), - GAMEPAK_WS0_ADDR | GAMEPAK_MIRROR_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => { - (&self.gamepak, addr & 0x01ff_ffff) + OAM_ADDR => (&self.io.gpu.oam, addr & 0x3ff), + GAMEPAK_WS0_LO | GAMEPAK_WS0_HI | GAMEPAK_WS1_LO | GAMEPAK_WS1_HI | GAMEPAK_WS2_LO => { + (&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 fn map_mut(&mut self, addr: Addr) -> (&mut dyn Bus, Addr) { - let ofs = addr & 0x00ff_ffff; match addr & 0xff000000 { - BIOS_ADDR => (&mut self.dummy, ofs), - EWRAM_ADDR => (&mut self.onboard_work_ram, ofs & 0x3_ffff), - IWRAM_ADDR => (&mut self.internal_work_ram, ofs & 0x7fff), + BIOS_ADDR => (&mut self.dummy, addr), + EWRAM_ADDR => (&mut self.onboard_work_ram, addr & 0x3_ffff), + IWRAM_ADDR => (&mut self.internal_work_ram, addr & 0x7fff), IOMEM_ADDR => (&mut self.io, { - if ofs & 0xffff == 0x8000 { + if addr & 0xffff == 0x8000 { 0x800 } 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, { - let mut ofs = ofs & ((VIDEO_RAM_SIZE as u32) - 1); + let mut ofs = addr & ((VIDEO_RAM_SIZE as u32) - 1); if ofs > 0x18000 { ofs -= 0x8000; } ofs }), - OAM_ADDR => (&mut self.io.gpu.oam, ofs & 0x3ff), - GAMEPAK_WS0_ADDR | GAMEPAK_MIRROR_WS0_ADDR | GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => { - (&mut self.gamepak, addr & 0x01ff_ffff) + OAM_ADDR => (&mut self.io.gpu.oam, addr & 0x3ff), + GAMEPAK_WS0_LO | GAMEPAK_WS0_HI => (&mut self.dummy, addr), + 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; } } - GAMEPAK_WS0_ADDR | GAMEPAK_MIRROR_WS0_ADDR => match access.0 { + GAMEPAK_WS0_LO | GAMEPAK_WS0_HI => match access.0 { MemoryAccessType::NonSeq => match access.1 { MemoryAccessWidth::MemoryAccess32 => { cycles += nonseq_cycles[self.io.waitcnt.ws0_first_access() as usize]; @@ -231,9 +263,40 @@ impl SysBus { } } }, - GAMEPAK_WS1_ADDR | GAMEPAK_WS2_ADDR => { - panic!("unimplemented - need to refactor code with a nice macro :(") - } + GAMEPAK_WS1_LO | GAMEPAK_WS1_HI => match access.0 { + 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 { fn read_32(&self, addr: Addr) -> u32 { - let (dev, addr) = self.map(addr); - dev.read_32(addr & 0x1ff_fffc) + if addr & 3 != 0 { + 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 { - let (dev, addr) = self.map(addr); - dev.read_16(addr & 0x1ff_fffe) + if addr & 1 != 0 { + 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 { 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) { - let (dev, addr) = self.map_mut(addr); - dev.write_32(addr & 0x1ff_fffc, value); + if addr & 3 != 0 { + 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) { - let (dev, addr) = self.map_mut(addr); - dev.write_16(addr & 0x1ff_fffe, value); + if addr & 1 != 0 { + 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) { let (dev, addr) = self.map_mut(addr); - dev.write_8(addr & 0x1ff_ffff, value); + dev.write_8(addr, value); } }