diff --git a/NES.Core.cs b/NES.Core.cs
new file mode 100644
index 0000000000..fbf81ed7cf
--- /dev/null
+++ b/NES.Core.cs
@@ -0,0 +1,672 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+using BizHawk.Common;
+using BizHawk.Emulation.Common;
+using BizHawk.Emulation.Cores.Components.M6502;
+
+#pragma warning disable 162
+
+namespace BizHawk.Emulation.Cores.Nintendo.NES
+{
+ public partial class NES : IEmulator
+ {
+ //hardware/state
+ public MOS6502X cpu;
+ int cpu_accumulate; //cpu timekeeper
+ public PPU ppu;
+ public APU apu;
+ public byte[] ram;
+ NESWatch[] sysbus_watch = new NESWatch[65536];
+ public byte[] CIRAM; //AKA nametables
+ string game_name = string.Empty; //friendly name exposed to user and used as filename base
+ CartInfo cart; //the current cart prototype. should be moved into the board, perhaps
+ internal INESBoard Board; //the board hardware that is currently driving things
+ EDetectionOrigin origin = EDetectionOrigin.None;
+ int sprdma_countdown;
+ bool _irq_apu; //various irq signals that get merged to the cpu irq pin
+ /// clock speed of the main cpu in hz
+ public int cpuclockrate { get; private set; }
+
+ //irq state management
+ public bool irq_apu { get { return _irq_apu; } set { _irq_apu = value; } }
+
+ //user configuration
+ int[] palette_compiled = new int[64*8];
+
+ // new input system
+ NESControlSettings ControllerSettings; // this is stored internally so that a new change of settings won't replace
+ IControllerDeck ControllerDeck;
+ byte latched4016;
+
+ private DisplayType _display_type = DisplayType.NTSC;
+
+ //Sound config
+ 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; }
+
+ ///
+ /// for debugging only!
+ ///
+ ///
+ public INESBoard GetBoard()
+ {
+ return Board;
+ }
+
+ public void Dispose()
+ {
+ if (magicSoundProvider != null)
+ magicSoundProvider.Dispose();
+ magicSoundProvider = null;
+ }
+
+ class MagicSoundProvider : ISoundProvider, ISyncSoundProvider, IDisposable
+ {
+ BlipBuffer blip;
+ NES nes;
+
+ const int blipbuffsize = 4096;
+
+ public MagicSoundProvider(NES nes, uint infreq)
+ {
+ this.nes = nes;
+
+ blip = new BlipBuffer(blipbuffsize);
+ blip.SetRates(infreq, 44100);
+
+ //var actualMetaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
+ //1.789773mhz NTSC
+ //resampler = new Sound.Utilities.SpeexResampler(2, infreq, 44100 * APU.DECIMATIONFACTOR, infreq, 44100, actualMetaspu.buffer.enqueue_samples);
+ //output = new Sound.Utilities.DCFilter(actualMetaspu);
+ }
+
+ public void GetSamples(short[] samples)
+ {
+ //Console.WriteLine("Sync: {0}", nes.apu.dlist.Count);
+ 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];
+
+ //mix in the cart's extra sound circuit
+ nes.Board.ApplyCustomAudio(samples);
+ }
+
+ public void GetSamples(out short[] samples, out int nsamp)
+ {
+ //Console.WriteLine("ASync: {0}", nes.apu.dlist.Count);
+ 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];
+
+ nes.Board.ApplyCustomAudio(samples);
+ }
+
+ public void DiscardSamples()
+ {
+ nes.apu.dlist.Clear();
+ nes.apu.sampleclock = 0;
+ }
+
+ public int MaxVolume { get; set; }
+
+ public void Dispose()
+ {
+ if (blip != null)
+ {
+ blip.Dispose();
+ blip = null;
+ }
+ }
+ }
+ MagicSoundProvider magicSoundProvider;
+
+ public void HardReset()
+ {
+ cpu = new MOS6502X();
+ cpu.SetCallbacks(ReadMemory, ReadMemory, PeekMemory, WriteMemory);
+
+ cpu.BCD_Enabled = false;
+ cpu.OnExecFetch = ExecFetch;
+ ppu = new PPU(this);
+ ram = new byte[0x800];
+ CIRAM = new byte[0x800];
+
+ // wire controllers
+ // todo: allow changing this
+ ControllerDeck = ControllerSettings.Instantiate(ppu.LightGunCallback);
+ // 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");
+ if (Board is FDS)
+ {
+ var b = Board as FDS;
+ ControllerDefinition.BoolButtons.Add("FDS Eject");
+ for (int i = 0; i < b.NumSides; i++)
+ ControllerDefinition.BoolButtons.Add("FDS Insert " + i);
+ }
+ }
+
+ // don't replace the magicSoundProvider on reset, as it's not needed
+ // if (magicSoundProvider != null) magicSoundProvider.Dispose();
+
+ // set up region
+ switch (_display_type)
+ {
+ case Common.DisplayType.PAL:
+ apu = new APU(this, apu, true);
+ ppu.region = PPU.Region.PAL;
+ CoreComm.VsyncNum = 50;
+ CoreComm.VsyncDen = 1;
+ cpuclockrate = 1662607;
+ cpu_sequence = cpu_sequence_PAL;
+ _display_type = DisplayType.PAL;
+ break;
+ case Common.DisplayType.NTSC:
+ apu = new APU(this, apu, false);
+ ppu.region = PPU.Region.NTSC;
+ CoreComm.VsyncNum = 39375000;
+ CoreComm.VsyncDen = 655171;
+ cpuclockrate = 1789773;
+ cpu_sequence = cpu_sequence_NTSC;
+ break;
+ // this is in bootgod, but not used at all
+ case Common.DisplayType.DENDY:
+ apu = new APU(this, apu, false);
+ ppu.region = PPU.Region.Dendy;
+ CoreComm.VsyncNum = 50;
+ CoreComm.VsyncDen = 1;
+ cpuclockrate = 1773448;
+ cpu_sequence = cpu_sequence_NTSC;
+ _display_type = DisplayType.DENDY;
+ break;
+ default:
+ throw new Exception("Unknown displaytype!");
+ }
+ if (magicSoundProvider == null)
+ magicSoundProvider = new MagicSoundProvider(this, (uint)cpuclockrate);
+
+ BoardSystemHardReset();
+
+ //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;
+
+ SetupMemoryDomains();
+
+ //in this emulator, reset takes place instantaneously
+ cpu.PC = (ushort)(ReadMemory(0xFFFC) | (ReadMemory(0xFFFD) << 8));
+ cpu.P = 0x34;
+ cpu.S = 0xFD;
+ }
+
+ bool resetSignal;
+ bool hardResetSignal;
+ public void FrameAdvance(bool render, bool rendersound)
+ {
+ if (Tracer.Enabled)
+ cpu.TraceCallback = (s) => Tracer.Put(s);
+ else
+ cpu.TraceCallback = null;
+
+ lagged = true;
+ if (resetSignal)
+ {
+ Board.NESSoftReset();
+ cpu.NESSoftReset();
+ apu.NESSoftReset();
+ ppu.NESSoftReset();
+ }
+ else if (hardResetSignal)
+ {
+ HardReset();
+ }
+
+ Frame++;
+
+ //if (resetSignal)
+ //Controller.UnpressButton("Reset"); TODO fix this
+ resetSignal = Controller["Reset"];
+ hardResetSignal = Controller["Power"];
+
+ 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);
+ }
+
+ ppu.FrameAdvance();
+ if (lagged)
+ {
+ _lagcount++;
+ islag = true;
+ }
+ else
+ islag = false;
+
+ videoProvider.FillFrameBuffer();
+ }
+
+ //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: 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
+ //NTSC:
+ //sequence of ppu clocks per cpu clock: 3
+ ByteBuffer cpu_sequence;
+ 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});
+ public int cpu_step, cpu_stepcounter, cpu_deadcounter;
+
+#if VS2012
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ internal void RunCpuOne()
+ {
+ cpu_stepcounter++;
+ if (cpu_stepcounter == cpu_sequence[cpu_step])
+ {
+ cpu_step++;
+ if(cpu_step == 5) cpu_step=0;
+ cpu_stepcounter = 0;
+
+ 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
+ // according to nesdev wiki, http://wiki.nesdev.com/w/index.php/PPU_OAM this is 513 on even cycles and 514 on odd cycles
+ // TODO: Implement that
+ cpu_deadcounter += 514;
+ }
+ }
+
+ if (apu.dmc_dma_countdown>0)
+ {
+ cpu.RDY = false;
+ apu.dmc_dma_countdown--;
+ if (apu.dmc_dma_countdown==0)
+ {
+ apu.RunDMCFetch();
+ cpu.RDY = true;
+ }
+
+ if (apu.dmc_dma_countdown==0)
+ {
+
+
+ apu.dmc_dma_countdown = -1;
+ }
+ }
+
+ if (cpu_deadcounter > 0)
+ {
+ cpu_deadcounter--;
+ }
+ else
+ {
+ cpu.IRQ = _irq_apu || Board.IRQSignal;
+ cpu.ExecuteOne();
+ }
+
+ ppu.ppu_open_bus_decay(0);
+ apu.RunOne();
+ Board.ClockCPU();
+ ppu.PostCpuInstructionOne();
+ }
+ }
+
+#if VS2012
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ public byte ReadReg(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.ReadReg(addr);
+ case 0x4014: /*OAM DMA*/ break;
+ case 0x4015: return apu.ReadReg(addr);
+ case 0x4016:
+ case 0x4017:
+ return read_joyport(addr);
+ default:
+ //Console.WriteLine("read register: {0:x4}", addr);
+ break;
+
+ }
+ return 0xFF;
+ }
+
+ 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;
+ }
+
+ void WriteReg(int addr, byte val)
+ {
+ 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:
+ apu.WriteReg(addr, val);
+ break;
+ case 0x4014: Exec_OAMDma(val); break;
+ case 0x4015: apu.WriteReg(addr, val); break;
+ case 0x4016:
+ write_joyport(val);
+ break;
+ case 0x4017: apu.WriteReg(addr, val); break;
+ default:
+ //Console.WriteLine("wrote register: {0:x4} = {1:x2}", addr, val);
+ break;
+ }
+ }
+
+ void write_joyport(byte value)
+ {
+ var si = new StrobeInfo(latched4016, value);
+ ControllerDeck.Strobe(si, Controller);
+ latched4016 = value;
+ }
+
+ byte read_joyport(int addr)
+ {
+ InputCallbacks.Call();
+ lagged = false;
+ byte ret = addr == 0x4016 ? ControllerDeck.ReadA(Controller) : ControllerDeck.ReadB(Controller);
+ ret &= 0x1f;
+ ret |= (byte)(0xe0 & DB);
+ return ret;
+ }
+
+ byte peek_joyport(int addr)
+ {
+ // at the moment, the new system doesn't support peeks
+ 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++;
+ }
+ //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;
+ }
+
+ ///
+ /// Sets the provided palette as current.
+ /// Applies the current deemph settings if needed to expand a 64-entry palette to 512
+ ///
+ private void SetPalette(byte[,] pal)
+ {
+ 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
+ {
+ //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);
+ }
+ }
+ }
+
+ ///
+ /// looks up an internal NES pixel value to an rgb int (applying the core's current palette and assuming no deemph)
+ ///
+ public int LookupColor(int pixel)
+ {
+ return palette_compiled[pixel];
+ }
+
+ public byte DummyReadMemory(ushort addr) { return 0; }
+
+ 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
+ }
+ }
+
+ 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];
+ }
+ else if (addr < 0x2000)
+ {
+ ret = ram[addr & 0x7FF];
+ }
+ else if (addr < 0x4000)
+ {
+ ret = Board.PeekReg2xxx(addr);
+ }
+ else if (addr < 0x4020)
+ {
+ ret = PeekReg(addr); //we're not rebasing the register just to keep register names canonical
+ }
+ else
+ {
+ throw new Exception("Woopsie-doodle!");
+ ret = 0xFF;
+ }
+
+ return ret;
+ }
+
+ //old data bus values from previous reads
+ public byte DB;
+
+ public void ExecFetch(ushort addr)
+ {
+ MemoryCallbacks.CallExecutes(addr);
+ }
+
+ public byte ReadMemory(ushort addr)
+ {
+ byte ret;
+
+ 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];
+ }
+ else if (addr < 0x2000)
+ {
+ ret = ram[addr & 0x7FF];
+ }
+ else if (addr < 0x4000)
+ {
+ ret = Board.ReadReg2xxx(addr);
+ }
+ 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);
+ }
+
+ //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
+ if (sysbus_watch[addr] != null)
+ {
+ sysbus_watch[addr].Sync();
+ ret = sysbus_watch[addr].ApplyGameGenie(ret);
+ }
+
+ MemoryCallbacks.CallReads(addr);
+
+ DB = ret;
+
+ return ret;
+ }
+
+ 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();
+ }
+ }
+
+ public void WriteMemory(ushort addr, byte value)
+ {
+ if (addr < 0x0800)
+ {
+ ram[addr] = value;
+ }
+ else if (addr < 0x2000)
+ {
+ ram[addr & 0x7FF] = value;
+ }
+ else if (addr < 0x4000)
+ {
+ Board.WriteReg2xxx(addr,value);
+ }
+ else if (addr < 0x4020)
+ {
+ WriteReg(addr, value); //we're not rebasing the register just to keep register names canonical
+ }
+ else if (addr < 0x6000)
+ {
+ Board.WriteEXP(addr - 0x4000, value);
+ }
+ else if (addr < 0x8000)
+ {
+ Board.WriteWRAM(addr - 0x6000, value);
+ }
+ else
+ {
+ Board.WritePRG(addr - 0x8000, value);
+ }
+
+ MemoryCallbacks.CallWrites(addr);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/NES.cs b/NES.cs
new file mode 100644
index 0000000000..7d84c76b43
--- /dev/null
+++ b/NES.cs
@@ -0,0 +1,827 @@
+using System;
+using System.Linq;
+using System.IO;
+using System.Collections.Generic;
+using System.Reflection;
+
+using BizHawk.Common;
+using BizHawk.Common.BufferExtensions;
+
+using BizHawk.Emulation.Common;
+
+//TODO - redo all timekeeping in terms of master clock
+namespace BizHawk.Emulation.Cores.Nintendo.NES
+{
+ [CoreAttributes(
+ "NesHawk",
+ "zeromus, natt, adelikat",
+ isPorted: false,
+ isReleased: true
+ )]
+ public partial class NES : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable,
+ ISettable
+ {
+ static readonly bool USE_DATABASE = true;
+ public RomStatus RomStatus;
+
+ [CoreConstructor("NES")]
+ public NES(CoreComm comm, GameInfo game, byte[] rom, object Settings, object SyncSettings)
+ {
+ var ser = new BasicServiceProvider(this);
+ ServiceProvider = ser;
+
+ byte[] fdsbios = comm.CoreFileProvider.GetFirmware("NES", "Bios_FDS", false);
+ if (fdsbios != null && fdsbios.Length == 40976)
+ {
+ comm.ShowMessage("Your FDS BIOS is a bad dump. BizHawk will attempt to use it, but no guarantees! You should find a new one.");
+ var tmp = new byte[8192];
+ Buffer.BlockCopy(fdsbios, 16 + 8192 * 3, tmp, 0, 8192);
+ fdsbios = tmp;
+ }
+
+ this.SyncSettings = (NESSyncSettings)SyncSettings ?? new NESSyncSettings();
+ this.ControllerSettings = this.SyncSettings.Controls;
+ CoreComm = comm;
+
+ MemoryCallbacks = new MemoryCallbackSystem();
+ BootGodDB.Initialize();
+ videoProvider = new MyVideoProvider(this);
+ Init(game, rom, fdsbios);
+ if (Board is FDS)
+ {
+ DriveLightEnabled = true;
+ (Board as FDS).SetDriveLightCallback((val) => DriveLightOn = val);
+ // bit of a hack: we don't have a private gamedb for FDS, but the frontend
+ // expects this to be set.
+ RomStatus = game.Status;
+ }
+ PutSettings((NESSettings)Settings ?? new NESSettings());
+
+
+ ser.Register(cpu);
+
+ Tracer = new TraceBuffer { Header = cpu.TraceHeader };
+ ser.Register(Tracer);
+ ser.Register(videoProvider);
+
+ if (Board is BANDAI_FCG_1)
+ {
+ var reader = (Board as BANDAI_FCG_1).reader;
+ // not all BANDAI FCG 1 boards have a barcode reader
+ if (reader != null)
+ ser.Register(reader);
+ }
+ }
+
+ public IEmulatorServiceProvider ServiceProvider { get; private set; }
+
+ private NES()
+ {
+ BootGodDB.Initialize();
+ }
+
+ public void WriteLogTimestamp()
+ {
+ if (ppu != null)
+ Console.Write("[{0:d5}:{1:d3}:{2:d3}]", Frame, ppu.ppur.status.sl, ppu.ppur.status.cycle);
+ }
+ public void LogLine(string format, params object[] args)
+ {
+ if (ppu != null)
+ Console.WriteLine("[{0:d5}:{1:d3}:{2:d3}] {3}", Frame, ppu.ppur.status.sl, ppu.ppur.status.cycle, string.Format(format, args));
+ }
+
+ public bool HasMapperProperties
+ {
+ get
+ {
+ var fields = Board.GetType().GetFields();
+ foreach (var field in fields)
+ {
+ var attrib = field.GetCustomAttributes(typeof(MapperPropAttribute), false).OfType().SingleOrDefault();
+ if (attrib != null)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ NESWatch GetWatch(NESWatch.EDomain domain, int address)
+ {
+ if (domain == NESWatch.EDomain.Sysbus)
+ {
+ NESWatch ret = sysbus_watch[address] ?? new NESWatch(this, domain, address);
+ sysbus_watch[address] = ret;
+ return ret;
+ }
+ return null;
+ }
+
+ class NESWatch
+ {
+ public enum EDomain
+ {
+ Sysbus
+ }
+
+ 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;
+
+ public void Sync()
+ {
+ if (flags == EFlags.None)
+ watches[Address] = null;
+ else watches[Address] = this;
+ }
+
+ public void SetGameGenie(byte? compare, byte value)
+ {
+ flags |= EFlags.GameGenie;
+ Compare = compare;
+ Value = value;
+ Sync();
+ }
+
+ public bool HasGameGenie
+ {
+ get
+ {
+ return (flags & EFlags.GameGenie) != 0;
+ }
+ }
+
+ public byte ApplyGameGenie(byte curr)
+ {
+ if (!HasGameGenie)
+ {
+ return curr;
+ }
+ else if (curr == Compare || Compare == null)
+ {
+ Console.WriteLine("applied game genie");
+ return (byte)Value;
+ }
+ else
+ {
+ return curr;
+ }
+ }
+
+ public void RemoveGameGenie()
+ {
+ flags &= ~EFlags.GameGenie;
+ Sync();
+ }
+
+ byte? Compare;
+ byte Value;
+
+ NESWatch[] watches;
+ }
+
+ public CoreComm CoreComm { get; private set; }
+
+ public DisplayType Region { get { return _display_type; } }
+
+ class MyVideoProvider : IVideoProvider
+ {
+ //public int ntsc_top = 8;
+ //public int ntsc_bottom = 231;
+ //public int pal_top = 0;
+ //public int pal_bottom = 239;
+ public int left = 0;
+ public int right = 255;
+
+ NES emu;
+ public MyVideoProvider(NES emu)
+ {
+ this.emu = emu;
+ }
+
+ int[] pixels = new int[256 * 240];
+ public int[] GetVideoBuffer()
+ {
+ return pixels;
+ }
+
+ public void FillFrameBuffer()
+ {
+ int the_top;
+ int the_bottom;
+ if (emu.Region == DisplayType.NTSC)
+ {
+ the_top = emu.Settings.NTSC_TopLine;
+ the_bottom = emu.Settings.NTSC_BottomLine;
+ }
+ else
+ {
+ the_top = emu.Settings.PAL_TopLine;
+ the_bottom = emu.Settings.PAL_BottomLine;
+ }
+
+ int backdrop = 0;
+ backdrop = emu.Settings.BackgroundColor;
+ bool useBackdrop = (backdrop & 0xFF000000) != 0;
+
+ if (useBackdrop)
+ {
+ int width = BufferWidth;
+ for (int x = left; x <= right; x++)
+ {
+ for (int y = the_top; y <= the_bottom; y++)
+ {
+ 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;
+ }
+ }
+ }
+ }
+ }
+ public int VirtualWidth { get { return (int)(BufferWidth * 1.146); } }
+ public int VirtualHeight { get { return BufferHeight; } }
+ public int BufferWidth { get { return right - left + 1; } }
+ public int BackgroundColor { get { return 0; } }
+ public int BufferHeight
+ {
+ get
+ {
+ if (emu.Region == DisplayType.NTSC)
+ {
+ return emu.Settings.NTSC_BottomLine - emu.Settings.NTSC_TopLine + 1;
+ }
+ else
+ {
+ return emu.Settings.PAL_BottomLine - emu.Settings.PAL_TopLine + 1;
+ }
+ }
+ }
+
+ }
+
+ MyVideoProvider videoProvider;
+ public ISoundProvider SoundProvider { get { return magicSoundProvider; } }
+ public ISyncSoundProvider SyncSoundProvider { get { return magicSoundProvider; } }
+ public bool StartAsyncSound() { return true; }
+ public void EndAsyncSound() { }
+
+ [Obsolete] // with the changes to both nes and quicknes cores, nothing uses this anymore
+ public static readonly ControllerDefinition NESController =
+ new ControllerDefinition
+ {
+ Name = "NES Controller",
+ BoolButtons = {
+ "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"
+ }
+ };
+
+ public ControllerDefinition ControllerDefinition { get; private set; }
+
+ IController controller;
+ public IController Controller
+ {
+ get { return controller; }
+ set { controller = value; }
+ }
+
+ int _frame;
+
+ public int Frame { get { return _frame; } set { _frame = value; } }
+
+ public void ResetCounters()
+ {
+ _frame = 0;
+ _lagcount = 0;
+ islag = false;
+ }
+
+ public long Timestamp { get; private set; }
+
+ public bool DeterministicEmulation { get { return true; } }
+
+ public string SystemId { get { return "NES"; } }
+
+ public string GameName { get { return game_name; } }
+
+ public enum EDetectionOrigin
+ {
+ None, BootGodDB, GameDB, INES, UNIF, FDS, NSF
+ }
+
+ StringWriter LoadReport;
+ void LoadWriteLine(string format, params object[] arg)
+ {
+ Console.WriteLine(format, arg);
+ LoadReport.WriteLine(format, arg);
+ }
+ void LoadWriteLine(object arg) { LoadWriteLine("{0}", arg); }
+
+ class MyWriter : StringWriter
+ {
+ public MyWriter(TextWriter _loadReport)
+ {
+ loadReport = _loadReport;
+ }
+ TextWriter loadReport;
+ public override void WriteLine(string format, params object[] arg)
+ {
+ Console.WriteLine(format, arg);
+ loadReport.WriteLine(format, arg);
+ }
+ public override void WriteLine(string value)
+ {
+ Console.WriteLine(value);
+ loadReport.WriteLine(value);
+ }
+ }
+
+ public void Init(GameInfo gameInfo, byte[] rom, byte[] fdsbios = null)
+ {
+ LoadReport = new StringWriter();
+ LoadWriteLine("------");
+ LoadWriteLine("BEGIN NES rom analysis:");
+ byte[] file = rom;
+
+ Type boardType = null;
+ CartInfo choice = null;
+ CartInfo iNesHeaderInfo = null;
+ CartInfo iNesHeaderInfoV2 = null;
+ List hash_sha1_several = new List();
+ string hash_sha1 = null, hash_md5 = null;
+ Unif unif = null;
+
+ Dictionary InitialMapperRegisterValues = new Dictionary(SyncSettings.BoardProperties);
+
+ origin = EDetectionOrigin.None;
+
+ 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")))
+ {
+ unif = new Unif(new MemoryStream(file));
+ LoadWriteLine("Found UNIF header:");
+ LoadWriteLine(unif.CartInfo);
+ LoadWriteLine("Since this is UNIF we can confidently parse PRG/CHR banks to hash.");
+ hash_sha1 = unif.CartInfo.sha1;
+ hash_sha1_several.Add(hash_sha1);
+ LoadWriteLine("headerless rom hash: {0}", hash_sha1);
+ }
+ 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();
+ AutoMapperProps.Populate(Board, SyncSettings);
+
+ Console.WriteLine("Using NTSC display type for NSF for now");
+ _display_type = Common.DisplayType.NTSC;
+
+ HardReset();
+
+ return;
+ }
+ else if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("FDS\x1A"))
+ || file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("\x01*NI")))
+ {
+ // 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
+ origin = EDetectionOrigin.FDS;
+ LoadWriteLine("Found FDS header.");
+ if (fdsbios == null)
+ throw new MissingFirmwareException("Missing FDS Bios");
+ cart = new CartInfo();
+ var fdsboard = new FDS();
+ fdsboard.biosrom = fdsbios;
+ fdsboard.SetDiskImage(rom);
+ fdsboard.Create(this);
+ // at the moment, FDS doesn't use the IRVs, but it could at some point in the future
+ fdsboard.InitialRegisterValues = InitialMapperRegisterValues;
+ fdsboard.Configure(origin);
+
+ Board = fdsboard;
+
+ //create the vram and wram if necessary
+ if (cart.wram_size != 0)
+ Board.WRAM = new byte[cart.wram_size * 1024];
+ if (cart.vram_size != 0)
+ Board.VRAM = new byte[cart.vram_size * 1024];
+
+ Board.PostConfigure();
+ AutoMapperProps.Populate(Board, SyncSettings);
+
+ Console.WriteLine("Using NTSC display type for FDS disk image");
+ _display_type = Common.DisplayType.NTSC;
+
+ HardReset();
+
+ return;
+ }
+ else
+ {
+ byte[] nesheader = new byte[16];
+ Buffer.BlockCopy(file, 0, nesheader, 0, 16);
+
+ if (!DetectFromINES(nesheader, out iNesHeaderInfo, out iNesHeaderInfoV2))
+ throw new InvalidOperationException("iNES header not found");
+
+ //now that we know we have an iNES header, we can try to ignore it.
+
+ hash_sha1 = "sha1:" + file.HashSHA1(16, file.Length - 16);
+ hash_sha1_several.Add(hash_sha1);
+ hash_md5 = "md5:" + file.HashMD5(16, file.Length - 16);
+
+ 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.");
+
+ LoadWriteLine("headerless rom hash: {0}", hash_sha1);
+ LoadWriteLine("headerless rom hash: {0}", hash_md5);
+
+ 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
+ msTemp.Write(file, 16 + 16 * 1024, iNesHeaderInfo.chr_size * 1024); //add chr
+ 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);
+ }
+ }
+
+ if (USE_DATABASE)
+ {
+ if (hash_md5 != null) choice = IdentifyFromGameDB(hash_md5);
+ if (choice == null)
+ choice = IdentifyFromGameDB(hash_sha1);
+ if (choice == null)
+ LoadWriteLine("Could not locate game in bizhawk gamedb");
+ else
+ {
+ 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
+ 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)
+ {
+ if (choice.prg_size == -1) choice.prg_size = unif.CartInfo.prg_size;
+ if (choice.chr_size == -1) choice.chr_size = unif.CartInfo.chr_size;
+ // 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;
+ }
+ }
+
+ //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;
+ }
+ }
+ }
+
+ //if choice is still null, try UNIF and iNES
+ if (choice == null)
+ {
+ if (unif != null)
+ {
+ LoadWriteLine("Using information from UNIF header");
+ choice = unif.CartInfo;
+ //ok, i have this Q-Boy rom with no VROM and no VRAM.
+ //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;
+ //(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)
+ origin = EDetectionOrigin.UNIF;
+ }
+ if (iNesHeaderInfo != null)
+ {
+ LoadWriteLine("Attempting inference from iNES header");
+ // try to spin up V2 header first, then V1 header
+ if (iNesHeaderInfoV2 != null)
+ {
+ 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
+ }
+ if (boardType == null)
+ {
+ choice = iNesHeaderInfo; // we're out of options, really
+ boardType = FindBoard(iNesHeaderInfo, origin, InitialMapperRegisterValues);
+ 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
+ }
+
+ LoadWriteLine("Chose board from iNES heuristics:");
+ LoadWriteLine(choice);
+ origin = EDetectionOrigin.INES;
+ }
+ }
+
+ game_name = choice.name;
+
+ //find a INESBoard to handle this
+ if (choice != null)
+ boardType = FindBoard(choice, origin, InitialMapperRegisterValues);
+ else
+ throw new Exception("Unable to detect ROM");
+ if (boardType == null)
+ throw new Exception("No class implements the necessary board type: " + choice.board_type);
+
+ if (choice.DB_GameInfo != null)
+ choice.bad = choice.DB_GameInfo.IsRomStatusBad();
+
+ 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 ~~");
+ }
+
+ LoadWriteLine("END NES rom analysis");
+ LoadWriteLine("------");
+
+ Board = CreateBoardInstance(boardType);
+
+ cart = choice;
+ Board.Create(this);
+ Board.InitialRegisterValues = InitialMapperRegisterValues;
+ Board.Configure(origin);
+
+ if (origin == EDetectionOrigin.BootGodDB)
+ {
+ RomStatus = RomStatus.GoodDump;
+ CoreComm.RomStatusAnnotation = "Identified from BootGod's database";
+ }
+ if (origin == EDetectionOrigin.UNIF)
+ {
+ RomStatus = RomStatus.NotInDatabase;
+ CoreComm.RomStatusAnnotation = "Inferred from UNIF header; somewhat suspicious";
+ }
+ if (origin == EDetectionOrigin.INES)
+ {
+ RomStatus = RomStatus.NotInDatabase;
+ CoreComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong";
+ }
+ if (origin == EDetectionOrigin.GameDB)
+ {
+ if (choice.bad)
+ {
+ RomStatus = RomStatus.BadDump;
+ }
+ else
+ {
+ RomStatus = choice.DB_GameInfo.Status;
+ }
+ }
+
+ byte[] trainer = null;
+
+ //create the board's rom and vrom
+ if (iNesHeaderInfo != null)
+ {
+ var ms = new MemoryStream(file, false);
+ ms.Seek(16, SeekOrigin.Begin); // ines header
+ //pluck the necessary bytes out of the file
+ if (iNesHeaderInfo.trainer_size != 0)
+ {
+ trainer = new byte[512];
+ ms.Read(trainer, 0, 512);
+ }
+
+ 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);
+ }
+ if (choice.prg_size != iNesHeaderInfo.prg_size || choice.chr_size != iNesHeaderInfo.chr_size)
+ LoadWriteLine("Warning: Detected choice has different filesizes than the INES header!");
+ }
+ else
+ {
+ Board.ROM = unif.PRG;
+ Board.VROM = unif.CHR;
+ }
+
+ LoadReport.Flush();
+ CoreComm.RomStatusDetails = LoadReport.ToString();
+
+ // IF YOU DO ANYTHING AT ALL BELOW THIS LINE, MAKE SURE THE APPROPRIATE CHANGE IS MADE TO FDS (if applicable)
+
+ //create the vram and wram if necessary
+ if (cart.wram_size != 0)
+ Board.WRAM = new byte[cart.wram_size * 1024];
+ if (cart.vram_size != 0)
+ Board.VRAM = new byte[cart.vram_size * 1024];
+
+ Board.PostConfigure();
+ AutoMapperProps.Populate(Board, SyncSettings);
+
+ // 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:
+ _display_type = Common.DisplayType.DENDY;
+ 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);
+
+ HardReset();
+
+ if (trainer != null)
+ {
+ Console.WriteLine("Applying trainer");
+ for (int i = 0; i < 512; i++)
+ WriteMemory((ushort)(0x7000 + i), trainer[i]);
+ }
+ }
+
+ static NESSyncSettings.Region DetectRegion(string system)
+ {
+ 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;
+ }
+ }
+
+ private ITraceable Tracer { get; set; }
+ }
+}
+
+//todo
+//http://blog.ntrq.net/?p=428
+//cpu bus junk bits
+
+//UBER DOC
+//http://nocash.emubase.de/everynes.htm
+
+//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
+
+//some ppu tests
+//http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb
\ No newline at end of file
diff --git a/PPU.cs b/PPU.cs
new file mode 100644
index 0000000000..ac81b90224
--- /dev/null
+++ b/PPU.cs
@@ -0,0 +1,251 @@
+//http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb
+
+using System;
+using System.Runtime.CompilerServices;
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Nintendo.NES
+{
+ public sealed partial class PPU
+ {
+ // this only handles region differences within the PPU
+ int preNMIlines;
+ int postNMIlines;
+ bool chopdot;
+ public enum Region { NTSC, PAL, Dendy, RGB };
+ Region _region;
+ public Region region { set { _region = value; SyncRegion(); } get { return _region; } }
+ void SyncRegion()
+ {
+ switch (region)
+ {
+ case Region.NTSC:
+ preNMIlines = 1; postNMIlines = 20; chopdot = true; break;
+ case Region.PAL:
+ preNMIlines = 1; postNMIlines = 70; chopdot = false; break;
+ case Region.Dendy:
+ preNMIlines = 51; postNMIlines = 20; chopdot = false; break;
+ case Region.RGB:
+ preNMIlines = 1; postNMIlines = 20; chopdot = false; break;
+ }
+ }
+
+ public class DebugCallback
+ {
+ public int Scanline;
+ //public int Dot; //not supported
+ public Action Callback;
+ }
+
+ public DebugCallback NTViewCallback;
+ public DebugCallback PPUViewCallback;
+
+ // true = light sensed
+ public bool LightGunCallback(int x, int y)
+ {
+ // the actual light gun circuit is very complex
+ // and this doesn't do it justice at all, as expected
+
+ const int radius = 10; // look at pixel values up to this far away, roughly
+
+ int sum = 0;
+ int ymin = Math.Max(Math.Max(y - radius, ppur.status.sl - 20), 0);
+ int ymax = Math.Min(y + radius, 239);
+ int xmin = Math.Max(x - radius, 0);
+ int xmax = Math.Min(x + radius, 255);
+
+ int ystop = ppur.status.sl - 2;
+ int xstop = ppur.status.cycle - 20;
+
+ for (int j = ymin; j <= ymax; j++)
+ {
+ for (int i = xmin; i <= xmax; i++)
+ {
+ if (j >= ystop && i >= xstop || j > ystop)
+ goto loopout;
+
+ short s = xbuf[j * 256 + i];
+ int lum = s & 0x30;
+ if ((s & 0x0f) >= 0x0e)
+ lum = 0;
+ sum += lum;
+ }
+ }
+ loopout:
+ return sum >= 2000;
+ }
+
+
+ //when the ppu issues a write it goes through here and into the game board
+ public void ppubus_write(int addr, byte value)
+ {
+ nes.Board.AddressPPU(addr);
+ nes.Board.WritePPU(addr, value);
+ }
+
+ //when the ppu issues a read it goes through here and into the game board
+ public byte ppubus_read(int addr, bool ppu)
+ {
+ //hardware doesnt touch the bus when the PPU is disabled
+ if (!reg_2001.PPUON && ppu)
+ return 0xFF;
+
+ nes.Board.AddressPPU(addr);
+ return nes.Board.ReadPPU(addr);
+ }
+
+ //debug tools peek into the ppu through this
+ public byte ppubus_peek(int addr)
+ {
+ return nes.Board.PeekPPU(addr);
+ }
+
+ public enum PPUPHASE
+ {
+ VBL, BG, OBJ
+ };
+ public PPUPHASE ppuphase;
+
+ private readonly NES nes;
+ public PPU(NES nes)
+ {
+ this.nes = nes;
+
+ OAM = new byte[0x100];
+ PALRAM = new byte[0x20];
+
+ //power-up palette verified by blargg's power_up_palette test.
+ //he speculates that these may differ depending on the system tested..
+ //and I don't see why the ppu would waste any effort setting these..
+ //but for the sake of uniformity, we'll do it.
+ Array.Copy(new byte[] {
+ 0x09,0x01,0x00,0x01,0x00,0x02,0x02,0x0D,0x08,0x10,0x08,0x24,0x00,0x00,0x04,0x2C,
+ 0x09,0x01,0x34,0x03,0x00,0x04,0x00,0x14,0x08,0x3A,0x00,0x02,0x00,0x20,0x2C,0x08
+ }, PALRAM, 0x20);
+
+ Reset();
+ }
+
+ public void NESSoftReset()
+ {
+ //this hasn't been brought up to date since NEShawk was first made.
+ //in particular http://wiki.nesdev.com/w/index.php/PPU_power_up_state should be studied, but theres no use til theres test cases
+ Reset();
+ }
+
+ //state
+ int ppudead; //measured in frames
+ bool idleSynch;
+ int NMI_PendingInstructions;
+ byte PPUGenLatch;
+ bool vtoggle;
+ byte VRAMBuffer;
+ public byte[] OAM;
+ public byte[] PALRAM;
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("ppudead", ref ppudead);
+ ser.Sync("idleSynch", ref idleSynch);
+ ser.Sync("NMI_PendingInstructions", ref NMI_PendingInstructions);
+ ser.Sync("PPUGenLatch", ref PPUGenLatch);
+ ser.Sync("vtoggle", ref vtoggle);
+ ser.Sync("VRAMBuffer", ref VRAMBuffer);
+ ser.Sync("ppu_addr_temp", ref ppu_addr_temp);
+
+ ser.Sync("Read_Value", ref read_value);
+ ser.Sync("Prev_soam_index", ref soam_index_prev);
+ ser.Sync("Spr_Zero_Go", ref sprite_zero_go);
+ ser.Sync("Spr_zero_in_Range", ref sprite_zero_in_range);
+ ser.Sync("Is_even_cycle", ref is_even_cycle);
+ ser.Sync("soam_index", ref soam_index);
+
+ ser.Sync("ppu_open_bus", ref ppu_open_bus);
+ ser.Sync("ppu_open_bus_decay_timer", ref ppu_open_bus_decay_timer, false);
+
+ ser.Sync("OAM", ref OAM, false);
+ ser.Sync("PALRAM", ref PALRAM, false);
+
+ ser.Sync("Reg2002_objoverflow", ref Reg2002_objoverflow);
+ ser.Sync("Reg2002_objhit", ref Reg2002_objhit);
+ ser.Sync("Reg2002_vblank_active", ref Reg2002_vblank_active);
+ ser.Sync("Reg2002_vblank_active_pending", ref Reg2002_vblank_active_pending);
+ ser.Sync("Reg2002_vblank_clear_pending", ref Reg2002_vblank_clear_pending);
+ ppur.SyncState(ser);
+ byte temp8 = reg_2000.Value; ser.Sync("reg_2000.Value", ref temp8); reg_2000.Value = temp8;
+ temp8 = reg_2001.Value; ser.Sync("reg_2001.Value", ref temp8); reg_2001.Value = temp8;
+ ser.Sync("reg_2003", ref reg_2003);
+
+ //don't sync framebuffer into binary (rewind) states
+ if(ser.IsText)
+ ser.Sync("xbuf", ref xbuf, false);
+ }
+
+ public void Reset()
+ {
+ regs_reset();
+ ppudead = 2;
+ idleSynch = true;
+ ppu_open_bus = 0;
+ ppu_open_bus_decay_timer = new int[8];
+ }
+
+#if VS2012
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ void TriggerNMI()
+ {
+ nes.cpu.NMI = true;
+ }
+
+ //this gets called once after each cpu instruction executes.
+ //anything that needs to happen at instruction granularity can get checked here
+ //to save having to check it at ppu cycle granularity
+ public void PostCpuInstructionOne()
+ {
+ if (NMI_PendingInstructions > 0)
+ {
+ NMI_PendingInstructions--;
+ if (NMI_PendingInstructions <= 0)
+ {
+ TriggerNMI();
+ }
+ }
+ }
+
+#if VS2012
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ void runppu(int x)
+ {
+ //run one ppu cycle at a time so we can interact with the ppu and clockPPU at high granularity
+ for (int i = 0; i < x; i++)
+ {
+ ppur.status.cycle++;
+ is_even_cycle = !is_even_cycle;
+ //might not actually run a cpu cycle if there are none to be run right now
+ nes.RunCpuOne();
+
+ if (Reg2002_vblank_active_pending)
+ {
+ //if (Reg2002_vblank_active_pending)
+ Reg2002_vblank_active = 1;
+ Reg2002_vblank_active_pending = false;
+ }
+
+ if (Reg2002_vblank_clear_pending)
+ {
+ Reg2002_vblank_active = 0;
+ Reg2002_vblank_clear_pending = false;
+ }
+
+ nes.Board.ClockPPU();
+ }
+ }
+
+ //hack
+ //public bool PAL = false;
+ //bool SPRITELIMIT = true;
+
+ }
+}
diff --git a/PPU.regs.cs b/PPU.regs.cs
new file mode 100644
index 0000000000..401963421c
--- /dev/null
+++ b/PPU.regs.cs
@@ -0,0 +1,720 @@
+//TODO - better sprite hit handling (be sure to test world runner)
+//http://nesdev.parodius.com/bbs/viewtopic.php?t=626
+
+//TODO - Reg2002_objoverflow is not working in the dummy reads test.. why are we setting it when nintendulator doesnt>
+
+//blargg: Reading from $2007 when the VRAM address is $3fxx will fill the internal read buffer with the contents at VRAM address $3fxx, in addition to reading the palette RAM.
+
+ //static const byte powerUpPalette[] =
+ //{
+ // 0x3F,0x01,0x00,0x01, 0x00,0x02,0x02,0x0D, 0x08,0x10,0x08,0x24, 0x00,0x00,0x04,0x2C,
+ // 0x09,0x01,0x34,0x03, 0x00,0x04,0x00,0x14, 0x08,0x3A,0x00,0x02, 0x00,0x20,0x2C,0x08
+ //};
+
+using System;
+using BizHawk.Common;
+
+
+namespace BizHawk.Emulation.Cores.Nintendo.NES
+{
+ sealed partial class PPU
+ {
+ public sealed class Reg_2001
+ {
+ public Bit color_disable; //Color disable (0: normal color; 1: AND all palette entries with 110000, effectively producing a monochrome display)
+ public Bit show_bg_leftmost; //Show leftmost 8 pixels of background
+ public Bit show_obj_leftmost; //Show sprites in leftmost 8 pixels
+ public Bit show_bg; //Show background
+ public Bit show_obj; //Show sprites
+ public Bit intense_green; //Intensify greens (and darken other colors)
+ public Bit intense_blue; //Intensify blues (and darken other colors)
+ public Bit intense_red; //Intensify reds (and darken other colors)
+
+ public int intensity_lsl_6; //an optimization..
+
+ public bool PPUON { get { return show_bg || show_obj; } }
+
+ public byte Value
+ {
+ get
+ {
+ return (byte)(color_disable | (show_bg_leftmost << 1) | (show_obj_leftmost << 2) | (show_bg << 3) | (show_obj << 4) | (intense_green << 5) | (intense_blue << 6) | (intense_red << 7));
+ }
+ set
+ {
+ color_disable = (value & 1);
+ show_bg_leftmost = (value >> 1) & 1;
+ show_obj_leftmost = (value >> 2) & 1;
+ show_bg = (value >> 3) & 1;
+ show_obj = (value >> 4) & 1;
+ intense_green = (value >> 5) & 1;
+ intense_blue = (value >> 6) & 1;
+ intense_red = (value >> 7) & 1;
+ intensity_lsl_6 = ((value >> 5) & 7)<<6;
+ }
+ }
+ }
+
+ // this byte is used to simulate open bus reads and writes
+ // it should be modified by every read and write to a ppu register
+ public byte ppu_open_bus;
+ public int[] ppu_open_bus_decay_timer = new int[8];
+
+ public struct PPUSTATUS
+ {
+ public int sl;
+ public bool rendering { get { return sl >= 0 && sl < 241; } }
+ public int cycle;
+ }
+
+ //uses the internal counters concept at http://nesdev.icequake.net/PPU%20addressing.txt
+ //TODO - this should be turned into a state machine
+ public sealed class PPUREGS
+ {
+ PPU ppu;
+ public PPUREGS(PPU ppu)
+ {
+ this.ppu = ppu;
+ reset();
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("fv", ref fv);
+ ser.Sync("v", ref v);
+ ser.Sync("h", ref h);
+ ser.Sync("vt", ref vt);
+ ser.Sync("ht", ref ht);
+ ser.Sync("_fv", ref _fv);
+ ser.Sync("_v", ref _v);
+ ser.Sync("_h", ref _h);
+ ser.Sync("_vt", ref _vt);
+ ser.Sync("_ht", ref _ht);
+ ser.Sync("fh", ref fh);
+ ser.Sync("status.cycle", ref status.cycle);
+ int junk = 0;
+ ser.Sync("status.end_cycle", ref junk);
+ ser.Sync("status.sl", ref status.sl);
+ }
+
+ //normal clocked regs. as the game can interfere with these at any time, they need to be savestated
+ public int fv;//3
+ public int v;//1
+ public int h;//1
+ public int vt;//5
+ public int ht;//5
+
+ //temp unlatched regs (need savestating, can be written to at any time)
+ public int _fv, _vt, _v, _h, _ht;
+
+ //other regs that need savestating
+ public int fh;//3 (horz scroll)
+
+ //cached state data. these are always reset at the beginning of a frame and don't need saving
+ //but just to be safe, we're gonna save it
+ public PPUSTATUS status = new PPUSTATUS();
+
+ //public int ComputeIndex()
+ //{
+ // return fv | (v << 3) | (h << 4) | (vt << 5) | (ht << 10) | (fh << 15);
+ //}
+ //public void DecodeIndex(int index)
+ //{
+ // fv = index & 7;
+ // v = (index >> 3) & 1;
+ // h = (index >> 4) & 1;
+ // vt = (index >> 5) & 0x1F;
+ // ht = (index >> 10) & 0x1F;
+ // fh = (index >> 15) & 7;
+ //}
+
+ //const int tbl_size = 1 << 18;
+ //int[] tbl_increment_hsc = new int[tbl_size];
+ //int[] tbl_increment_vs = new int[tbl_size];
+ //public void BuildTables()
+ //{
+ // for (int i = 0; i < tbl_size; i++)
+ // {
+ // DecodeIndex(i);
+ // increment_hsc();
+ // tbl_increment_hsc[i] = ComputeIndex();
+ // DecodeIndex(i);
+ // increment_vs();
+ // tbl_increment_vs[i] = ComputeIndex();
+ // }
+ //}
+
+ public void reset()
+ {
+ fv = v = h = vt = ht = 0;
+ fh = 0;
+ _fv = _v = _h = _vt = _ht = 0;
+ status.cycle = 0;
+ status.sl = 241;
+ }
+
+ public void install_latches()
+ {
+ fv = _fv;
+ v = _v;
+ h = _h;
+ vt = _vt;
+ ht = _ht;
+ }
+
+ public void install_h_latches()
+ {
+ ht = _ht;
+ h = _h;
+ }
+
+ public void clear_latches()
+ {
+ _fv = _v = _h = _vt = _ht = 0;
+ fh = 0;
+ }
+
+ public void increment_hsc()
+ {
+ //The first one, the horizontal scroll counter, consists of 6 bits, and is
+ //made up by daisy-chaining the HT counter to the H counter. The HT counter is
+ //then clocked every 8 pixel dot clocks (or every 8/3 CPU clock cycles).
+ ht++;
+ h += (ht >> 5);
+ ht &= 31;
+ h &= 1;
+ }
+
+ public void increment_vs()
+ {
+ fv++;
+ int fv_overflow = (fv >> 3);
+ vt += fv_overflow;
+ vt &= 31; //fixed tecmo super bowl
+ if (vt == 30 && fv_overflow==1) //caution here (only do it at the exact instant of overflow) fixes p'radikus conflict
+ {
+ v++;
+ vt = 0;
+ }
+ fv &= 7;
+ v &= 1;
+ }
+
+ public int get_ntread()
+ {
+ return 0x2000 | (v << 0xB) | (h << 0xA) | (vt << 5) | ht;
+ }
+
+ public int get_2007access()
+ {
+ return ((fv & 3) << 0xC) | (v << 0xB) | (h << 0xA) | (vt << 5) | ht;
+ }
+
+ //The PPU has an internal 4-position, 2-bit shifter, which it uses for
+ //obtaining the 2-bit palette select data during an attribute table byte
+ //fetch. To represent how this data is shifted in the diagram, letters a..c
+ //are used in the diagram to represent the right-shift position amount to
+ //apply to the data read from the attribute data (a is always 0). This is why
+ //you only see bits 0 and 1 used off the read attribute data in the diagram.
+ public int get_atread()
+ {
+ return 0x2000 | (v << 0xB) | (h << 0xA) | 0x3C0 | ((vt & 0x1C) << 1) | ((ht & 0x1C) >> 2);
+ }
+
+ //address line 3 relates to the pattern table fetch occuring (the PPU always makes them in pairs).
+ public int get_ptread(int par)
+ {
+ int s = ppu.reg_2000.bg_pattern_hi;
+ return (s << 0xC) | (par << 0x4) | fv;
+ }
+
+ public void increment2007(bool rendering, bool by32)
+ {
+ if (rendering)
+ {
+ //don't do this:
+ //if (by32) increment_vs();
+ //else increment_hsc();
+ //do this instead:
+ increment_vs(); //yes, even if we're moving by 32
+ return;
+ }
+
+ //If the VRAM address increment bit (2000.2) is clear (inc. amt. = 1), all the
+ //scroll counters are daisy-chained (in the order of HT, VT, H, V, FV) so that
+ //the carry out of each counter controls the next counter's clock rate. The
+ //result is that all 5 counters function as a single 15-bit one. Any access to
+ //2007 clocks the HT counter here.
+ //
+ //If the VRAM address increment bit is set (inc. amt. = 32), the only
+ //difference is that the HT counter is no longer being clocked, and the VT
+ //counter is now being clocked by access to 2007.
+ if (by32)
+ {
+ vt++;
+ }
+ else
+ {
+ ht++;
+ vt += (ht >> 5) & 1;
+ }
+ h += (vt >> 5);
+ v += (h >> 1);
+ fv += (v >> 1);
+ ht &= 31;
+ vt &= 31;
+ h &= 1;
+ v &= 1;
+ fv &= 7;
+ }
+ };
+
+ public sealed class Reg_2000
+ {
+ PPU ppu;
+ public Reg_2000(PPU ppu)
+ {
+ this.ppu = ppu;
+ }
+ //these bits go straight into PPUR
+ //(00 = $2000; 01 = $2400; 02 = $2800; 03 = $2c00)
+
+ public Bit vram_incr32; //(0: increment by 1, going across; 1: increment by 32, going down)
+ public Bit obj_pattern_hi; //Sprite pattern table address for 8x8 sprites (0: $0000; 1: $1000)
+ public Bit bg_pattern_hi; //Background pattern table address (0: $0000; 1: $1000)
+ public Bit obj_size_16; //Sprite size (0: 8x8 sprites; 1: 8x16 sprites)
+ public Bit ppu_layer; //PPU layer select (should always be 0 in the NES; some Nintendo arcade boards presumably had two PPUs)
+ public Bit vblank_nmi_gen; //Vertical blank NMI generation (0: off; 1: on)
+
+
+ public byte Value
+ {
+ get
+ {
+ return (byte)(ppu.ppur._h | (ppu.ppur._v << 1) | (vram_incr32 << 2) | (obj_pattern_hi << 3) | (bg_pattern_hi << 4) | (obj_size_16 << 5) | (ppu_layer << 6) | (vblank_nmi_gen << 7));
+ }
+ set
+ {
+ ppu.ppur._h = value & 1;
+ ppu.ppur._v = (value >> 1) & 1;
+ vram_incr32 = (value >> 2) & 1;
+ obj_pattern_hi = (value >> 3) & 1;
+ bg_pattern_hi = (value >> 4) & 1;
+ obj_size_16 = (value >> 5) & 1;
+ ppu_layer = (value >> 6) & 1;
+ vblank_nmi_gen = (value >> 7) & 1;
+ }
+ }
+ }
+
+
+ Bit Reg2002_objoverflow; //Sprite overflow. The PPU can handle only eight sprites on one scanline and sets this bit if it starts drawing sprites.
+ Bit Reg2002_objhit; //Sprite 0 overlap. Set when a nonzero pixel of sprite 0 is drawn overlapping a nonzero background pixel. Used for raster timing.
+ Bit Reg2002_vblank_active; //Vertical blank start (0: has not started; 1: has started)
+ bool Reg2002_vblank_active_pending; //set if Reg2002_vblank_active is pending
+ bool Reg2002_vblank_clear_pending; //ppu's clear of vblank flag is pending
+ public PPUREGS ppur;
+ public Reg_2000 reg_2000;
+ public Reg_2001 reg_2001;
+ byte reg_2003;
+ void regs_reset()
+ {
+ //TODO - would like to reconstitute the entire PPU instead of all this..
+ reg_2000 = new Reg_2000(this);
+ reg_2001 = new Reg_2001();
+ ppur = new PPUREGS(this);
+ Reg2002_objoverflow = false;
+ Reg2002_objhit = false;
+ Reg2002_vblank_active = false;
+ PPUGenLatch = 0;
+ reg_2003 = 0;
+ vtoggle = false;
+ VRAMBuffer = 0;
+ }
+ //---------------------
+
+ //PPU CONTROL (write)
+ void write_2000(byte value)
+ {
+ if (!reg_2000.vblank_nmi_gen & ((value & 0x80) != 0) && (Reg2002_vblank_active) && !Reg2002_vblank_clear_pending)
+ {
+ //if we just unleashed the vblank interrupt then activate it now
+ NMI_PendingInstructions = 2;
+ }
+ reg_2000.Value = value;
+
+
+ }
+ byte read_2000() { return ppu_open_bus; }
+ byte peek_2000() { return ppu_open_bus; }
+
+ //PPU MASK (write)
+ void write_2001(byte value)
+ {
+ //printf("%04x:$%02x, %d\n",A,V,scanline);
+ reg_2001.Value = value;
+ }
+ byte read_2001() { return ppu_open_bus; }
+ byte peek_2001() { return ppu_open_bus; }
+
+ //PPU STATUS (read)
+ void write_2002(byte value) { }
+ byte read_2002()
+ {
+ //once we thought we clear latches here, but that caused midframe glitches.
+ //i think we should only reset the state machine for 2005/2006
+ //ppur.clear_latches();
+
+ byte ret = peek_2002();
+
+ vtoggle = false;
+ Reg2002_vblank_active = 0;
+ Reg2002_vblank_active_pending = false;
+
+ // update the open bus here
+ ppu_open_bus = ret;
+ ppu_open_bus_decay(2);
+ return ret;
+ }
+ byte peek_2002()
+ {
+ return (byte)((Reg2002_vblank_active << 7) | (Reg2002_objhit << 6) | (Reg2002_objoverflow << 5) | (ppu_open_bus & 0x1F));
+ }
+
+ void clear_2002()
+ {
+ Reg2002_objhit = Reg2002_objoverflow = 0;
+ Reg2002_vblank_clear_pending = true;
+ }
+
+ //OAM ADDRESS (write)
+ void write_2003(byte value)
+ {
+ //just record the oam buffer write target
+ reg_2003 = value;
+ }
+ byte read_2003() { return ppu_open_bus; }
+ byte peek_2003() { return ppu_open_bus; }
+
+ //OAM DATA (write)
+ void write_2004(byte value)
+ {
+ if ((reg_2003 & 3) == 2) value &= 0xE3; //some of the OAM bits are unwired so we mask them out here
+ //otherwise we just write this value and move on to the next oam byte
+ OAM[reg_2003] = value;
+ reg_2003++;
+ }
+ byte read_2004()
+ {
+ byte ret;
+ // behaviour depends on whether things are being rendered or not
+ if (reg_2001.show_bg || reg_2001.show_obj)
+ {
+ if (ppur.status.sl < 241)
+ {
+ if (ppur.status.cycle < 64)
+ {
+ ret = 0xFF; // during this time all reads return FF
+ }
+ else if (ppur.status.cycle < 256)
+ {
+ ret = read_value;
+ }
+ else if (ppur.status.cycle < 320)
+ {
+ ret = read_value;
+ }
+ else
+ {
+ ret = soam[0];
+ }
+ }
+ else
+ {
+ ret = OAM[reg_2003];
+ }
+ }
+ else
+ {
+ ret = OAM[reg_2003];
+ }
+ ppu_open_bus = ret;
+ ppu_open_bus_decay(1);
+ return ret;
+ }
+ byte peek_2004() { return OAM[reg_2003]; }
+
+ //SCROLL (write)
+ void write_2005(byte value)
+ {
+ if (!vtoggle)
+ {
+ ppur._ht= value >> 3;
+ ppur.fh = value & 7;
+ //nes.LogLine("scroll wrote ht = {0} and fh = {1}", ppur._ht, ppur.fh);
+ }
+ else
+ {
+ ppur._vt = value >> 3;
+ ppur._fv = value & 7;
+ //nes.LogLine("scroll wrote vt = {0} and fv = {1}", ppur._vt, ppur._fv);
+ }
+ vtoggle ^= true;
+ }
+ byte read_2005() { return ppu_open_bus; }
+ byte peek_2005() { return ppu_open_bus; }
+
+ //VRAM address register (write)
+ void write_2006(byte value)
+ {
+ if (!vtoggle)
+ {
+ ppur._vt &= 0x07;
+ ppur._vt |= (value & 0x3) << 3;
+ ppur._h = (value >> 2) & 1;
+ ppur._v = (value >> 3) & 1;
+ ppur._fv = (value >> 4) & 3;
+ //nes.LogLine("addr wrote fv = {0}", ppur._fv);
+ }
+ else
+ {
+ ppur._vt &= 0x18;
+ ppur._vt |= (value >> 5);
+ ppur._ht = value & 31;
+ ppur.install_latches();
+ //nes.LogLine("addr wrote vt = {0}, ht = {1}", ppur._vt, ppur._ht);
+
+ //normally the address isnt observed by the board till it gets clocked by a read or write.
+ //but maybe thats just because a ppu read/write shoves it on the address bus
+ //apparently this shoves it on the address bus, too, or else blargg's mmc3 tests dont pass
+ nes.Board.AddressPPU(ppur.get_2007access());
+ }
+
+ vtoggle ^= true;
+ }
+ byte read_2006() { return ppu_open_bus; }
+ byte peek_2006() { return ppu_open_bus; }
+
+ //VRAM data register (r/w)
+ void write_2007(byte value)
+ {
+ //does this take 4x longer? nestopia indicates so perhaps...
+
+ int addr = ppur.get_2007access() & 0x3FFF;
+ if ((addr & 0x3F00) == 0x3F00)
+ {
+ //handle palette. this is being done nestopia style, because i found some documentation for it (appendix 1)
+ addr &= 0x1F;
+ byte color = (byte)(value & 0x3F); //are these bits really unwired? can they be read back somehow?
+
+ //this little hack will help you debug things while the screen is black
+ //color = (byte)(addr & 0x3F);
+
+ PALRAM[addr] = color;
+ if ((addr & 3) == 0)
+ {
+ PALRAM[addr ^ 0x10] = color;
+ }
+ }
+ else
+ {
+ ppubus_write(addr, value);
+ }
+
+ ppur.increment2007(ppur.status.rendering && reg_2001.PPUON, reg_2000.vram_incr32 != 0);
+
+ //see comments in $2006
+ nes.Board.AddressPPU(ppur.get_2007access());
+ }
+ byte read_2007()
+ {
+ int addr = ppur.get_2007access() & 0x3FFF;
+ int bus_case = 0;
+ //ordinarily we return the buffered values
+ byte ret = VRAMBuffer;
+
+ //in any case, we read from the ppu bus
+ VRAMBuffer = ppubus_read(addr,false);
+
+ //but reads from the palette are implemented in the PPU and return immediately
+ if ((addr & 0x3F00) == 0x3F00)
+ {
+ //TODO apply greyscale shit?
+ ret = (byte)(PALRAM[addr & 0x1F] + ((byte)(ppu_open_bus & 0xC0)));
+ bus_case = 1;
+ }
+
+ ppur.increment2007(ppur.status.rendering && reg_2001.PPUON, reg_2000.vram_incr32 != 0);
+
+ //see comments in $2006
+ nes.Board.AddressPPU(ppur.get_2007access());
+
+ // update open bus here
+ ppu_open_bus = ret;
+ if (bus_case==0)
+ {
+ ppu_open_bus_decay(1);
+ } else
+ {
+ ppu_open_bus_decay(3);
+ }
+
+ return ret;
+ }
+ byte peek_2007()
+ {
+ int addr = ppur.get_2007access() & 0x3FFF;
+
+ //ordinarily we return the buffered values
+ byte ret = VRAMBuffer;
+
+ //in any case, we read from the ppu bus
+ // can't do this in peek; updates the value that will be used later
+ // VRAMBuffer = ppubus_peek(addr);
+
+ //but reads from the palette are implemented in the PPU and return immediately
+ if ((addr & 0x3F00) == 0x3F00)
+ {
+ //TODO apply greyscale shit?
+ ret = PALRAM[addr & 0x1F];
+ }
+
+ return ret;
+ }
+ //--------
+
+ public byte ReadReg(int addr)
+ {
+ switch (addr)
+ {
+ case 0: return read_2000(); case 1: return read_2001(); case 2: return read_2002(); case 3: return read_2003();
+ case 4: return read_2004(); case 5: return read_2005(); case 6: return read_2006(); case 7: return read_2007();
+ default: throw new InvalidOperationException();
+ }
+ }
+ public byte PeekReg(int addr)
+ {
+ switch (addr)
+ {
+ case 0: return peek_2000(); case 1: return peek_2001(); case 2: return peek_2002(); case 3: return peek_2003();
+ case 4: return peek_2004(); case 5: return peek_2005(); case 6: return peek_2006(); case 7: return peek_2007();
+ default: throw new InvalidOperationException();
+ }
+ }
+ public void WriteReg(int addr, byte value)
+ {
+ PPUGenLatch = value;
+ ppu_open_bus = value;
+ switch (addr)
+ {
+ case 0: write_2000(value); break; case 1: write_2001(value); break; case 2: write_2002(value); break; case 3: write_2003(value); break;
+ case 4: write_2004(value); break; case 5: write_2005(value); break; case 6: write_2006(value); break; case 7: write_2007(value); break;
+ default: throw new InvalidOperationException();
+ }
+ }
+
+
+ public void ppu_open_bus_decay(byte action)
+ {
+ // if there is no action, decrement the timer
+ if (action==0)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ if (ppu_open_bus_decay_timer[i] == 0)
+ {
+ ppu_open_bus = (byte)(ppu_open_bus & (0xff - (1 << i)));
+ ppu_open_bus_decay_timer[i] = 1786840; // about 1 second worth of cycles
+ }
+ else
+ {
+ ppu_open_bus_decay_timer[i]--;
+ }
+
+ }
+ }
+
+ // reset the timer for all bits (reg 2004 / 2007 (non-palette)
+ if (action==1)
+ {
+ for (int i=0; i<8; i++)
+ {
+ ppu_open_bus_decay_timer[i] = 1786840;
+ }
+
+ }
+
+ // reset the timer for high 3 bits (reg 2002)
+ if (action == 2)
+ {
+ ppu_open_bus_decay_timer[7] = 1786840;
+ ppu_open_bus_decay_timer[6] = 1786840;
+ ppu_open_bus_decay_timer[5] = 1786840;
+ }
+
+ // reset the timer for all low 6 bits (reg 2007 (palette))
+ if (action == 3)
+ {
+ for (int i = 0; i < 6; i++)
+ {
+ ppu_open_bus_decay_timer[i] = 1786840;
+ }
+ }
+ // other values of action are reserved for possibly needed expansions, but this passes
+ // ppu_open_bus for now.
+ }
+ }
+}
+
+
+ //ARead[x]=A200x;
+ //BWrite[x]=B2000;
+ //ARead[x+1]=A200x;
+ //BWrite[x+1]=B2001;
+ //ARead[x+2]=A2002;
+ //BWrite[x+2]=B2002;
+ //ARead[x+3]=A200x;
+ //BWrite[x+3]=B2003;
+ //ARead[x+4]=A2004; //A2004;
+ //BWrite[x+4]=B2004;
+ //ARead[x+5]=A200x;
+ //BWrite[x+5]=B2005;
+ //ARead[x+6]=A200x;
+ //BWrite[x+6]=B2006;
+ //ARead[x+7]=A2007;
+ //BWrite[x+7]=B2007;
+
+
+//Address Size Description
+//$0000 $1000 Pattern Table 0
+//$1000 $1000 Pattern Table 1
+//$2000 $3C0 Name Table 0
+//$23C0 $40 Attribute Table 0
+//$2400 $3C0 Name Table 1
+//$27C0 $40 Attribute Table 1
+//$2800 $3C0 Name Table 2
+//$2BC0 $40 Attribute Table 2
+//$2C00 $3C0 Name Table 3
+//$2FC0 $40 Attribute Table 3
+//$3000 $F00 Mirror of 2000h-2EFFh
+//$3F00 $10 BG Palette
+//$3F10 $10 Sprite Palette
+//$3F20 $E0 Mirror of 3F00h-3F1Fh
+
+
+//appendix 1
+//http://nocash.emubase.de/everynes.htm#ppupalettes
+//Palette Memory (25 entries used)
+// 3F00h Background Color (Color 0)
+// 3F01h-3F03h Background Palette 0 (Color 1-3)
+// 3F05h-3F07h Background Palette 1 (Color 1-3)
+// 3F09h-3F0Bh Background Palette 2 (Color 1-3)
+// 3F0Dh-3F0Fh Background Palette 3 (Color 1-3)
+// 3F11h-3F13h Sprite Palette 0 (Color 1-3)
+// 3F15h-3F17h Sprite Palette 1 (Color 1-3)
+// 3F19h-3F1Bh Sprite Palette 2 (Color 1-3)
+// 3F1Dh-3F1Fh Sprite Palette 3 (Color 1-3)
+//Palette Gaps and Mirrors
+// 3F04h,3F08h,3F0Ch - Three general purpose 6bit data registers.
+// 3F10h,3F14h,3F18h,3F1Ch - Mirrors of 3F00h,3F04h,3F08h,3F0Ch.
+// 3F20h-3FFFh - Mirrors of 3F00h-3F1Fh.
\ No newline at end of file