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 ;
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
{
2017-07-12 19:10:55 +00:00
[ Core (
2014-04-25 01:19:57 +00:00
"NesHawk" ,
2016-09-02 16:25:20 +00:00
"zeromus, natt, alyosha, adelikat" ,
2014-04-25 01:19:57 +00:00
isPorted : false ,
2017-05-06 00:05:36 +00:00
isReleased : true ) ]
2015-08-06 00:12:09 +00:00
public partial class NES : IEmulator , ISaveRam , IDebuggable , IStatable , IInputPollable , IRegionable ,
2017-10-26 13:58:24 +00:00
IBoardInfo , ISettable < NES . NESSettings , NES . NESSyncSettings > , ICodeDataLogger
2011-02-20 02:49:37 +00:00
{
2014-08-23 19:06:37 +00:00
[CoreConstructor("NES")]
2017-05-06 00:05:36 +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 ;
}
2017-05-06 00:05:36 +00:00
SyncSettings = ( NESSyncSettings ) syncSettings ? ? new NESSyncSettings ( ) ;
ControllerSettings = SyncSettings . Controls ;
2012-12-10 00:43:43 +00:00
CoreComm = comm ;
2017-04-27 00:09:00 +00:00
2017-08-02 03:05:17 +00:00
MemoryCallbacks = new MemoryCallbackSystem ( new [ ] { "System Bus" } ) ;
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
}
2017-05-06 00:05:36 +00:00
PutSettings ( ( NESSettings ) settings ? ? new NESSettings ( ) ) ;
2014-12-14 02:01:38 +00:00
2016-10-26 23:29:10 +00:00
// we need to put this here because the line directly above will overwrite palette intialization anywhere else
2016-10-30 16:26:32 +00:00
// TODO: What if settings are later loaded?
2016-10-26 23:29:10 +00:00
if ( _isVS )
{
2016-10-30 20:36:52 +00:00
PickVSPalette ( cart ) ;
2016-10-26 23:29:10 +00:00
}
2017-04-27 00:09:00 +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 ) ;
2016-12-11 17:14:42 +00:00
ser . Register < ISoundProvider > ( magicSoundProvider ) ;
2017-04-27 00:09:00 +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
}
2017-05-06 00:05:36 +00:00
static readonly bool USE_DATABASE = true ;
public RomStatus RomStatus ;
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 ;
}
}
2016-10-30 02:35:46 +00:00
public bool IsVS
{
get { return _isVS ; }
}
2016-12-13 21:24:53 +00:00
public bool IsFDS
{
get { return Board is FDS ; }
}
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
2017-05-05 16:25:38 +00:00
public int VsyncNumerator = > emu . VsyncNum ;
public int VsyncDenominator = > emu . VsyncDen ;
2011-02-20 02:49:37 +00:00
}
2011-03-21 01:51:06 +00:00
MyVideoProvider videoProvider ;
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
2017-04-27 00:09:00 +00:00
private int _frame ;
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 ) ;
2017-05-30 00:27:07 +00:00
bool exists = true ;
2014-04-09 18:13:19 +00:00
if ( ! DetectFromINES ( nesheader , out iNesHeaderInfo , out iNesHeaderInfoV2 ) )
2017-05-30 00:27:07 +00:00
{
// we don't have an ines header, check if the game hash is in the game db
exists = false ;
Console . WriteLine ( "headerless ROM, using Game DB" ) ;
hash_md5 = "md5:" + file . HashMD5 ( 0 , file . Length ) ;
hash_sha1 = "sha1:" + file . HashSHA1 ( 0 , file . Length ) ;
if ( hash_md5 ! = null ) choice = IdentifyFromGameDB ( hash_md5 ) ;
if ( choice = = null )
choice = IdentifyFromGameDB ( hash_sha1 ) ;
2017-06-05 19:56:28 +00:00
if ( choice = = null )
{
hash_sha1_several . Add ( hash_sha1 ) ;
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 ;
}
}
2017-05-30 00:27:07 +00:00
if ( choice = = null )
throw new InvalidOperationException ( "iNES header not found and no gamedb entry" ) ;
}
2011-02-27 09:45:50 +00:00
2017-05-30 00:27:07 +00:00
if ( exists )
{
//now that we know we have an iNES header, we can try to ignore it.
2012-03-06 07:51:41 +00:00
2017-05-30 00:27:07 +00:00
hash_sha1 = "sha1:" + file . HashSHA1 ( 16 , file . Length - 16 ) ;
hash_sha1_several . Add ( hash_sha1 ) ;
hash_md5 = "md5:" + file . HashMD5 ( 16 , file . Length - 16 ) ;
2011-06-08 06:17:41 +00:00
2017-05-30 00:27:07 +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
2017-05-30 00:27:07 +00:00
LoadWriteLine ( "headerless rom hash: {0}" , hash_sha1 ) ;
LoadWriteLine ( "headerless rom hash: {0}" , hash_md5 ) ;
2011-03-07 10:41:46 +00:00
2017-05-30 00:27:07 +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
2017-06-07 00:04:59 +00:00
if ( file . Length > = ( 16 * 1024 + iNesHeaderInfo . chr_size * 1024 + 16 ) )
2017-06-05 19:56:28 +00:00
{
// This assumes that even though the PRG is only 8k the CHR is still written
// 16k into the file, which is not always the case (e.x. Galaxian RevA)
msTemp . Write ( file , 16 + 16 * 1024 , iNesHeaderInfo . chr_size * 1024 ) ; //add chr
}
2017-06-07 00:04:59 +00:00
else if ( file . Length > = ( 8 * 1024 + iNesHeaderInfo . chr_size * 1024 + 16 ) )
2017-06-05 19:56:28 +00:00
{
2017-06-07 00:04:59 +00:00
// maybe the PRG is only 8k
2017-06-05 19:56:28 +00:00
msTemp . Write ( file , 16 + 8 * 1024 , iNesHeaderInfo . chr_size * 1024 ) ; //add chr
}
2017-06-07 00:04:59 +00:00
else
{
// we failed somehow
// most likely the header is wrong
Console . WriteLine ( "WARNING: 16kb PRG iNES header but unable to parse" ) ;
}
2017-05-30 00:27:07 +00:00
msTemp . Flush ( ) ;
var bytes = msTemp . ToArray ( ) ;
var hash = "sha1:" + bytes . HashSHA1 ( 0 , bytes . Length ) ;
LoadWriteLine ( " PRG (8KB) + CHR hash: {0}" , hash ) ;
hash_sha1_several . Add ( hash ) ;
hash = "md5:" + bytes . HashMD5 ( 0 , bytes . Length ) ;
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
}
2017-06-05 19:56:28 +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?
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
}
2017-06-05 19:56:28 +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
}
2017-05-30 00:27:07 +00:00
else if ( unif ! = null )
2012-10-16 22:27:48 +00:00
{
2015-01-17 21:02:59 +00:00
Board . ROM = unif . PRG ;
Board . VROM = unif . CHR ;
2012-10-16 22:27:48 +00:00
}
2017-05-30 00:27:07 +00:00
else
{
// we should only get here for boards with no header
var ms = new MemoryStream ( file , false ) ;
ms . Seek ( 0 , SeekOrigin . Begin ) ;
Board . ROM = new byte [ choice . prg_size * 1024 ] ;
ms . Read ( Board . ROM , 0 , Board . ROM . Length ) ;
if ( choice . chr_size > 0 )
{
Board . VROM = new byte [ choice . chr_size * 1024 ] ;
int vrom_copy_size = ms . Read ( Board . VROM , 0 , Board . VROM . Length ) ;
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-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 :
2017-04-27 16:45:44 +00:00
_display_type = Common . DisplayType . Dendy ;
2014-02-06 02:06:17 +00:00
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
//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