From ec27890aba1348cc25f3cfd498311097eabd452b Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 09:20:52 -0400 Subject: [PATCH 1/8] Improve DMC DMA timing wtih RDY passes dpcmletterbox Far more accurate but still needs to interact correctly with OAM DMA --- .../Consoles/Nintendo/NES/APU.cs | 47 +++++++++++++------ .../Consoles/Nintendo/NES/NES.Core.cs | 24 +++++++++- .../Consoles/Nintendo/NES/PPU.cs | 5 +- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs index 9108d62e37..984bded955 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs @@ -29,6 +29,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public int NoiseV = 247; public int DMCV = 167; + public int dmc_dma_countdown=-1; + public bool recalculate = false; NES nes; @@ -677,6 +679,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES out_silence = true; DMC_RATE = pal ? DMC_RATE_PAL : DMC_RATE_NTSC; timer_reload = DMC_RATE[0]; + timer = timer_reload; sample_buffer_filled = false; out_deltacounter = 64; out_bits_remaining = 0; @@ -687,8 +690,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES int timer_reload; int timer; - int user_address, user_length; - int sample_address, sample_length, sample_buffer; + int user_address; + uint user_length, sample_length; + int sample_address, sample_buffer; bool sample_buffer_filled; int out_shift, out_bits_remaining, out_deltacounter; @@ -731,9 +735,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES Clock(); } - //Any time the sample buffer is in an empty state and bytes remaining is not zero, the following occur: - if (!sample_buffer_filled && sample_length > 0) - Fetch(); + //Any time the sample buffer is in an empty state and bytes remaining is not zero, the following occur: + // also note that the halt for DMC DMA occurs on APU cycles only (hence the timer check) + if (!sample_buffer_filled && sample_length > 0 && timer % 2 == 1 && apu.dmc_dma_countdown==-1) + apu.dmc_dma_countdown = 5; } @@ -798,11 +803,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES else { //only start playback if playback is stopped + //Console.Write(sample_length); Console.Write(" "); Console.Write(sample_buffer_filled); Console.Write(" "); Console.Write(apu.dmc_irq); Console.Write("\n"); if (sample_length == 0) { sample_address = user_address; sample_length = user_length; - + } } @@ -837,7 +843,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES user_address = 0xC000 | (val << 6); break; case 3: - user_length = (val << 4) + 1; + user_length = ((uint)val << 4) + 1; break; } } @@ -845,10 +851,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public void Fetch() { //TODO - cpu/apu DMC reads need to be emulated better! - sample_buffer = apu.nes.ReadMemory((ushort)sample_address); - sample_buffer_filled = true; - sample_address = (ushort)(sample_address + 1); - sample_length--; + if (sample_length != 0) + { + sample_buffer = apu.nes.ReadMemory((ushort)sample_address); + sample_buffer_filled = true; + sample_address = (ushort)(sample_address + 1); + //Console.WriteLine(sample_length); + //Console.WriteLine(user_length); + sample_length--; + } if (sample_length == 0) { if (loop_flag) @@ -878,6 +889,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("sequencer_irq_clear_pending", ref sequencer_irq_clear_pending); ser.Sync("sequencer_irq_assert", ref sequencer_irq_assert); + ser.Sync("dmc_dma_countdown", ref dmc_dma_countdown); + ser.Sync("toggle", ref toggle); + + pulse[0].SyncState(ser); pulse[1].SyncState(ser); triangle.SyncState(ser); @@ -1042,6 +1057,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES triangle.set_lenctr_en((val >> 2) & 1); noise.set_lenctr_en((val >> 3) & 1); dmc.set_lenctr_en(val.Bit(4)); + } else if (addr == 0x4017) { @@ -1150,14 +1166,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //it can change the ouput values of the pulse/triangle channels //we want the changes to affect it on the *next* cycle. - if(DebugCallbackDivider != 0) + if (DebugCallbackDivider != 0) { - if(DebugCallbackTimer==0) + if (DebugCallbackTimer == 0) { - if(DebugCallback != null) + if (DebugCallback != null) DebugCallback(); DebugCallbackTimer = DebugCallbackDivider; - } else DebugCallbackTimer--; + } + else DebugCallbackTimer--; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index d320f931f0..daa6dfb5a4 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -317,10 +317,30 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES 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 + } + else { cpu.IRQ = _irq_apu || Board.IRQSignal; cpu.ExecuteOne(); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs index e54bc3a1d6..14a98b6bad 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs @@ -157,8 +157,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES 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("OAM", ref OAM, false); + + ser.Sync("OAM", ref OAM, false); ser.Sync("PALRAM", ref PALRAM, false); ser.Sync("Reg2002_objoverflow", ref Reg2002_objoverflow); From ff4feb5ee83fd7d5f68103733ce2e1d1bfc86075 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 17:11:06 -0400 Subject: [PATCH 2/8] ppu open bus emulation cpu_dummy_writes_ppumem - passes ppu_open_bus - passes --- NES.Core.cs | 672 ++++++++++++++++++++++++++++++++++++++++++ NES.cs | 827 ++++++++++++++++++++++++++++++++++++++++++++++++++++ PPU.cs | 251 ++++++++++++++++ PPU.regs.cs | 720 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2470 insertions(+) create mode 100644 NES.Core.cs create mode 100644 NES.cs create mode 100644 PPU.cs create mode 100644 PPU.regs.cs 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 From 3371a8093c219b0017fac378acf506dfb9601459 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 17:13:10 -0400 Subject: [PATCH 3/8] Delete PPU.regs.cs --- PPU.regs.cs | 720 ---------------------------------------------------- 1 file changed, 720 deletions(-) delete mode 100644 PPU.regs.cs diff --git a/PPU.regs.cs b/PPU.regs.cs deleted file mode 100644 index 401963421c..0000000000 --- a/PPU.regs.cs +++ /dev/null @@ -1,720 +0,0 @@ -//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 From 0ab22cb10a96c09128e8eb385a8ed957c2f61ff9 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 17:13:21 -0400 Subject: [PATCH 4/8] Delete PPU.cs --- PPU.cs | 251 --------------------------------------------------------- 1 file changed, 251 deletions(-) delete mode 100644 PPU.cs diff --git a/PPU.cs b/PPU.cs deleted file mode 100644 index ac81b90224..0000000000 --- a/PPU.cs +++ /dev/null @@ -1,251 +0,0 @@ -//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; - - } -} From 82fe1f0d6be5a059ce6c0b1849c6deea5a87c54f Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 17:13:33 -0400 Subject: [PATCH 5/8] Delete NES.cs --- NES.cs | 827 --------------------------------------------------------- 1 file changed, 827 deletions(-) delete mode 100644 NES.cs diff --git a/NES.cs b/NES.cs deleted file mode 100644 index 7d84c76b43..0000000000 --- a/NES.cs +++ /dev/null @@ -1,827 +0,0 @@ -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 From 08da6ddbfeece51b4a542f8640fd4c14daabb1e5 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 17:13:46 -0400 Subject: [PATCH 6/8] Delete NES.Core.cs --- NES.Core.cs | 672 ---------------------------------------------------- 1 file changed, 672 deletions(-) delete mode 100644 NES.Core.cs diff --git a/NES.Core.cs b/NES.Core.cs deleted file mode 100644 index fbf81ed7cf..0000000000 --- a/NES.Core.cs +++ /dev/null @@ -1,672 +0,0 @@ -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 From 27c4bac062660845b61300782d6a309f54492f9b Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 17:14:46 -0400 Subject: [PATCH 7/8] ppu open bus emulation ppu_open_bus - passes cpu_dummy_writes_ppumem - passes --- .../Consoles/Nintendo/NES/NES.Core.cs | 1 + .../Consoles/Nintendo/NES/PPU.cs | 4 + .../Consoles/Nintendo/NES/PPU.regs.cs | 120 ++++++++++++++---- 3 files changed, 103 insertions(+), 22 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index daa6dfb5a4..fbf81ed7cf 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -346,6 +346,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES cpu.ExecuteOne(); } + ppu.ppu_open_bus_decay(0); apu.RunOne(); Board.ClockCPU(); ppu.PostCpuInstructionOne(); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs index 14a98b6bad..ac81b90224 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs @@ -160,6 +160,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES 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); @@ -184,6 +186,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES regs_reset(); ppudead = 2; idleSynch = true; + ppu_open_bus = 0; + ppu_open_bus_decay_timer = new int[8]; } #if VS2012 diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs index 013c21c4ac..401963421c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs @@ -55,6 +55,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } } + // 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 { @@ -338,9 +342,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES NMI_PendingInstructions = 2; } reg_2000.Value = value; + + } - byte read_2000() { return PPUGenLatch; } - byte peek_2000() { return PPUGenLatch; } + byte read_2000() { return ppu_open_bus; } + byte peek_2000() { return ppu_open_bus; } //PPU MASK (write) void write_2001(byte value) @@ -348,8 +354,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //printf("%04x:$%02x, %d\n",A,V,scanline); reg_2001.Value = value; } - byte read_2001() { return PPUGenLatch; } - byte peek_2001() { return PPUGenLatch; } + byte read_2001() { return ppu_open_bus; } + byte peek_2001() { return ppu_open_bus; } //PPU STATUS (read) void write_2002(byte value) { } @@ -365,11 +371,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES 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) | (PPUGenLatch & 0x1F)); + return (byte)((Reg2002_vblank_active << 7) | (Reg2002_objhit << 6) | (Reg2002_objoverflow << 5) | (ppu_open_bus & 0x1F)); } void clear_2002() @@ -384,8 +393,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //just record the oam buffer write target reg_2003 = value; } - byte read_2003() { return PPUGenLatch; } - byte peek_2003() { return PPUGenLatch; } + byte read_2003() { return ppu_open_bus; } + byte peek_2003() { return ppu_open_bus; } //OAM DATA (write) void write_2004(byte value) @@ -397,37 +406,41 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } byte read_2004() { - // behaviour depends on whether things are being rendered or not + 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) { - return 0xFF; // during this time all reads return FF + ret = 0xFF; // during this time all reads return FF } else if (ppur.status.cycle < 256) { - return read_value; + ret = read_value; } else if (ppur.status.cycle < 320) { - return read_value; + ret = read_value; } else { - return soam[0]; + ret = soam[0]; } } else { - return OAM[reg_2003]; + ret = OAM[reg_2003]; } } else { - return OAM[reg_2003]; + ret = OAM[reg_2003]; } + ppu_open_bus = ret; + ppu_open_bus_decay(1); + return ret; } byte peek_2004() { return OAM[reg_2003]; } @@ -448,8 +461,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } vtoggle ^= true; } - byte read_2005() { return PPUGenLatch; } - byte peek_2005() { return PPUGenLatch; } + byte read_2005() { return ppu_open_bus; } + byte peek_2005() { return ppu_open_bus; } //VRAM address register (write) void write_2006(byte value) @@ -479,8 +492,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES vtoggle ^= true; } - byte read_2006() { return PPUGenLatch; } - byte peek_2006() { return PPUGenLatch; } + byte read_2006() { return ppu_open_bus; } + byte peek_2006() { return ppu_open_bus; } //VRAM data register (r/w) void write_2007(byte value) @@ -516,7 +529,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES byte read_2007() { int addr = ppur.get_2007access() & 0x3FFF; - + int bus_case = 0; //ordinarily we return the buffered values byte ret = VRAMBuffer; @@ -527,14 +540,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if ((addr & 0x3F00) == 0x3F00) { //TODO apply greyscale shit? - ret = PALRAM[addr & 0x1F]; + 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()); - + 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() @@ -580,12 +604,64 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES 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. } } } From d9456b0167790f9c7493477cc521ca1089767724 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Tue, 21 Jun 2016 17:33:00 -0400 Subject: [PATCH 8/8] Consistency Check So far so good --- BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs index 8e1b801a12..a6e86246f6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs @@ -369,7 +369,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //1. is it sprite#0? //2. is the bg pixel nonzero? //then, it is spritehit. - Reg2002_objhit |= (sprite_zero_go && s==0 && pixel != 0 && rasterpos < 255); + Reg2002_objhit |= (sprite_zero_go && s==0 && pixel != 0 && rasterpos < 255 && reg_2001.show_bg && reg_2001.show_obj); //priority handling, if in front of BG: bool drawsprite = !(((t_oam[s].oam_attr & 0x20) != 0) && ((pixel & 3) != 0));