using System.Collections.Generic; using BizHawk.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64 { // EasyFlash cartridge // No official games came on one of these but there // are a few dumps from GameBase64 that use this mapper // There are 64 banks total, DE00 is bank select. // Selecing a bank will select both Lo and Hi ROM. // DE02 will switch exrom/game bits: bit 0=game, // bit 1=exrom, bit 2=for our cases, always set true. // These two registers are write only. // This cartridge always starts up in Ultimax mode, // with Game set high and ExRom set low. // There is also 256 bytes RAM at DF00-DFFF. // We emulate having the AM29F040 chip. sealed public class Mapper0020 : Cart { private byte[][] banksA = new byte[64][]; //8000 private byte[][] banksB = new byte[64][]; //A000 private int bankNumber; private bool boardLed; private byte[] currentBankA; private byte[] currentBankB; private byte[] dummyBank; private bool jumper = false; private int stateBits; private byte[] ram = new byte[256]; private bool commandLatch55; private bool commandLatchAA; private int internalRomState; public Mapper0020(List newAddresses, List newBanks, List newData) { int count = newAddresses.Count; // build dummy bank dummyBank = new byte[0x2000]; for (int i = 0; i < 0x2000; i++) dummyBank[i] = 0xFF; // todo: determine if this is correct // force ultimax mode (the cart SHOULD set this // otherwise on load, according to the docs) pinGame = false; pinExRom = true; // for safety, initialize all banks to dummy for (int i = 0; i < 64; i++) banksA[i] = dummyBank; for (int i = 0; i < 64; i++) banksB[i] = dummyBank; // load in all banks for (int i = 0; i < count; i++) { if (newAddresses[i] == 0x8000) { banksA[newBanks[i]] = newData[i]; } else if (newAddresses[i] == 0xA000 || newAddresses[i] == 0xE000) { banksB[newBanks[i]] = newData[i]; } } // default to bank 0 BankSet(0); // internal operation settings commandLatch55 = false; commandLatchAA = false; internalRomState = 0; } private void BankSet(int index) { bankNumber = index & 0x3F; UpdateState(); } public override byte Peek8000(int addr) { return currentBankA[addr]; } public override byte PeekA000(int addr) { return currentBankB[addr]; } public override byte PeekDE00(int addr) { // normally you can't read these regs // but Peek is provided here for debug reasons // and may not stay around addr &= 0x02; if (addr == 0x00) return (byte)bankNumber; else return (byte)stateBits; } public override byte PeekDF00(int addr) { return ram[addr]; } public override void PokeDE00(int addr, byte val) { addr &= 0x02; if (addr == 0x00) BankSet(val); else StateSet(val); } public override void PokeDF00(int addr, byte val) { ram[addr] = val; } public override byte Read8000(int addr) { return ReadInternal(addr); } public override byte ReadA000(int addr) { return ReadInternal(addr | 0x2000); } public override byte ReadDF00(int addr) { return ram[addr]; } private byte ReadInternal(int addr) { switch (internalRomState) { case 0x80: break; case 0x90: switch (addr & 0x1FFF) { case 0x0000: return 0x01; case 0x0001: return 0xA4; case 0x0002: return 0x00; } break; case 0xA0: break; case 0xF0: break; } if ((addr & 0x3FFF) < 0x2000) { return currentBankA[addr & 0x1FFF]; } else { return currentBankB[addr & 0x1FFF]; } } private void StateSet(byte val) { stateBits = val &= 0x87; if ((val & 0x04) != 0) pinGame = ((val & 0x01) == 0); else pinGame = jumper; pinExRom = ((val & 0x02) == 0); boardLed = ((val & 0x80) != 0); internalRomState = 0; UpdateState(); } private void UpdateState() { currentBankA = banksA[bankNumber]; currentBankB = banksB[bankNumber]; } public override void Write8000(int addr, byte val) { WriteInternal(addr, val); } public override void WriteA000(int addr, byte val) { WriteInternal(addr | 0x2000, val); } private void WriteInternal(int addr, byte val) { if (!pinGame && pinExRom) { System.Diagnostics.Debug.WriteLine("EasyFlash Write: $" + C64Util.ToHex(addr | 0x8000, 4) + " = " + C64Util.ToHex(val, 2)); if (val == 0xF0) // any address, resets flash { internalRomState = 0; commandLatch55 = false; commandLatchAA = false; } else if (internalRomState != 0x00 && internalRomState != 0xF0) { switch (internalRomState) { case 0xA0: if ((addr & 0x2000) == 0) { addr &= 0x1FFF; banksA[bankNumber][addr] = val; currentBankA[addr] = val; } else { addr &= 0x1FFF; banksB[bankNumber][addr] = val; currentBankB[addr] = val; } break; } } else if (addr == 0x0555) // $8555 { if (!commandLatchAA) { if (val == 0xAA) { commandLatch55 = true; } } else { // process EZF command internalRomState = val; } } else if (addr == 0x02AA) // $82AA { if (commandLatch55 && val == 0x55) { commandLatchAA = true; } else { commandLatch55 = false; } } else { commandLatch55 = false; commandLatchAA = false; } } } public override void WriteDE00(int addr, byte val) { addr &= 0x02; if (addr == 0x00) BankSet(val); else StateSet(val); } public override void WriteDF00(int addr, byte val) { ram[addr] = val; } public override void SyncState(Serializer ser) { base.SyncState(ser); if (ser.IsReader) UpdateState(); } } }