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
{
public partial class NES : IEmulator
{
//hardware/state
2012-10-31 01:55:26 +00:00
public MOS6502X cpu ;
2011-03-08 07:25:35 +00:00
int cpu_accumulate ; //cpu timekeeper
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
2014-05-10 20:00:30 +00:00
string game_name = string . Empty ; //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
2012-06-25 06:32:54 +00:00
bool _irq_apu ; //various irq signals that get merged to the cpu irq pin
2012-12-09 17:07:34 +00:00
/// <summary>clock speed of the main cpu in hz</summary>
public int cpuclockrate { get ; private set ; }
2012-03-25 09:25:27 +00:00
//irq state management
2012-06-25 06:32:54 +00:00
public bool irq_apu { get { return _irq_apu ; } set { _irq_apu = value ; } }
2011-03-19 09:12:56 +00:00
2011-03-08 07:25:35 +00:00
//user configuration
2011-03-21 01:49:20 +00:00
int [ ] palette_compiled = new int [ 64 * 8 ] ;
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
2013-05-10 18:38:58 +00:00
public void SetSquare1 ( int v ) { apu . Square1V = v ; }
public void SetSquare2 ( int v ) { apu . Square2V = v ; }
public void SetTriangle ( int v ) { apu . TriangleV = v ; }
public void SetNoise ( int v ) { apu . NoiseV = v ; }
public void SetDMC ( int v ) { apu . DMCV = 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 ;
}
2012-12-09 03:13:47 +00:00
class MagicSoundProvider : ISoundProvider , ISyncSoundProvider , 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
}
public void GetSamples ( short [ ] samples )
{
2012-12-09 17:07:34 +00:00
//Console.WriteLine("Sync: {0}", nes.apu.dlist.Count);
2012-12-09 03:13:47 +00:00
int nsamp = samples . Length / 2 ;
if ( nsamp > blipbuffsize ) // oh well.
nsamp = blipbuffsize ;
uint targetclock = ( uint ) blip . ClocksNeeded ( nsamp ) ;
uint actualclock = nes . apu . sampleclock ;
foreach ( var d in nes . apu . dlist )
blip . AddDelta ( d . time * targetclock / actualclock , d . value ) ;
nes . apu . dlist . Clear ( ) ;
blip . EndFrame ( targetclock ) ;
nes . apu . sampleclock = 0 ;
blip . ReadSamples ( samples , nsamp , true ) ;
// duplicate to stereo
for ( int i = 0 ; i < nsamp * 2 ; i + = 2 )
samples [ i + 1 ] = samples [ i ] ;
2012-09-29 22:19:49 +00:00
//mix in the cart's extra sound circuit
2015-01-17 21:02:59 +00:00
nes . Board . ApplyCustomAudio ( samples ) ;
2012-09-29 22:19:49 +00:00
}
2012-12-09 03:13:47 +00:00
public void GetSamples ( out short [ ] samples , out int nsamp )
{
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 int MaxVolume { get ; set ; }
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 ] ;
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 ) ;
}
}
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 ;
2012-12-10 00:43:43 +00:00
CoreComm . VsyncNum = 50 ;
CoreComm . 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 ;
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 ;
2014-01-07 01:52:00 +00:00
CoreComm . VsyncNum = 39375000 ;
CoreComm . 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 ;
break ;
// this is in bootgod, but not used at all
2014-02-06 02:06:17 +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 ;
2012-12-10 00:43:43 +00:00
CoreComm . VsyncNum = 50 ;
CoreComm . 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 ;
2013-03-25 01:59:34 +00:00
_display_type = DisplayType . DENDY ;
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
{
ram [ i ] = 0x00 ;
}
}
}
2011-03-08 07:25:35 +00:00
2012-11-06 03:05:43 +00:00
SetupMemoryDomains ( ) ;
2011-03-08 07:25:35 +00:00
//in this emulator, reset takes place instantaneously
cpu . PC = ( ushort ) ( ReadMemory ( 0xFFFC ) | ( ReadMemory ( 0xFFFD ) < < 8 ) ) ;
2011-03-20 02:12:10 +00:00
cpu . P = 0x34 ;
cpu . S = 0xFD ;
2011-03-08 07:25:35 +00:00
}
2011-03-20 02:12:10 +00:00
bool resetSignal ;
2012-11-06 00:40:51 +00:00
bool hardResetSignal ;
2012-09-20 19:52:47 +00:00
public void FrameAdvance ( bool render , bool rendersound )
2011-03-08 07:25:35 +00:00
{
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)
//Controller.UnpressButton("Reset"); TODO fix this
2011-03-20 02:12:10 +00:00
resetSignal = Controller [ "Reset" ] ;
2012-11-06 00:40:51 +00:00
hardResetSignal = Controller [ "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 ;
2012-10-26 18:51:08 +00:00
if ( Controller [ "FDS Eject" ] )
b . Eject ( ) ;
for ( int i = 0 ; i < b . NumSides ; i + + )
if ( Controller [ "FDS Insert " + i ] )
b . InsertSide ( i ) ;
}
2011-03-08 07:25:35 +00:00
ppu . FrameAdvance ( ) ;
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 ( ) ;
2011-03-08 07:25:35 +00:00
}
2012-03-15 21:28:37 +00:00
//PAL:
//0 15 30 45 60 -> 12 27 42 57 -> 9 24 39 54 -> 6 21 36 51 -> 3 18 33 48 -> 0
2016-05-05 16:04:51 +00:00
//sequence of ppu clocks per cpu clock: 3,3,3,3,4
//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
2012-10-31 18:25:46 +00:00
ByteBuffer cpu_sequence ;
2016-05-05 16:04:51 +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 } ) ;
2012-03-15 21:28:37 +00:00
public int cpu_step , cpu_stepcounter , cpu_deadcounter ;
2013-04-11 02:04:13 +00:00
2016-06-29 13:37:47 +00:00
public int oam_dma_index ;
public bool oam_dma_exec = false ;
public ushort oam_dma_addr ;
public byte oam_dma_byte ;
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 ;
2016-06-29 13:37:47 +00:00
2013-04-14 20:04:38 +00:00
#if VS2012
2013-04-11 02:04:13 +00:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2013-04-14 20:04:38 +00:00
#endif
2015-01-17 21:09:33 +00:00
internal void RunCpuOne ( )
2012-03-15 21:28:37 +00:00
{
cpu_stepcounter + + ;
2012-10-31 18:25:46 +00:00
if ( cpu_stepcounter = = cpu_sequence [ cpu_step ] )
2011-03-13 00:34:24 +00:00
{
2012-03-15 21:28:37 +00:00
cpu_step + + ;
2016-05-05 16:04:51 +00:00
if ( cpu_step = = 5 ) cpu_step = 0 ;
2012-03-15 21:28:37 +00:00
cpu_stepcounter = 0 ;
2012-03-25 09:25:27 +00:00
2016-06-29 13:37:47 +00:00
///////////////////////////
// OAM DMA start
///////////////////////////
2012-03-25 09:25:27 +00:00
if ( sprdma_countdown > 0 )
{
sprdma_countdown - - ;
if ( sprdma_countdown = = 0 )
{
2016-06-30 22:30:12 +00:00
if ( cpu . TotalExecutedCycles % 2 = = 0 )
2016-06-29 13:37:47 +00:00
{
cpu_deadcounter = 2 ;
} else
{
cpu_deadcounter = 1 ;
}
oam_dma_exec = true ;
cpu . RDY = false ;
oam_dma_index = 0 ;
2016-06-30 22:30:12 +00:00
special_case_delay = true ;
2016-06-29 13:37:47 +00:00
}
}
2016-06-30 22:30:12 +00:00
if ( oam_dma_exec & & apu . dmc_dma_countdown ! = 1 & & ! dmc_realign )
2016-06-29 13:37:47 +00:00
{
if ( cpu_deadcounter = = 0 )
{
if ( oam_dma_index % 2 = = 0 ) {
oam_dma_byte = ReadMemory ( oam_dma_addr ) ;
oam_dma_addr + + ;
} else
{
WriteMemory ( 0x2004 , oam_dma_byte ) ;
}
oam_dma_index + + ;
if ( oam_dma_index = = 512 ) oam_dma_exec = false ;
} else
{
cpu_deadcounter - - ;
2012-03-25 09:25:27 +00:00
}
2016-06-30 22:30:12 +00:00
} else if ( apu . dmc_dma_countdown = = 1 )
{
dmc_realign = true ;
} else if ( dmc_realign )
{
dmc_realign = false ;
2012-03-25 09:25:27 +00:00
}
2016-06-29 13:37:47 +00:00
/////////////////////////////
// OAM DMA end
/////////////////////////////
2016-06-21 13:20:52 +00:00
2016-06-29 13:37:47 +00:00
/////////////////////////////
// dmc dma start
/////////////////////////////
2016-06-21 13:20:52 +00:00
if ( apu . dmc_dma_countdown > 0 )
{
cpu . RDY = false ;
2016-06-29 13:37:47 +00:00
dmc_dma_exec = true ;
2016-06-21 13:20:52 +00:00
apu . dmc_dma_countdown - - ;
if ( apu . dmc_dma_countdown = = 0 )
{
apu . RunDMCFetch ( ) ;
2016-06-29 13:37:47 +00:00
dmc_dma_exec = false ;
2016-06-21 13:20:52 +00:00
apu . dmc_dma_countdown = - 1 ;
2016-07-02 02:31:06 +00:00
do_the_reread = true ;
2016-06-21 13:20:52 +00:00
}
}
2016-06-29 13:37:47 +00:00
/////////////////////////////
// dmc dma end
/////////////////////////////
apu . RunOne ( true ) ;
2016-06-30 22:30:12 +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 ;
}
cpu . ExecuteOne ( ) ;
2016-06-29 13:37:47 +00:00
apu . RunOne ( false ) ;
2016-06-30 22:30:12 +00:00
2016-07-02 02:31:06 +00:00
if ( ppu . double_2007_read > 0 )
ppu . double_2007_read - - ;
if ( do_the_reread & & cpu . RDY )
do_the_reread = false ;
2016-06-30 22:30:12 +00:00
if ( IRQ_delay )
IRQ_delay = false ;
2016-06-29 13:37:47 +00:00
if ( ! dmc_dma_exec & & ! oam_dma_exec & & ! cpu . RDY )
2016-06-30 22:30:12 +00:00
{
2016-06-29 13:37:47 +00:00
cpu . RDY = true ;
2016-06-30 22:30:12 +00:00
IRQ_delay = true ;
}
2016-06-29 13:37:47 +00:00
2012-03-25 09:25:27 +00:00
2016-06-21 21:14:46 +00:00
ppu . ppu_open_bus_decay ( 0 ) ;
2016-06-29 13:37:47 +00:00
2015-01-17 21:02:59 +00:00
Board . ClockCPU ( ) ;
2012-03-15 21:28:37 +00:00
ppu . PostCpuInstructionOne ( ) ;
2011-03-13 00:34:24 +00:00
}
2011-03-08 07:25:35 +00:00
}
2013-04-14 20:04:38 +00:00
#if VS2012
2013-04-11 02:04:13 +00:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2013-04-14 20:04:38 +00:00
#endif
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 )
{
2011-03-13 00:34:24 +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 ;
//return apu.ReadReg(addr);
2011-03-13 00:34:24 +00:00
case 0x4014 : /*OAM DMA*/ break ;
2016-06-28 23:56:44 +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-07-02 02:31:06 +00:00
{
// special hardware glitch case
ret_spec = read_joyport ( addr ) ;
if ( do_the_reread )
{
ret_spec = read_joyport ( addr ) ;
do_the_reread = false ;
}
return ret_spec ;
}
2011-06-18 21:47:20 +00:00
case 0x4017 :
2011-03-08 07:25:35 +00:00
return read_joyport ( addr ) ;
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 )
{
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 :
return apu . PeekReg ( addr ) ;
case 0x4014 : /*OAM DMA*/ break ;
case 0x4015 : return apu . PeekReg ( addr ) ;
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 )
{
2011-03-13 00:34:24 +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 :
apu . WriteReg ( addr , val ) ;
break ;
2011-03-08 07:25:35 +00:00
case 0x4014 : Exec_OAMDma ( val ) ; 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 :
2014-02-28 04:05:36 +00:00
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 ) ;
ControllerDeck . Strobe ( si , Controller ) ;
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 ;
byte ret = addr = = 0x4016 ? ControllerDeck . ReadA ( Controller ) : ControllerDeck . ReadB ( Controller ) ;
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
}
void Exec_OAMDma ( byte val )
{
2012-03-25 09:25:27 +00:00
//schedule a sprite dma event for beginning 1 cycle in the future.
//this receives 2 because thats just the way it works out.
2016-06-29 13:37:47 +00:00
oam_dma_addr = ( ushort ) ( val < < 8 ) ;
sprdma_countdown = 1 ;
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>
2015-09-13 19:09:49 +00:00
private 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 )
{
ppu . WriteReg ( ( addr & 0x07 ) , value ) ;
}
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 ;
}
2012-09-29 08:39:59 +00:00
//old data bus values from previous reads
public byte DB ;
2013-11-12 01:06:33 +00:00
public void ExecFetch ( ushort addr )
{
2014-12-07 18:53:56 +00:00
MemoryCallbacks . CallExecutes ( addr ) ;
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 ;
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
}
2011-03-16 03:13:51 +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
2014-12-07 18:53:56 +00:00
MemoryCallbacks . CallReads ( addr ) ;
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 )
{
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 )
{
2015-03-11 09:46: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
2014-12-07 18:53:56 +00:00
MemoryCallbacks . CallWrites ( addr ) ;
2011-03-08 07:25:35 +00:00
}
}
2016-06-29 13:37:47 +00:00
}