2011-02-20 02:49:37 +00:00
using System ;
2011-03-02 06:18:26 +00:00
using System.Linq ;
2011-02-20 02:49:37 +00:00
using System.IO ;
using System.Collections.Generic ;
2015-08-07 21:15:50 +00:00
using System.Reflection ;
2011-02-20 02:49:37 +00:00
2013-11-04 00:36:15 +00:00
using BizHawk.Common ;
2014-07-03 19:20:34 +00:00
using BizHawk.Common.BufferExtensions ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2011-03-13 08:13:32 +00:00
2014-07-03 19:20:34 +00:00
//TODO - redo all timekeeping in terms of master clock
2013-11-14 13:15:41 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.NES
2011-02-20 02:49:37 +00:00
{
2014-04-25 01:19:57 +00:00
[ CoreAttributes (
"NesHawk" ,
2016-09-02 16:25:20 +00:00
"zeromus, natt, alyosha, adelikat" ,
2014-04-25 01:19:57 +00:00
isPorted : false ,
isReleased : true
) ]
2015-08-06 00:12:09 +00:00
public partial class NES : IEmulator , ISaveRam , IDebuggable , IStatable , IInputPollable , IRegionable ,
2014-10-19 01:22:47 +00:00
ISettable < NES . NESSettings , NES . NESSyncSettings >
2011-02-20 02:49:37 +00:00
{
2011-09-03 17:13:42 +00:00
static readonly bool USE_DATABASE = true ;
public RomStatus RomStatus ;
2011-06-08 01:03:32 +00:00
2014-08-23 19:06:37 +00:00
[CoreConstructor("NES")]
2014-01-01 03:03:10 +00:00
public NES ( CoreComm comm , GameInfo game , byte [ ] rom , object Settings , object SyncSettings )
2011-02-28 06:16:20 +00:00
{
2015-01-15 15:52:52 +00:00
var ser = new BasicServiceProvider ( this ) ;
ServiceProvider = ser ;
2013-12-10 17:58:12 +00:00
byte [ ] fdsbios = comm . CoreFileProvider . GetFirmware ( "NES" , "Bios_FDS" , false ) ;
2013-12-09 20:36:24 +00:00
if ( fdsbios ! = null & & fdsbios . Length = = 40976 )
{
2013-12-10 17:58:12 +00:00
comm . ShowMessage ( "Your FDS BIOS is a bad dump. BizHawk will attempt to use it, but no guarantees! You should find a new one." ) ;
2013-12-09 20:36:24 +00:00
var tmp = new byte [ 8192 ] ;
Buffer . BlockCopy ( fdsbios , 16 + 8192 * 3 , tmp , 0 , 8192 ) ;
fdsbios = tmp ;
}
2014-01-01 03:03:10 +00:00
this . SyncSettings = ( NESSyncSettings ) SyncSettings ? ? new NESSyncSettings ( ) ;
2014-03-04 23:18:10 +00:00
this . ControllerSettings = this . SyncSettings . Controls ;
2012-12-10 00:43:43 +00:00
CoreComm = comm ;
2014-12-23 01:58:12 +00:00
2014-12-05 01:56:45 +00:00
MemoryCallbacks = new MemoryCallbackSystem ( ) ;
2011-03-08 07:25:35 +00:00
BootGodDB . Initialize ( ) ;
2011-03-21 01:49:20 +00:00
videoProvider = new MyVideoProvider ( this ) ;
2012-10-21 15:58:24 +00:00
Init ( game , rom , fdsbios ) ;
2015-01-17 21:02:59 +00:00
if ( Board is FDS )
2012-10-26 18:51:08 +00:00
{
2014-12-12 01:49:54 +00:00
DriveLightEnabled = true ;
2015-01-17 21:02:59 +00:00
( Board as FDS ) . SetDriveLightCallback ( ( val ) = > DriveLightOn = val ) ;
2015-03-28 15:42:02 +00:00
// bit of a hack: we don't have a private gamedb for FDS, but the frontend
// expects this to be set.
RomStatus = game . Status ;
2012-10-26 18:51:08 +00:00
}
2014-10-19 01:22:47 +00:00
PutSettings ( ( NESSettings ) Settings ? ? new NESSettings ( ) ) ;
2014-12-14 02:01:38 +00:00
2015-01-15 15:52:52 +00:00
2014-12-15 22:52:22 +00:00
ser . Register < IDisassemblable > ( cpu ) ;
2016-02-28 13:28:00 +00:00
Tracer = new TraceBuffer { Header = cpu . TraceHeader } ;
2014-12-23 01:58:12 +00:00
ser . Register < ITraceable > ( Tracer ) ;
2015-01-14 22:37:37 +00:00
ser . Register < IVideoProvider > ( videoProvider ) ;
2014-12-23 01:58:12 +00:00
2015-01-17 21:02:59 +00:00
if ( Board is BANDAI_FCG_1 )
2014-12-14 02:01:38 +00:00
{
2015-01-17 21:02:59 +00:00
var reader = ( Board as BANDAI_FCG_1 ) . reader ;
2014-12-14 17:19:54 +00:00
// not all BANDAI FCG 1 boards have a barcode reader
if ( reader ! = null )
2014-12-15 22:52:22 +00:00
ser . Register < DatachBarcode > ( reader ) ;
2014-12-14 02:01:38 +00:00
}
2011-02-28 06:16:20 +00:00
}
2014-12-04 03:38:30 +00:00
public IEmulatorServiceProvider ServiceProvider { get ; private set ; }
2011-09-24 17:07:48 +00:00
private NES ( )
{
BootGodDB . Initialize ( ) ;
}
2011-08-04 03:20:54 +00:00
2011-06-07 01:05:57 +00:00
public void WriteLogTimestamp ( )
{
2011-06-09 19:45:07 +00:00
if ( ppu ! = null )
Console . Write ( "[{0:d5}:{1:d3}:{2:d3}]" , Frame , ppu . ppur . status . sl , ppu . ppur . status . cycle ) ;
2011-06-07 01:05:57 +00:00
}
public void LogLine ( string format , params object [ ] args )
{
2011-07-30 20:49:36 +00:00
if ( ppu ! = null )
2011-06-09 19:45:07 +00:00
Console . WriteLine ( "[{0:d5}:{1:d3}:{2:d3}] {3}" , Frame , ppu . ppur . status . sl , ppu . ppur . status . cycle , string . Format ( format , args ) ) ;
2011-06-07 01:05:57 +00:00
}
2015-08-07 21:15:50 +00:00
public bool HasMapperProperties
{
get
{
var fields = Board . GetType ( ) . GetFields ( ) ;
foreach ( var field in fields )
{
var attrib = field . GetCustomAttributes ( typeof ( MapperPropAttribute ) , false ) . OfType < MapperPropAttribute > ( ) . SingleOrDefault ( ) ;
if ( attrib ! = null )
{
return true ;
}
}
return false ;
}
}
2011-03-17 03:51:31 +00:00
NESWatch GetWatch ( NESWatch . EDomain domain , int address )
{
if ( domain = = NESWatch . EDomain . Sysbus )
{
2011-07-30 20:49:36 +00:00
NESWatch ret = sysbus_watch [ address ] ? ? new NESWatch ( this , domain , address ) ;
2011-03-17 03:51:31 +00:00
sysbus_watch [ address ] = ret ;
return ret ;
}
return null ;
}
2011-03-16 05:06:21 +00:00
class NESWatch
{
2011-03-17 03:51:31 +00:00
public enum EDomain
{
Sysbus
}
2012-09-01 14:13:36 +00:00
2011-03-17 03:51:31 +00:00
public NESWatch ( NES nes , EDomain domain , int address )
{
Address = address ;
Domain = domain ;
if ( domain = = EDomain . Sysbus )
{
watches = nes . sysbus_watch ;
}
}
public int Address ;
public EDomain Domain ;
public enum EFlags
{
None = 0 ,
GameGenie = 1 ,
ReadPrint = 2
}
EFlags flags ;
2011-07-30 20:49:36 +00:00
2011-03-17 03:51:31 +00:00
public void Sync ( )
{
if ( flags = = EFlags . None )
watches [ Address ] = null ;
else watches [ Address ] = this ;
}
2012-09-01 14:13:36 +00:00
public void SetGameGenie ( byte? compare , byte value )
2011-03-17 03:51:31 +00:00
{
flags | = EFlags . GameGenie ;
2012-09-01 14:13:36 +00:00
Compare = compare ;
Value = value ;
2011-03-17 03:51:31 +00:00
Sync ( ) ;
}
2012-09-01 14:13:36 +00:00
public bool HasGameGenie
{
get
{
return ( flags & EFlags . GameGenie ) ! = 0 ;
}
}
2014-02-28 04:05:36 +00:00
2011-03-17 03:51:31 +00:00
public byte ApplyGameGenie ( byte curr )
{
2012-09-01 14:13:36 +00:00
if ( ! HasGameGenie )
{
return curr ;
}
else if ( curr = = Compare | | Compare = = null )
{
Console . WriteLine ( "applied game genie" ) ;
return ( byte ) Value ;
}
else
{
return curr ;
}
2011-03-17 03:51:31 +00:00
}
2011-03-16 05:06:21 +00:00
2011-03-17 03:51:31 +00:00
public void RemoveGameGenie ( )
2011-03-16 05:06:21 +00:00
{
2011-03-17 03:51:31 +00:00
flags & = ~ EFlags . GameGenie ;
Sync ( ) ;
2011-03-16 05:06:21 +00:00
}
2011-03-17 03:51:31 +00:00
2012-09-01 14:13:36 +00:00
byte? Compare ;
byte Value ;
2011-03-17 03:51:31 +00:00
NESWatch [ ] watches ;
2011-03-16 05:06:21 +00:00
}
2012-12-10 00:43:43 +00:00
public CoreComm CoreComm { get ; private set ; }
2011-06-11 22:15:08 +00:00
2015-08-06 00:12:09 +00:00
public DisplayType Region { get { return _display_type ; } }
2012-10-06 16:56:46 +00:00
2011-02-20 02:49:37 +00:00
class MyVideoProvider : IVideoProvider
{
2013-12-22 00:44:39 +00:00
//public int ntsc_top = 8;
//public int ntsc_bottom = 231;
//public int pal_top = 0;
//public int pal_bottom = 239;
2011-09-04 01:58:16 +00:00
public int left = 0 ;
2012-03-04 01:41:14 +00:00
public int right = 255 ;
2014-02-28 04:05:36 +00:00
2011-02-20 02:49:37 +00:00
NES emu ;
public MyVideoProvider ( NES emu )
{
this . emu = emu ;
}
2011-03-21 01:49:20 +00:00
int [ ] pixels = new int [ 256 * 240 ] ;
2011-02-20 02:49:37 +00:00
public int [ ] GetVideoBuffer ( )
2012-08-19 20:01:17 +00:00
{
return pixels ;
}
public void FillFrameBuffer ( )
2011-02-20 02:49:37 +00:00
{
2013-03-25 01:59:34 +00:00
int the_top ;
int the_bottom ;
2015-08-06 00:12:09 +00:00
if ( emu . Region = = DisplayType . NTSC )
2013-03-25 01:59:34 +00:00
{
2013-12-22 00:44:39 +00:00
the_top = emu . Settings . NTSC_TopLine ;
the_bottom = emu . Settings . NTSC_BottomLine ;
2013-03-25 01:59:34 +00:00
}
else
{
2013-12-22 00:44:39 +00:00
the_top = emu . Settings . PAL_TopLine ;
the_bottom = emu . Settings . PAL_BottomLine ;
2013-03-25 01:59:34 +00:00
}
2012-09-20 00:53:21 +00:00
int backdrop = 0 ;
2013-12-22 00:44:39 +00:00
backdrop = emu . Settings . BackgroundColor ;
2011-06-11 22:15:08 +00:00
bool useBackdrop = ( backdrop & 0xFF000000 ) ! = 0 ;
2011-09-24 17:07:48 +00:00
2014-05-14 15:46:16 +00:00
if ( useBackdrop )
2011-03-21 01:49:20 +00:00
{
2014-05-14 15:46:16 +00:00
int width = BufferWidth ;
for ( int x = left ; x < = right ; x + + )
2011-06-11 22:15:08 +00:00
{
2014-05-14 15:46:16 +00:00
for ( int y = the_top ; y < = the_bottom ; y + + )
2011-09-04 01:12:12 +00:00
{
2014-05-14 15:46:16 +00:00
short pixel = emu . ppu . xbuf [ ( y < < 8 ) + x ] ;
if ( ( pixel & 0x8000 ) ! = 0 & & useBackdrop )
{
pixels [ ( ( y - the_top ) * width ) + ( x - left ) ] = backdrop ;
}
else pixels [ ( ( y - the_top ) * width ) + ( x - left ) ] = emu . palette_compiled [ pixel & 0x7FFF ] ;
}
}
}
else
{
unsafe
{
fixed ( int * dst_ = pixels )
fixed ( short * src_ = emu . ppu . xbuf )
fixed ( int * pal = emu . palette_compiled )
{
int * dst = dst_ ;
short * src = src_ + 256 * the_top + left ;
int xcount = right - left + 1 ;
int srcinc = 256 - xcount ;
int ycount = the_bottom - the_top + 1 ;
xcount / = 16 ;
for ( int y = 0 ; y < ycount ; y + + )
{
for ( int x = 0 ; x < xcount ; x + + )
{
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
* dst + + = pal [ 0x7fff & * src + + ] ;
}
src + = srcinc ;
}
2011-09-04 01:12:12 +00:00
}
2011-06-11 22:15:08 +00:00
}
2011-03-21 01:49:20 +00:00
}
2011-02-20 02:49:37 +00:00
}
2014-04-30 23:48:37 +00:00
public int VirtualWidth { get { return ( int ) ( BufferWidth * 1.146 ) ; } }
public int VirtualHeight { get { return BufferHeight ; } }
2012-03-04 01:41:14 +00:00
public int BufferWidth { get { return right - left + 1 ; } }
2011-02-20 02:49:37 +00:00
public int BackgroundColor { get { return 0 ; } }
2013-03-25 01:59:34 +00:00
public int BufferHeight
{
get
{
2015-08-06 00:12:09 +00:00
if ( emu . Region = = DisplayType . NTSC )
2013-03-25 01:59:34 +00:00
{
2013-12-22 00:44:39 +00:00
return emu . Settings . NTSC_BottomLine - emu . Settings . NTSC_TopLine + 1 ;
2013-03-25 01:59:34 +00:00
}
else
{
2013-12-22 00:44:39 +00:00
return emu . Settings . PAL_BottomLine - emu . Settings . PAL_TopLine + 1 ;
2013-03-25 01:59:34 +00:00
}
}
}
2014-02-28 04:05:36 +00:00
2011-02-20 02:49:37 +00:00
}
2011-03-21 01:51:06 +00:00
MyVideoProvider videoProvider ;
2012-09-29 22:19:49 +00:00
public ISoundProvider SoundProvider { get { return magicSoundProvider ; } }
2012-12-09 03:13:47 +00:00
public ISyncSoundProvider SyncSoundProvider { get { return magicSoundProvider ; } }
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public bool StartAsyncSound ( ) { return true ; }
public void EndAsyncSound ( ) { }
2011-02-20 02:49:37 +00:00
2014-11-01 17:44:04 +00:00
[Obsolete] // with the changes to both nes and quicknes cores, nothing uses this anymore
2011-02-20 02:49:37 +00:00
public static readonly ControllerDefinition NESController =
new ControllerDefinition
{
2012-02-19 07:09:24 +00:00
Name = "NES Controller" ,
BoolButtons = {
2013-07-29 02:11:00 +00:00
"P1 Up" , "P1 Down" , "P1 Left" , "P1 Right" , "P1 Start" , "P1 Select" , "P1 B" , "P1 A" , "Reset" , "Power" ,
"P2 Up" , "P2 Down" , "P2 Left" , "P2 Right" , "P2 Start" , "P2 Select" , "P2 B" , "P2 A"
2012-02-19 07:09:24 +00:00
}
2011-02-20 02:49:37 +00:00
} ;
2012-10-26 18:51:08 +00:00
public ControllerDefinition ControllerDefinition { get ; private set ; }
2011-02-20 02:49:37 +00:00
IController controller ;
public IController Controller
{
get { return controller ; }
set { controller = value ; }
}
2011-04-17 22:51:53 +00:00
int _frame ;
2015-01-15 19:19:43 +00:00
2011-04-17 22:51:53 +00:00
public int Frame { get { return _frame ; } set { _frame = value ; } }
2011-07-30 20:49:36 +00:00
2013-11-03 16:29:51 +00:00
public void ResetCounters ( )
2011-07-30 20:49:36 +00:00
{
_frame = 0 ;
2012-11-25 15:41:40 +00:00
_lagcount = 0 ;
islag = false ;
2011-07-30 20:49:36 +00:00
}
2011-06-07 07:14:34 +00:00
public long Timestamp { get ; private set ; }
2012-09-14 22:28:38 +00:00
2014-12-04 00:43:12 +00:00
public bool DeterministicEmulation { get { return true ; } }
2012-09-14 22:28:38 +00:00
2011-02-20 02:49:37 +00:00
public string SystemId { get { return "NES" ; } }
2011-02-21 09:48:53 +00:00
2011-03-07 10:41:46 +00:00
public string GameName { get { return game_name ; } }
2011-03-20 02:12:10 +00:00
public enum EDetectionOrigin
2011-03-19 20:12:06 +00:00
{
2015-03-11 09:46:27 +00:00
None , BootGodDB , GameDB , INES , UNIF , FDS , NSF
2011-03-19 20:12:06 +00:00
}
2011-07-10 21:00:28 +00:00
StringWriter LoadReport ;
void LoadWriteLine ( string format , params object [ ] arg )
{
Console . WriteLine ( format , arg ) ;
LoadReport . WriteLine ( format , arg ) ;
}
2011-07-30 20:49:36 +00:00
void LoadWriteLine ( object arg ) { LoadWriteLine ( "{0}" , arg ) ; }
2011-09-24 17:07:48 +00:00
2012-03-06 08:01:48 +00:00
class MyWriter : StringWriter
{
2014-02-28 04:05:36 +00:00
public MyWriter ( TextWriter _loadReport )
2012-03-06 08:01:48 +00:00
{
loadReport = _loadReport ;
}
TextWriter loadReport ;
public override void WriteLine ( string format , params object [ ] arg )
{
Console . WriteLine ( format , arg ) ;
loadReport . WriteLine ( format , arg ) ;
}
2012-12-03 15:40:20 +00:00
public override void WriteLine ( string value )
{
Console . WriteLine ( value ) ;
loadReport . WriteLine ( value ) ;
}
2012-03-06 08:01:48 +00:00
}
2014-07-02 15:21:42 +00:00
public void Init ( GameInfo gameInfo , byte [ ] rom , byte [ ] fdsbios = null )
2011-02-27 09:45:50 +00:00
{
2011-07-10 21:00:28 +00:00
LoadReport = new StringWriter ( ) ;
LoadWriteLine ( "------" ) ;
LoadWriteLine ( "BEGIN NES rom analysis:" ) ;
2011-08-04 03:20:54 +00:00
byte [ ] file = rom ;
2012-10-16 22:27:48 +00:00
Type boardType = null ;
CartInfo choice = null ;
CartInfo iNesHeaderInfo = null ;
2014-04-09 18:13:19 +00:00
CartInfo iNesHeaderInfoV2 = null ;
2012-10-16 22:27:48 +00:00
List < string > hash_sha1_several = new List < string > ( ) ;
string hash_sha1 = null , hash_md5 = null ;
Unif unif = null ;
2012-11-06 03:05:43 +00:00
2014-01-01 03:03:10 +00:00
Dictionary < string , string > InitialMapperRegisterValues = new Dictionary < string , string > ( SyncSettings . BoardProperties ) ;
2012-10-16 22:27:48 +00:00
origin = EDetectionOrigin . None ;
2011-03-02 06:18:26 +00:00
if ( file . Length < 16 ) throw new Exception ( "Alleged NES rom too small to be anything useful" ) ;
if ( file . Take ( 4 ) . SequenceEqual ( System . Text . Encoding . ASCII . GetBytes ( "UNIF" ) ) )
2011-02-27 09:45:50 +00:00
{
2014-04-09 19:39:04 +00:00
unif = new Unif ( new MemoryStream ( file ) ) ;
2012-10-16 22:27:48 +00:00
LoadWriteLine ( "Found UNIF header:" ) ;
2014-07-23 15:45:30 +00:00
LoadWriteLine ( unif . CartInfo ) ;
2012-10-16 22:27:48 +00:00
LoadWriteLine ( "Since this is UNIF we can confidently parse PRG/CHR banks to hash." ) ;
2014-07-23 15:45:30 +00:00
hash_sha1 = unif . CartInfo . sha1 ;
2012-10-16 22:27:48 +00:00
hash_sha1_several . Add ( hash_sha1 ) ;
2012-10-17 00:59:22 +00:00
LoadWriteLine ( "headerless rom hash: {0}" , hash_sha1 ) ;
2012-10-16 22:27:48 +00:00
}
2015-03-11 09:46:27 +00:00
else if ( file . Take ( 5 ) . SequenceEqual ( System . Text . Encoding . ASCII . GetBytes ( "NESM\x1A" ) ) )
{
origin = EDetectionOrigin . NSF ;
LoadWriteLine ( "Loading as NSF" ) ;
var nsf = new NSFFormat ( ) ;
nsf . WrapByteArray ( file ) ;
cart = new CartInfo ( ) ;
var nsfboard = new NSFBoard ( ) ;
nsfboard . Create ( this ) ;
nsfboard . ROM = rom ;
nsfboard . InitNSF ( nsf ) ;
nsfboard . InitialRegisterValues = InitialMapperRegisterValues ;
nsfboard . Configure ( origin ) ;
nsfboard . WRAM = new byte [ cart . wram_size * 1024 ] ;
Board = nsfboard ;
Board . PostConfigure ( ) ;
2015-08-06 00:55:35 +00:00
AutoMapperProps . Populate ( Board , SyncSettings ) ;
2015-03-11 09:46:27 +00:00
Console . WriteLine ( "Using NTSC display type for NSF for now" ) ;
_display_type = Common . DisplayType . NTSC ;
HardReset ( ) ;
return ;
}
2013-12-08 21:39:17 +00:00
else if ( file . Take ( 4 ) . SequenceEqual ( System . Text . Encoding . ASCII . GetBytes ( "FDS\x1A" ) )
| | file . Take ( 4 ) . SequenceEqual ( System . Text . Encoding . ASCII . GetBytes ( "\x01*NI" ) ) )
2012-10-21 15:58:24 +00:00
{
2014-07-02 15:21:42 +00:00
// danger! this is a different codepath with an early return. accordingly, some
// code is duplicated twice...
// FDS roms are just fed to the board, we don't do much else with them
2012-10-21 15:58:24 +00:00
origin = EDetectionOrigin . FDS ;
LoadWriteLine ( "Found FDS header." ) ;
if ( fdsbios = = null )
2014-07-31 21:15:07 +00:00
throw new MissingFirmwareException ( "Missing FDS Bios" ) ;
2012-10-21 15:58:24 +00:00
cart = new CartInfo ( ) ;
var fdsboard = new FDS ( ) ;
fdsboard . biosrom = fdsbios ;
2012-10-27 14:01:55 +00:00
fdsboard . SetDiskImage ( rom ) ;
2012-10-21 15:58:24 +00:00
fdsboard . Create ( this ) ;
2014-07-02 15:21:42 +00:00
// at the moment, FDS doesn't use the IRVs, but it could at some point in the future
fdsboard . InitialRegisterValues = InitialMapperRegisterValues ;
2012-10-21 15:58:24 +00:00
fdsboard . Configure ( origin ) ;
2015-01-17 21:02:59 +00:00
Board = fdsboard ;
2012-10-21 15:58:24 +00:00
//create the vram and wram if necessary
if ( cart . wram_size ! = 0 )
2015-01-17 21:02:59 +00:00
Board . WRAM = new byte [ cart . wram_size * 1024 ] ;
2012-10-21 15:58:24 +00:00
if ( cart . vram_size ! = 0 )
2015-01-17 21:02:59 +00:00
Board . VRAM = new byte [ cart . vram_size * 1024 ] ;
2012-10-21 15:58:24 +00:00
2015-01-17 21:02:59 +00:00
Board . PostConfigure ( ) ;
2015-08-06 00:55:35 +00:00
AutoMapperProps . Populate ( Board , SyncSettings ) ;
2012-10-21 15:58:24 +00:00
2014-07-02 15:21:42 +00:00
Console . WriteLine ( "Using NTSC display type for FDS disk image" ) ;
_display_type = Common . DisplayType . NTSC ;
2012-10-21 15:58:24 +00:00
HardReset ( ) ;
2014-07-02 15:21:42 +00:00
2012-10-21 15:58:24 +00:00
return ;
}
2012-10-16 22:27:48 +00:00
else
{
2014-04-09 18:13:19 +00:00
byte [ ] nesheader = new byte [ 16 ] ;
Buffer . BlockCopy ( file , 0 , nesheader , 0 , 16 ) ;
if ( ! DetectFromINES ( nesheader , out iNesHeaderInfo , out iNesHeaderInfoV2 ) )
throw new InvalidOperationException ( "iNES header not found" ) ;
2011-02-27 09:45:50 +00:00
2014-04-09 18:13:19 +00:00
//now that we know we have an iNES header, we can try to ignore it.
2012-03-06 07:51:41 +00:00
2014-07-03 19:20:34 +00:00
hash_sha1 = "sha1:" + file . HashSHA1 ( 16 , file . Length - 16 ) ;
2014-04-09 18:13:19 +00:00
hash_sha1_several . Add ( hash_sha1 ) ;
2014-07-03 19:20:34 +00:00
hash_md5 = "md5:" + file . HashMD5 ( 16 , file . Length - 16 ) ;
2011-06-08 06:17:41 +00:00
2014-04-09 18:13:19 +00:00
LoadWriteLine ( "Found iNES header:" ) ;
LoadWriteLine ( iNesHeaderInfo . ToString ( ) ) ;
if ( iNesHeaderInfoV2 ! = null )
{
LoadWriteLine ( "Found iNES V2 header:" ) ;
LoadWriteLine ( iNesHeaderInfoV2 ) ;
}
LoadWriteLine ( "Since this is iNES we can (somewhat) confidently parse PRG/CHR banks to hash." ) ;
2011-06-08 06:17:41 +00:00
2014-04-09 18:13:19 +00:00
LoadWriteLine ( "headerless rom hash: {0}" , hash_sha1 ) ;
LoadWriteLine ( "headerless rom hash: {0}" , hash_md5 ) ;
2011-03-07 10:41:46 +00:00
2014-04-09 18:13:19 +00:00
if ( iNesHeaderInfo . prg_size = = 16 )
{
//8KB prg can't be stored in iNES format, which counts 16KB prg banks.
//so a correct hash will include only 8KB.
LoadWriteLine ( "Since this rom has a 16 KB PRG, we'll hash it as 8KB too for bootgod's DB:" ) ;
var msTemp = new MemoryStream ( ) ;
msTemp . Write ( file , 16 , 8 * 1024 ) ; //add prg
msTemp . Write ( file , 16 + 16 * 1024 , iNesHeaderInfo . chr_size * 1024 ) ; //add chr
msTemp . Flush ( ) ;
var bytes = msTemp . ToArray ( ) ;
2014-07-03 19:20:34 +00:00
var hash = "sha1:" + bytes . HashSHA1 ( 0 , bytes . Length ) ;
2014-04-09 18:13:19 +00:00
LoadWriteLine ( " PRG (8KB) + CHR hash: {0}" , hash ) ;
hash_sha1_several . Add ( hash ) ;
2014-07-03 19:20:34 +00:00
hash = "md5:" + bytes . HashMD5 ( 0 , bytes . Length ) ;
2014-04-09 18:13:19 +00:00
LoadWriteLine ( " PRG (8KB) + CHR hash: {0}" , hash ) ;
2012-03-06 07:51:41 +00:00
}
2012-10-16 22:27:48 +00:00
}
2012-03-06 07:51:41 +00:00
2012-10-16 22:27:48 +00:00
if ( USE_DATABASE )
{
2014-02-16 06:16:55 +00:00
if ( hash_md5 ! = null ) choice = IdentifyFromGameDB ( hash_md5 ) ;
if ( choice = = null )
choice = IdentifyFromGameDB ( hash_sha1 ) ;
2011-03-20 20:42:12 +00:00
if ( choice = = null )
2012-10-16 22:27:48 +00:00
LoadWriteLine ( "Could not locate game in bizhawk gamedb" ) ;
2011-03-20 20:42:12 +00:00
else
{
2012-10-16 22:27:48 +00:00
origin = EDetectionOrigin . GameDB ;
LoadWriteLine ( "Chose board from bizhawk gamedb: " + choice . board_type ) ;
//gamedb entries that dont specify prg/chr sizes can infer it from the ines header
2012-10-24 23:30:46 +00:00
if ( iNesHeaderInfo ! = null )
{
if ( choice . prg_size = = - 1 ) choice . prg_size = iNesHeaderInfo . prg_size ;
if ( choice . chr_size = = - 1 ) choice . chr_size = iNesHeaderInfo . chr_size ;
if ( choice . vram_size = = - 1 ) choice . vram_size = iNesHeaderInfo . vram_size ;
if ( choice . wram_size = = - 1 ) choice . wram_size = iNesHeaderInfo . wram_size ;
}
else if ( unif ! = null )
{
2014-07-23 15:45:30 +00:00
if ( choice . prg_size = = - 1 ) choice . prg_size = unif . CartInfo . prg_size ;
if ( choice . chr_size = = - 1 ) choice . chr_size = unif . CartInfo . chr_size ;
2012-10-24 23:30:46 +00:00
// unif has no wram\vram sizes; hope the board impl can figure it out...
if ( choice . vram_size = = - 1 ) choice . vram_size = 0 ;
if ( choice . wram_size = = - 1 ) choice . wram_size = 0 ;
}
2014-02-16 06:16:55 +00:00
}
2012-10-24 23:30:46 +00:00
2014-02-16 06:16:55 +00:00
//if this is still null, we have to try it some other way. nescartdb perhaps?
2014-02-28 04:05:36 +00:00
2014-02-16 06:16:55 +00:00
if ( choice = = null )
{
choice = IdentifyFromBootGodDB ( hash_sha1_several ) ;
if ( choice = = null )
LoadWriteLine ( "Could not locate game in nescartdb" ) ;
else
{
LoadWriteLine ( "Chose board from nescartdb:" ) ;
LoadWriteLine ( choice ) ;
origin = EDetectionOrigin . BootGodDB ;
}
2011-03-20 20:42:12 +00:00
}
2012-10-16 22:27:48 +00:00
}
2014-02-16 06:16:55 +00:00
//if choice is still null, try UNIF and iNES
if ( choice = = null )
2012-10-16 22:27:48 +00:00
{
2014-02-16 06:16:55 +00:00
if ( unif ! = null )
{
LoadWriteLine ( "Using information from UNIF header" ) ;
2014-07-23 15:45:30 +00:00
choice = unif . CartInfo ;
2014-12-14 00:16:05 +00:00
//ok, i have this Q-Boy rom with no VROM and no VRAM.
2015-08-18 21:37:34 +00:00
//we also certainly have games with VROM and no VRAM.
//looks like FCEUX policy is to allocate 8KB of chr ram no matter what UNLESS certain flags are set. but what's the justification for this? please leave a note if you go debugging in it again.
//well, we know we can't have much of a NES game if there's no VROM unless there's VRAM instead.
//so if the VRAM isn't set, choose 8 for it.
//TODO - unif loading code may need to use VROR flag to transform chr_size=8 to vram_size=8 (need example)
if ( choice . chr_size = = 0 & & choice . vram_size = = 0 )
choice . vram_size = 8 ;
2014-12-14 00:16:05 +00:00
//(do we need to suppress this in case theres a CHR rom? probably not. nes board base will use ram if no rom is available)
2014-02-16 06:16:55 +00:00
origin = EDetectionOrigin . UNIF ;
}
if ( iNesHeaderInfo ! = null )
{
LoadWriteLine ( "Attempting inference from iNES header" ) ;
2014-04-09 18:13:19 +00:00
// try to spin up V2 header first, then V1 header
if ( iNesHeaderInfoV2 ! = null )
2014-02-16 06:16:55 +00:00
{
2014-04-09 18:13:19 +00:00
try
{
boardType = FindBoard ( iNesHeaderInfoV2 , origin , InitialMapperRegisterValues ) ;
}
catch { }
if ( boardType = = null )
LoadWriteLine ( "Failed to load as iNES V2" ) ;
else
choice = iNesHeaderInfoV2 ;
// V2 might fail but V1 might succeed because we don't have most V2 aliases setup; and there's
// no reason to do so except when needed
2014-02-16 06:16:55 +00:00
}
if ( boardType = = null )
{
2014-04-09 22:23:19 +00:00
choice = iNesHeaderInfo ; // we're out of options, really
boardType = FindBoard ( iNesHeaderInfo , origin , InitialMapperRegisterValues ) ;
2014-04-09 18:13:19 +00:00
if ( boardType = = null )
LoadWriteLine ( "Failed to load as iNES V1" ) ;
// do not further meddle in wram sizes. a board that is being loaded from a "MAPPERxxx"
// entry should know and handle the situation better for the individual board
2014-02-16 06:16:55 +00:00
}
2014-04-09 18:13:19 +00:00
LoadWriteLine ( "Chose board from iNES heuristics:" ) ;
LoadWriteLine ( choice ) ;
2014-02-16 06:16:55 +00:00
origin = EDetectionOrigin . INES ;
}
2012-10-16 22:27:48 +00:00
}
2011-02-28 06:16:20 +00:00
2014-05-23 15:10:14 +00:00
game_name = choice . name ;
2011-03-07 10:41:46 +00:00
2012-10-16 22:27:48 +00:00
//find a INESBoard to handle this
2014-04-09 22:23:19 +00:00
if ( choice ! = null )
boardType = FindBoard ( choice , origin , InitialMapperRegisterValues ) ;
else
throw new Exception ( "Unable to detect ROM" ) ;
2012-10-16 22:27:48 +00:00
if ( boardType = = null )
throw new Exception ( "No class implements the necessary board type: " + choice . board_type ) ;
2011-03-19 20:12:06 +00:00
2012-10-16 22:27:48 +00:00
if ( choice . DB_GameInfo ! = null )
choice . bad = choice . DB_GameInfo . IsRomStatusBad ( ) ;
2011-07-10 21:00:28 +00:00
2012-10-16 22:27:48 +00:00
LoadWriteLine ( "Final game detection results:" ) ;
LoadWriteLine ( choice ) ;
LoadWriteLine ( "\"" + game_name + "\"" ) ;
LoadWriteLine ( "Implemented by: class " + boardType . Name ) ;
if ( choice . bad )
{
LoadWriteLine ( "~~ ONE WAY OR ANOTHER, THIS DUMP IS KNOWN TO BE *BAD* ~~" ) ;
LoadWriteLine ( "~~ YOU SHOULD FIND A BETTER FILE ~~" ) ;
}
2011-06-08 06:17:41 +00:00
2012-10-16 22:27:48 +00:00
LoadWriteLine ( "END NES rom analysis" ) ;
LoadWriteLine ( "------" ) ;
2011-06-08 06:17:41 +00:00
2015-01-17 21:02:59 +00:00
Board = CreateBoardInstance ( boardType ) ;
2011-02-28 06:16:20 +00:00
2012-10-16 22:27:48 +00:00
cart = choice ;
2015-01-17 21:02:59 +00:00
Board . Create ( this ) ;
Board . InitialRegisterValues = InitialMapperRegisterValues ;
Board . Configure ( origin ) ;
2011-03-03 19:56:16 +00:00
2012-10-16 22:27:48 +00:00
if ( origin = = EDetectionOrigin . BootGodDB )
{
RomStatus = RomStatus . GoodDump ;
2012-12-10 00:43:43 +00:00
CoreComm . RomStatusAnnotation = "Identified from BootGod's database" ;
2012-10-16 22:27:48 +00:00
}
if ( origin = = EDetectionOrigin . UNIF )
{
RomStatus = RomStatus . NotInDatabase ;
2012-12-10 00:43:43 +00:00
CoreComm . RomStatusAnnotation = "Inferred from UNIF header; somewhat suspicious" ;
2012-10-16 22:27:48 +00:00
}
if ( origin = = EDetectionOrigin . INES )
{
RomStatus = RomStatus . NotInDatabase ;
2012-12-10 00:43:43 +00:00
CoreComm . RomStatusAnnotation = "Inferred from iNES header; potentially wrong" ;
2012-10-16 22:27:48 +00:00
}
if ( origin = = EDetectionOrigin . GameDB )
{
if ( choice . bad )
2011-07-10 21:00:28 +00:00
{
2012-10-16 22:27:48 +00:00
RomStatus = RomStatus . BadDump ;
2011-07-10 21:00:28 +00:00
}
2012-10-16 22:27:48 +00:00
else
2011-07-10 21:00:28 +00:00
{
2012-10-16 22:27:48 +00:00
RomStatus = choice . DB_GameInfo . Status ;
2011-07-10 21:00:28 +00:00
}
2012-10-16 22:27:48 +00:00
}
2011-07-10 21:00:28 +00:00
2015-08-10 23:41:50 +00:00
byte [ ] trainer = null ;
2011-07-10 21:00:28 +00:00
2012-10-16 22:27:48 +00:00
//create the board's rom and vrom
if ( iNesHeaderInfo ! = null )
{
2015-08-10 23:41:50 +00:00
var ms = new MemoryStream ( file , false ) ;
ms . Seek ( 16 , SeekOrigin . Begin ) ; // ines header
2012-10-16 22:27:48 +00:00
//pluck the necessary bytes out of the file
2015-08-10 23:41:50 +00:00
if ( iNesHeaderInfo . trainer_size ! = 0 )
{
trainer = new byte [ 512 ] ;
ms . Read ( trainer , 0 , 512 ) ;
}
2015-01-17 21:02:59 +00:00
Board . ROM = new byte [ choice . prg_size * 1024 ] ;
2015-08-10 23:41:50 +00:00
ms . Read ( Board . ROM , 0 , Board . ROM . Length ) ;
2011-03-08 07:25:35 +00:00
if ( choice . chr_size > 0 )
2011-03-01 07:25:14 +00:00
{
2015-01-17 21:02:59 +00:00
Board . VROM = new byte [ choice . chr_size * 1024 ] ;
2015-08-10 23:41:50 +00:00
int vrom_copy_size = ms . Read ( Board . VROM , 0 , Board . VROM . Length ) ;
2014-01-21 22:29:51 +00:00
2015-01-17 21:02:59 +00:00
if ( vrom_copy_size < Board . VROM . Length )
LoadWriteLine ( "Less than the expected VROM was found in the file: {0} < {1}" , vrom_copy_size , Board . VROM . Length ) ;
2011-03-01 07:25:14 +00:00
}
2014-11-27 18:03:00 +00:00
if ( choice . prg_size ! = iNesHeaderInfo . prg_size | | choice . chr_size ! = iNesHeaderInfo . chr_size )
LoadWriteLine ( "Warning: Detected choice has different filesizes than the INES header!" ) ;
2012-10-16 22:27:48 +00:00
}
else
{
2015-01-17 21:02:59 +00:00
Board . ROM = unif . PRG ;
Board . VROM = unif . CHR ;
2012-10-16 22:27:48 +00:00
}
2011-03-08 07:25:35 +00:00
2014-11-27 18:03:00 +00:00
LoadReport . Flush ( ) ;
CoreComm . RomStatusDetails = LoadReport . ToString ( ) ;
2014-07-02 15:21:42 +00:00
// IF YOU DO ANYTHING AT ALL BELOW THIS LINE, MAKE SURE THE APPROPRIATE CHANGE IS MADE TO FDS (if applicable)
2012-10-16 22:27:48 +00:00
//create the vram and wram if necessary
if ( cart . wram_size ! = 0 )
2015-01-17 21:02:59 +00:00
Board . WRAM = new byte [ cart . wram_size * 1024 ] ;
2012-10-16 22:27:48 +00:00
if ( cart . vram_size ! = 0 )
2015-01-17 21:02:59 +00:00
Board . VRAM = new byte [ cart . vram_size * 1024 ] ;
2011-03-07 10:41:46 +00:00
2015-01-17 21:02:59 +00:00
Board . PostConfigure ( ) ;
2015-08-06 00:55:35 +00:00
AutoMapperProps . Populate ( Board , SyncSettings ) ;
2012-06-23 08:52:12 +00:00
2014-02-06 02:06:17 +00:00
// set up display type
NESSyncSettings . Region fromrom = DetectRegion ( cart . system ) ;
NESSyncSettings . Region fromsettings = SyncSettings . RegionOverride ;
if ( fromsettings ! = NESSyncSettings . Region . Default )
{
Console . WriteLine ( "Using system region override" ) ;
fromrom = fromsettings ;
}
switch ( fromrom )
{
case NESSyncSettings . Region . Dendy :
_display_type = Common . DisplayType . DENDY ;
break ;
case NESSyncSettings . Region . NTSC :
_display_type = Common . DisplayType . NTSC ;
break ;
case NESSyncSettings . Region . PAL :
_display_type = Common . DisplayType . PAL ;
break ;
default :
_display_type = Common . DisplayType . NTSC ;
break ;
}
Console . WriteLine ( "Using NES system region of {0}" , _display_type ) ;
2012-10-16 22:27:48 +00:00
HardReset ( ) ;
2015-08-10 23:41:50 +00:00
if ( trainer ! = null )
{
Console . WriteLine ( "Applying trainer" ) ;
for ( int i = 0 ; i < 512 ; i + + )
WriteMemory ( ( ushort ) ( 0x7000 + i ) , trainer [ i ] ) ;
}
2011-02-27 09:45:50 +00:00
}
2011-03-01 09:32:12 +00:00
2014-07-02 15:21:42 +00:00
static NESSyncSettings . Region DetectRegion ( string system )
2014-02-06 02:06:17 +00:00
{
switch ( system )
{
case "NES-PAL" :
case "NES-PAL-A" :
case "NES-PAL-B" :
return NESSyncSettings . Region . PAL ;
case "NES-NTSC" :
case "Famicom" :
return NESSyncSettings . Region . NTSC ;
// this is in bootgod, but not used at all
case "Dendy" :
return NESSyncSettings . Region . Dendy ;
case null :
Console . WriteLine ( "Rom is of unknown NES region!" ) ;
return NESSyncSettings . Region . Default ;
default :
Console . WriteLine ( "Unrecognized region {0}" , system ) ;
return NESSyncSettings . Region . Default ;
}
}
2014-12-23 01:58:12 +00:00
private ITraceable Tracer { get ; set ; }
2011-07-30 20:49:36 +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
2011-03-07 10:41:46 +00:00
//some ppu tests
//http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb