[NES] ELROM emulation (cv3 and laser invasion tested) and core savestate logic brought up to date. savestates for newer mappers still need reworking.

This commit is contained in:
zeromus 2011-06-09 19:45:07 +00:00
parent b66cb2a28c
commit 72100bd304
11 changed files with 621 additions and 38 deletions

View File

@ -70,6 +70,7 @@
<Compile Include="Consoles\Nintendo\NES\Boards\CxROM.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Consoles\Nintendo\NES\Boards\ExROM.cs" />
<Compile Include="Consoles\Nintendo\NES\Boards\GxROM.cs">
<SubType>Code</SubType>
</Compile>
@ -228,6 +229,7 @@
<Compile Include="Sound\Utilities\Waves.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Consoles\Nintendo\Docs\notes_for_disch.txt" />
<Content Include="Consoles\Nintendo\Docs\test_status.txt" />
<Content Include="Consoles\PC Engine\Compat.txt" />
<Content Include="Consoles\Sega\SMS\Compat.txt" />

View File

@ -80,4 +80,20 @@ namespace BizHawk
#endif
}
}
public class IntBuffer : CBuffer<int>
{
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
}
}
}

View File

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

View File

@ -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];
}
}
}

View File

@ -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

View File

@ -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

View File

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

View File

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

View File

@ -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..

View File

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

View File

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