feat(flash): Implement Flash save type
Pokemon games now boot and save. Fixes #7 Former-commit-id: c60cda3352cc5117c88d4aec272c4e9d2662d21d
This commit is contained in:
parent
23a6bf1637
commit
eda3d3e3e7
|
@ -48,8 +48,7 @@ You know what they say, *third time's a charm*.
|
|||
No issues so far
|
||||
|
||||
### 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
|
||||
~~crashes when entering in-game menu, other than that works fine.~~
|
||||
|
|
199
src/core/backup/flash.rs
Normal file
199
src/core/backup/flash.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ impl CartridgeHeader {
|
|||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum BackupMedia {
|
||||
Sram(BackupMemory),
|
||||
Flash,
|
||||
Flash(Flash),
|
||||
Eeprom,
|
||||
Undetected,
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ impl BackupMedia {
|
|||
use BackupMedia::*;
|
||||
match self {
|
||||
Sram(..) => "SRAM",
|
||||
Flash => "FLASH",
|
||||
Flash(..) => "FLASH",
|
||||
Eeprom => "EEPROM",
|
||||
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);
|
||||
if let Some(backup_type) = detect_backup_type(bytes) {
|
||||
match backup_type {
|
||||
BackupType::Flash | BackupType::Flash512 | BackupType::Flash1M => {
|
||||
BackupMedia::Flash
|
||||
BackupType::Flash | BackupType::Flash512 => {
|
||||
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::Eeprom => BackupMedia::Eeprom,
|
||||
|
@ -196,6 +199,7 @@ impl Bus for Cartridge {
|
|||
match addr & 0xff000000 {
|
||||
SRAM_LO | SRAM_HI => match &self.backup {
|
||||
BackupMedia::Sram(memory) => memory.read((addr & 0x7FFF) as usize),
|
||||
BackupMedia::Flash(flash) => flash.read(addr),
|
||||
_ => 0,
|
||||
},
|
||||
_ => {
|
||||
|
@ -212,6 +216,7 @@ impl Bus for Cartridge {
|
|||
let offset = (addr & 0x01ff_ffff) as usize;
|
||||
match addr & 0xff000000 {
|
||||
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),
|
||||
_ => {}
|
||||
},
|
||||
|
|
Reference in a new issue