diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
index 865b834ad4..28d256678e 100644
--- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
+++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
@@ -284,6 +284,7 @@
+
diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper030.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper030.cs
new file mode 100644
index 0000000000..454cf1088f
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/Mapper030.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Nintendo.NES
+{
+ public class Mapper030 : NES.NESBoardBase
+ {
+
+ enum flashmode { fm_default, fm_erase, fm_write, fm_id }
+
+ // config
+ int prg_bank_mask_16k;
+ int vram_bank_mask_8k;
+
+ // state
+ int prg;
+ int chr;
+ int flash_state = 0;
+ flashmode flash_mode = flashmode.fm_default;
+ byte[] flash_rom = null;
+
+ int get_flash_write_count(int addr)
+ {
+ if (flash_rom == null)
+ return 0;
+ int[] value = new int[1];
+ int bank = (addr >= 0x4000) ? prg_bank_mask_16k : prg;
+ Buffer.BlockCopy(flash_rom, (bank << 2 | (addr >> 12) & 3) << 2, value, 0, 4);
+ return value[0];
+ }
+
+ void increment_flash_write_count(int addr, bool direct = false)
+ {
+ if (flash_rom == null)
+ return;
+ int[] value = new int[1];
+ int bank = (addr >= 0x4000) ? prg_bank_mask_16k : prg;
+ if (!direct)
+ {
+ Buffer.BlockCopy(flash_rom, (bank << 2 | (addr >> 12) & 3) << 2, value, 0, 4);
+ value[0]++;
+ Buffer.BlockCopy(value, 0, flash_rom, (bank << 2 | (addr >> 12) & 3) << 2, 4);
+ }
+ else
+ {
+ Buffer.BlockCopy(flash_rom, addr << 2, value, 0, 4);
+ value[0]++;
+ Buffer.BlockCopy(value, 0, flash_rom, addr << 2, 4);
+ }
+ }
+
+ public override void SyncState(Serializer ser)
+ {
+ base.SyncState(ser);
+ ser.Sync("prg", ref prg);
+ ser.Sync("chr", ref chr);
+ ser.Sync("flash_state", ref flash_state);
+ int tmp = (int)flash_mode;
+ ser.Sync("flash_mode", ref tmp);
+ flash_mode = (flashmode)tmp;
+ ser.Sync("flash_rom", ref flash_rom, true);
+ }
+
+ public override bool Configure(NES.EDetectionOrigin origin)
+ {
+ switch (Cart.board_type)
+ {
+ case "MAPPER030":
+ Cart.vram_size = 32;
+ break;
+ case "MAPPER0030-00":
+ AssertVram(8, 16, 32);
+ break;
+ case "UNIF_UNROM-512-8":
+ Cart.vram_size = 8;
+ break;
+ case "UNIF_UNROM-512-16":
+ Cart.vram_size = 16;
+ break;
+ case "UNIF_UNROM-512-32":
+ Cart.vram_size = 32;
+ break;
+ default:
+ return false;
+ }
+
+ if (Cart.wram_battery)
+ {
+ flash_state = 0;
+ flash_mode = flashmode.fm_default;
+ if (flash_rom == null)
+ {
+ // extra space is used to hold information about what sectors have been flashed
+ flash_rom = new byte[Cart.prg_size * 1024 + Cart.prg_size];
+ }
+ }
+ SetMirrorType(CalculateMirrorType(Cart.pad_h, Cart.pad_v));
+ AssertChr(0);
+ AssertPrg(128, 256, 512); //Flash chip sizes that fits sealie unrom-512 are 39SF010, 39SF020, 39SF040.
+ Cart.wram_size = 0;
+ prg_bank_mask_16k = Cart.prg_size / 16 - 1;
+ vram_bank_mask_8k = Cart.vram_size / 8 - 1;
+ return true;
+ }
+
+ static readonly int[] addr_state = new int[5] { 0x1555, 0x2AAA, 0x1555, 0x1555, 0x2AAA };
+ static readonly int[] addr_bank = new int[5] { 1, 0, 1, 1, 0 };
+ static readonly byte[] addr_data = new byte[5] { 0xAA, 0x55, 0x80, 0xAA, 0x55 };
+
+ public override void WritePRG(int addr, byte value)
+ {
+ if ((!Cart.wram_battery) || (addr >= 0x4000))
+ {
+ byte value2 = value;
+
+ if (!Cart.wram_battery)
+ value2 = HandleNormalPRGConflict(addr, value);
+ chr = value2 >> 5 & 3 & vram_bank_mask_8k;
+ prg = value2 & prg_bank_mask_16k;
+ if ((Cart.pad_h == 0) && (Cart.pad_v == 0))
+ {
+ int mirror = (value2 & 0x80) >> 7;
+ SetMirrorType(CalculateMirrorType(mirror, mirror));
+ }
+ }
+ else
+ {
+ if (flash_mode == flashmode.fm_default)
+ {
+ if (addr_state[flash_state] == addr && addr_bank[flash_state] == prg && addr_data[flash_state] == value)
+ {
+ flash_state++;
+ if (flash_state == 5)
+ flash_mode = flashmode.fm_erase;
+ }
+ else if (flash_state == 2 && addr == 0x1555 && prg == 1 && value == 0x90)
+ {
+ flash_mode = flashmode.fm_id;
+ }
+ else if (flash_state == 2 && addr == 0x1555 && prg == 1 && value == 0xA0)
+ {
+ flash_state++;
+ flash_mode = flashmode.fm_write;
+ }
+ else
+ {
+ flash_state = 0;
+ flash_mode = flashmode.fm_default;
+ }
+ }
+ else if (flash_mode == flashmode.fm_erase)
+ {
+ if (value == 0x10) //You probably don't want to do this, as this is erase entire flash chip. :)
+ { //Of course, we gotta emulate the behaviour.
+ for (int i = 0; i < (Cart.prg_size / 4); i++)
+ increment_flash_write_count(i, true);
+ for (int i = 0; i < flash_rom.Count(); i++)
+ flash_rom[Cart.prg_size + i] = 0xFF;
+ }
+ else if (value == 0x30)
+ {
+ increment_flash_write_count(addr);
+ for (int i = 0; i < 0x1000; i++)
+ flash_rom[(prg << 14 | addr & 0x3000) + i + Cart.prg_size] = 0xFF;
+ }
+ flash_mode = 0;
+ flash_state = 0;
+ }
+ else if (flash_mode == flashmode.fm_write)
+ {
+ if (get_flash_write_count(addr) == 0)
+ {
+ increment_flash_write_count(addr);
+ for (int i = 0; i < 0x1000; i++)
+ flash_rom[(prg << 14 | addr & 0x3000) + i + Cart.prg_size] = ROM[(prg << 14 | addr & 0x3000) + i];
+ }
+ flash_rom[Cart.prg_size + (prg << 14 | addr & 0x3fff)] &= value;
+ flash_state = 0;
+ flash_mode = 0;
+ }
+ if (flash_mode == flashmode.fm_id && value == 0xF0)
+ {
+ flash_state = 0;
+ flash_mode = 0;
+ }
+ }
+ }
+
+ public override byte ReadPRG(int addr)
+ {
+ int bank = addr >= 0x4000 ? prg_bank_mask_16k : prg;
+ if (Cart.wram_battery)
+ {
+ if (flash_mode == flashmode.fm_id)
+ {
+ if ((addr & 1) == 0)
+ return 0xBF;
+ else
+ switch (Cart.prg_size)
+ {
+ case 128:
+ return 0xB5;
+ case 256:
+ return 0xB6;
+ case 512:
+ return 0xB7;
+ }
+ }
+ if (get_flash_write_count(addr) > 0)
+ return flash_rom[Cart.prg_size + (bank << 14 | addr & 0x3fff)];
+ }
+ return ROM[bank << 14 | addr & 0x3fff];
+ }
+
+ public override byte[] SaveRam { get { return flash_rom; } }
+
+ public override byte ReadPPU(int addr)
+ {
+ if (addr < 0x2000)
+ return VRAM[addr | chr << 13];
+ else
+ return base.ReadPPU(addr);
+ }
+ public override void WritePPU(int addr, byte value)
+ {
+ if (addr < 0x2000)
+ VRAM[addr | chr << 13] = value;
+ else
+ base.WritePPU(addr, value);
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs
index 8092a11fd1..379f696813 100644
--- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs
+++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs
@@ -36,9 +36,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
CartV2.prg_size *= 16;
CartV2.chr_size *= 8;
+ CartV2.wram_battery = (data[6] & 2) != 0; // should this be respected in v2 mode??
+
int wrambat = iNES2Wram(data[10] >> 4);
int wramnon = iNES2Wram(data[10] & 15);
- CartV2.wram_battery = wrambat > 0;
+ CartV2.wram_battery |= wrambat > 0;
// fixme - doesn't handle sizes not divisible by 1024
CartV2.wram_size = (short)((wrambat + wramnon) / 1024);