diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index 3a8867bf19..3136b0e97b 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -70,6 +70,7 @@ Code + Code @@ -228,6 +229,7 @@ + diff --git a/BizHawk.Emulation/Buffer.cs b/BizHawk.Emulation/Buffer.cs index c20925a022..a2d44c3fd1 100644 --- a/BizHawk.Emulation/Buffer.cs +++ b/BizHawk.Emulation/Buffer.cs @@ -80,4 +80,20 @@ namespace BizHawk #endif } } + + public class IntBuffer : CBuffer + { + public IntBuffer(int amt) : base(amt) { } + public IntBuffer(int[] arr) : base(arr) { } + public int this[int index] + { + #if DEBUG + get { return arr[index]; } + set { arr[index] = value; } + #else + set { Write32(index, value); } + get { return Write32(index);} + #endif + } + } } \ No newline at end of file diff --git a/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs b/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs index 2bd0e9a016..091eb245b9 100644 --- a/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs +++ b/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs @@ -105,7 +105,6 @@ namespace BizHawk.Emulation.CPUs.M6502 public bool NMI; public bool CLI_Pending; public bool SEI_Pending; - public bool EscapeRequest; public void SyncState(Serializer ser) { @@ -118,6 +117,8 @@ namespace BizHawk.Emulation.CPUs.M6502 ser.Sync("S", ref S); ser.Sync("NMI", ref NMI); ser.Sync("IRQ", ref IRQ); + ser.Sync("CLI_Pending", ref CLI_Pending); + ser.Sync("SEI_Pending", ref SEI_Pending); ser.Sync("TotalExecutedCycles", ref TotalExecutedCycles); ser.Sync("PendingCycles", ref PendingCycles); ser.EndSection(); diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/ExROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/ExROM.cs new file mode 100644 index 0000000000..223ef3be4c --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/ExROM.cs @@ -0,0 +1,525 @@ +using System; +using System.IO; +using System.Diagnostics; + +//simplifications/approximations: +//* "Note that no commercial games rely on this mirroring -- therefore you can take the easy way out and simply give all MMC5 games 64k PRG-RAM." +// (i.e. ignore chipselect/page select on prg-ram) +//* in general PPU state is peeked directly instead of figuring out how the mmc5 actually accounts for things. +//* Specifically, the tall sprite mode is peeked. this is annoying.. the mmc5 should not know about that until the first tall sprite appears and asks +// for something from the right page. there should be a better way to determine this +//* Specifically, the dot number / BG/OBJ phase status is used instead of counting reads. +//* Specifically, the scanline number is used for IRQ instead of counting reads or whatever + +//TODO - tweak nametable / chr viewer to be more useful + +namespace BizHawk.Emulation.Consoles.Nintendo +{ + class MMC5 + { + NES.NESBoardBase board; + public MMC5(NES.NESBoardBase board) + { + this.board = board; + } + } + + public class ExROM : NES.NESBoardBase + { + //configuraton + int prg_bank_mask_8k, chr_bank_mask_1k; //board setup (to be isolated from ExROM later into mmc5 class) + + //state + int irq_target, irq_counter; + bool irq_enabled, irq_pending, in_frame; + int exram_mode, chr_mode, prg_mode; + int chr_reg_high; + int ab_mode; + IntBuffer regs_a = new IntBuffer(8); + IntBuffer regs_b = new IntBuffer(4); + IntBuffer regs_prg = new IntBuffer(4); + IntBuffer nt_modes = new IntBuffer(4); + byte nt_fill_tile, nt_fill_attrib; + int wram_bank; + byte[] EXRAM = new byte[1024]; + byte multiplicand, multiplier; + //regeneratable state + IntBuffer a_banks_1k = new IntBuffer(8); + IntBuffer b_banks_1k = new IntBuffer(8); + IntBuffer prg_banks_8k = new IntBuffer(4); + byte product_low, product_high; + + public override void SyncState(Serializer ser) + { + ser.Sync("irq_target", ref irq_target); + ser.Sync("irq_counter", ref irq_counter); + ser.Sync("irq_enabled", ref irq_enabled); + ser.Sync("irq_pending", ref irq_pending); + ser.Sync("in_frame", ref in_frame); + ser.Sync("exram_mode", ref exram_mode); + ser.Sync("chr_mode", ref chr_mode); + ser.Sync("prg_mode", ref prg_mode); + ser.Sync("chr_reg_high", ref chr_reg_high); + ser.Sync("ab_mode", ref chr_reg_high); + ser.Sync("regs_a", ref regs_a); + ser.Sync("regs_b", ref regs_b); + ser.Sync("regs_prg", ref regs_prg); + ser.Sync("nt_modes", ref nt_modes); + ser.Sync("nt_fill_tile", ref nt_fill_tile); + ser.Sync("nt_fill_attrib", ref nt_fill_attrib); + ser.Sync("wram_bank", ref wram_bank); + ser.Sync("EXRAM", ref EXRAM, false); + + if (ser.IsReader) + { + SyncPRGBanks(); + SyncCHRBanks(); + SyncMultiplier(); + } + } + + public override void Dispose() + { + regs_a.Dispose(); + regs_b.Dispose(); + regs_prg.Dispose(); + a_banks_1k.Dispose(); + b_banks_1k.Dispose(); + prg_banks_8k.Dispose(); + nt_modes.Dispose(); + } + + public override bool Configure(NES.EDetectionOrigin origin) + { + //analyze board type + switch (Cart.board_type) + { + case "NES-ELROM": //Castlevania 3 - Dracula's Curse (U) + AssertPrg(128,256); AssertChr(128); + break; + default: + return false; + } + + prg_bank_mask_8k = Cart.prg_size/8-1; + chr_bank_mask_1k = Cart.chr_size - 1; + + PoweronState(); + + return true; + } + + void PoweronState() + { + //set all prg regs to use ROM + regs_prg[0] = 0x80; + regs_prg[1] = 0x80; + regs_prg[2] = 0x80; + regs_prg[3] = 0xFF; + prg_mode = 3; + + SyncPRGBanks(); + SyncCHRBanks(); + SetMirrorType(EMirrorType.Vertical); + } + + int MapWRAM(int addr) + { + int bank_8k = wram_bank; + int ofs = addr & ((1 << 13) - 1); + addr = (bank_8k << 13) | ofs; + return addr; + } + + int MapPRG(int addr, out bool ram) + { + int bank_8k = addr >> 13; + int ofs = addr & ((1 << 13) - 1); + bank_8k = prg_banks_8k[bank_8k]; + ram = (bank_8k & 0x80) == 0; + bank_8k &= ~0x80; + if (!ram) + bank_8k &= prg_bank_mask_8k; + return (bank_8k << 13) | ofs; + } + + int MapCHR(int addr) + { + int bank_1k = addr >> 10; + int ofs = addr & ((1 << 10) - 1); + + //wish this logic could be smaller.. + if (NES.ppu.reg_2000.obj_size_16) + { + if (NES.ppu.ppuphase == NES.PPU.PPUPHASE.OBJ) + bank_1k = a_banks_1k[bank_1k]; + else + bank_1k = b_banks_1k[bank_1k]; + } + else + if (ab_mode == 0) + bank_1k = a_banks_1k[bank_1k]; + else + bank_1k = b_banks_1k[bank_1k]; + + //bank_1k = NES.Frame; + + //something like this..? + //bool special_sel = NES.ppu.reg_2000.obj_size_16 && NES.ppu.ppuphase == NES.PPU.PPUPHASE.OBJ; + //bool a_sel = special_sel || (!a_sel && ab_mode == 0); + + + + bank_1k &= chr_bank_mask_1k; + addr = (bank_1k<<10)|ofs; + return addr; + } + + public override byte ReadPPU(int addr) + { + if (addr < 0x2000) + { + addr = MapCHR(addr); + return VROM[addr]; + } + else + { + addr -= 0x2000; + int nt = addr >> 10; + int offset = addr & ((1<<10)-1); + nt = nt_modes[nt]; + switch (nt) + { + case 0: //NES internal NTA + return base.ReadPPU(0x2000 + offset); + case 1: //NES internal NTB + return base.ReadPPU(0x2400 + offset); + case 2: //use ExRAM as NT + //TODO - additional r/w security + if (exram_mode >= 2) return 0; + else return EXRAM[offset]; + case 3: //Fill Mode + return 0xFF; //TODO + default: throw new Exception(); + } + } + } + + public override void WritePPU(int addr, byte value) + { + if (addr < 0x2000) + { + throw new InvalidOperationException(); + } + else + { + addr -= 0x2000; + int nt = addr >> 10; + int offset = addr & ((1 << 10) - 1); + nt = nt_modes[nt]; + switch (nt) + { + case 0: //NES internal NTA + base.WritePPU(0x2000 + offset, value); + break; + case 1: //NES internal NTB + base.WritePPU(0x2400 + offset, value); + break; + case 2: //use ExRAM as NT + //TODO - additional r/w security + EXRAM[offset] = value; + break; + case 3: //Fill Mode + //what to do? + break; + default: throw new Exception(); + } + } + } + + public override void WriteWRAM(int addr, byte value) + { + addr = MapWRAM(addr); + WRAM[addr] = value; + } + + public override byte ReadWRAM(int addr) + { + addr = MapWRAM(addr); + return WRAM[addr]; + } + + public override byte ReadPRG(int addr) + { + bool ram; + addr = MapPRG(addr, out ram); + if (ram) return WRAM[addr]; + else return ROM[addr]; + } + + public override void WritePRG(int addr, byte value) + { + bool ram; + addr = MapPRG(addr, out ram); + if (ram) WRAM[addr] = value; + } + + public override void WriteEXP(int addr, byte value) + { + //NES.LogLine("MMC5 WriteEXP: ${0:x4} = ${1:x2}", addr, value); + switch (addr) + { + case 0x1100: //$5100: [.... ..PP] PRG Mode Select: + prg_mode = value & 3; + SyncPRGBanks(); + break; + + case 0x1101: //$5101: [.... ..CC] + chr_mode = value & 3; + SyncCHRBanks(); + break; + + case 0x1102: //$5102: [.... ..AA] PRG-RAM Protect A + case 0x1103: //$5103: [.... ..BB] PRG-RAM Protect B + break; + + case 0x1104: //$5104: [.... ..XX] ExRAM mode + exram_mode = value & 3; + //NES.LogLine("exram mode set to: {0}", exram_mode); + break; + + case 0x1105: //$5105: [DDCC BBAA] (nametable config) + nt_modes[0] = (value >> 0) & 3; + nt_modes[1] = (value >> 2) & 3; + nt_modes[2] = (value >> 4) & 3; + nt_modes[3] = (value >> 6) & 3; + //NES.LogLine("nt_modes set to {0},{1},{2},{3}", nt_modes[0], nt_modes[1], nt_modes[2], nt_modes[3]); + break; + case 0x1106: //$5106: [TTTT TTTT] Fill Tile + nt_fill_tile = value; + break; + case 0x1107: //$5107: [.... ..AA] Fill Attribute bits + nt_fill_attrib = value; + break; + + + case 0x1113: //$5113: [.... .PPP] (simplified, but technically inaccurate -- see below) + wram_bank = value & 7; + break; + + //$5114-5117: [RPPP PPPP] PRG select + case 0x1114: case 0x1115: case 0x1116: case 0x1117: + if (addr == 0x1117) value |= 0x80; + regs_prg[addr - 0x1114] = value; + SyncPRGBanks(); + break; + + //$5120 - $5127 'A' Regs: + case 0x1120: case 0x1121: case 0x1122: case 0x1123: + case 0x1124: case 0x1125: case 0x1126: case 0x1127: + ab_mode = 0; + regs_a[addr - 0x1120] = value | (chr_reg_high<<8); + //NES.LogLine("set bank A {0:x4} to {1:x2}", addr+0x4000, value); + SyncCHRBanks(); + break; + + //$5128 - $512B 'B' Regs: + case 0x1128: case 0x1129: case 0x112A: case 0x112B: + ab_mode = 1; + regs_b[addr - 0x1128] = value | (chr_reg_high<<8); + //NES.LogLine("set bank B {0:x4} to {1:x2}", addr + 0x4000, value); + SyncCHRBanks(); + break; + + case 0x1130: //$5130 [.... ..HH] 'High' CHR Reg: + chr_reg_high = value & 3; + break; + + case 0x1203: //$5203: [IIII IIII] IRQ Target + irq_target = value; + SyncIRQ(); + break; + + case 0x1204: //$5204: [E... ....] IRQ Enable (0=disabled, 1=enabled) + irq_enabled = (value & 0x80) != 0; + SyncIRQ(); + break; + + case 0x1205: //$5205: multiplicand + multiplicand = value; + SyncMultiplier(); + break; + case 0x1206: //$5206: multiplier + multiplier = value; + SyncMultiplier(); + break; + } + + //TODO - additional r/w timing security + if (addr >= 0x1C00) + { + if(exram_mode != 3) + EXRAM[addr - 0x1C00] = value; + } + } + + void SyncMultiplier() + { + int result = multiplicand*multiplier; + product_low = (byte)(result&0xFF); + product_high = (byte)((result>>8) & 0xFF); + } + + public override byte ReadEXP(int addr) + { + byte ret = 0xFF; + switch (addr) + { + case 0x1204: //$5204: [E... ....] IRQ Enable (0=disabled, 1=enabled) + ret = (byte)((irq_pending ? 0x80 : 0) | (in_frame ? 0x40 : 0)); + irq_pending = false; + SyncIRQ(); + break; + + case 0x1205: //$5205: low 8 bits of product + ret = product_low; + break; + case 0x1206: //$5206: high 8 bits of product + ret = product_high; + break; + } + + //TODO - additional r/w timing security + if (addr >= 0x1C00) + { + if (exram_mode < 2) + ret = 0xFF; + else ret = EXRAM[addr - 0x1C00]; + } + + return ret; ; + } + + void SyncIRQ() + { + NES.irq_cart = (irq_pending && irq_enabled); + } + + public override void ClockPPU() + { + if (NES.ppu.ppur.status.cycle != 336) + return; + + int sl = NES.ppu.ppur.status.sl + 1; + + //not a visible scanline + if (sl >= 241) + { + in_frame = false; + return; + } + + if (!in_frame) + { + in_frame = true; + irq_counter = 0; + irq_pending = false; + SyncIRQ(); + } + else + { + irq_counter++; + if (irq_counter == irq_target) + { + irq_pending = true; + SyncIRQ(); + } + } + + } + + void SetBank(IntBuffer target, int offset, int size, int value) + { + value &= ~(size-1); + for (int i = 0; i < size; i++) + { + int index = i+offset; + target[index] = value; + value++; + } + } + + void SyncPRGBanks() + { + switch (prg_mode) + { + case 0: + SetBank(prg_banks_8k, 0, 4, regs_prg[3]&~3); + break; + case 1: + SetBank(prg_banks_8k, 0, 2, regs_prg[1] & ~1); + SetBank(prg_banks_8k, 2, 2, regs_prg[3] & ~1); + break; + case 2: + SetBank(prg_banks_8k, 0, 2, regs_prg[1] & ~1); + SetBank(prg_banks_8k, 2, 1, regs_prg[2]); + SetBank(prg_banks_8k, 3, 1, regs_prg[3]); + break; + case 3: + SetBank(prg_banks_8k, 0, 1, regs_prg[0]); + SetBank(prg_banks_8k, 1, 1, regs_prg[1]); + SetBank(prg_banks_8k, 2, 1, regs_prg[2]); + SetBank(prg_banks_8k, 3, 1, regs_prg[3]); + break; + } + } + + void SyncCHRBanks() + { + //MASTER LOGIC: something like this this might be enough to work, but i'll play with it later + //bank_1k >> (3 - chr_mode) << chr_mode | bank_1k & ( etc.etc. + + switch (chr_mode) + { + case 0: + SetBank(a_banks_1k, 0, 8, regs_a[7]); + SetBank(b_banks_1k, 0, 8, regs_a[7]); + break; + case 1: + SetBank(a_banks_1k, 0, 4, regs_a[3]); + SetBank(a_banks_1k, 4, 4, regs_a[7]); + SetBank(b_banks_1k, 0, 4, regs_b[3]); + break; + case 2: + SetBank(a_banks_1k, 0, 2, regs_a[1]); + SetBank(a_banks_1k, 2, 2, regs_a[3]); + SetBank(a_banks_1k, 4, 2, regs_a[5]); + SetBank(a_banks_1k, 6, 2, regs_a[7]); + SetBank(b_banks_1k, 0, 2, regs_b[1]); + SetBank(b_banks_1k, 2, 2, regs_b[3]); + break; + case 3: + SetBank(a_banks_1k, 0, 1, regs_a[0]); + SetBank(a_banks_1k, 1, 1, regs_a[1]); + SetBank(a_banks_1k, 2, 1, regs_a[2]); + SetBank(a_banks_1k, 3, 1, regs_a[3]); + SetBank(a_banks_1k, 4, 1, regs_a[4]); + SetBank(a_banks_1k, 5, 1, regs_a[5]); + SetBank(a_banks_1k, 6, 1, regs_a[6]); + SetBank(a_banks_1k, 7, 1, regs_a[7]); + SetBank(b_banks_1k, 0, 1, regs_b[0]); + SetBank(b_banks_1k, 1, 1, regs_b[1]); + SetBank(b_banks_1k, 2, 1, regs_b[2]); + SetBank(b_banks_1k, 3, 1, regs_b[3]); + break; + } + b_banks_1k[4] = b_banks_1k[0]; + b_banks_1k[5] = b_banks_1k[1]; + b_banks_1k[6] = b_banks_1k[2]; + b_banks_1k[7] = b_banks_1k[3]; + + } + + } +} + diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs index 0598659563..15ad626cb5 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs @@ -266,6 +266,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo AssertPrg(128, 256); AssertChr(16, 32, 64); AssertVram(0); AssertWram(0); break; case "NES-SGROM": //bionic commando + case "HVC-SGROM": //Ankoku Shinwa - Yamato Takeru Densetsu (J) AssertPrg(128, 256); AssertChr(0); AssertVram(8); AssertWram(0); break; case "NES-SHROM": //family feud diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs index ab46e15456..0ab5f46e73 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs @@ -18,7 +18,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo byte[] ram; MemoryDomain.FreezeData[] sysbus_freeze = new MemoryDomain.FreezeData[65536]; NESWatch[] sysbus_watch = new NESWatch[65536]; - protected byte[] CIRAM; //AKA nametables + public byte[] CIRAM; //AKA nametables string game_name; //friendly name exposed to user and used as filename base CartInfo cart; //the current cart prototype. should be moved into the board, perhaps INESBoard board; //the board hardware that is currently driving things diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs index 0e74212c25..05bbcb8842 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs @@ -45,11 +45,13 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void WriteLogTimestamp() { - Console.Write("[{0:d5}:{1:d3}:{2:d3}]", Frame, ppu.ppur.status.sl, ppu.ppur.status.cycle); + 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) { - Console.WriteLine("[{0:d5}:{1:d3}:{2:d3}] {3}", Frame, ppu.ppur.status.sl, ppu.ppur.status.cycle, string.Format(format, 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)); } NESWatch GetWatch(NESWatch.EDomain domain, int address) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs index a14e9f9bde..643331230d 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs @@ -44,10 +44,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo return nes.board.PeekPPU(addr); } - enum PPUPHASE { + public enum PPUPHASE + { VBL, BG, OBJ }; - PPUPHASE ppuphase; + public PPUPHASE ppuphase; NES nes; public PPU(NES nes) @@ -59,29 +60,40 @@ namespace BizHawk.Emulation.Consoles.Nintendo //state int ppudead; //measured in frames bool idleSynch; + int NMI_PendingInstructions; + byte PPUGenLatch; + bool vtoggle; + byte VRAMBuffer; + byte[] OAM; + public byte[] PALRAM; public void SyncState(Serializer ser) { + byte temp8; + 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("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("PPUGenLatch", ref PPUGenLatch); - ser.Sync("reg_2003", ref reg_2003); - ser.Sync("OAM", ref OAM, false); - ser.Sync("PALRAM", ref PALRAM, false); - ser.Sync("vtoggle", ref vtoggle); - ser.Sync("VRAMBuffer", ref VRAMBuffer); + 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); + 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); - - byte temp; - - temp = reg_2000.Value; ser.Sync("reg_2000.Value", ref temp); reg_2000.Value = temp; - temp = reg_2001.Value; ser.Sync("reg_2001.Value", ref temp); reg_2001.Value = temp; } public void Reset() @@ -113,13 +125,13 @@ namespace BizHawk.Emulation.Consoles.Nintendo void runppu(int x) { - ppur.status.cycle += x; - while(ppur.status.cycle >= ppur.status.end_cycle) - ppur.status.cycle -= ppur.status.end_cycle; - //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++; + if (ppur.status.cycle == ppur.status.end_cycle) + ppur.status.cycle = 0; + //might not actually run a cpu cycle if there are none to be run right now nes.RunCpu(1); diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs index e1d226cd43..b8719b8920 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs @@ -294,18 +294,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo 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 of Reg2002_vblank_active is pending + 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 - int NMI_PendingInstructions; - byte PPUGenLatch; public PPUREGS ppur; public Reg_2000 reg_2000; Reg_2001 reg_2001; byte reg_2003; - byte[] OAM; - public byte[] PALRAM; - bool vtoggle; - byte VRAMBuffer; void regs_reset() { //TODO - would like to reconstitute the entire PPU instead of all this.. diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs index 2e59682749..9f77554d38 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs @@ -2,6 +2,8 @@ //todo - read http://wiki.nesdev.com/w/index.php/PPU_sprite_priority +//TODO - correctly emulate PPU OFF state + using System; using System.Diagnostics; using System.Globalization; @@ -89,6 +91,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo Reg2002_vblank_active_pending = true; ppuphase = PPUPHASE.VBL; + ppur.status.sl = 241; //Not sure if this is correct. According to Matt Conte and my own tests, it is. Timing is probably off, though. //NOTE: Not having this here breaks a Super Donkey Kong game. @@ -113,11 +116,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo int oamslot=0; int oamcount=0; - //capture the initial xscroll - //int xscroll = ppur.fh; //render 241 scanlines (including 1 dummy at beginning) for (int sl = 0; sl < 241; sl++) { + //TODO - correctly emulate PPU OFF state + if (!reg_2001.PPUON) + { + runppu(kLineTime); + continue; + } + ppur.status.sl = sl; int yp = sl - 1; @@ -135,10 +143,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo //two of those tiles were read in the last scanline. for (int xt = 0; xt < 32; xt++) { - if (sl == 31 && xt == 31) - { - int zzz = 9; - } Read_bgdata(ref bgdata[xt + 2]); //ok, we're also going to draw here. @@ -301,7 +305,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo bool realSprite = (s < 8); TempOAM* oam = &oams[(scanslot << 6) + s]; - //fixed (TempOAM* oam = &oams[scanslot, s]) { int line = yp - oam->oam[0]; if ((oam->oam[2] & 0x80) != 0) //vflip @@ -424,8 +427,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo } //scanline loop - //hacks... - //if (MMC5Hack && PPUON) MMC5_hb(240); + ppur.status.sl = 241; //idle for one line runppu(kLineTime); diff --git a/BizHawk.Emulation/Util.cs b/BizHawk.Emulation/Util.cs index f82d2bc886..c9e511cd54 100644 --- a/BizHawk.Emulation/Util.cs +++ b/BizHawk.Emulation/Util.cs @@ -537,7 +537,7 @@ namespace BizHawk } - public class Serializer + public unsafe class Serializer { BinaryReader br; BinaryWriter bw; @@ -599,6 +599,34 @@ namespace BizHawk else tw.WriteLine("{0} {1}", name, val.ToString()); } + unsafe void SyncBuffer(string name, int elemsize, int len, void* ptr) + { + if (IsReader) + { + byte[] temp = null; + Sync(name, ref temp, false); + int todo = Math.Min(temp.Length, len * elemsize); + System.Runtime.InteropServices.Marshal.Copy(temp, 0, new IntPtr(ptr), todo); + } + else + { + int todo = len * elemsize; + byte[] temp = new byte[todo]; + System.Runtime.InteropServices.Marshal.Copy(new IntPtr(ptr), temp, 0, todo); + Sync(name, ref temp, false); + } + } + + public unsafe void Sync(string name, ref ByteBuffer byteBuf) + { + SyncBuffer(name, 1, byteBuf.len, byteBuf.ptr); + } + + public unsafe void Sync(string name, ref IntBuffer byteBuf) + { + SyncBuffer(name, 4, byteBuf.len, byteBuf.ptr); + } + public void Sync(string name, ref byte[] val, bool use_null) { if (IsText) SyncText(name, ref val, use_null);