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
protected MOS6502 cpu ;
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-16 03:13:51 +00:00
MemoryDomain . FreezeData [ ] sysbus_freeze = new MemoryDomain . FreezeData [ 65536 ] ;
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
2011-05-20 18:55:01 +00:00
bool _irq_apu , _irq_cart ;
2011-03-19 09:12:56 +00:00
public bool irq_apu { get { return _irq_apu ; } set { _irq_apu = value ; sync_irq ( ) ; } }
2011-05-20 18:55:01 +00:00
public bool irq_cart { get { return _irq_cart ; } set { _irq_cart = value ; sync_irq ( ) ; } }
2011-03-19 09:12:56 +00:00
void sync_irq ( )
{
2011-05-20 18:55:01 +00:00
cpu . IRQ = _irq_apu | | _irq_cart ;
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 ;
public void HardReset ( )
{
cpu = new MOS6502 ( ) ;
cpu . ReadMemory = ReadMemory ;
cpu . WriteMemory = WriteMemory ;
ppu = new PPU ( this ) ;
2011-03-13 00:34:24 +00:00
apu = new APU ( this ) ;
2011-03-08 07:25:35 +00:00
ram = new byte [ 0x800 ] ;
CIRAM = new byte [ 0x800 ] ;
ports = new IPortDevice [ 2 ] ;
ports [ 0 ] = new JoypadPortDevice ( this ) ;
ports [ 1 ] = new NullPortDevice ( ) ;
//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
//cpu.debug = true;
}
2011-03-20 02:12:10 +00:00
bool resetSignal ;
2011-03-08 07:25:35 +00:00
public void FrameAdvance ( bool render )
{
2011-05-01 12:59:26 +00:00
lagged = true ;
if ( resetSignal )
2011-03-20 20:42:12 +00:00
{
cpu . PC = cpu . ReadWord ( MOS6502 . ResetVector ) ;
apu . WriteReg ( 0x4015 , 0 ) ;
cpu . FlagI = true ;
}
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" ] ;
2011-03-08 07:25:35 +00:00
ppu . FrameAdvance ( ) ;
2011-05-01 12:59:26 +00:00
if ( lagged )
2011-05-01 16:04:53 +00:00
{
2011-05-01 12:59:26 +00:00
_lagcount + + ;
2011-05-01 16:04:53 +00:00
islag = true ;
}
else
islag = false ;
2011-03-08 07:25:35 +00:00
}
2011-03-13 00:34:24 +00:00
protected void RunCpu ( int ppu_cycles )
2011-03-08 07:25:35 +00:00
{
2011-06-07 07:14:34 +00:00
//not being used right now. maybe needed later.
//Timestamp += ppu_cycles;
2011-03-13 00:34:24 +00:00
int cycles = ppu_cycles ;
2011-03-08 07:25:35 +00:00
if ( ppu . PAL )
2011-03-20 20:42:12 +00:00
cycles * = 15 ;
2011-03-08 07:25:35 +00:00
else
2011-03-20 20:42:12 +00:00
cycles < < = 4 ;
2011-03-08 07:25:35 +00:00
2011-06-06 10:27:42 +00:00
//tricky logic to try to run one instruction at a time
2011-03-08 07:25:35 +00:00
cpu_accumulate + = cycles ;
2011-06-06 10:27:42 +00:00
int cpu_cycles = cpu_accumulate / 48 ;
for ( ; ; )
2011-03-13 00:34:24 +00:00
{
2011-06-06 10:27:42 +00:00
if ( cpu_cycles = = 0 ) break ;
int need_cpu = - cpu . PendingCycles + 1 ;
if ( cpu_cycles < need_cpu ) break ;
if ( need_cpu = = 0 ) need_cpu = 1 ;
int todo = need_cpu ;
cpu_cycles - = todo ;
cpu_accumulate - = 48 * todo ;
2011-03-08 07:25:35 +00:00
cpu . Execute ( todo ) ;
2011-03-13 00:34:24 +00:00
apu . Run ( todo ) ;
2011-06-07 07:14:34 +00:00
ppu . PostCpuInstruction ( todo ) ;
2011-03-13 00:34:24 +00:00
}
2011-03-08 07:25:35 +00:00
}
public byte ReadPPUReg ( int addr )
{
return ppu . ReadReg ( addr ) ;
}
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 ;
case 0x4015 : return apu . ReadReg ( addr ) ; break ;
2011-03-08 07:25:35 +00:00
case 0x4016 :
return read_joyport ( addr ) ;
2011-03-13 00:34:24 +00:00
case 0x4017 : return apu . ReadReg ( addr ) ; break ;
2011-03-08 07:25:35 +00:00
default :
//Console.WriteLine("read register: {0:x4}", addr);
break ;
}
return 0xFF ;
}
void WritePPUReg ( int addr , byte val )
{
ppu . WriteReg ( addr , val ) ;
}
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 ) ;
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 )
{
//read joystick port
//many todos here
if ( addr = = 0x4016 )
{
2011-05-01 12:59:26 +00:00
lagged = false ;
byte ret = ports [ 0 ] . Read ( ) ;
2011-03-08 07:25:35 +00:00
return ret ;
}
else return 0 ;
}
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 + + ;
}
cpu . PendingCycles - = 512 ;
}
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 ] ;
}
2011-03-08 07:25:35 +00:00
public byte ReadMemory ( ushort addr )
{
2011-03-16 03:13:51 +00:00
byte ret ;
if ( addr < 0x0800 ) ret = ram [ addr ] ;
else if ( addr < 0x1000 ) ret = ram [ addr - 0x0800 ] ;
else if ( addr < 0x1800 ) ret = ram [ addr - 0x1000 ] ;
else if ( addr < 0x2000 ) ret = ram [ addr - 0x1800 ] ;
else if ( addr < 0x4000 ) ret = ReadPPUReg ( addr & 7 ) ;
else if ( addr < 0x4020 ) ret = ReadReg ( addr ) ; //we're not rebasing the register just to keep register names canonical
2011-04-19 01:58:12 +00:00
else if ( addr < 0x6000 ) ret = board . ReadEXP ( addr - 0x4000 ) ;
else if ( addr < 0x8000 ) ret = board . ReadWRAM ( addr - 0x6000 ) ;
2011-03-16 03:13:51 +00:00
else ret = board . ReadPRG ( addr - 0x8000 ) ;
//apply freeze
if ( sysbus_freeze [ addr ] . IsFrozen ) ret = sysbus_freeze [ addr ] . value ;
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
2011-03-17 03:51:31 +00:00
if ( sysbus_watch [ addr ] ! = null )
2011-03-16 05:06:21 +00:00
{
2011-03-17 03:51:31 +00:00
sysbus_watch [ addr ] . Sync ( ) ;
ret = sysbus_watch [ addr ] . ApplyGameGenie ( ret ) ;
2011-03-16 05:06:21 +00:00
}
2011-03-16 03:13:51 +00:00
return ret ;
2011-03-08 07:25:35 +00:00
}
public void WriteMemory ( ushort addr , byte value )
{
if ( addr < 0x0800 ) ram [ addr ] = value ;
else if ( addr < 0x1000 ) ram [ addr - 0x0800 ] = value ;
else if ( addr < 0x1800 ) ram [ addr - 0x1000 ] = value ;
else if ( addr < 0x2000 ) ram [ addr - 0x1800 ] = value ;
else if ( addr < 0x4000 ) WritePPUReg ( addr & 7 , value ) ;
else if ( addr < 0x4020 ) WriteReg ( addr , value ) ; //we're not rebasing the register just to keep register names canonical
2011-04-19 01:58:12 +00:00
else if ( addr < 0x6000 ) board . WriteEXP ( addr - 0x4000 , value ) ;
else if ( addr < 0x8000 ) board . WriteWRAM ( addr - 0x6000 , value ) ;
2011-03-08 07:25:35 +00:00
else board . WritePRG ( addr - 0x8000 , value ) ;
}
}
}