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);