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
|
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
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)]
|
#[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),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
Reference in a new issue