feat(flash): Implement Flash save type

Pokemon games now boot and save.
Fixes #7


Former-commit-id: c60cda3352cc5117c88d4aec272c4e9d2662d21d
This commit is contained in:
Michel Heily 2020-01-26 02:16:54 +02:00
parent 23a6bf1637
commit eda3d3e3e7
3 changed files with 209 additions and 6 deletions

View file

@ -48,8 +48,7 @@ You know what they say, *third time's a charm*.
No issues so far No issues so far
### Pokemon - Emerald ### Pokemon - Emerald
~~Won't boot unless binary patched to remove a loop querying the flash chip~~
Won't boot unless binary patched to remove a loop querying the flash chip
### Dragon Ball - Legacy of Goku 2 ### Dragon Ball - Legacy of Goku 2
~~crashes when entering in-game menu, other than that works fine.~~ ~~crashes when entering in-game menu, other than that works fine.~~

199
src/core/backup/flash.rs Normal file
View file

@ -0,0 +1,199 @@
use super::BackupMemory;
use num::FromPrimitive;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Serialize, Deserialize, Clone, Debug)]
enum FlashWriteSequence {
Initial,
Magic,
Command,
Argument,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
enum FlashMode {
Initial,
ChipId,
Erase,
Write,
Select,
}
#[derive(Debug, Primitive)]
enum FlashCommand {
EnterIdMode = 0x90,
TerminateIdMode = 0xf0,
Erase = 0x80,
EraseEntireChip = 0x10,
EraseSector = 0x30,
WriteByte = 0xa0,
SelectBank = 0xb0,
}
#[derive(Debug)]
pub enum FlashSize {
Flash64k,
Flash128k,
}
impl Into<usize> for FlashSize {
fn into(self) -> usize {
match self {
FlashSize::Flash64k => 64 * 1024,
FlashSize::Flash128k => 128 * 1024,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Flash {
chip_id: u16,
size: usize,
wrseq: FlashWriteSequence,
mode: FlashMode,
bank: usize,
memory: BackupMemory,
}
const MACRONIX_64K_CHIP_ID: u16 = 0x1CC2;
const MACRONIX_128K_CHIP_ID: u16 = 0x09c2;
const SECTOR_SIZE: usize = 0x1000;
const BANK_SIZE: usize = 0x10000;
impl Flash {
pub fn new(flash_path: PathBuf, flash_size: FlashSize) -> Flash {
let chip_id = match flash_size {
FlashSize::Flash64k => MACRONIX_64K_CHIP_ID,
FlashSize::Flash128k => MACRONIX_128K_CHIP_ID,
};
let size: usize = flash_size.into();
let memory = BackupMemory::new(size, flash_path);
Flash {
chip_id: chip_id,
wrseq: FlashWriteSequence::Initial,
mode: FlashMode::Initial,
size: size,
bank: 0,
memory: memory,
}
}
fn reset_sequence(&mut self) {
self.wrseq = FlashWriteSequence::Initial;
}
fn command(&mut self, addr: u32, value: u8) {
const COMMAND_ADDR: u32 = 0x0E00_5555;
if let Some(command) = FlashCommand::from_u8(value) {
match (addr, command) {
(COMMAND_ADDR, FlashCommand::EnterIdMode) => {
self.mode = FlashMode::ChipId;
self.reset_sequence();
}
(COMMAND_ADDR, FlashCommand::TerminateIdMode) => {
self.mode = FlashMode::Initial;
self.reset_sequence();
}
(COMMAND_ADDR, FlashCommand::Erase) => {
self.mode = FlashMode::Erase;
self.reset_sequence();
}
(COMMAND_ADDR, FlashCommand::EraseEntireChip) => {
if self.mode == FlashMode::Erase {
for i in 0..self.size {
self.memory.write(i, 0xff);
}
}
self.reset_sequence();
self.mode = FlashMode::Initial;
}
(sector_n, FlashCommand::EraseSector) => {
let sector_offset = self.flash_offset((sector_n & 0xf000) as usize);
for i in 0..SECTOR_SIZE {
self.memory.write(sector_offset + i, 0xff);
}
self.reset_sequence();
self.mode = FlashMode::Initial;
}
(COMMAND_ADDR, FlashCommand::WriteByte) => {
self.mode = FlashMode::Write;
self.wrseq = FlashWriteSequence::Argument;
}
(COMMAND_ADDR, FlashCommand::SelectBank) => {
self.mode = FlashMode::Select;
self.wrseq = FlashWriteSequence::Argument;
}
(addr, command) => {
panic!("[FLASH] Invalid command {:?} addr {:#x}", command, addr);
}
};
} else {
panic!("[FLASH] unknown command {:x}", value);
}
}
/// Returns the phyiscal offset inside the flash file according to the selected bank
#[inline]
fn flash_offset(&self, offset: usize) -> usize {
let offset = (offset & 0xffff) as usize;
return self.bank * BANK_SIZE + offset;
}
pub fn read(&self, addr: u32) -> u8 {
let offset = (addr & 0xffff) as usize;
let result = if self.mode == FlashMode::ChipId {
match offset {
0 => (self.chip_id & 0xff) as u8,
1 => (self.chip_id >> 8) as u8,
_ => panic!("Tried to read invalid flash offset while reading chip ID"),
}
} else {
self.memory.read(self.flash_offset(offset))
};
result
}
pub fn write(&mut self, addr: u32, value: u8) {
// println!("[FLASH] write {:#x}={:#x}", addr, value);
match self.wrseq {
FlashWriteSequence::Initial => {
if addr == 0x0E00_5555 && value == 0xAA {
self.wrseq = FlashWriteSequence::Magic;
}
}
FlashWriteSequence::Magic => {
if addr == 0xE00_2AAA && value == 0x55 {
self.wrseq = FlashWriteSequence::Command;
}
}
FlashWriteSequence::Command => {
self.command(addr, value);
}
FlashWriteSequence::Argument => {
match self.mode {
FlashMode::Write => {
self.memory
.write(self.flash_offset((addr & 0xffff) as usize), value);
}
FlashMode::Select => {
if addr == 0x0E00_0000 {
self.bank = value as usize;
}
}
_ => panic!("Flash sequence is invalid"),
};
self.mode = FlashMode::Initial;
self.reset_sequence();
}
}
}
}

View file

@ -76,7 +76,7 @@ impl CartridgeHeader {
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub enum BackupMedia { pub enum BackupMedia {
Sram(BackupMemory), Sram(BackupMemory),
Flash, Flash(Flash),
Eeprom, Eeprom,
Undetected, Undetected,
} }
@ -86,7 +86,7 @@ impl BackupMedia {
use BackupMedia::*; use BackupMedia::*;
match self { match self {
Sram(..) => "SRAM", Sram(..) => "SRAM",
Flash => "FLASH", Flash(..) => "FLASH",
Eeprom => "EEPROM", Eeprom => "EEPROM",
Undetected => "Undetected", Undetected => "Undetected",
} }
@ -162,8 +162,11 @@ fn create_backup(bytes: &[u8], rom_path: &Path) -> BackupMedia {
let backup_path = rom_path.with_extension(BACKUP_FILE_EXT); let backup_path = rom_path.with_extension(BACKUP_FILE_EXT);
if let Some(backup_type) = detect_backup_type(bytes) { if let Some(backup_type) = detect_backup_type(bytes) {
match backup_type { match backup_type {
BackupType::Flash | BackupType::Flash512 | BackupType::Flash1M => { BackupType::Flash | BackupType::Flash512 => {
BackupMedia::Flash BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash64k))
}
BackupType::Flash1M => {
BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash128k))
} }
BackupType::Sram => BackupMedia::Sram(BackupMemory::new(0x8000, backup_path)), BackupType::Sram => BackupMedia::Sram(BackupMemory::new(0x8000, backup_path)),
BackupType::Eeprom => BackupMedia::Eeprom, BackupType::Eeprom => BackupMedia::Eeprom,
@ -196,6 +199,7 @@ impl Bus for Cartridge {
match addr & 0xff000000 { match addr & 0xff000000 {
SRAM_LO | SRAM_HI => match &self.backup { SRAM_LO | SRAM_HI => match &self.backup {
BackupMedia::Sram(memory) => memory.read((addr & 0x7FFF) as usize), BackupMedia::Sram(memory) => memory.read((addr & 0x7FFF) as usize),
BackupMedia::Flash(flash) => flash.read(addr),
_ => 0, _ => 0,
}, },
_ => { _ => {
@ -212,6 +216,7 @@ impl Bus for Cartridge {
let offset = (addr & 0x01ff_ffff) as usize; let offset = (addr & 0x01ff_ffff) as usize;
match addr & 0xff000000 { match addr & 0xff000000 {
SRAM_LO | SRAM_HI => match &mut self.backup { SRAM_LO | SRAM_HI => match &mut self.backup {
BackupMedia::Flash(flash) => flash.write(addr, value),
BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value), BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value),
_ => {} _ => {}
}, },