2019-12-04 23:15:49 +00:00
use std ::ffi ::OsStr ;
2019-12-04 21:02:51 +00:00
use std ::fs ::File ;
use std ::io ::prelude ::* ;
use std ::path ::Path ;
2019-07-02 23:40:08 +01:00
use std ::str ::from_utf8 ;
2019-07-15 05:30:52 +01:00
use byteorder ::{ LittleEndian , ReadBytesExt , WriteBytesExt } ;
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
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")
///
#[ derive(Debug) ]
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,
}
}
}
#[ derive(Debug) ]
pub struct Cartridge {
pub header : CartridgeHeader ,
bytes : Box < [ u8 ] > ,
}
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-07-07 21:33:47 +01:00
const MIN_SIZE : usize = 4 * 1024 * 1024 ;
2019-12-04 23:15:49 +00:00
pub fn from_path ( rom_path : & Path ) -> GBAResult < Cartridge > {
2019-12-04 21:02:51 +00:00
let mut rom_bin = load_rom ( rom_path ) ? ;
2019-12-04 23:15:49 +00:00
println! ( " loaded {} bytes " , rom_bin . len ( ) ) ;
2019-12-04 21:02:51 +00:00
2019-07-07 21:33:47 +01:00
if rom_bin . len ( ) < Cartridge ::MIN_SIZE {
rom_bin . resize_with ( Cartridge ::MIN_SIZE , Default ::default ) ;
}
let header = CartridgeHeader ::parse ( & rom_bin ) ;
2019-12-04 21:02:51 +00:00
Ok ( Cartridge {
2019-07-02 23:40:08 +01:00
header : header ,
2019-07-07 21:33:47 +01:00
bytes : rom_bin . into_boxed_slice ( ) ,
2019-12-04 21:02:51 +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 {
2019-12-31 19:18:28 +00:00
self . bytes [ addr as usize ]
2019-07-15 05:30:52 +01:00
}
fn write_8 ( & mut self , addr : Addr , value : u8 ) {
2019-12-31 19:18:28 +00:00
self . bytes [ addr as usize ] = value ;
2019-07-15 05:30:52 +01:00
}
2019-07-02 23:40:08 +01:00
}