2011-02-20 02:49:37 +00:00
using System ;
2011-02-28 10:16:07 +00:00
using System.Diagnostics ;
2011-02-20 02:49:37 +00:00
using System.Globalization ;
using System.IO ;
using System.Collections.Generic ;
using BizHawk.Emulation.CPUs.M6502 ;
namespace BizHawk.Emulation.Consoles.Nintendo
{
2011-02-27 09:45:50 +00:00
public partial class NES : IEmulator
2011-02-20 02:49:37 +00:00
{
2011-02-28 06:16:20 +00:00
//the main rom class that contains all information necessary for the board to operate
public class RomInfo
{
public enum EHeaderType
{
None , INes
}
public EHeaderType HeaderType ;
2011-02-28 07:53:19 +00:00
public int PRG_Size = - 1 , CHR_Size = - 1 ;
2011-03-01 07:25:14 +00:00
public int CRAM_Size = - 1 , PRAM_Size = - 1 ;
2011-02-28 06:16:20 +00:00
public string BoardName ;
public EMirrorType MirrorType ;
public bool Battery ;
public int MapperNumber ; //it annoys me that this junky mapper number is even in here. it might be nice to wrap this class in something else to contain the MapperNumber
public string MD5 ;
public byte [ ] ROM , VROM ;
}
2011-02-28 09:39:24 +00:00
public enum EMirrorType
{
Vertical , Horizontal ,
OneScreenA , OneScreenB ,
//unknown or controlled by the board
External
}
2011-02-27 09:45:50 +00:00
public interface INESBoard
{
byte ReadPRG ( int addr ) ;
byte ReadPPU ( int addr ) ;
2011-03-01 07:25:14 +00:00
byte ReadPRAM ( int addr ) ;
2011-02-27 09:45:50 +00:00
void WritePRG ( int addr , byte value ) ;
void WritePPU ( int addr , byte value ) ;
2011-03-01 07:25:14 +00:00
void WritePRAM ( int addr , byte value ) ;
2011-02-27 09:45:50 +00:00
void Initialize ( RomInfo romInfo , NES nes ) ;
2011-03-01 07:25:14 +00:00
byte [ ] SaveRam { get ; }
2011-03-01 09:32:12 +00:00
void SaveStateBinary ( BinaryWriter bw ) ;
void LoadStateBinary ( BinaryReader br ) ;
2011-02-27 09:45:50 +00:00
} ;
public abstract class NESBoardBase : INESBoard
{
2011-02-28 06:16:20 +00:00
public virtual void Initialize ( RomInfo romInfo , NES nes )
2011-02-27 09:45:50 +00:00
{
this . RomInfo = romInfo ;
this . NES = nes ;
2011-02-28 09:39:24 +00:00
SetMirrorType ( romInfo . MirrorType ) ;
2011-02-27 09:45:50 +00:00
}
public RomInfo RomInfo { get ; set ; }
public NES NES { get ; set ; }
2011-03-01 09:32:12 +00:00
public virtual void SaveStateBinary ( BinaryWriter bw )
{
for ( int i = 0 ; i < 4 ; i + + ) bw . Write ( mirroring [ i ] ) ;
}
public virtual void LoadStateBinary ( BinaryReader br )
{
for ( int i = 0 ; i < 4 ; i + + ) mirroring [ i ] = br . ReadInt32 ( ) ;
}
2011-02-27 09:45:50 +00:00
int [ ] mirroring = new int [ 4 ] ;
protected void SetMirroring ( int a , int b , int c , int d )
{
mirroring [ 0 ] = a ;
mirroring [ 1 ] = b ;
mirroring [ 2 ] = c ;
mirroring [ 3 ] = d ;
}
2011-02-28 09:39:24 +00:00
protected void SetMirrorType ( EMirrorType mirrorType )
{
switch ( mirrorType )
{
case EMirrorType . Horizontal : SetMirroring ( 0 , 0 , 1 , 1 ) ; break ;
case EMirrorType . Vertical : SetMirroring ( 0 , 1 , 0 , 1 ) ; break ;
case EMirrorType . OneScreenA : SetMirroring ( 0 , 0 , 0 , 0 ) ; break ;
case EMirrorType . OneScreenB : SetMirroring ( 1 , 1 , 1 , 1 ) ; break ;
default : SetMirroring ( - 1 , - 1 , - 1 , - 1 ) ; break ; //crash!
}
}
2011-02-28 06:16:20 +00:00
int ApplyMirroring ( int addr )
{
int block = ( addr > > 10 ) & 3 ;
block = mirroring [ block ] ;
int ofs = addr & 0x3FF ;
return ( block < < 10 ) | ofs | 0x2000 ;
}
2011-02-28 10:16:07 +00:00
protected byte HandleNormalPRGConflict ( int addr , byte value )
{
byte old_value = value ;
value & = ReadPRG ( addr ) ;
Debug . Assert ( old_value = = value , "Found a test case of bus conflict. please report." ) ;
return value ;
}
2011-02-27 09:45:50 +00:00
public virtual byte ReadPRG ( int addr ) { return RomInfo . ROM [ addr ] ; }
public virtual void WritePRG ( int addr , byte value ) { }
2011-03-01 07:25:14 +00:00
public virtual void WritePRAM ( int addr , byte value ) { }
public virtual byte ReadPRAM ( int addr ) { return 0xFF ; }
2011-02-28 06:16:20 +00:00
2011-02-27 09:45:50 +00:00
public virtual void WritePPU ( int addr , byte value )
{
if ( addr < 0x2000 )
{
}
else
{
2011-02-28 06:16:20 +00:00
NES . ppu . ppu_defaultWrite ( ApplyMirroring ( addr ) , value ) ;
2011-02-27 09:45:50 +00:00
}
}
public virtual byte ReadPPU ( int addr )
{
if ( addr < 0x2000 )
{
return RomInfo . VROM [ addr ] ;
}
else
{
2011-02-28 06:16:20 +00:00
return NES . ppu . ppu_defaultRead ( ApplyMirroring ( addr ) ) ;
2011-02-27 09:45:50 +00:00
}
}
2011-03-01 07:25:14 +00:00
public virtual byte [ ] SaveRam { get { return null ; } }
2011-02-27 09:45:50 +00:00
}
2011-03-01 09:32:12 +00:00
//hardware/state
2011-02-27 09:45:50 +00:00
protected MOS6502 cpu ;
INESBoard board ;
2011-02-28 09:13:27 +00:00
public PPU ppu ;
2011-02-20 02:49:37 +00:00
byte [ ] ram ;
2011-03-01 09:32:12 +00:00
int cpu_accumulate ;
2011-02-27 11:40:08 +00:00
2011-02-27 09:45:50 +00:00
//user configuration
int [ , ] palette ; //TBD!!
2011-03-01 09:32:12 +00:00
IPortDevice [ ] ports ;
RomInfo romInfo ;
2011-02-27 09:45:50 +00:00
2011-02-20 02:49:37 +00:00
public byte ReadPPUReg ( int addr )
{
2011-02-27 09:45:50 +00:00
return ppu . ReadReg ( addr ) ;
2011-02-20 02:49:37 +00:00
}
public byte ReadReg ( int addr )
{
2011-02-27 11:40:08 +00:00
switch ( addr )
{
case 0x4016 :
case 0x4017 :
return read_joyport ( addr ) ;
default :
//Console.WriteLine("read register: {0:x4}", addr);
break ;
}
2011-02-20 02:49:37 +00:00
return 0xFF ;
}
2011-02-27 09:45:50 +00:00
void WritePPUReg ( int addr , byte val )
{
ppu . WriteReg ( addr , val ) ;
}
void WriteReg ( int addr , byte val )
{
switch ( addr )
{
case 0x4014 : Exec_OAMDma ( val ) ; break ;
2011-02-27 11:40:08 +00:00
case 0x4016 :
ports [ 0 ] . Write ( val & 1 ) ;
ports [ 1 ] . Write ( val & 1 ) ;
break ;
default :
//Console.WriteLine("wrote register: {0:x4} = {1:x2}", addr, val);
break ;
}
}
byte read_joyport ( int addr )
{
//read joystick port
//many todos here
if ( addr = = 0x4016 )
{
byte ret = ports [ 0 ] . Read ( ) ;
return ret ;
2011-02-27 09:45:50 +00:00
}
2011-02-27 11:40:08 +00:00
else return 0 ;
2011-02-27 09:45:50 +00:00
}
void Exec_OAMDma ( byte val )
{
ushort addr = ( ushort ) ( val < < 8 ) ;
for ( int i = 0 ; i < 256 ; i + + )
{
byte db = ReadMemory ( ( ushort ) addr ) ;
WriteMemory ( 0x2004 , db ) ;
addr + + ;
}
cpu . PendingCycles - = 512 ;
}
2011-02-20 02:49:37 +00:00
public byte ReadMemory ( ushort addr )
{
if ( addr < 0x0800 ) return ram [ addr ] ;
2011-02-27 09:45:50 +00:00
else if ( addr < 0x1000 ) return ram [ addr - 0x0800 ] ;
else if ( addr < 0x1800 ) return ram [ addr - 0x1000 ] ;
else if ( addr < 0x2000 ) return ram [ addr - 0x1800 ] ;
else if ( addr < 0x4000 ) return ReadPPUReg ( addr & 7 ) ;
else if ( addr < 0x4020 ) return ReadReg ( addr ) ; //we're not rebasing the register just to keep register names canonical
else if ( addr < 0x6000 ) return 0xFF ; //exp rom
2011-03-01 07:25:14 +00:00
else if ( addr < 0x8000 ) return board . ReadPRAM ( addr ) ;
2011-02-27 09:45:50 +00:00
else return board . ReadPRG ( addr - 0x8000 ) ;
2011-02-20 02:49:37 +00:00
}
public void WriteMemory ( ushort addr , byte value )
{
if ( addr < 0x0800 ) ram [ addr ] = value ;
2011-02-27 09:45:50 +00:00
else if ( addr < 0x1000 ) ram [ addr - 0x0800 ] = value ;
else if ( addr < 0x1800 ) ram [ addr - 0x1000 ] = value ;
else if ( addr < 0x2000 ) ram [ addr - 0x1800 ] = value ;
else if ( addr < 0x4000 ) WritePPUReg ( addr & 7 , value ) ;
else if ( addr < 0x4020 ) WriteReg ( addr , value ) ; //we're not rebasing the register just to keep register names canonical
else if ( addr < 0x6000 ) { } //exp rom
2011-03-01 07:25:14 +00:00
else if ( addr < 0x8000 ) board . WritePRAM ( addr , value ) ;
2011-02-27 09:45:50 +00:00
else board . WritePRG ( addr - 0x8000 , value ) ;
2011-02-20 02:49:37 +00:00
}
public NES ( )
{
2011-02-27 09:45:50 +00:00
palette = Palettes . FCEUX_Standard ;
2011-02-20 02:49:37 +00:00
}
class MyVideoProvider : IVideoProvider
{
NES emu ;
public MyVideoProvider ( NES emu )
{
this . emu = emu ;
}
public int [ ] GetVideoBuffer ( )
{
int [ ] pixels = new int [ 256 * 256 ] ;
int i = 0 ;
for ( int y = 0 ; y < 256 ; y + + )
for ( int x = 0 ; x < 256 ; x + + )
{
2011-02-27 09:45:50 +00:00
int pixel = emu . ppu . xbuf [ i ] ;
int deemph = pixel > > 8 ;
int palentry = pixel & 0xFF ;
2011-02-28 09:13:27 +00:00
int r = emu . palette [ palentry , 0 ] ;
int g = emu . palette [ palentry , 1 ] ;
int b = emu . palette [ palentry , 2 ] ;
2011-02-27 10:55:38 +00:00
Palettes . ApplyDeemphasis ( ref r , ref g , ref b , deemph ) ;
2011-02-27 09:45:50 +00:00
pixels [ i ] = ( r < < 16 ) | ( g < < 8 ) | b ;
i + + ;
2011-02-20 02:49:37 +00:00
}
return pixels ;
}
public int BufferWidth { get { return 256 ; } }
public int BufferHeight { get { return 256 ; } }
public int BackgroundColor { get { return 0 ; } }
}
public IVideoProvider VideoProvider { get { return new MyVideoProvider ( this ) ; } }
public ISoundProvider SoundProvider { get { return new NullEmulator ( ) ; } }
public static readonly ControllerDefinition NESController =
new ControllerDefinition
{
Name = "NES Controls" ,
2011-02-27 11:40:08 +00:00
BoolButtons = { "A" , "B" , "Select" , "Start" , "Left" , "Up" , "Down" , "Right" , "Reset" }
2011-02-20 02:49:37 +00:00
} ;
public ControllerDefinition ControllerDefinition { get { return NESController ; } }
IController controller ;
public IController Controller
{
get { return controller ; }
set { controller = value ; }
}
public void FrameAdvance ( bool render )
{
//TODO!
//cpu.Execute(10000);
2011-03-01 14:38:52 +00:00
Controller . UpdateControls ( Frame + + ) ;
ppu . FrameAdvance ( ) ;
2011-02-27 09:45:50 +00:00
}
protected void RunCpu ( int cycles )
{
2011-02-28 09:13:27 +00:00
if ( ppu . PAL )
cycles * = 15 ;
else
cycles * = 16 ;
cpu_accumulate + = cycles ;
int todo = cpu_accumulate / 48 ;
cpu_accumulate - = todo * 48 ;
if ( todo > 0 )
cpu . Execute ( todo ) ;
2011-02-20 02:49:37 +00:00
}
2011-02-27 11:40:08 +00:00
interface IPortDevice
{
void Write ( int value ) ;
byte Read ( ) ;
void Update ( ) ;
}
//static INPUTC GPC = { ReadGP, 0, StrobeGP, UpdateGP, 0, 0, LogGP, LoadGP };
class JoypadPortDevice : NullPortDevice
{
int state ;
NES nes ;
public JoypadPortDevice ( NES nes )
{
this . nes = nes ;
}
void Strobe ( )
{
value = 0 ;
foreach ( string str in new string [ ] { "Right" , "Left" , "Down" , "Up" , "Start" , "Select" , "B" , "A" } )
{
value < < = 1 ;
value | = nes . Controller . IsPressed ( str ) ? 1 : 0 ;
}
}
public override void Write ( int value )
{
if ( state = = 1 & & value = = 0 )
Strobe ( ) ;
state = value ;
}
public override byte Read ( )
{
int ret = value & 1 ;
value > > = 1 ;
return ( byte ) ret ;
}
public override void Update ( )
{
}
int value ;
}
class NullPortDevice : IPortDevice
{
public virtual void Write ( int value )
{
}
public virtual byte Read ( )
{
return 0xFF ;
}
public virtual void Update ( )
{
}
}
2011-02-20 02:49:37 +00:00
public void HardReset ( )
{
2011-02-27 09:45:50 +00:00
cpu = new MOS6502 ( ) ;
cpu . ReadMemory = ReadMemory ;
cpu . WriteMemory = WriteMemory ;
ppu = new PPU ( this ) ;
2011-02-20 02:49:37 +00:00
ram = new byte [ 0x800 ] ;
2011-02-27 11:40:08 +00:00
ports = new IPortDevice [ 2 ] ;
ports [ 0 ] = new JoypadPortDevice ( this ) ;
ports [ 1 ] = new NullPortDevice ( ) ;
2011-02-27 09:45:50 +00:00
//fceux uses this technique, which presumably tricks some games into thinking the memory is randomized
2011-02-20 02:49:37 +00:00
for ( int i = 0 ; i < 0x800 ; i + + )
2011-02-27 09:45:50 +00:00
{
if ( ( i & 4 ) ! = 0 ) ram [ i ] = 0xFF ; else ram [ i ] = 0x00 ;
}
//in this emulator, reset takes place instantaneously
cpu . PC = ( ushort ) ( ReadMemory ( 0xFFFC ) | ( ReadMemory ( 0xFFFD ) < < 8 ) ) ;
//cpu.debug = true;
2011-02-20 02:49:37 +00:00
}
2011-03-01 14:38:52 +00:00
public int Frame { get ; set ; }
private byte Port01 = 0xFF ;
2011-02-20 02:49:37 +00:00
public bool DeterministicEmulation { get { return true ; } set { } }
2011-03-01 07:25:14 +00:00
public byte [ ] SaveRam
{
get
{
if ( board = = null ) return null ;
return board . SaveRam ;
}
}
2011-02-20 02:49:37 +00:00
public bool SaveRamModified
{
2011-03-01 07:25:14 +00:00
get { if ( board = = null ) return false ; if ( board . SaveRam = = null ) return false ; return true ; }
2011-02-20 02:49:37 +00:00
set { }
}
public string SystemId { get { return "NES" ; } }
2011-02-27 09:45:50 +00:00
public IList < MemoryDomain > MemoryDomains { get { return new List < MemoryDomain > ( ) ; } }
public MemoryDomain MainMemory
{
get
{
return new MemoryDomain ( "x" , 8 , Endian . Little ,
addr = > 0 ,
( addr , value ) = > { } ) ;
}
}
2011-02-21 09:48:53 +00:00
public object Query ( EmulatorQuery query )
{
return null ;
}
2011-02-26 21:36:46 +00:00
2011-02-27 22:44:06 +00:00
public void SetControllersAsMnemonic ( string mnemonic )
2011-02-26 21:36:46 +00:00
{
2011-02-27 22:44:06 +00:00
if ( mnemonic . Length = = 0 ) return ;
if ( mnemonic [ 1 ] ! = '.' )
Controller . ForceButton ( "Up" ) ;
if ( mnemonic [ 2 ] ! = '.' )
Controller . ForceButton ( "Down" ) ;
if ( mnemonic [ 3 ] ! = '.' )
Controller . ForceButton ( "Left" ) ;
if ( mnemonic [ 4 ] ! = '.' )
Controller . ForceButton ( "Right" ) ;
if ( mnemonic [ 5 ] ! = '.' )
Controller . ForceButton ( "B" ) ;
if ( mnemonic [ 6 ] ! = '.' )
Controller . ForceButton ( "A" ) ;
if ( mnemonic [ 7 ] ! = '.' )
Controller . ForceButton ( "Select" ) ;
if ( mnemonic [ 8 ] ! = '.' )
Controller . ForceButton ( "Start" ) ;
if ( mnemonic [ 10 ] ! = '.' & & mnemonic [ 10 ] ! = '0' )
Controller . ForceButton ( "Reset" ) ;
2011-02-26 21:36:46 +00:00
}
2011-02-27 09:45:50 +00:00
2011-02-27 22:44:06 +00:00
public string GetControllersAsMnemonic ( )
{
string input = "|" ;
if ( Controller . IsPressed ( "Up" ) ) input + = "U" ;
else input + = "." ;
if ( Controller . IsPressed ( "Down" ) ) input + = "D" ;
else input + = "." ;
if ( Controller . IsPressed ( "Left" ) ) input + = "L" ;
else input + = "." ;
if ( Controller . IsPressed ( "Right" ) ) input + = "R" ;
else input + = "." ;
if ( Controller . IsPressed ( "A" ) ) input + = "A" ;
else input + = "." ;
if ( Controller . IsPressed ( "B" ) ) input + = "B" ;
else input + = "." ;
if ( Controller . IsPressed ( "Select" ) ) input + = "s" ;
else input + = "." ;
if ( Controller . IsPressed ( "Start" ) ) input + = "S" ;
else input + = "." ;
input + = "|" ;
if ( Controller . IsPressed ( "Reset" ) ) input + = "R" ;
else input + = "." ;
input + = "|" ;
return input ;
}
2011-02-27 22:33:25 +00:00
2011-02-28 06:16:20 +00:00
public class RomHeaderInfo
2011-02-27 09:45:50 +00:00
{
2011-02-28 06:16:20 +00:00
public int MapperNo , Mirroring , Num_PRG_Banks , Num_CHR_Banks , Num_PRAM_Banks ;
public bool Battery ;
2011-02-27 09:45:50 +00:00
public byte [ ] ROM , VROM ;
}
unsafe struct iNES_HEADER {
public fixed byte ID [ 4 ] ; /*NES^Z*/
public byte ROM_size ;
public byte VROM_size ;
public byte ROM_type ;
public byte ROM_type2 ;
public fixed byte reserve [ 8 ] ;
public bool CheckID ( )
{
fixed ( iNES_HEADER * self = & this )
return 0 = = Util . memcmp ( self , "NES\x1A" , 4 ) ;
}
//some cleanup code recommended by fceux
public void Cleanup ( )
{
fixed ( iNES_HEADER * self = & this )
{
if ( 0 = = Util . memcmp ( ( char * ) ( self ) + 0x7 , "DiskDude" , 8 ) )
{
Util . memset ( ( char * ) ( self ) + 0x7 , 0 , 0x9 ) ;
}
if ( 0 = = Util . memcmp ( ( char * ) ( self ) + 0x7 , "demiforce" , 9 ) )
{
Util . memset ( ( char * ) ( self ) + 0x7 , 0 , 0x9 ) ;
}
if ( 0 = = Util . memcmp ( ( char * ) ( self ) + 0xA , "Ni03" , 4 ) )
{
if ( 0 = = Util . memcmp ( ( char * ) ( self ) + 0x7 , "Dis" , 3 ) )
Util . memset ( ( char * ) ( self ) + 0x7 , 0 , 0x9 ) ;
else
Util . memset ( ( char * ) ( self ) + 0xA , 0 , 0x6 ) ;
}
}
}
public RomInfo Analyze ( )
{
var ret = new RomInfo ( ) ;
2011-02-28 06:16:20 +00:00
ret . MapperNumber = ( ROM_type > > 4 ) ;
ret . MapperNumber | = ( ROM_type2 & 0xF0 ) ;
int mirroring = ( ROM_type & 1 ) ;
if ( ( ROM_type & 8 ) ! = 0 ) mirroring = 2 ;
2011-02-28 09:39:24 +00:00
if ( mirroring = = 0 ) ret . MirrorType = EMirrorType . Horizontal ;
else if ( mirroring = = 1 ) ret . MirrorType = EMirrorType . Vertical ;
else ret . MirrorType = EMirrorType . External ;
2011-02-28 06:16:20 +00:00
ret . PRG_Size = ROM_size ;
if ( ret . PRG_Size = = 0 )
ret . PRG_Size = 256 ;
ret . CHR_Size = VROM_size ;
ret . Battery = ( ROM_type & 2 ) ! = 0 ;
2011-03-01 07:25:14 +00:00
fixed ( iNES_HEADER * self = & this ) ret . PRAM_Size = self - > reserve [ 0 ] * 8 ;
2011-02-28 06:16:20 +00:00
//0 is supposed to mean 1 (for compatibility, as this is an extension to original iNES format)
2011-03-01 07:25:14 +00:00
if ( ret . PRAM_Size = = 0 ) ret . PRAM_Size = 8 ;
2011-02-28 06:16:20 +00:00
2011-03-01 07:25:14 +00:00
Console . WriteLine ( "iNES header: map:{0}, mirror:{1}, PRG:{2}, CHR:{3}, CRAM:{4}, PRAM:{5}, bat:{6}" , ret . MapperNumber , ret . MirrorType , ret . PRG_Size , ret . CHR_Size , ret . CRAM_Size , ret . PRAM_Size , ret . Battery ? 1 : 0 ) ;
2011-02-28 06:16:20 +00:00
2011-02-27 09:45:50 +00:00
//fceux calls uppow2(PRG_Banks) here, and also ups the chr size as well
//then it does something complicated that i don't understand with making sure it doesnt read too much data
//fceux only allows this condition for mappers in the list "not_power2" which is only 228
return ret ;
}
}
2011-02-28 06:16:20 +00:00
const bool ENABLE_DB = true ;
2011-02-27 09:45:50 +00:00
public unsafe void LoadGame ( IGame game )
{
2011-02-28 06:16:20 +00:00
byte [ ] file = game . GetFileData ( ) ;
2011-02-27 09:45:50 +00:00
if ( file . Length < 16 ) throw new InvalidOperationException ( "Alleged NES rom too small to be anything useful" ) ;
fixed ( byte * bfile = & file [ 0 ] )
{
var header = ( iNES_HEADER * ) bfile ;
if ( ! header - > CheckID ( ) ) throw new InvalidOperationException ( "iNES header not found" ) ;
header - > Cleanup ( ) ;
2011-02-28 06:16:20 +00:00
//now that we know we have an iNES header, we can try to ignore it.
string hash ;
using ( var md5 = System . Security . Cryptography . MD5 . Create ( ) )
{
md5 . TransformFinalBlock ( file , 16 , file . Length - 16 ) ;
hash = Util . BytesToHexString ( md5 . Hash ) ;
}
Console . WriteLine ( "headerless rom hash: {0}" , hash ) ;
GameInfo gi = null ;
if ( ENABLE_DB ) gi = Database . CheckDatabase ( hash ) ;
else Console . WriteLine ( "database check disabled" ) ;
if ( gi = = null )
{
romInfo = header - > Analyze ( ) ;
string board = BoardDetector . Detect ( romInfo ) ;
if ( board = = null )
throw new InvalidOperationException ( "Couldn't detect board type" ) ;
romInfo . BoardName = board ;
Console . WriteLine ( "board detected as " + board ) ;
}
else
{
Console . WriteLine ( "found game in database: {0}" , gi . Name ) ;
romInfo = new RomInfo ( ) ;
romInfo . MD5 = hash ;
var dict = gi . ParseOptionsDictionary ( ) ;
if ( dict . ContainsKey ( "board" ) )
romInfo . BoardName = dict [ "board" ] ;
2011-03-01 07:25:14 +00:00
if ( dict . ContainsKey ( "mirror" ) )
switch ( dict [ "mirror" ] )
{
case "V" : romInfo . MirrorType = EMirrorType . Vertical ; break ;
case "H" : romInfo . MirrorType = EMirrorType . Horizontal ; break ;
case "X" : romInfo . MirrorType = EMirrorType . External ; break ;
default : throw new InvalidOperationException ( ) ;
}
2011-02-28 06:16:20 +00:00
if ( dict . ContainsKey ( "PRG" ) )
romInfo . PRG_Size = int . Parse ( dict [ "PRG" ] ) ;
if ( dict . ContainsKey ( "CHR" ) )
romInfo . CHR_Size = int . Parse ( dict [ "CHR" ] ) ;
2011-02-28 07:53:19 +00:00
if ( dict . ContainsKey ( "CRAM" ) )
romInfo . CRAM_Size = int . Parse ( dict [ "CRAM" ] ) ;
2011-03-01 07:25:14 +00:00
if ( dict . ContainsKey ( "PRAM" ) )
romInfo . PRAM_Size = int . Parse ( dict [ "PRAM" ] ) ;
if ( dict . ContainsKey ( "bat" ) )
romInfo . Battery = true ;
2011-02-28 07:53:19 +00:00
if ( dict . ContainsKey ( "bug" ) )
Console . WriteLine ( "game is known to be BUGGED!!!" ) ;
2011-02-28 06:16:20 +00:00
}
//construct board (todo)
switch ( romInfo . BoardName )
{
case "NROM" : board = new Boards . NROM ( ) ; break ;
2011-02-28 07:53:19 +00:00
case "UNROM" : board = new Boards . UxROM ( "UNROM" ) ; break ;
2011-02-28 08:19:06 +00:00
case "UOROM" : board = new Boards . UxROM ( "UOROM" ) ; break ;
2011-02-28 09:13:27 +00:00
case "CNROM" : board = new Boards . CxROM ( "CNROM" ) ; break ;
2011-02-28 09:39:24 +00:00
case "ANROM" : board = new Boards . AxROM ( "ANROM" ) ; break ;
case "AOROM" : board = new Boards . AxROM ( "AOROM" ) ; break ;
2011-02-28 10:16:07 +00:00
case "Discrete_74x377" : board = new Boards . Discrete_74x377 ( ) ; break ;
case "CPROM" : board = new Boards . CPROM ( ) ; break ;
2011-03-01 07:25:14 +00:00
case "GxROM" : board = new Boards . GxROM ( ) ; break ;
case "SGROM" : board = new Boards . SxROM ( "SGROM" ) ; break ;
case "SNROM" : board = new Boards . SxROM ( "SNROM" ) ; break ;
case "SL2ROM" : board = new Boards . SxROM ( "SL2ROM" ) ; break ;
2011-02-28 06:16:20 +00:00
}
2011-02-27 09:45:50 +00:00
if ( board = = null ) throw new InvalidOperationException ( "Couldn't classify NES rom" ) ;
//we're going to go ahead and copy these out, just in case we need to pad them alter
2011-02-28 06:16:20 +00:00
romInfo . ROM = new byte [ romInfo . PRG_Size * 16 * 1024 ] ;
2011-02-27 09:45:50 +00:00
Array . Copy ( file , 16 , romInfo . ROM , 0 , romInfo . ROM . Length ) ;
2011-03-01 07:25:14 +00:00
if ( romInfo . CHR_Size > 0 )
{
romInfo . VROM = new byte [ romInfo . CHR_Size * 8 * 1024 ] ;
Array . Copy ( file , 16 + romInfo . ROM . Length , romInfo . VROM , 0 , romInfo . VROM . Length ) ;
}
2011-02-28 10:16:07 +00:00
board . Initialize ( romInfo , this ) ;
2011-02-27 09:45:50 +00:00
}
HardReset ( ) ;
}
2011-03-01 09:32:12 +00:00
public void SaveStateText ( TextWriter writer )
{
writer . WriteLine ( "[NES]" ) ;
byte [ ] lol = SaveStateBinary ( ) ;
writer . WriteLine ( "blob {0}" , Util . BytesToHexString ( lol ) ) ;
writer . WriteLine ( "[/NES]" ) ;
}
public void LoadStateText ( TextReader reader )
{
byte [ ] blob = null ;
while ( true )
{
string [ ] args = reader . ReadLine ( ) . Split ( ' ' ) ;
if ( args [ 0 ] = = "blob" )
blob = Util . HexStringToBytes ( args [ 1 ] ) ;
else if ( args [ 0 ] = = "[/NES]" ) break ;
}
if ( blob = = null ) throw new ArgumentException ( ) ;
LoadStateBinary ( new BinaryReader ( new MemoryStream ( blob ) ) ) ;
}
public byte [ ] SaveStateBinary ( )
{
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
return ms . ToArray ( ) ;
}
public void SaveStateBinary ( BinaryWriter bw )
{
using ( var sw = new StringWriter ( ) )
{
cpu . SaveStateText ( sw ) ;
sw . Flush ( ) ;
Util . WriteByteBuffer ( bw , System . Text . Encoding . ASCII . GetBytes ( sw . ToString ( ) ) ) ;
}
Util . WriteByteBuffer ( bw , ram ) ;
bw . Write ( cpu_accumulate ) ;
board . SaveStateBinary ( bw ) ;
ppu . SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
}
public void LoadStateBinary ( BinaryReader br )
{
using ( var sr = new StringReader ( System . Text . Encoding . ASCII . GetString ( Util . ReadByteBuffer ( br , false ) ) ) )
cpu . LoadStateText ( sr ) ;
ram = Util . ReadByteBuffer ( br , false ) ;
cpu_accumulate = br . ReadInt32 ( ) ;
board . LoadStateBinary ( br ) ;
ppu . LoadStateBinary ( br ) ;
}
2011-02-20 02:49:37 +00:00
}
2011-02-28 06:16:20 +00:00
}
//todo
//http://blog.ntrq.net/?p=428
2011-03-01 07:25:14 +00:00
//cpu bus junk bits
//UBER DOC
//http://nocash.emubase.de/everynes.htm
//A VERY NICE board assignments list
//http://personales.epsg.upv.es/~jogilmo1/nes/TEXTOS/ARXIUS/BOARDTABLE.TXT
//why not make boards communicate over the actual board pinouts
//http://wiki.nesdev.com/w/index.php/Cartridge_connector
//a mappers list
//http://tuxnes.sourceforge.net/nesmapper.txt