2011-03-08 07:25:35 +00:00
using System ;
using System.Linq ;
using System.Diagnostics ;
using System.Globalization ;
using System.IO ;
using System.Collections.Generic ;
using BizHawk.Emulation.CPUs.M6502 ;
namespace BizHawk.Emulation.Consoles.Nintendo
{
public partial class NES : IEmulator
{
//hardware/state
2012-10-30 23:01:54 +00:00
// any of the 3 cpus are drop in replacements
2012-10-31 01:55:26 +00:00
public MOS6502X cpu ;
//public MOS6502X_CPP cpu;
2012-10-30 23:01:54 +00:00
//public MOS6502XDouble cpu;
// dispose list as the native core can't keep track of its own stuff
List < System . Runtime . InteropServices . GCHandle > DisposeList = new List < System . Runtime . InteropServices . GCHandle > ( ) ;
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 ;
2011-03-08 07:25:35 +00:00
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
2011-03-08 07:25:35 +00:00
string game_name ; //friendly name exposed to user and used as filename base
CartInfo cart ; //the current cart prototype. should be moved into the board, perhaps
INESBoard board ; //the board hardware that is currently driving things
2012-10-16 22:27:48 +00:00
EDetectionOrigin origin = EDetectionOrigin . None ;
2011-08-27 15:49:16 +00:00
public bool SoundOn = true ;
2012-09-29 08:39:59 +00:00
int sprdma_countdown ;
2012-06-25 06:32:54 +00:00
bool _irq_apu ; //various irq signals that get merged to the cpu irq pin
2012-10-31 21:15:44 +00:00
/// <summary>if true, use VS. system arrangement of $4016..$4020</summary>
bool vs_io = false ;
bool vs_coin1 ;
bool vs_coin2 ;
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-13 02:48:45 +00:00
int [ , ] palette = new int [ 64 , 3 ] ;
2011-03-21 01:49:20 +00:00
int [ ] palette_compiled = new int [ 64 * 8 ] ;
2011-03-08 07:25:35 +00:00
IPortDevice [ ] ports ;
2012-07-12 23:13:22 +00:00
//Sound config
public void SetSquare1 ( bool enabled ) { apu . EnableSquare1 = enabled ; }
public void SetSquare2 ( bool enabled ) { apu . EnableSquare2 = enabled ; }
public void SetTriangle ( bool enabled ) { apu . EnableTriangle = enabled ; }
public void SetNoise ( bool enabled ) { apu . EnableNoise = enabled ; }
public void SetDMC ( bool enabled ) { apu . EnableDMC = enabled ; }
2011-03-08 07:25:35 +00:00
2012-09-29 22:19:49 +00:00
public void Dispose ( )
{
if ( magicSoundProvider ! = null ) magicSoundProvider . Dispose ( ) ;
magicSoundProvider = null ;
2012-10-30 23:01:54 +00:00
if ( DisposeList ! = null )
{
foreach ( var h in DisposeList )
h . Free ( ) ;
DisposeList = null ;
}
2012-09-29 22:19:49 +00:00
}
class MagicSoundProvider : ISoundProvider , IDisposable
{
Sound . Utilities . SpeexResampler resampler ;
ISoundProvider output ;
NES nes ;
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 ;
var actualMetaspu = new Sound . MetaspuSoundProvider ( Sound . ESynchMethod . ESynchMethod_V ) ;
//1.789773mhz NTSC
2012-10-31 18:35:34 +00:00
resampler = new Sound . Utilities . SpeexResampler ( 2 , infreq , 44100 * APU . DECIMATIONFACTOR , infreq , 44100 , actualMetaspu . buffer . enqueue_samples ) ;
2012-09-29 22:19:49 +00:00
output = new Sound . Utilities . DCFilter ( actualMetaspu ) ;
}
Random r = new Random ( ) ;
public void GetSamples ( short [ ] samples )
{
2012-10-18 20:57:53 +00:00
if ( nes . apu . squeue . Count = = 0 )
return ;
2012-09-29 22:19:49 +00:00
var monosampbuf = nes . apu . squeue . ToArray ( 2 ) ;
nes . apu . squeue . Clear ( ) ;
if ( monosampbuf . Length > 0 )
{
var stereosampbuf = new short [ monosampbuf . Length * 2 ] ;
for ( int i = 0 ; i < monosampbuf . Length ; i + + )
{
stereosampbuf [ i * 2 + 0 ] = monosampbuf [ i ] ;
stereosampbuf [ i * 2 + 1 ] = monosampbuf [ i ] ;
}
resampler . EnqueueSamples ( stereosampbuf , monosampbuf . Length ) ;
resampler . Flush ( ) ;
output . GetSamples ( samples ) ;
}
//mix in the cart's extra sound circuit
nes . board . ApplyCustomAudio ( samples ) ;
}
public void DiscardSamples ( )
{
output . DiscardSamples ( ) ;
}
public int MaxVolume { get ; set ; }
public void Dispose ( )
{
resampler . Dispose ( ) ;
resampler = null ;
}
}
MagicSoundProvider magicSoundProvider ;
2011-03-08 07:25:35 +00:00
public void HardReset ( )
{
2012-10-31 18:48:06 +00:00
cpu = new MOS6502X ( ( h ) = > DisposeList . Add ( h ) ) ;
2012-10-31 01:55:26 +00:00
//cpu = new MOS6502X_CPP((h) => DisposeList.Add(h));
2012-10-30 23:01:54 +00:00
//cpu = new MOS6502XDouble((h) => DisposeList.Add(h));
2012-11-02 19:28:00 +00:00
cpu . SetCallbacks ( ReadMemory , ReadMemory , PeekMemory , WriteMemory , ( h ) = > DisposeList . Add ( h ) ) ;
2012-03-24 03:45:47 +00:00
cpu . BCD_Enabled = false ;
2011-03-08 07:25:35 +00:00
ppu = new PPU ( this ) ;
ram = new byte [ 0x800 ] ;
CIRAM = new byte [ 0x800 ] ;
ports = new IPortDevice [ 2 ] ;
2012-10-31 18:48:06 +00:00
ports [ 0 ] = new JoypadPortDevice ( this , 0 ) ;
ports [ 1 ] = new JoypadPortDevice ( this , 1 ) ;
2011-03-08 07:25:35 +00:00
2012-11-06 02:13:16 +00:00
apu = new APU ( this , apu ) ;
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
2012-10-31 18:48:06 +00:00
switch ( cart . system )
2012-10-31 18:25:46 +00:00
{
2012-10-31 18:48:06 +00:00
case "NES-PAL" :
case "NES-PAL-A" :
case "NES-PAL-B" :
ppu . region = PPU . Region . PAL ;
CoreOutputComm . VsyncNum = 50 ;
CoreOutputComm . VsyncDen = 1 ;
cpu_sequence = cpu_sequence_PAL ;
2012-11-06 01:41:22 +00:00
if ( magicSoundProvider = = null )
magicSoundProvider = new MagicSoundProvider ( this , 1662607 ) ;
2012-10-31 18:48:06 +00:00
break ;
case "NES-NTSC" :
case "Famicom" :
ppu . region = PPU . Region . NTSC ;
cpu_sequence = cpu_sequence_NTSC ;
2012-11-06 01:41:22 +00:00
if ( magicSoundProvider = = null )
magicSoundProvider = new MagicSoundProvider ( this , 1789773 ) ;
2012-10-31 18:48:06 +00:00
break ;
// there's no official name for these in bootgod, not sure what we should use
2012-10-31 21:15:44 +00:00
//case "PC10"://TODO
2012-10-31 18:48:06 +00:00
case "VS" :
ppu . region = PPU . Region . RGB ;
cpu_sequence = cpu_sequence_NTSC ;
2012-11-06 01:41:22 +00:00
if ( magicSoundProvider = = null )
magicSoundProvider = new MagicSoundProvider ( this , 1789773 ) ;
2012-10-31 21:15:44 +00:00
vs_io = true ;
2012-10-31 18:48:06 +00:00
break ;
// this is in bootgod, but not used at all
case "Dendy" :
ppu . region = PPU . Region . Dendy ;
CoreOutputComm . VsyncNum = 50 ;
CoreOutputComm . VsyncDen = 1 ;
cpu_sequence = cpu_sequence_NTSC ;
2012-11-06 01:41:22 +00:00
if ( magicSoundProvider = = null )
magicSoundProvider = new MagicSoundProvider ( this , 1773448 ) ;
2012-10-31 18:48:06 +00:00
break ;
case null :
Console . WriteLine ( "Unknown NES system! Defaulting to NTSC." ) ;
goto case "NES-NTSC" ;
default :
Console . WriteLine ( "Unrecognized NES system \"{0}\"! Defaulting to NTSC." , cart . system ) ;
goto case "NES-NTSC" ;
2012-10-31 18:25:46 +00:00
}
2012-09-29 22:19:49 +00:00
2011-03-08 07:25:35 +00:00
//fceux uses this technique, which presumably tricks some games into thinking the memory is randomized
for ( int i = 0 ; i < 0x800 ; i + + )
{
if ( ( i & 4 ) ! = 0 ) ram [ i ] = 0xFF ; else ram [ i ] = 0x00 ;
}
//in this emulator, reset takes place instantaneously
cpu . PC = ( ushort ) ( ReadMemory ( 0xFFFC ) | ( ReadMemory ( 0xFFFD ) < < 8 ) ) ;
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
{
2011-06-18 21:47:20 +00:00
lagged = true ;
if ( resetSignal )
2011-03-20 20:42:12 +00:00
{
2012-07-17 23:05:58 +00:00
board . NESSoftReset ( ) ;
2012-03-18 03:46:06 +00:00
cpu . NESSoftReset ( ) ;
2012-04-14 13:51:26 +00:00
apu . NESSoftReset ( ) ;
2012-03-18 03:46:06 +00:00
//need to study what happens to ppu and apu and stuff..
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
2011-03-08 07:25:35 +00:00
Controller . UpdateControls ( 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
if ( board is FDS )
{
var b = board as FDS ;
if ( Controller [ "FDS Eject" ] )
b . Eject ( ) ;
for ( int i = 0 ; i < b . NumSides ; i + + )
if ( Controller [ "FDS Insert " + i ] )
b . InsertSide ( i ) ;
}
2012-10-31 21:15:44 +00:00
if ( vs_io )
{
if ( Controller [ "VS Coin 1" ] )
vs_coin1 = true ;
if ( Controller [ "VS Coin 2" ] )
vs_coin2 = true ;
}
2012-10-26 18:51:08 +00:00
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
//sequence of ppu clocks per cpu clock: 4,3,3,3,3
//NTSC:
//sequence of ppu clocks per cpu clock: 3
2012-10-31 18:25:46 +00:00
ByteBuffer cpu_sequence ;
2012-03-15 21:28:37 +00:00
static ByteBuffer cpu_sequence_NTSC = new ByteBuffer ( new byte [ ] { 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 } ) ;
static ByteBuffer cpu_sequence_PAL = new ByteBuffer ( new byte [ ] { 4 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 3 , 3 } ) ;
public int cpu_step , cpu_stepcounter , cpu_deadcounter ;
protected void RunCpuOne ( )
{
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 + + ;
cpu_step & = 31 ;
cpu_stepcounter = 0 ;
2012-03-25 09:25:27 +00:00
if ( sprdma_countdown > 0 )
{
sprdma_countdown - - ;
if ( sprdma_countdown = = 0 )
{
//its weird that this is 514.. normally itd be 512 (and people would say its wrong) or 513 (and people would say its right)
//but 514 passes test 4-irq_and_dma
2012-09-29 08:39:59 +00:00
cpu_deadcounter + = 514 ;
2012-03-25 09:25:27 +00:00
}
}
2012-06-25 06:32:54 +00:00
if ( cpu_deadcounter > 0 )
2012-03-25 09:25:27 +00:00
cpu_deadcounter - - ;
else
2012-06-25 06:32:54 +00:00
{
cpu . IRQ = _irq_apu | | board . IRQSignal ;
2012-09-30 02:37:00 +00:00
if ( CoreInputComm . Tracer . Enabled )
{
2012-11-02 22:27:22 +00:00
CoreInputComm . Tracer . Put ( cpu . TraceState ( ) ) ;
2012-09-30 02:37:00 +00:00
}
2012-03-15 21:28:37 +00:00
cpu . ExecuteOne ( ) ;
2012-06-25 06:32:54 +00:00
}
2012-03-25 09:25:27 +00:00
2012-08-04 01:47:54 +00:00
apu . RunOne ( ) ;
2012-10-31 14:36:43 +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
}
public byte ReadReg ( int addr )
{
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 :
return apu . ReadReg ( addr ) ;
case 0x4014 : /*OAM DMA*/ break ;
2011-06-20 09:07:38 +00:00
case 0x4015 : return apu . ReadReg ( addr ) ;
2011-03-08 07:25:35 +00:00
case 0x4016 :
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 ;
}
return 0xFF ;
}
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 :
ports [ 0 ] . Write ( val & 1 ) ;
ports [ 1 ] . Write ( val & 1 ) ;
2012-10-31 21:15:44 +00:00
if ( vs_io & & board is Mapper099 )
{
// happily, there aren't any other "VS exceptions" like this
var b = board as Mapper099 ;
b . Signal4016 ( val > > 2 & 1 ) ;
}
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 ;
}
}
byte read_joyport ( int addr )
{
2012-10-06 13:34:04 +00:00
if ( CoreInputComm . InputCallback ! = null ) CoreInputComm . InputCallback ( ) ;
2012-11-02 19:28:00 +00:00
return handle_read_joyport ( addr , false ) ;
}
byte peek_joyport ( int addr )
{
return handle_read_joyport ( addr , true ) ;
}
byte handle_read_joyport ( int addr , bool peek )
{
2011-03-08 07:25:35 +00:00
//read joystick port
//many todos here
2012-03-25 09:25:27 +00:00
lagged = false ;
2011-06-18 21:47:20 +00:00
byte ret ;
2012-10-31 21:15:44 +00:00
if ( addr = = 0x4016 )
2012-11-02 19:28:00 +00:00
ret = ports [ vs_io ? 1 : 0 ] . Read ( peek ) ;
2012-10-31 21:15:44 +00:00
else
2012-11-02 19:28:00 +00:00
ret = ports [ vs_io ? 0 : 1 ] . Read ( peek ) ;
2012-10-31 21:15:44 +00:00
if ( vs_io )
{
if ( addr = = 0x4016 )
{
// clear bits 2-6
ret & = 0x83 ;
if ( false ) // service switch
ret | = 0x04 ;
if ( false ) // DIP1
ret | = 0x08 ;
if ( false ) // DIP2
ret | = 0x10 ;
if ( vs_coin1 )
ret | = 0x20 ;
if ( vs_coin2 )
ret | = 0x40 ;
}
else
{
// clear bits 2-7
ret & = 0x03 ;
if ( false ) // DIP3
ret | = 0x04 ;
if ( false ) // DIP4
ret | = 0x08 ;
if ( false ) // DIP5
ret | = 0x10 ;
if ( false ) // DIP6
ret | = 0x20 ;
if ( false ) // DIP7
ret | = 0x40 ;
if ( false ) // DIP8
ret | = 0x80 ;
}
}
2011-06-18 21:47:20 +00:00
return ret ;
2011-03-08 07:25:35 +00:00
}
void Exec_OAMDma ( byte val )
{
ushort addr = ( ushort ) ( val < < 8 ) ;
for ( int i = 0 ; i < 256 ; i + + )
{
byte db = ReadMemory ( ( ushort ) addr ) ;
WriteMemory ( 0x2004 , db ) ;
addr + + ;
}
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.
sprdma_countdown = 2 ;
2011-03-08 07:25:35 +00:00
}
2011-03-13 02:48:45 +00:00
/// <summary>
/// sets the provided palette as current
/// </summary>
2011-06-10 05:31:46 +00:00
public void SetPalette ( int [ , ] pal )
2011-03-13 02:48:45 +00:00
{
Array . Copy ( pal , palette , 64 * 3 ) ;
2011-03-21 01:49:20 +00:00
for ( int i = 0 ; i < 64 * 8 ; i + + )
2011-03-13 02:48:45 +00:00
{
2011-03-21 01:49:20 +00:00
int d = i > > 6 ;
int c = i & 63 ;
int r = palette [ c , 0 ] ;
int g = palette [ c , 1 ] ;
int b = palette [ c , 2 ] ;
Palettes . ApplyDeemphasis ( ref r , ref g , ref b , d ) ;
2011-03-13 02:48:45 +00:00
palette_compiled [ i ] = ( int ) unchecked ( ( int ) 0xFF000000 | ( r < < 16 ) | ( g < < 8 ) | b ) ;
}
}
/// <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 )
{
ret = board . PeekCart ( addr ) ; //easy optimization, since rom reads are so common, move this up (reordering the rest of these elseifs is not easy)
}
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 )
{
ret = ppu . ReadReg ( addr & 7 ) ;
}
else if ( addr < 0x4020 )
{
ret = ReadReg ( addr ) ; //we're not rebasing the register just to keep register names canonical
}
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 ;
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 )
{
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)
}
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 )
{
ret = ppu . ReadReg ( addr & 7 ) ;
}
else if ( addr < 0x4020 )
{
ret = ReadReg ( addr ) ; //we're not rebasing the register just to keep register names canonical
}
else if ( addr < 0x6000 )
{
ret = board . ReadEXP ( addr - 0x4000 ) ;
}
else
{
ret = board . ReadWRAM ( addr - 0x6000 ) ;
}
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
2012-10-14 14:08:25 +00:00
if ( CoreInputComm . MemoryCallbackSystem . HasRead )
2012-10-13 18:59:09 +00:00
{
CoreInputComm . MemoryCallbackSystem . TriggerRead ( addr ) ;
}
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 )
{
ppu . WriteReg ( addr & 7 , value ) ;
}
else if ( addr < 0x4020 )
{
WriteReg ( addr , value ) ; //we're not rebasing the register just to keep register names canonical
}
else if ( addr < 0x6000 )
{
2012-10-31 21:15:44 +00:00
if ( vs_io & & addr = = 0x4020 & & ( value & 1 ) ! = 0 )
{
// acknowledge coin insertion
vs_coin1 = false ;
vs_coin2 = false ;
}
2012-08-19 21:09:48 +00:00
board . WriteEXP ( addr - 0x4000 , value ) ;
}
else if ( addr < 0x8000 )
{
board . WriteWRAM ( addr - 0x6000 , value ) ;
}
else
{
board . WritePRG ( addr - 0x8000 , value ) ;
}
2012-10-13 18:59:09 +00:00
2012-10-14 14:08:25 +00:00
if ( CoreInputComm . MemoryCallbackSystem . HasWrite )
2012-10-13 18:59:09 +00:00
{
2012-10-14 14:08:25 +00:00
CoreInputComm . MemoryCallbackSystem . TriggerWrite ( addr ) ;
2012-10-13 18:59:09 +00:00
}
2011-03-08 07:25:35 +00:00
}
}
}