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; uint[] value = new uint[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); if(value[0] < 0xFFFFFFFF) 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); if (value[0] < 0xFFFFFFFF) 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) { switch (addr & 0x1FF) { case 0: return 0xBF; case 1: switch (Cart.prg_size) { case 128: return 0xB5; case 256: return 0xB6; case 512: return 0xB7; } return 0xFF; //Shouldn't ever reach here, as the size was asserted earlier. default: return 0xFF; //Other unknown data is returned from addresses 2-511, in software ID mode, mostly 0xFF. } } 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); } } }