2019-12-04 21:02:51 +00:00
use std ::fs ::File ;
use std ::io ::prelude ::* ;
2020-01-26 00:06:44 +00:00
use std ::path ::{ Path , PathBuf } ;
2019-07-02 23:40:08 +01:00
use std ::str ::from_utf8 ;
2020-01-17 14:08:23 +00:00
use memmem ::{ Searcher , TwoWaySearcher } ;
use num ::FromPrimitive ;
2020-01-26 00:06:44 +00:00
use serde ::{ Deserialize , Serialize } ;
2019-12-04 21:02:51 +00:00
use zip ::ZipArchive ;
2019-07-15 05:30:52 +01:00
2019-12-04 21:02:51 +00:00
use super ::super ::util ::read_bin_file ;
2019-12-29 21:03:57 +00:00
use super ::{ Addr , Bus , GBAResult } ;
2019-08-07 07:50:33 +01:00
2020-01-29 19:48:38 +00:00
mod backup ;
use backup ::eeprom ::* ;
use backup ::flash ::* ;
use backup ::{ BackupMemory , BackupMemoryInterface , BackupType , BACKUP_FILE_EXT } ;
2020-01-26 00:06:44 +00:00
2019-07-02 23:40:08 +01:00
/// 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).
///
/// Header Overview
/// Address Bytes Expl.
/// 000h 4 ROM Entry Point (32bit ARM branch opcode, eg. "B rom_start")
/// 004h 156 Nintendo Logo (compressed bitmap, required!)
/// 0A0h 12 Game Title (uppercase ascii, max 12 characters)
/// 0ACh 4 Game Code (uppercase ascii, 4 characters)
/// 0B0h 2 Maker Code (uppercase ascii, 2 characters)
/// 0B2h 1 Fixed value (must be 96h, required!)
/// 0B3h 1 Main unit code (00h for current GBA models)
/// 0B4h 1 Device type (usually 00h) (bit7=DACS/debug related)
/// 0B5h 7 Reserved Area (should be zero filled)
/// 0BCh 1 Software version (usually 00h)
/// 0BDh 1 Complement check (header checksum, required!)
/// 0BEh 2 Reserved Area (should be zero filled)
/// --- Additional Multiboot Header Entries ---
/// 0C0h 4 RAM Entry Point (32bit ARM branch opcode, eg. "B ram_start")
/// 0C4h 1 Boot mode (init as 00h - BIOS overwrites this value!)
/// 0C5h 1 Slave ID Number (init as 00h - BIOS overwrites this value!)
/// 0C6h 26 Not used (seems to be unused)
/// 0E0h 4 JOYBUS Entry Pt. (32bit ARM branch opcode, eg. "B joy_start")
///
2020-01-16 18:06:22 +00:00
#[ derive(Serialize, Deserialize, Clone, Debug) ]
2019-07-02 23:40:08 +01:00
pub struct CartridgeHeader {
// rom_entry_point: Addr,
game_title : String ,
game_code : String ,
maker_code : String ,
software_version : u8 ,
checksum : u8 ,
// ram_entry_point: Addr,
// joybus_entry_point: Addr,
}
impl CartridgeHeader {
fn parse ( bytes : & [ u8 ] ) -> CartridgeHeader {
// let (_, rom_entry_point) = le_u32(bytes).unwrap();
let game_title = from_utf8 ( & bytes [ 0xa0 .. 0xac ] ) . unwrap ( ) ;
let game_code = from_utf8 ( & bytes [ 0xac .. 0xb0 ] ) . unwrap ( ) ;
let maker_code = from_utf8 ( & bytes [ 0xb0 .. 0xb2 ] ) . unwrap ( ) ;
// let (_, ram_entry_point) = le_u32(&bytes[0xc0..]).unwrap();
// let (_, joybus_entry_point) = le_u32(&bytes[0xc0..]).unwrap();
CartridgeHeader {
// rom_entry_point: rom_entry_point,
game_title : String ::from ( game_title ) ,
game_code : String ::from ( game_code ) ,
maker_code : String ::from ( maker_code ) ,
software_version : bytes [ 0xbc ] ,
checksum : bytes [ 0xbd ] ,
// ram_entry_point: ram_entry_point,
// joybus_entry_point: joybus_entry_point,
}
}
}
2020-01-26 00:06:44 +00:00
#[ derive(Serialize, Deserialize, Clone, Debug) ]
pub enum BackupMedia {
Sram ( BackupMemory ) ,
2020-01-26 00:16:54 +00:00
Flash ( Flash ) ,
2020-01-28 08:25:52 +00:00
Eeprom ( SpiController < BackupMemory > ) ,
2020-01-26 00:06:44 +00:00
Undetected ,
}
impl BackupMedia {
pub fn type_string ( & self ) -> & 'static str {
use BackupMedia ::* ;
match self {
Sram ( .. ) = > " SRAM " ,
2020-01-26 00:16:54 +00:00
Flash ( .. ) = > " FLASH " ,
2020-01-28 08:25:52 +00:00
Eeprom ( .. ) = > " EEPROM " ,
2020-01-26 00:06:44 +00:00
Undetected = > " Undetected " ,
}
}
2020-01-17 14:08:23 +00:00
}
2020-01-16 18:06:22 +00:00
#[ derive(Serialize, Deserialize, Clone, Debug) ]
2019-07-02 23:40:08 +01:00
pub struct Cartridge {
pub header : CartridgeHeader ,
bytes : Box < [ u8 ] > ,
2020-01-16 23:39:25 +00:00
size : usize ,
2020-01-31 10:41:13 +00:00
pub ( in crate ) backup : BackupMedia ,
2019-07-02 23:40:08 +01:00
}
2019-12-04 23:15:49 +00:00
fn load_rom ( path : & Path ) -> GBAResult < Vec < u8 > > {
match path . extension ( ) {
Some ( extension ) = > match extension . to_str ( ) {
Some ( " zip " ) = > {
let zipfile = File ::open ( path ) ? ;
let mut archive = ZipArchive ::new ( zipfile ) ? ;
for i in 0 .. archive . len ( ) {
let mut file = archive . by_index ( i ) ? ;
if file . name ( ) . ends_with ( " .gba " ) {
let mut buf = Vec ::new ( ) ;
file . read_to_end ( & mut buf ) ? ;
return Ok ( buf ) ;
}
}
panic! ( " no .gba file contained in the zip file " ) ;
}
_ = > {
let buf = read_bin_file ( path ) ? ;
2019-12-04 21:02:51 +00:00
return Ok ( buf ) ;
}
2019-12-04 23:15:49 +00:00
} ,
_ = > {
let buf = read_bin_file ( path ) ? ;
return Ok ( buf ) ;
2019-12-04 21:02:51 +00:00
}
}
}
2019-07-02 23:40:08 +01:00
impl Cartridge {
2019-12-04 23:15:49 +00:00
pub fn from_path ( rom_path : & Path ) -> GBAResult < Cartridge > {
2020-01-16 23:39:25 +00:00
let rom_bin = load_rom ( rom_path ) ? ;
2020-01-26 00:06:44 +00:00
Ok ( Cartridge ::from_bytes (
& rom_bin ,
Some ( rom_path . to_path_buf ( ) ) ,
) )
2019-07-02 23:40:08 +01:00
}
2020-01-16 23:28:11 +00:00
2020-01-26 00:06:44 +00:00
pub fn from_bytes ( bytes : & [ u8 ] , rom_path : Option < PathBuf > ) -> Cartridge {
2020-01-16 23:39:25 +00:00
let size = bytes . len ( ) ;
2020-01-16 23:28:11 +00:00
let header = CartridgeHeader ::parse ( & bytes ) ;
2020-01-26 00:06:44 +00:00
let backup = if let Some ( path ) = rom_path {
create_backup ( bytes , & path )
} else {
BackupMedia ::Undetected
} ;
2020-01-28 08:25:52 +00:00
2020-01-31 00:12:38 +00:00
info! ( " Header: {:?} " , header ) ;
info! ( " Backup: {} " , backup . type_string ( ) ) ;
2020-01-26 00:06:44 +00:00
2020-01-16 23:28:11 +00:00
Cartridge {
header : header ,
bytes : bytes . into ( ) ,
2020-01-16 23:39:25 +00:00
size : size ,
2020-01-26 00:06:44 +00:00
backup : backup ,
2020-01-16 23:28:11 +00:00
}
}
2020-01-26 00:06:44 +00:00
}
2020-01-17 14:08:23 +00:00
2020-01-26 00:06:44 +00:00
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 {
2020-01-26 00:16:54 +00:00
BackupType ::Flash | BackupType ::Flash512 = > {
BackupMedia ::Flash ( Flash ::new ( backup_path , FlashSize ::Flash64k ) )
}
BackupType ::Flash1M = > {
BackupMedia ::Flash ( Flash ::new ( backup_path , FlashSize ::Flash128k ) )
2020-01-17 14:08:23 +00:00
}
2020-01-26 00:06:44 +00:00
BackupType ::Sram = > BackupMedia ::Sram ( BackupMemory ::new ( 0x8000 , backup_path ) ) ,
2020-01-28 08:25:52 +00:00
BackupType ::Eeprom = > {
BackupMedia ::Eeprom ( SpiController ::new ( BackupMemory ::new ( 0x200 , backup_path ) ) )
}
2020-01-26 00:06:44 +00:00
}
} else {
BackupMedia ::Undetected
}
}
fn detect_backup_type ( bytes : & [ u8 ] ) -> Option < BackupType > {
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 ( ) ) ,
_ = > { }
2020-01-17 14:08:23 +00:00
}
}
2020-01-31 00:12:38 +00:00
warn! ( " could not detect backup type " ) ;
2020-01-26 00:06:44 +00:00
return None ;
2019-07-02 23:40:08 +01:00
}
2020-01-28 08:25:52 +00:00
use super ::sysbus ::consts ::* ;
2020-01-31 10:41:13 +00:00
pub const EEPROM_BASE_ADDR : u32 = 0x0DFF_FF00 ;
2020-01-26 00:06:44 +00:00
2019-07-02 23:40:08 +01:00
impl Bus for Cartridge {
2019-07-15 05:30:52 +01:00
fn read_8 ( & self , addr : Addr ) -> u8 {
2020-01-26 00:06:44 +00:00
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 ) ,
2020-01-26 00:16:54 +00:00
BackupMedia ::Flash ( flash ) = > flash . read ( addr ) ,
2020-01-26 00:06:44 +00:00
_ = > 0 ,
} ,
_ = > {
if offset > = self . size {
0xDD // TODO - open bus implementation
} else {
self . bytes [ offset as usize ]
}
}
2020-01-16 23:39:25 +00:00
}
2019-07-15 05:30:52 +01:00
}
2020-01-28 08:25:52 +00:00
fn read_16 ( & self , addr : u32 ) -> u16 {
2020-01-31 10:41:13 +00:00
if addr & 0xff000000 = = GAMEPAK_WS2_HI
& & ( self . bytes . len ( ) < = 16 * 1024 * 1024 | | addr > = EEPROM_BASE_ADDR )
{
2020-01-28 08:25:52 +00:00
if let BackupMedia ::Eeprom ( spi ) = & self . backup {
2020-01-31 10:41:13 +00:00
return spi . read_half ( addr ) ;
2020-01-28 08:25:52 +00:00
}
}
self . default_read_16 ( addr )
}
2020-01-26 00:06:44 +00:00
fn write_8 ( & mut self , addr : u32 , value : u8 ) {
match addr & 0xff000000 {
SRAM_LO | SRAM_HI = > match & mut self . backup {
2020-01-26 00:16:54 +00:00
BackupMedia ::Flash ( flash ) = > flash . write ( addr , value ) ,
2020-01-26 00:06:44 +00:00
BackupMedia ::Sram ( memory ) = > memory . write ( ( addr & 0x7FFF ) as usize , value ) ,
_ = > { }
} ,
2020-01-31 10:41:13 +00:00
_ = > { } // TODO allow the debugger to write
2020-01-26 00:06:44 +00:00
} ;
2019-07-15 05:30:52 +01:00
}
2020-01-28 08:25:52 +00:00
fn write_16 ( & mut self , addr : u32 , value : u16 ) {
2020-01-31 10:41:13 +00:00
if addr & 0xff000000 = = GAMEPAK_WS2_HI
& & ( self . bytes . len ( ) < = 16 * 1024 * 1024 | | addr > = EEPROM_BASE_ADDR )
{
2020-01-28 08:25:52 +00:00
if let BackupMedia ::Eeprom ( spi ) = & mut self . backup {
2020-01-31 10:41:13 +00:00
return spi . write_half ( addr , value ) ;
2020-01-28 08:25:52 +00:00
}
}
self . default_write_16 ( addr , value ) ;
}
2019-07-02 23:40:08 +01:00
}