From 99ca3be22a9b4331fb2544a3adb57eeb70195dee Mon Sep 17 00:00:00 2001 From: saxxonpike Date: Fri, 3 Jan 2025 00:32:02 -0600 Subject: [PATCH] [C64] EasyFlash overhaul - Implement the AM29F040B flash device as its own component - Fix save data for games that use the standard EasyAPI driver - Fix "Briley Witch Chronicles 2": the flash driver checks AutoSelect register 02 to see if the cartridge is write-protected - Rom is stored as bytes instead of ints, saves a lot of memory - EasyFlash now has proper hard reset and clock operations - SaveRam deltas fixed --- .../Commodore64/C64.IMemoryDomains.cs | 6 + .../Computers/Commodore64/C64.cs | 9 + .../Commodore64/Cartridge/Am29F040B.cs | 315 +++++++++++++++ .../Commodore64/Cartridge/CartridgeChip.cs | 8 + .../Commodore64/Cartridge/CartridgeDevice.cs | 15 +- .../Commodore64/Cartridge/CartridgePort.cs | 10 + .../Commodore64/Cartridge/Mapper0020.cs | 358 +++++++++--------- 7 files changed, 542 insertions(+), 179 deletions(-) create mode 100644 src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Am29F040B.cs create mode 100644 src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeChip.cs diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs index 942b495ff4..e063931d7e 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IMemoryDomains.cs @@ -11,6 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 { bool diskDriveEnabled = _board.DiskDrive != null; bool tapeDriveEnabled = _board.TapeDrive != null; + bool cartEnabled = _board.CartPort.IsConnected; var domains = new List { @@ -41,6 +42,11 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 }); } + if (cartEnabled) + { + domains.AddRange(_board.CartPort.CreateMemoryDomains()); + } + _memoryDomains = new MemoryDomainList(domains); ((BasicServiceProvider)ServiceProvider).Register(_memoryDomains); } diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs index 7a187cf991..b6dea718c9 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs @@ -36,6 +36,12 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } InitMedia(_roms[_currentDisk]); + + if (_board.CartPort.SaveRam is { } cartSaveRam) + { + ser.Register(cartSaveRam); + } + HardReset(); switch (SyncSettings.VicType) @@ -329,6 +335,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 if (cart != null) { _board.CartPort.Connect(cart); + if (_board.CartPort.SaveRam != null) + { + } } break; case C64Format.TAP: diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Am29F040B.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Am29F040B.cs new file mode 100644 index 0000000000..59e118ffef --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Am29F040B.cs @@ -0,0 +1,315 @@ +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge; + +/// +/// AMD flash chip used for EasyFlash emulation. +/// +public class Am29F040B +{ + // Source: + // https://www.mouser.com/datasheet/2/196/spansion_inc_am29f040b_eol_21445e8-3004346.pdf + // + // Flash erase suspend/resume are not implemented. + + public const int ImageSize = 1 << 19; + public const int ImageMask = ImageSize - 1; + private const int SectorSize = 1 << 16; + private const int SectorMask = SectorSize - 1; + private const int RegisterMask = (1 << 11) - 1; + + private const byte ToggleBit2 = 1 << 2; + private const byte ErrorBit = 1 << 5; + private const byte ToggleBit = 1 << 6; + private const byte PollingBit = 1 << 7; + private const byte EraseBit = 1 << 3; + + private const int WriteLatency = 7; + private const int EraseSectorLatency = 1000000; + private const int EraseChipLatency = 8000000; + private const int EraseValue = 0xFF; + + private const byte ManufacturerCode = 0x01; + private const byte DeviceCode = 0xA4; + private const byte WriteProtect = 0x00; // can be set to 1 to tell software it is write-protected + + private enum Sequence + { + None, + Start, + Complete, + Command + } + + private enum Mode + { + Read, + Erase, + AutoSelect, + Write + } + + private enum Register + { + Command0 = 0x0555, + Command1 = 0x02AA + } + + private enum Signal + { + Command0 = 0xAA, + Command1 = 0x55, + Erase = 0x80, + AutoSelect = 0x90, + Program = 0xA0, + ChipErase = 0x10, + SectorErase = 0x30, + Reset = 0xF0 + } + + private int _busyTimeRemaining; + private int _status; + private byte[] _data = new byte[ImageSize]; + private Mode _mode; + private Sequence _sequence; + private bool _returnStatus; + private int _startAddress; + private int _endAddress; + private bool _errorPending; + private bool _dataDirty; + + public MemoryDomain CreateMemoryDomain(string name) => + new MemoryDomainByteArray( + name: name, + endian: MemoryDomain.Endian.Little, + data: _data, + writable: true, + wordSize: 1 + ); + + public void Clock() + { + if (_busyTimeRemaining <= 0) + return; + + _busyTimeRemaining--; + + if (_busyTimeRemaining != 0) + return; + + _status ^= PollingBit; + + if (_errorPending) + { + _errorPending = false; + _status |= ErrorBit; + } + } + + /// + /// Synchronize state. + /// + /// + /// State serializer. + /// + /// + /// True only if the raw data should be synchronized. If false, + /// the caller is responsible for synchronizing deltas. + /// + public void SyncState(Serializer ser, bool withData) + { + ser.Sync("BusyTimeRemaining", ref _busyTimeRemaining); + ser.Sync("Status", ref _status); + ser.SyncEnum("Mode", ref _mode); + ser.SyncEnum("Sequence", ref _sequence); + ser.Sync("ReturnStatus", ref _returnStatus); + ser.Sync("StartAddress", ref _startAddress); + ser.Sync("EndAddress", ref _endAddress); + ser.Sync("ErrorPending", ref _errorPending); + ser.Sync("DataDirty", ref _dataDirty); + + if (withData) + ser.Sync("Data", ref _data, false); + } + + public void Reset() + { + _busyTimeRemaining = 0; + _status = 0; + _mode = Mode.Read; + _sequence = Sequence.None; + _errorPending = false; + _startAddress = 0; + _endAddress = ImageMask; + } + + public Span Data => + _data.AsSpan(); + + public int Peek(int addr) => + _data[addr & ImageMask] & 0xFF; + + public int Poke(int addr, int val) + { + var newData = val & 0xFF; + _dataDirty |= _data[addr & ImageMask] != newData; + return _data[addr & ImageMask] = unchecked((byte)newData); + } + + // From the datasheet: + // Address bits A18-A11 = X = Don’t Care for all address + // commands except for Program Address (PA), Sector Address (SA), Read + // Address (RA), and AutoSelect sector protect verify. + + public int Read(int addr) + { + int data; + + if (_busyTimeRemaining > 0) + { + if (addr >= _startAddress && addr <= _endAddress) + _status ^= ToggleBit2; + + _status ^= ToggleBit; + return _status; + } + + // Some commands allow one read of status before going back to read mode. + // Areas being written or erased will always return status during modification. + if (_returnStatus && addr >= _startAddress && addr <= _endAddress) + { + _returnStatus = false; + return _status; + } + + // Read manufacturer registers or memory. + switch (_mode) + { + case Mode.AutoSelect: + { + switch (addr & 0xFF) + { + case 0x00: + data = ManufacturerCode; + break; + case 0x01: + data = DeviceCode; + break; + case 0x02: + data = WriteProtect; + break; + default: + data = 0xFF; + break; + } + break; + } + default: + { + data = _data[addr & ImageMask]; + break; + } + } + + return data; + } + + public void Write(int addr, int data) + { + switch (_mode, _sequence, (Register)(addr & RegisterMask), (Signal)data) + { + case (Mode.Write, _, _, _): + { + _mode = Mode.Read; + _sequence = Sequence.None; + + if (_busyTimeRemaining > 0) + break; + + var originalData = _data[addr & ImageMask]; + var newData = originalData & data & 0xFF; + _dataDirty |= newData != originalData; + _errorPending = data != newData; + _data[addr & ImageMask] = unchecked((byte)newData); + _busyTimeRemaining = WriteLatency; // 7-30us + _status = (data & 0x80) ^ PollingBit; + _returnStatus = true; + _startAddress = _endAddress = addr; + break; + } + case (_, _, Register.Command0, Signal.Command0): + { + _sequence = Sequence.Start; + break; + } + case (_, Sequence.Start, Register.Command1, Signal.Command1): + { + _sequence = Sequence.Complete; + break; + } + case (_, Sequence.Complete, Register.Command0, Signal.Erase): + { + _mode = Mode.Erase; + _sequence = Sequence.None; + break; + } + case (Mode.Erase, Sequence.Complete, Register.Command0, Signal.ChipErase): + { + _mode = Mode.Read; + _sequence = Sequence.None; + + if (_busyTimeRemaining > 0) + break; + + _busyTimeRemaining = EraseChipLatency; // 8-64sec + _data.AsSpan().Fill(EraseValue); + _dataDirty = true; + _returnStatus = true; + _status = EraseBit; // bit 7 = complete + break; + } + case (Mode.Erase, Sequence.Complete, _, Signal.SectorErase): + { + _mode = Mode.Read; + _sequence = Sequence.None; + + if (_busyTimeRemaining > 0) + break; + + _busyTimeRemaining = EraseSectorLatency; // ~1sec + _data.AsSpan(addr & ~SectorMask, SectorSize).Fill(0xFF); + _dataDirty = true; + _returnStatus = true; + _status = EraseBit; // bit 7 = complete + break; + } + case (Mode.Read, Sequence.Complete, Register.Command0, Signal.AutoSelect): + { + _mode = Mode.AutoSelect; + _sequence = Sequence.None; + break; + } + case (Mode.Read, Sequence.Complete, Register.Command0, Signal.Program): + { + _mode = Mode.Write; + break; + } + case (_, _, _, Signal.Reset): + { + _mode = Mode.Read; + _sequence = Sequence.None; + break; + } + } + } + + public bool IsDataDirty => _dataDirty; + + public bool CheckDataDirty() + { + var result = _dataDirty; + _dataDirty = false; + return result; + } +} \ No newline at end of file diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeChip.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeChip.cs new file mode 100644 index 0000000000..e370bce84b --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeChip.cs @@ -0,0 +1,8 @@ +namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge; + +public class CartridgeChip +{ + public int Address; + public int Bank; + public int[] Data; +} \ No newline at end of file diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeDevice.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeDevice.cs index 693dfc36a8..e61507d16a 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeDevice.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgeDevice.cs @@ -106,7 +106,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge result = new Mapper0013(chipAddress, chipBank, chipData); break; case 0x0020: // EasyFlash - result = new Mapper0020(chipAddress, chipBank, chipData); + result = new Mapper0020(BuildChipList(chipAddress, chipBank, chipData)); break; case 0x002B: // Prophet 64 result = new Mapper002B(chipAddress, chipBank, chipData); @@ -119,6 +119,16 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge return result; } + private static List BuildChipList(IList addresses, IList banks, IList data) => + Enumerable.Range(0, addresses.Count) + .Select(i => new CartridgeChip + { + Address = addresses[i], + Bank = banks[i], + Data = data[i] + }) + .ToList(); + private static int ReadCRTShort(BinaryReader reader) { return (reader.ReadByte() << 8) | @@ -256,6 +266,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge { } + public virtual IEnumerable CreateMemoryDomains() => + Array.Empty(); + private bool _driveLightEnabled; private bool _driveLightOn; diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgePort.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgePort.cs index 8c1e0c27e3..6025cd059f 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgePort.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/CartridgePort.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using BizHawk.Common; using BizHawk.Emulation.Common; @@ -133,6 +134,15 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge ser.EndSection(); } + public ISaveRam SaveRam => _cartridgeDevice as ISaveRam; + + public IEnumerable CreateMemoryDomains() + { + if (_connected) + return _cartridgeDevice.CreateMemoryDomains(); + return Array.Empty(); + } + public bool DriveLightEnabled => _connected && _cartridgeDevice.DriveLightEnabled; public bool DriveLightOn => _connected && _cartridgeDevice.DriveLightOn; diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper0020.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper0020.cs index 65b20a04e9..04cc2f1d76 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper0020.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/Cartridge/Mapper0020.cs @@ -1,125 +1,154 @@ using System.Collections.Generic; +using System.IO; using System.Linq; - using BizHawk.Common; +using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge { - // 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. - internal sealed class Mapper0020 : CartridgeDevice + /// + /// Implements the EasyFlash cartridge format. + /// + /// The most common EasyFlash implementation uses 2x AM29F040 programmable ROMs + /// and a 256-byte memory. + /// + /// The address bus is 19 bits wide. Bits 18-13 are set by the "bank" + /// register (implemented as a separate bank of flip-flops on the board) and + /// bits 12-0 are set from the system bus. "RomH" and "RomL" are directly + /// tied to the respective chip-enable signals for each flash ROM, which means + /// that address range $8000-$9FFF will correspond to one flash ROM, and $A000-$BFFF + /// (or $E000-$FFFF in UltiMax configuration) will correspond to the other. + /// + /// Control registers are mapped to $DE00 and $DE02. The 256-byte RAM is mapped to $DF00-$DFFF. + /// + /// + /// Two registers can be accessed: + /// + /// $DE00 - bank register (bits: 00BBBBBB) + /// B = bank ($00-$3F) + /// + /// $DE02 - control register (bits: L0000MXG) + /// L = light control + /// M = Game pin control; 1=software controlled, 0=onboard jumper controlled + /// X = ExRom pin level; 1=low, 0=high + /// G = Game pin level; 1=low, 0=high + /// + internal sealed class Mapper0020 : CartridgeDevice, ISaveRam { - private int _bankOffset = 63 << 13; + private readonly byte[] _originalMediaA; // 8000 + private readonly byte[] _originalMediaB; // A000 - private int[] _banksA = new int[64 << 13]; // 8000 - private int[] _banksB = new int[64 << 13]; // A000 + private byte[] _deltaA; // 8000 + private byte[] _deltaB; // A000 - private readonly int[] _originalMediaA; // 8000 - private readonly int[] _originalMediaB; // A000 + private readonly Am29F040B _chipA = new(); + private readonly Am29F040B _chipB = new(); + + private bool _saveRamDirty; private bool _boardLed; - private bool _jumper; - private int _stateBits; - private int[] _ram = new int[256]; + private byte[] _ram = new byte[256]; + private int _bankNumber; - private bool _commandLatch55; - private bool _commandLatchAa; - - private int _internalRomState; - - public Mapper0020(IList newAddresses, IList newBanks, IList newData) + public Mapper0020(IReadOnlyList chips) { DriveLightEnabled = true; - var count = newAddresses.Count; + var count = chips.Count; // 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 (var i = 0; i < 64 * 0x2000; i++) - { - _banksA[i] = 0xFF; - _banksB[i] = 0xFF; - } - // load in all banks - for (var i = 0; i < count; i++) + foreach (var chip in chips) { - switch (newAddresses[i]) + switch (chip.Address) { case 0x8000: - Array.Copy(newData[i], 0, _banksA, newBanks[i] * 0x2000, 0x2000); + chip.Data.Select(b => unchecked((byte)b)) + .ToArray() + .CopyTo(_chipA.Data.Slice(chip.Bank * 0x2000, 0x2000)); break; case 0xA000: case 0xE000: - Array.Copy(newData[i], 0, _banksB, newBanks[i] * 0x2000, 0x2000); + chip.Data.Select(b => unchecked((byte)b)) + .ToArray() + .CopyTo(_chipB.Data.Slice(chip.Bank * 0x2000, 0x2000)); break; } } // default to bank 0 - BankSet(0); - - // internal operation settings - _commandLatch55 = false; - _commandLatchAa = false; - _internalRomState = 0; + _bankNumber = 0; // back up original media - _originalMediaA = _banksA.Select(d => d).ToArray(); - _originalMediaB = _banksB.Select(d => d).ToArray(); + _originalMediaA = _chipA.Data.ToArray(); + _originalMediaB = _chipB.Data.ToArray(); + } + + public override void HardReset() + { + _chipA.Reset(); + _chipB.Reset(); + base.HardReset(); + } + + private void FlushSaveRam() + { + if (_chipA.CheckDataDirty() || _deltaA == null) + _deltaA = DeltaSerializer.GetDelta(_originalMediaA, _chipA.Data).ToArray(); + + if (_chipB.CheckDataDirty() || _deltaB == null) + _deltaB = DeltaSerializer.GetDelta(_originalMediaB, _chipB.Data).ToArray(); + + _saveRamDirty = false; } protected override void SyncStateInternal(Serializer ser) { - ser.Sync("BankOffset", ref _bankOffset); + if (!ser.IsReader) + FlushSaveRam(); + + ser.Sync("BankNumber", ref _bankNumber); ser.Sync("BoardLed", ref _boardLed); ser.Sync("Jumper", ref _jumper); ser.Sync("StateBits", ref _stateBits); ser.Sync("RAM", ref _ram, useNull: false); - ser.Sync("CommandLatch55", ref _commandLatchAa); - ser.Sync("CommandLatchAA", ref _commandLatchAa); - ser.Sync("InternalROMState", ref _internalRomState); - ser.SyncDelta("MediaStateA", _originalMediaA, _banksA); - ser.SyncDelta("MediaStateB", _originalMediaB, _banksB); + ser.Sync("MediaStateA", ref _deltaA, useNull: false); + ser.Sync("MediaStateB", ref _deltaB, useNull: false); + + ser.BeginSection("FlashA"); + _chipA.SyncState(ser, withData: false); + ser.EndSection(); + + ser.BeginSection("FlashB"); + _chipB.SyncState(ser, withData: false); + ser.EndSection(); + + if (ser.IsReader) + { + if (_deltaA != null) + DeltaSerializer.ApplyDelta(_originalMediaA, _chipA.Data, _deltaA); + + if (_deltaB != null) + DeltaSerializer.ApplyDelta(_originalMediaB, _chipB.Data, _deltaB); + } + DriveLightOn = _boardLed; } - private void BankSet(int index) - { - _bankOffset = (index & 0x3F) << 13; - } + private int CalculateBankOffset(int addr) => + (addr & 0x1FFF) | (_bankNumber << 13); + + public override int Peek8000(int addr) => + _chipA.Peek(CalculateBankOffset(addr)); - public override int Peek8000(int addr) - { - addr &= 0x1FFF; - return _banksA[addr | _bankOffset]; - } - - public override int PeekA000(int addr) - { - addr &= 0x1FFF; - return _banksB[addr | _bankOffset]; - } + public override int PeekA000(int addr) => + _chipB.Peek(CalculateBankOffset(addr)); public override int PeekDE00(int addr) { @@ -127,9 +156,15 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge // but Peek is provided here for debug reasons // and may not stay around addr &= 0x02; - return addr == 0x00 ? _bankOffset >> 13 : _stateBits; + return addr == 0x00 ? _bankNumber : _stateBits; } + public override void Poke8000(int addr, int val) => + _chipA.Poke(addr, val); + + public override void PokeA000(int addr, int val) => + _chipB.Poke(addr, val); + public override int PeekDF00(int addr) { addr &= 0xFF; @@ -141,7 +176,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge addr &= 0x02; if (addr == 0x00) { - BankSet(val); + _bankNumber = val & 0x3F; } else { @@ -152,18 +187,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge public override void PokeDF00(int addr, int val) { addr &= 0xFF; - _ram[addr] = val & 0xFF; + _ram[addr] = unchecked((byte)val); } - public override int Read8000(int addr) - { - return ReadInternal(addr & 0x1FFF, _banksA); - } + public override int Read8000(int addr) => + _chipA.Read(CalculateBankOffset(addr)); - public override int ReadA000(int addr) - { - return ReadInternal(addr & 0x1FFF, _banksB); - } + public override int ReadA000(int addr) => + _chipB.Read(CalculateBankOffset(addr)); public override int ReadDF00(int addr) { @@ -171,33 +202,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge return _ram[addr]; } - private int ReadInternal(int addr, int[] bank) - { - 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; - } - - return bank[addr | _bankOffset]; - } - private void StateSet(int val) { _stateBits = val &= 0x87; @@ -212,83 +216,23 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge pinExRom = (val & 0x02) == 0; _boardLed = (val & 0x80) != 0; - _internalRomState = 0; DriveLightOn = _boardLed; } public override void Write8000(int addr, int val) { - WriteInternal(addr, val); + if (pinGame || !pinExRom) + return; + + _chipA.Write(CalculateBankOffset(addr), val); } public override void WriteA000(int addr, int val) - { - WriteInternal(addr | 0x2000, val); - } - - private void WriteInternal(int addr, int val) { if (pinGame || !pinExRom) - { return; - } - 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[addr | _bankOffset] = val & 0xFF; - } - else - { - addr &= 0x1FFF; - _banksB[addr | _bankOffset] = val & 0xFF; - } - - 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; - } + _chipB.Write(CalculateBankOffset(addr), val); } public override void WriteDE00(int addr, int val) @@ -296,7 +240,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge addr &= 0x02; if (addr == 0x00) { - BankSet(val); + _bankNumber = val & 0x3F; } else { @@ -306,7 +250,65 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge public override void WriteDF00(int addr, int val) { - _ram[addr] = val & 0xFF; + _ram[addr] = unchecked((byte)val); } + + public override void ExecutePhase() + { + _chipA.Clock(); + _chipB.Clock(); + _saveRamDirty |= _chipA.IsDataDirty | _chipB.IsDataDirty; + } + + public override IEnumerable CreateMemoryDomains() + { + yield return _chipA.CreateMemoryDomain("EF LoROM"); + yield return _chipB.CreateMemoryDomain("EF HiROM"); + + yield return new MemoryDomainByteArray( + name: "EF RAM", + endian: MemoryDomain.Endian.Little, + data: _ram, + writable: true, + wordSize: 1 + ); + } + + public byte[] CloneSaveRam() + { + FlushSaveRam(); + + using var result = new MemoryStream(); + using var writer = new BinaryWriter(result); + + writer.Write(_deltaA.Length); + writer.Write(_deltaA); + writer.Write(_deltaB.Length); + writer.Write(_deltaB); + writer.Flush(); + + _saveRamDirty = false; + return result.ToArray(); + } + + /// + /// Applies a SaveRam block to the flash memory. + /// + public void StoreSaveRam(byte[] data) + { + using var stream = new MemoryStream(data); + using var reader = new BinaryReader(stream); + + var deltaASize = reader.ReadInt32(); + _deltaA = reader.ReadBytes(deltaASize); + var deltaBSize = reader.ReadInt32(); + _deltaB = reader.ReadBytes(deltaBSize); + + DeltaSerializer.ApplyDelta(_originalMediaA, _chipA.Data, _deltaA); + DeltaSerializer.ApplyDelta(_originalMediaB, _chipB.Data, _deltaB); + _saveRamDirty = false; + } + + public bool SaveRamModified => _saveRamDirty; } }