2011-03-08 07:25:35 +00:00
using System ;
2016-09-03 15:53:18 +00:00
using System.Linq ;
2013-11-04 00:36:15 +00:00
using BizHawk.Common ;
2013-11-04 01:39:19 +00:00
using BizHawk.Emulation.Common ;
2014-01-22 01:14:36 +00:00
using BizHawk.Emulation.Cores.Components.M6502 ;
2011-03-08 07:25:35 +00:00
2013-02-24 20:17:12 +00:00
#pragma warning disable 162
2013-11-14 13:15:41 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.NES
2011-03-08 07:25:35 +00:00
{
2017-08-11 23:59:10 +00:00
public partial class NES : IEmulator , ICycleTiming
2011-03-08 07:25:35 +00:00
{
//hardware/state
2012-10-31 01:55:26 +00:00
public MOS6502X cpu ;
2011-03-08 07:25:35 +00:00
public PPU ppu ;
2011-03-13 00:34:24 +00:00
public APU apu ;
2015-03-11 09:46:27 +00:00
public byte [ ] ram ;
2011-03-17 03:51:31 +00:00
NESWatch [ ] sysbus_watch = new NESWatch [ 65536 ] ;
2011-06-09 19:45:07 +00:00
public byte [ ] CIRAM ; //AKA nametables
2017-05-10 11:45:23 +00:00
string game_name = "" ; //friendly name exposed to user and used as filename base
2011-03-08 07:25:35 +00:00
CartInfo cart ; //the current cart prototype. should be moved into the board, perhaps
2015-01-17 21:02:59 +00:00
internal INESBoard Board ; //the board hardware that is currently driving things
2012-10-16 22:27:48 +00:00
EDetectionOrigin origin = EDetectionOrigin . None ;
2012-09-29 08:39:59 +00:00
int sprdma_countdown ;
2016-06-26 20:16:22 +00:00
2017-12-06 00:36:02 +00:00
public bool _irq_apu ; //various irq signals that get merged to the cpu irq pin
2017-10-26 13:58:24 +00:00
/// <summary>clock speed of the main cpu in hz</summary>
2012-12-09 17:07:34 +00:00
public int cpuclockrate { get ; private set ; }
2012-03-25 09:25:27 +00:00
2011-03-08 07:25:35 +00:00
//user configuration
2016-09-08 00:13:27 +00:00
int [ ] palette_compiled = new int [ 64 * 8 ] ;
2016-10-26 23:29:10 +00:00
//variable set when VS system games are running
2016-10-30 02:35:46 +00:00
internal bool _isVS = false ;
2016-10-27 22:17:08 +00:00
//some VS games have a ppu that switches 2000 and 2001, so keep trcak of that
2016-10-28 01:27:35 +00:00
public byte _isVS2c05 = 0 ;
2016-10-26 23:29:10 +00:00
//since prg reg for VS System is set in the controller regs, it is convenient to have it here
//instead of in the board
public byte VS_chr_reg ;
public byte VS_prg_reg ;
//various VS controls
public byte [ ] VS_dips = new byte [ 8 ] ;
public byte VS_service = 0 ;
public byte VS_coin_inserted = 0 ;
public byte VS_ROM_control ;
2014-02-28 04:05:36 +00:00
2017-01-08 18:44:07 +00:00
// cheat addr index tracker
// disables all cheats each frame
public int [ ] cheat_indexes = new int [ 500 ] ;
public int num_cheats ;
2014-02-28 04:05:36 +00:00
// new input system
2014-03-04 23:18:10 +00:00
NESControlSettings ControllerSettings ; // this is stored internally so that a new change of settings won't replace
2014-02-28 04:05:36 +00:00
IControllerDeck ControllerDeck ;
byte latched4016 ;
2013-03-25 01:59:34 +00:00
private DisplayType _display_type = DisplayType . NTSC ;
2012-07-12 23:13:22 +00:00
//Sound config
2017-09-07 14:27:36 +00:00
public void SetVol1 ( int v ) { apu . m_vol = v ; }
2011-03-08 07:25:35 +00:00
2014-02-25 22:54:25 +00:00
/// <summary>
/// for debugging only!
/// </summary>
/// <returns></returns>
public INESBoard GetBoard ( )
{
2015-01-17 21:02:59 +00:00
return Board ;
2014-02-25 22:54:25 +00:00
}
2012-09-29 22:19:49 +00:00
public void Dispose ( )
{
2014-07-30 16:33:48 +00:00
if ( magicSoundProvider ! = null )
magicSoundProvider . Dispose ( ) ;
2012-09-29 22:19:49 +00:00
magicSoundProvider = null ;
}
2016-12-11 18:27:02 +00:00
class MagicSoundProvider : ISoundProvider , IDisposable
2012-09-29 22:19:49 +00:00
{
2013-11-14 19:33:13 +00:00
BlipBuffer blip ;
2012-09-29 22:19:49 +00:00
NES nes ;
2012-12-09 03:13:47 +00:00
const int blipbuffsize = 4096 ;
2012-10-31 18:35:34 +00:00
public MagicSoundProvider ( NES nes , uint infreq )
2012-09-29 22:19:49 +00:00
{
this . nes = nes ;
2012-12-09 03:13:47 +00:00
2013-11-14 19:33:13 +00:00
blip = new BlipBuffer ( blipbuffsize ) ;
2012-12-09 03:13:47 +00:00
blip . SetRates ( infreq , 44100 ) ;
//var actualMetaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
2012-09-29 22:19:49 +00:00
//1.789773mhz NTSC
2012-12-09 03:13:47 +00:00
//resampler = new Sound.Utilities.SpeexResampler(2, infreq, 44100 * APU.DECIMATIONFACTOR, infreq, 44100, actualMetaspu.buffer.enqueue_samples);
//output = new Sound.Utilities.DCFilter(actualMetaspu);
2012-09-29 22:19:49 +00:00
}
2016-12-11 17:14:42 +00:00
public bool CanProvideAsync
2012-09-29 22:19:49 +00:00
{
2016-12-11 17:14:42 +00:00
get { return false ; }
}
2012-12-09 03:13:47 +00:00
2016-12-11 17:14:42 +00:00
public SyncSoundMode SyncMode
{
get { return SyncSoundMode . Sync ; }
}
2012-09-29 22:19:49 +00:00
2016-12-11 17:14:42 +00:00
public void SetSyncMode ( SyncSoundMode mode )
{
if ( mode ! = SyncSoundMode . Sync )
{
throw new NotSupportedException ( "Only sync mode is supported" ) ;
}
}
public void GetSamplesAsync ( short [ ] samples )
{
throw new NotSupportedException ( "Async not supported" ) ;
2012-09-29 22:19:49 +00:00
}
2016-12-11 17:14:42 +00:00
public void GetSamplesSync ( out short [ ] samples , out int nsamp )
2012-12-09 03:13:47 +00:00
{
2012-12-09 17:07:34 +00:00
//Console.WriteLine("ASync: {0}", nes.apu.dlist.Count);
2012-12-09 03:13:47 +00:00
foreach ( var d in nes . apu . dlist )
blip . AddDelta ( d . time , d . value ) ;
nes . apu . dlist . Clear ( ) ;
blip . EndFrame ( nes . apu . sampleclock ) ;
nes . apu . sampleclock = 0 ;
nsamp = blip . SamplesAvailable ( ) ;
samples = new short [ nsamp * 2 ] ;
blip . ReadSamples ( samples , nsamp , true ) ;
// duplicate to stereo
for ( int i = 0 ; i < nsamp * 2 ; i + = 2 )
samples [ i + 1 ] = samples [ i ] ;
2015-01-17 21:02:59 +00:00
nes . Board . ApplyCustomAudio ( samples ) ;
2012-12-09 03:13:47 +00:00
}
2012-09-29 22:19:49 +00:00
public void DiscardSamples ( )
{
2012-12-09 03:13:47 +00:00
nes . apu . dlist . Clear ( ) ;
nes . apu . sampleclock = 0 ;
2012-09-29 22:19:49 +00:00
}
public void Dispose ( )
{
2012-12-09 03:13:47 +00:00
if ( blip ! = null )
{
blip . Dispose ( ) ;
blip = null ;
}
2012-09-29 22:19:49 +00:00
}
}
MagicSoundProvider magicSoundProvider ;
2011-03-08 07:25:35 +00:00
public void HardReset ( )
{
2014-07-30 16:33:48 +00:00
cpu = new MOS6502X ( ) ;
cpu . SetCallbacks ( ReadMemory , ReadMemory , PeekMemory , WriteMemory ) ;
2014-02-12 22:01:23 +00:00
2012-03-24 03:45:47 +00:00
cpu . BCD_Enabled = false ;
2013-11-12 01:06:33 +00:00
cpu . OnExecFetch = ExecFetch ;
2011-03-08 07:25:35 +00:00
ppu = new PPU ( this ) ;
ram = new byte [ 0x800 ] ;
CIRAM = new byte [ 0x800 ] ;
2016-09-08 00:13:27 +00:00
2014-02-28 04:05:36 +00:00
// wire controllers
// todo: allow changing this
2014-03-04 23:18:10 +00:00
ControllerDeck = ControllerSettings . Instantiate ( ppu . LightGunCallback ) ;
2014-02-28 04:05:36 +00:00
// set controller definition first time only
if ( ControllerDefinition = = null )
{
ControllerDefinition = new ControllerDefinition ( ControllerDeck . GetDefinition ( ) ) ;
ControllerDefinition . Name = "NES Controller" ;
// controls other than the deck
ControllerDefinition . BoolButtons . Add ( "Power" ) ;
ControllerDefinition . BoolButtons . Add ( "Reset" ) ;
2015-01-17 21:02:59 +00:00
if ( Board is FDS )
2014-02-28 04:05:36 +00:00
{
2015-01-17 21:02:59 +00:00
var b = Board as FDS ;
2014-02-28 04:05:36 +00:00
ControllerDefinition . BoolButtons . Add ( "FDS Eject" ) ;
for ( int i = 0 ; i < b . NumSides ; i + + )
ControllerDefinition . BoolButtons . Add ( "FDS Insert " + i ) ;
}
2016-10-29 23:39:47 +00:00
if ( _isVS )
{
ControllerDefinition . BoolButtons . Add ( "Insert Coin P1" ) ;
ControllerDefinition . BoolButtons . Add ( "Insert Coin P2" ) ;
ControllerDefinition . BoolButtons . Add ( "Service Switch" ) ;
}
2014-02-28 04:05:36 +00:00
}
2011-03-08 07:25:35 +00:00
2012-11-06 01:41:22 +00:00
// don't replace the magicSoundProvider on reset, as it's not needed
// if (magicSoundProvider != null) magicSoundProvider.Dispose();
2012-10-31 18:48:06 +00:00
2012-10-31 18:25:46 +00:00
// set up region
2014-02-06 02:06:17 +00:00
switch ( _display_type )
2012-10-31 18:25:46 +00:00
{
2014-02-06 02:06:17 +00:00
case Common . DisplayType . PAL :
2012-12-03 15:01:04 +00:00
apu = new APU ( this , apu , true ) ;
2012-10-31 18:48:06 +00:00
ppu . region = PPU . Region . PAL ;
2017-05-05 16:21:37 +00:00
VsyncNum = 50 ;
VsyncDen = 1 ;
2012-12-09 17:07:34 +00:00
cpuclockrate = 1662607 ;
2012-10-31 18:48:06 +00:00
cpu_sequence = cpu_sequence_PAL ;
2013-03-25 01:59:34 +00:00
_display_type = DisplayType . PAL ;
2017-08-11 23:59:10 +00:00
ClockRate = 5320342.5 ;
2012-10-31 18:48:06 +00:00
break ;
2014-02-06 02:06:17 +00:00
case Common . DisplayType . NTSC :
2012-12-03 15:01:04 +00:00
apu = new APU ( this , apu , false ) ;
2012-10-31 18:48:06 +00:00
ppu . region = PPU . Region . NTSC ;
2017-05-05 16:21:37 +00:00
VsyncNum = 39375000 ;
VsyncDen = 655171 ;
2012-12-09 17:07:34 +00:00
cpuclockrate = 1789773 ;
2012-10-31 18:48:06 +00:00
cpu_sequence = cpu_sequence_NTSC ;
2017-08-11 23:59:10 +00:00
ClockRate = 5369318.1818181818181818181818182 ;
2012-10-31 18:48:06 +00:00
break ;
// this is in bootgod, but not used at all
2017-04-27 16:45:44 +00:00
case Common . DisplayType . Dendy :
2012-12-03 15:01:04 +00:00
apu = new APU ( this , apu , false ) ;
2012-10-31 18:48:06 +00:00
ppu . region = PPU . Region . Dendy ;
2017-05-05 16:21:37 +00:00
VsyncNum = 50 ;
VsyncDen = 1 ;
2012-12-09 17:07:34 +00:00
cpuclockrate = 1773448 ;
2012-10-31 18:48:06 +00:00
cpu_sequence = cpu_sequence_NTSC ;
2017-04-27 16:45:44 +00:00
_display_type = DisplayType . Dendy ;
2017-08-11 23:59:10 +00:00
ClockRate = 5320342.5 ;
2012-10-31 18:48:06 +00:00
break ;
default :
2014-02-06 02:06:17 +00:00
throw new Exception ( "Unknown displaytype!" ) ;
2012-10-31 18:25:46 +00:00
}
2012-12-09 17:07:34 +00:00
if ( magicSoundProvider = = null )
magicSoundProvider = new MagicSoundProvider ( this , ( uint ) cpuclockrate ) ;
BoardSystemHardReset ( ) ;
2012-09-29 22:19:49 +00:00
2016-07-01 21:43:09 +00:00
// apu has some specific power up bahaviour that we will emulate here
apu . NESHardReset ( ) ;
2016-09-03 15:53:18 +00:00
if ( SyncSettings . InitialWRamStatePattern ! = null & & SyncSettings . InitialWRamStatePattern . Any ( ) )
{
for ( int i = 0 ; i < 0x800 ; i + + )
{
ram [ i ] = SyncSettings . InitialWRamStatePattern [ i % SyncSettings . InitialWRamStatePattern . Count ] ;
}
}
else
{
// check fceux's PowerNES and FCEU_MemoryRand function for more information:
// relevant games: Cybernoid; Minna no Taabou no Nakayoshi Daisakusen; Huang Di; and maybe mechanized attack
for ( int i = 0 ; i < 0x800 ; i + + )
{
if ( ( i & 4 ) ! = 0 )
{
ram [ i ] = 0xFF ;
}
else
{
2016-09-22 21:54:46 +00:00
ram [ i ] = 0x00 ;
2016-09-03 15:53:18 +00:00
}
}
}
2011-03-08 07:25:35 +00:00
2012-11-06 03:05:43 +00:00
SetupMemoryDomains ( ) ;
2016-09-22 21:52:41 +00:00
// some boards cannot have specific values in RAM upon initialization
// Let's hard code those cases here
// these will be defined through the gameDB exclusively for now.
if ( cart . DB_GameInfo ! = null )
{
2016-10-17 23:06:06 +00:00
if ( cart . DB_GameInfo . Hash = = "60FC5FA5B5ACCAF3AEFEBA73FC8BFFD3C4DAE558" // Camerica Golden 5
| | cart . DB_GameInfo . Hash = = "BAD382331C30B22A908DA4BFF2759C25113CC26A" // Camerica Golden 5
| | cart . DB_GameInfo . Hash = = "40409FEC8249EFDB772E6FFB2DCD41860C6CCA23" // Camerica Pegasus 4-in-1
)
2016-09-22 21:52:41 +00:00
{
ram [ 0x701 ] = 0xFF ;
}
2017-05-30 22:25:34 +00:00
if ( cart . DB_GameInfo . Hash = = "68ABE1E49C9E9CCEA978A48232432C252E5912C0" ) // Dancing Blocks
{
ram [ 0xEC ] = 0 ;
ram [ 0xED ] = 0 ;
}
2016-09-22 21:52:41 +00:00
}
2011-03-08 07:25:35 +00:00
}
2017-08-11 23:59:10 +00:00
public long CycleCount = > ppu . TotalCycles ;
public double ClockRate { get ; private set ; }
2017-05-05 16:21:37 +00:00
private int VsyncNum { get ; set ; }
private int VsyncDen { get ; set ; }
2017-05-02 01:09:11 +00:00
private IController _controller ;
2011-03-20 02:12:10 +00:00
bool resetSignal ;
2012-11-06 00:40:51 +00:00
bool hardResetSignal ;
2017-05-02 01:09:11 +00:00
public void FrameAdvance ( IController controller , bool render , bool rendersound )
2011-03-08 07:25:35 +00:00
{
2017-05-02 01:09:11 +00:00
_controller = controller ;
2014-12-05 00:05:40 +00:00
if ( Tracer . Enabled )
cpu . TraceCallback = ( s ) = > Tracer . Put ( s ) ;
2014-02-12 22:25:36 +00:00
else
cpu . TraceCallback = null ;
2011-06-18 21:47:20 +00:00
lagged = true ;
if ( resetSignal )
2011-03-20 20:42:12 +00:00
{
2015-01-17 21:02:59 +00:00
Board . NESSoftReset ( ) ;
2012-03-18 03:46:06 +00:00
cpu . NESSoftReset ( ) ;
2012-04-14 13:51:26 +00:00
apu . NESSoftReset ( ) ;
2014-11-30 05:02:12 +00:00
ppu . NESSoftReset ( ) ;
2011-03-20 20:42:12 +00:00
}
2012-11-06 00:40:51 +00:00
else if ( hardResetSignal )
{
HardReset ( ) ;
}
2011-03-20 20:42:12 +00:00
2013-12-07 00:53:06 +00:00
Frame + + ;
2011-05-08 00:06:43 +00:00
//if (resetSignal)
2016-09-08 00:13:27 +00:00
//Controller.UnpressButton("Reset"); TODO fix this
2017-05-02 01:09:11 +00:00
resetSignal = controller . IsPressed ( "Reset" ) ;
hardResetSignal = controller . IsPressed ( "Power" ) ;
2012-10-26 18:51:08 +00:00
2015-01-17 21:02:59 +00:00
if ( Board is FDS )
2012-10-26 18:51:08 +00:00
{
2015-01-17 21:02:59 +00:00
var b = Board as FDS ;
2017-05-02 01:09:11 +00:00
if ( controller . IsPressed ( "FDS Eject" ) )
2012-10-26 18:51:08 +00:00
b . Eject ( ) ;
for ( int i = 0 ; i < b . NumSides ; i + + )
2017-05-02 01:09:11 +00:00
if ( controller . IsPressed ( "FDS Insert " + i ) )
2012-10-26 18:51:08 +00:00
b . InsertSide ( i ) ;
}
2016-10-29 23:39:47 +00:00
if ( _isVS )
{
2017-05-02 01:09:11 +00:00
if ( controller . IsPressed ( "Service Switch" ) )
2016-10-29 23:39:47 +00:00
VS_service = 1 ;
else
VS_service = 0 ;
2017-05-02 01:09:11 +00:00
if ( controller . IsPressed ( "Insert Coin P1" ) )
2016-10-29 23:39:47 +00:00
VS_coin_inserted | = 1 ;
else
VS_coin_inserted & = 2 ;
2017-05-02 01:09:11 +00:00
if ( controller . IsPressed ( "Insert Coin P2" ) )
2016-10-29 23:39:47 +00:00
VS_coin_inserted | = 2 ;
else
VS_coin_inserted & = 1 ;
}
2017-09-02 02:11:41 +00:00
if ( ppu . ppudead > 0 )
{
while ( ppu . ppudead > 0 )
{
ppu . NewDeadPPU ( ) ;
}
}
else
{
ppu . ppu_init_frame ( ) ;
ppu . do_vbl = true ;
ppu . do_active_sl = true ;
ppu . do_pre_vbl = true ;
// do the vbl ticks seperate, that will save us a few checks that don't happen in active region
while ( ppu . do_vbl )
{
ppu . TickPPU_VBL ( ) ;
}
// now do the rest of the frame
while ( ppu . do_active_sl )
{
ppu . TickPPU_active ( ) ;
}
// now do the pre-NMI lines
while ( ppu . do_pre_vbl )
{
ppu . TickPPU_preVBL ( ) ;
}
}
2011-06-18 21:47:20 +00:00
if ( lagged )
{
_lagcount + + ;
islag = true ;
}
else
islag = false ;
2012-10-04 21:51:34 +00:00
videoProvider . FillFrameBuffer ( ) ;
2017-01-08 18:44:07 +00:00
//turn off all cheats
for ( int d = 0 ; d < num_cheats ; d + + )
{
RemoveGameGenie ( cheat_indexes [ d ] ) ;
}
num_cheats = 0 ;
2011-03-08 07:25:35 +00:00
}
2012-03-15 21:28:37 +00:00
//PAL:
2016-05-05 16:04:51 +00:00
//sequence of ppu clocks per cpu clock: 3,3,3,3,4
2016-09-08 00:13:27 +00:00
//at least it should be, but something is off with that (start up time?) so it is 3,3,3,4,3 for now
2012-03-15 21:28:37 +00:00
//NTSC:
//sequence of ppu clocks per cpu clock: 3
2017-06-17 01:41:13 +00:00
public ByteBuffer cpu_sequence ;
2016-09-08 00:13:27 +00:00
static ByteBuffer cpu_sequence_NTSC = new ByteBuffer ( new byte [ ] { 3 , 3 , 3 , 3 , 3 } ) ;
static ByteBuffer cpu_sequence_PAL = new ByteBuffer ( new byte [ ] { 3 , 3 , 3 , 4 , 3 } ) ;
2017-06-17 01:41:13 +00:00
public int cpu_deadcounter ;
2013-04-11 02:04:13 +00:00
2016-06-29 13:37:47 +00:00
public int oam_dma_index ;
2016-09-08 00:13:27 +00:00
public bool oam_dma_exec = false ;
2016-06-29 13:37:47 +00:00
public ushort oam_dma_addr ;
public byte oam_dma_byte ;
2016-09-08 00:13:27 +00:00
public bool dmc_dma_exec = false ;
2016-06-30 22:30:12 +00:00
public bool dmc_realign ;
public bool IRQ_delay ;
public bool special_case_delay ; // very ugly but the only option
2016-07-02 02:31:06 +00:00
public bool do_the_reread ;
2017-01-26 01:35:50 +00:00
public byte DB ; //old data bus values from previous reads
2015-01-17 21:09:33 +00:00
internal void RunCpuOne ( )
2012-03-15 21:28:37 +00:00
{
2017-06-17 01:41:13 +00:00
///////////////////////////
// OAM DMA start
///////////////////////////
2012-03-25 09:25:27 +00:00
2017-06-17 01:41:13 +00:00
if ( sprdma_countdown > 0 )
{
sprdma_countdown - - ;
if ( sprdma_countdown = = 0 )
2012-03-25 09:25:27 +00:00
{
2017-06-17 01:41:13 +00:00
if ( cpu . TotalExecutedCycles % 2 = = 0 )
2012-03-25 09:25:27 +00:00
{
2017-06-17 01:41:13 +00:00
cpu_deadcounter = 2 ;
}
else
{
cpu_deadcounter = 1 ;
2016-06-29 13:37:47 +00:00
}
2017-06-17 01:41:13 +00:00
oam_dma_exec = true ;
cpu . RDY = false ;
oam_dma_index = 0 ;
special_case_delay = true ;
2016-06-29 13:37:47 +00:00
}
2017-06-17 01:41:13 +00:00
}
2016-06-29 13:37:47 +00:00
2017-06-17 01:41:13 +00:00
if ( oam_dma_exec & & apu . dmc_dma_countdown ! = 1 & & ! dmc_realign )
{
if ( cpu_deadcounter = = 0 )
2016-06-29 13:37:47 +00:00
{
2017-06-17 01:41:13 +00:00
if ( oam_dma_index % 2 = = 0 )
{
oam_dma_byte = ReadMemory ( oam_dma_addr ) ;
oam_dma_addr + + ;
2016-09-08 00:13:27 +00:00
}
else
2016-06-29 13:37:47 +00:00
{
2017-06-17 01:41:13 +00:00
WriteMemory ( 0x2004 , oam_dma_byte ) ;
2012-03-25 09:25:27 +00:00
}
2017-06-17 01:41:13 +00:00
oam_dma_index + + ;
if ( oam_dma_index = = 512 ) oam_dma_exec = false ;
2016-09-08 00:13:27 +00:00
}
2017-06-17 01:41:13 +00:00
else
2016-06-30 22:30:12 +00:00
{
2017-06-17 01:41:13 +00:00
cpu_deadcounter - - ;
2012-03-25 09:25:27 +00:00
}
2017-06-17 01:41:13 +00:00
}
else if ( apu . dmc_dma_countdown = = 1 )
{
dmc_realign = true ;
}
else if ( dmc_realign )
{
dmc_realign = false ;
}
/////////////////////////////
// OAM DMA end
/////////////////////////////
2016-06-29 13:37:47 +00:00
2017-06-17 01:41:13 +00:00
/////////////////////////////
// dmc dma start
/////////////////////////////
2016-06-30 22:30:12 +00:00
2017-06-17 01:41:13 +00:00
if ( apu . dmc_dma_countdown > 0 )
{
cpu . RDY = false ;
dmc_dma_exec = true ;
apu . dmc_dma_countdown - - ;
if ( apu . dmc_dma_countdown = = 0 )
2016-06-30 22:30:12 +00:00
{
2017-06-17 01:41:13 +00:00
apu . RunDMCFetch ( ) ;
dmc_dma_exec = false ;
apu . dmc_dma_countdown = - 1 ;
do_the_reread = true ;
2016-06-30 22:30:12 +00:00
}
2017-06-17 01:41:13 +00:00
}
2016-06-30 22:30:12 +00:00
2017-06-17 01:41:13 +00:00
/////////////////////////////
// dmc dma end
/////////////////////////////
apu . RunOne ( true ) ;
2016-06-30 22:30:12 +00:00
2017-06-17 01:41:13 +00:00
if ( cpu . RDY & & ! IRQ_delay )
{
cpu . IRQ = _irq_apu | | Board . IRQSignal ;
}
else if ( special_case_delay | | apu . dmc_dma_countdown = = 3 )
{
cpu . IRQ = _irq_apu | | Board . IRQSignal ;
special_case_delay = false ;
}
2016-07-02 02:31:06 +00:00
2017-06-17 01:41:13 +00:00
cpu . ExecuteOne ( ) ;
apu . RunOne ( false ) ;
2016-07-02 02:31:06 +00:00
2017-06-17 01:41:13 +00:00
if ( ppu . double_2007_read > 0 )
ppu . double_2007_read - - ;
2016-06-30 22:30:12 +00:00
2017-06-17 01:41:13 +00:00
if ( do_the_reread & & cpu . RDY )
do_the_reread = false ;
2016-09-08 00:13:27 +00:00
2017-06-17 01:41:13 +00:00
if ( IRQ_delay )
IRQ_delay = false ;
2016-09-08 00:13:27 +00:00
2017-06-17 01:41:13 +00:00
if ( ! dmc_dma_exec & & ! oam_dma_exec & & ! cpu . RDY )
{
cpu . RDY = true ;
IRQ_delay = true ;
2011-03-13 00:34:24 +00:00
}
2017-06-17 01:41:13 +00:00
Board . ClockCPU ( ) ;
2011-03-08 07:25:35 +00:00
}
public byte ReadReg ( int addr )
{
2016-07-02 02:31:06 +00:00
byte ret_spec ;
2011-03-08 07:25:35 +00:00
switch ( addr )
{
2016-09-08 00:13:27 +00:00
case 0x4000 :
case 0x4001 :
case 0x4002 :
case 0x4003 :
case 0x4004 :
case 0x4005 :
case 0x4006 :
case 0x4007 :
case 0x4008 :
case 0x4009 :
case 0x400A :
case 0x400B :
case 0x400C :
case 0x400D :
case 0x400E :
case 0x400F :
case 0x4010 :
case 0x4011 :
case 0x4012 :
case 0x4013 :
2016-06-26 20:16:22 +00:00
return DB ;
2016-09-08 00:13:27 +00:00
//return apu.ReadReg(addr);
2011-03-13 00:34:24 +00:00
case 0x4014 : /*OAM DMA*/ break ;
2016-09-08 00:13:27 +00:00
case 0x4015 : return ( byte ) ( ( byte ) ( apu . ReadReg ( addr ) & 0xDF ) + ( byte ) ( DB & 0x20 ) ) ;
2011-03-08 07:25:35 +00:00
case 0x4016 :
2016-10-26 23:29:10 +00:00
if ( _isVS )
{
byte ret = 0 ;
2016-11-10 20:01:00 +00:00
ret = read_joyport ( 0x4016 ) ;
2016-10-26 23:29:10 +00:00
ret & = 1 ;
ret = ( byte ) ( ret | ( VS_service < < 2 ) | ( VS_dips [ 0 ] < < 3 ) | ( VS_dips [ 1 ] < < 4 ) | ( VS_coin_inserted < < 5 ) | ( VS_ROM_control < < 7 ) ) ;
return ret ;
}
else
2016-07-02 02:31:06 +00:00
{
// special hardware glitch case
ret_spec = read_joyport ( addr ) ;
2017-01-10 15:39:15 +00:00
if ( do_the_reread & & ppu . region = = PPU . Region . NTSC )
2016-07-02 02:31:06 +00:00
{
ret_spec = read_joyport ( addr ) ;
do_the_reread = false ;
}
2016-09-08 00:13:27 +00:00
return ret_spec ;
2016-07-02 02:31:06 +00:00
}
2011-06-18 21:47:20 +00:00
case 0x4017 :
2017-01-10 15:39:15 +00:00
if ( _isVS )
2016-09-08 00:13:27 +00:00
{
2017-01-10 15:39:15 +00:00
byte ret = 0 ;
ret = read_joyport ( 0x4017 ) ;
ret & = 1 ;
2016-10-26 23:29:10 +00:00
2017-01-10 15:39:15 +00:00
ret = ( byte ) ( ret | ( VS_dips [ 2 ] < < 2 ) | ( VS_dips [ 3 ] < < 3 ) | ( VS_dips [ 4 ] < < 4 ) | ( VS_dips [ 5 ] < < 5 ) | ( VS_dips [ 6 ] < < 6 ) | ( VS_dips [ 7 ] < < 7 ) ) ;
2016-10-26 23:29:10 +00:00
2017-01-10 15:39:15 +00:00
return ret ;
2016-10-26 23:29:10 +00:00
2017-01-10 15:39:15 +00:00
}
else
{
// special hardware glitch case
ret_spec = read_joyport ( addr ) ;
if ( do_the_reread & & ppu . region = = PPU . Region . NTSC )
2016-09-08 00:13:27 +00:00
{
2017-01-10 15:39:15 +00:00
ret_spec = read_joyport ( addr ) ;
do_the_reread = false ;
2016-09-08 00:13:27 +00:00
}
2017-01-10 15:39:15 +00:00
return ret_spec ;
2016-09-08 00:13:27 +00:00
}
2011-03-08 07:25:35 +00:00
default :
//Console.WriteLine("read register: {0:x4}", addr);
break ;
}
2016-06-26 20:16:22 +00:00
return DB ;
2011-03-08 07:25:35 +00:00
}
2012-11-02 19:28:00 +00:00
public byte PeekReg ( int addr )
{
switch ( addr )
{
2016-09-08 00:13:27 +00:00
case 0x4000 :
case 0x4001 :
case 0x4002 :
case 0x4003 :
case 0x4004 :
case 0x4005 :
case 0x4006 :
case 0x4007 :
case 0x4008 :
case 0x4009 :
case 0x400A :
case 0x400B :
case 0x400C :
case 0x400D :
case 0x400E :
case 0x400F :
case 0x4010 :
case 0x4011 :
case 0x4012 :
case 0x4013 :
2012-11-02 19:28:00 +00:00
return apu . PeekReg ( addr ) ;
case 0x4014 : /*OAM DMA*/ break ;
2016-09-08 00:13:27 +00:00
case 0x4015 : return apu . PeekReg ( addr ) ;
2012-11-02 19:28:00 +00:00
case 0x4016 :
case 0x4017 :
return peek_joyport ( addr ) ;
default :
//Console.WriteLine("read register: {0:x4}", addr);
break ;
}
return 0xFF ;
}
2011-03-08 07:25:35 +00:00
void WriteReg ( int addr , byte val )
{
switch ( addr )
{
2016-09-08 00:13:27 +00:00
case 0x4000 :
case 0x4001 :
case 0x4002 :
case 0x4003 :
case 0x4004 :
case 0x4005 :
case 0x4006 :
case 0x4007 :
case 0x4008 :
case 0x4009 :
case 0x400A :
case 0x400B :
case 0x400C :
case 0x400D :
case 0x400E :
case 0x400F :
case 0x4010 :
case 0x4011 :
case 0x4012 :
case 0x4013 :
2011-03-13 00:34:24 +00:00
apu . WriteReg ( addr , val ) ;
break ;
2017-06-17 01:41:13 +00:00
case 0x4014 :
//schedule a sprite dma event for beginning 1 cycle in the future.
//this receives 2 because thats just the way it works out.
oam_dma_addr = ( ushort ) ( val < < 8 ) ;
sprdma_countdown = 1 ;
break ;
2011-03-13 00:34:24 +00:00
case 0x4015 : apu . WriteReg ( addr , val ) ; break ;
2011-03-08 07:25:35 +00:00
case 0x4016 :
2016-10-31 21:40:36 +00:00
if ( _isVS )
2016-10-26 23:29:10 +00:00
{
write_joyport ( val ) ;
VS_chr_reg = ( byte ) ( ( val & 0x4 ) > > 2 ) ;
//TODO: does other stuff for dual system
//this is actually different then assignment
VS_prg_reg = ( byte ) ( ( val & 0x4 ) > > 2 ) ;
}
2016-09-08 00:13:27 +00:00
else
{
write_joyport ( val ) ;
}
2011-03-08 07:25:35 +00:00
break ;
2011-03-13 00:34:24 +00:00
case 0x4017 : apu . WriteReg ( addr , val ) ; break ;
2011-03-08 07:25:35 +00:00
default :
//Console.WriteLine("wrote register: {0:x4} = {1:x2}", addr, val);
break ;
}
}
2014-02-28 04:05:36 +00:00
void write_joyport ( byte value )
2011-03-08 07:25:35 +00:00
{
2014-02-28 04:05:36 +00:00
var si = new StrobeInfo ( latched4016 , value ) ;
2017-05-02 01:09:11 +00:00
ControllerDeck . Strobe ( si , _controller ) ;
2014-02-28 04:05:36 +00:00
latched4016 = value ;
2012-11-02 19:28:00 +00:00
}
2014-02-28 04:05:36 +00:00
byte read_joyport ( int addr )
2012-11-02 19:28:00 +00:00
{
2014-12-04 00:43:12 +00:00
InputCallbacks . Call ( ) ;
2014-02-28 04:05:36 +00:00
lagged = false ;
2016-11-10 20:01:00 +00:00
byte ret = 0 ;
if ( _isVS )
{
// for whatever reason, in VS left and right controller have swapped regs
2017-05-02 01:09:11 +00:00
ret = addr = = 0x4017 ? ControllerDeck . ReadA ( _controller ) : ControllerDeck . ReadB ( _controller ) ;
2016-11-10 20:01:00 +00:00
}
else
{
2017-05-02 01:09:11 +00:00
ret = addr = = 0x4016 ? ControllerDeck . ReadA ( _controller ) : ControllerDeck . ReadB ( _controller ) ;
2016-11-10 20:01:00 +00:00
}
2016-09-08 00:13:27 +00:00
ret & = 0x1f ;
ret | = ( byte ) ( 0xe0 & DB ) ;
return ret ;
2012-11-02 19:28:00 +00:00
}
2014-02-28 04:05:36 +00:00
byte peek_joyport ( int addr )
2012-11-02 19:28:00 +00:00
{
2014-02-28 04:05:36 +00:00
// at the moment, the new system doesn't support peeks
return 0 ;
2011-03-08 07:25:35 +00:00
}
2011-03-13 02:48:45 +00:00
/// <summary>
2015-09-13 19:09:49 +00:00
/// Sets the provided palette as current.
/// Applies the current deemph settings if needed to expand a 64-entry palette to 512
2011-03-13 02:48:45 +00:00
/// </summary>
2016-10-26 23:29:10 +00:00
public void SetPalette ( byte [ , ] pal )
2011-03-13 02:48:45 +00:00
{
2015-09-13 19:09:49 +00:00
int nColors = pal . GetLength ( 0 ) ;
int nElems = pal . GetLength ( 1 ) ;
if ( nColors = = 512 )
{
//just copy the palette directly
for ( int c = 0 ; c < 64 * 8 ; c + + )
{
int r = pal [ c , 0 ] ;
int g = pal [ c , 1 ] ;
int b = pal [ c , 2 ] ;
palette_compiled [ c ] = ( int ) unchecked ( ( int ) 0xFF000000 | ( r < < 16 ) | ( g < < 8 ) | b ) ;
}
}
else
2011-03-13 02:48:45 +00:00
{
2015-09-13 19:09:49 +00:00
//expand using deemph
for ( int i = 0 ; i < 64 * 8 ; i + + )
{
int d = i > > 6 ;
int c = i & 63 ;
int r = pal [ c , 0 ] ;
int g = pal [ c , 1 ] ;
int b = pal [ c , 2 ] ;
Palettes . ApplyDeemphasis ( ref r , ref g , ref b , d ) ;
palette_compiled [ i ] = ( int ) unchecked ( ( int ) 0xFF000000 | ( r < < 16 ) | ( g < < 8 ) | b ) ;
}
2011-03-13 02:48:45 +00:00
}
}
/// <summary>
2011-03-21 01:49:20 +00:00
/// looks up an internal NES pixel value to an rgb int (applying the core's current palette and assuming no deemph)
2011-03-13 02:48:45 +00:00
/// </summary>
public int LookupColor ( int pixel )
{
return palette_compiled [ pixel ] ;
}
2012-03-15 21:28:37 +00:00
public byte DummyReadMemory ( ushort addr ) { return 0 ; }
2012-09-01 18:34:31 +00:00
private void ApplySystemBusPoke ( int addr , byte value )
{
if ( addr < 0x2000 )
{
ram [ ( addr & 0x7FF ) ] = value ;
}
else if ( addr < 0x4000 )
{
2017-03-08 03:30:41 +00:00
ppu . WriteReg ( addr , value ) ;
2012-09-01 18:34:31 +00:00
}
else if ( addr < 0x4020 )
{
WriteReg ( addr , value ) ;
}
else
{
ApplyGameGenie ( addr , value , null ) ; //Apply a cheat to the remaining regions since they have no direct access, this may not be the best way to handle this situation
}
}
2012-08-19 20:38:20 +00:00
2012-11-02 19:28:00 +00:00
public byte PeekMemory ( ushort addr )
{
byte ret ;
if ( addr > = 0x4020 )
{
2015-01-17 21:02:59 +00:00
ret = Board . PeekCart ( addr ) ; //easy optimization, since rom reads are so common, move this up (reordering the rest of these elseifs is not easy)
2012-11-02 19:28:00 +00:00
}
else if ( addr < 0x0800 )
{
ret = ram [ addr ] ;
}
2012-11-05 06:26:52 +00:00
else if ( addr < 0x2000 )
2012-11-02 19:28:00 +00:00
{
ret = ram [ addr & 0x7FF ] ;
}
else if ( addr < 0x4000 )
{
2015-03-11 09:46:27 +00:00
ret = Board . PeekReg2xxx ( addr ) ;
2012-11-02 19:28:00 +00:00
}
else if ( addr < 0x4020 )
{
2014-02-27 20:58:00 +00:00
ret = PeekReg ( addr ) ; //we're not rebasing the register just to keep register names canonical
2012-11-02 19:28:00 +00:00
}
else
{
throw new Exception ( "Woopsie-doodle!" ) ;
ret = 0xFF ;
}
return ret ;
}
2013-11-12 01:06:33 +00:00
public void ExecFetch ( ushort addr )
{
2017-08-03 23:08:07 +00:00
MemoryCallbacks . CallExecutes ( addr , "System Bus" ) ;
2013-11-12 01:06:33 +00:00
}
2011-03-08 07:25:35 +00:00
public byte ReadMemory ( ushort addr )
{
2011-03-16 03:13:51 +00:00
byte ret ;
2016-09-08 00:13:27 +00:00
2012-08-19 20:38:20 +00:00
if ( addr > = 0x8000 )
{
2015-01-17 21:02:59 +00:00
ret = Board . ReadPRG ( addr - 0x8000 ) ; //easy optimization, since rom reads are so common, move this up (reordering the rest of these elseifs is not easy)
2012-08-19 20:38:20 +00:00
}
else if ( addr < 0x0800 )
{
ret = ram [ addr ] ;
}
2012-11-05 06:26:52 +00:00
else if ( addr < 0x2000 )
2012-08-19 20:38:20 +00:00
{
ret = ram [ addr & 0x7FF ] ;
}
else if ( addr < 0x4000 )
{
2015-03-11 09:46:27 +00:00
ret = Board . ReadReg2xxx ( addr ) ;
2012-08-19 20:38:20 +00:00
}
else if ( addr < 0x4020 )
{
2016-06-26 20:16:22 +00:00
ret = ReadReg ( addr ) ; //we're not rebasing the register just to keep register names canonical
2012-08-19 20:38:20 +00:00
}
else if ( addr < 0x6000 )
{
2015-01-17 21:02:59 +00:00
ret = Board . ReadEXP ( addr - 0x4000 ) ;
2012-08-19 20:38:20 +00:00
}
else
{
2015-01-17 21:02:59 +00:00
ret = Board . ReadWRAM ( addr - 0x6000 ) ;
2012-08-19 20:38:20 +00:00
}
2016-09-08 00:13:27 +00:00
2011-03-16 05:06:21 +00:00
//handle breakpoints and stuff.
//the idea is that each core can implement its own watch class on an address which will track all the different kinds of monitors and breakpoints and etc.
//but since freeze is a common case, it was implemented through its own mechanisms
2012-09-01 14:13:36 +00:00
if ( sysbus_watch [ addr ] ! = null )
{
sysbus_watch [ addr ] . Sync ( ) ;
ret = sysbus_watch [ addr ] . ApplyGameGenie ( ret ) ;
}
2011-03-16 05:06:21 +00:00
2017-08-03 23:08:07 +00:00
MemoryCallbacks . CallReads ( addr , "System Bus" ) ;
2012-10-13 18:59:09 +00:00
2012-09-29 08:39:59 +00:00
DB = ret ;
2011-03-16 03:13:51 +00:00
return ret ;
2011-03-08 07:25:35 +00:00
}
2012-09-01 14:13:36 +00:00
public void ApplyGameGenie ( int addr , byte value , byte? compare )
{
if ( addr < sysbus_watch . Length )
{
2017-01-08 18:44:07 +00:00
cheat_indexes [ num_cheats ] = addr ;
num_cheats + + ;
2012-09-01 14:13:36 +00:00
GetWatch ( NESWatch . EDomain . Sysbus , addr ) . SetGameGenie ( compare , value ) ;
}
}
public void RemoveGameGenie ( int addr )
{
if ( addr < sysbus_watch . Length )
{
GetWatch ( NESWatch . EDomain . Sysbus , addr ) . RemoveGameGenie ( ) ;
}
}
2011-03-08 07:25:35 +00:00
public void WriteMemory ( ushort addr , byte value )
{
2012-08-19 21:09:48 +00:00
if ( addr < 0x0800 )
{
ram [ addr ] = value ;
}
else if ( addr < 0x2000 )
{
ram [ addr & 0x7FF ] = value ;
}
else if ( addr < 0x4000 )
{
2016-09-08 00:13:27 +00:00
Board . WriteReg2xxx ( addr , value ) ;
2012-08-19 21:09:48 +00:00
}
else if ( addr < 0x4020 )
{
WriteReg ( addr , value ) ; //we're not rebasing the register just to keep register names canonical
}
else if ( addr < 0x6000 )
{
2015-01-17 21:02:59 +00:00
Board . WriteEXP ( addr - 0x4000 , value ) ;
2012-08-19 21:09:48 +00:00
}
else if ( addr < 0x8000 )
{
2015-01-17 21:02:59 +00:00
Board . WriteWRAM ( addr - 0x6000 , value ) ;
2012-08-19 21:09:48 +00:00
}
else
{
2015-01-17 21:02:59 +00:00
Board . WritePRG ( addr - 0x8000 , value ) ;
2012-08-19 21:09:48 +00:00
}
2012-10-13 18:59:09 +00:00
2017-08-03 23:08:07 +00:00
MemoryCallbacks . CallWrites ( addr , "System Bus" ) ;
2011-03-08 07:25:35 +00:00
}
2016-10-30 16:26:32 +00:00
// the palette for each VS game needs to be chosen explicitly since there are 6 different ones.
2016-10-30 20:36:52 +00:00
public void PickVSPalette ( CartInfo cart )
2016-10-27 21:35:10 +00:00
{
2016-10-30 20:36:52 +00:00
switch ( cart . palette )
2016-10-30 16:26:32 +00:00
{
2016-11-11 23:42:44 +00:00
case "2C05" : SetPalette ( Palettes . palette_2c03_2c05 ) ; ppu . CurrentLuma = PPU . PaletteLuma2C03 ; break ;
case "2C04-1" : SetPalette ( Palettes . palette_2c04_001 ) ; ppu . CurrentLuma = PPU . PaletteLuma2C04_1 ; break ;
case "2C04-2" : SetPalette ( Palettes . palette_2c04_002 ) ; ppu . CurrentLuma = PPU . PaletteLuma2C04_2 ; break ;
case "2C04-3" : SetPalette ( Palettes . palette_2c04_003 ) ; ppu . CurrentLuma = PPU . PaletteLuma2C04_3 ; break ;
case "2C04-4" : SetPalette ( Palettes . palette_2c04_004 ) ; ppu . CurrentLuma = PPU . PaletteLuma2C04_4 ; break ;
2016-10-30 16:26:32 +00:00
}
2016-10-31 13:58:13 +00:00
//since this will run for every VS game, let's get security setting too
2016-11-09 03:46:47 +00:00
//values below 16 are for the 2c05 PPU
//values 16,32,48 are for Namco games and dealt with in mapper 206
_isVS2c05 = ( byte ) ( cart . vs_security & 15 ) ;
2016-10-27 21:35:10 +00:00
}
2011-03-08 07:25:35 +00:00
}
2016-09-22 21:54:46 +00:00
}