[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:
parent
b66cb2a28c
commit
72100bd304
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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..
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue