[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
This commit is contained in:
saxxonpike 2025-01-03 00:32:02 -06:00 committed by YoshiRulz
parent 83ea4bfb1c
commit 99ca3be22a
7 changed files with 542 additions and 179 deletions

View File

@ -11,6 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
{ {
bool diskDriveEnabled = _board.DiskDrive != null; bool diskDriveEnabled = _board.DiskDrive != null;
bool tapeDriveEnabled = _board.TapeDrive != null; bool tapeDriveEnabled = _board.TapeDrive != null;
bool cartEnabled = _board.CartPort.IsConnected;
var domains = new List<MemoryDomain> var domains = new List<MemoryDomain>
{ {
@ -41,6 +42,11 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
}); });
} }
if (cartEnabled)
{
domains.AddRange(_board.CartPort.CreateMemoryDomains());
}
_memoryDomains = new MemoryDomainList(domains); _memoryDomains = new MemoryDomainList(domains);
((BasicServiceProvider)ServiceProvider).Register(_memoryDomains); ((BasicServiceProvider)ServiceProvider).Register(_memoryDomains);
} }

View File

@ -36,6 +36,12 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
} }
InitMedia(_roms[_currentDisk]); InitMedia(_roms[_currentDisk]);
if (_board.CartPort.SaveRam is { } cartSaveRam)
{
ser.Register<ISaveRam>(cartSaveRam);
}
HardReset(); HardReset();
switch (SyncSettings.VicType) switch (SyncSettings.VicType)
@ -329,6 +335,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
if (cart != null) if (cart != null)
{ {
_board.CartPort.Connect(cart); _board.CartPort.Connect(cart);
if (_board.CartPort.SaveRam != null)
{
}
} }
break; break;
case C64Format.TAP: case C64Format.TAP:

View File

@ -0,0 +1,315 @@
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge;
/// <summary>
/// AMD flash chip used for EasyFlash emulation.
/// </summary>
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;
}
}
/// <summary>
/// Synchronize state.
/// </summary>
/// <param name="ser">
/// State serializer.
/// </param>
/// <param name="withData">
/// True only if the raw data should be synchronized. If false,
/// the caller is responsible for synchronizing deltas.
/// </param>
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<byte> 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 = Dont 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;
}
}

View File

@ -0,0 +1,8 @@
namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge;
public class CartridgeChip
{
public int Address;
public int Bank;
public int[] Data;
}

View File

@ -106,7 +106,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
result = new Mapper0013(chipAddress, chipBank, chipData); result = new Mapper0013(chipAddress, chipBank, chipData);
break; break;
case 0x0020: // EasyFlash case 0x0020: // EasyFlash
result = new Mapper0020(chipAddress, chipBank, chipData); result = new Mapper0020(BuildChipList(chipAddress, chipBank, chipData));
break; break;
case 0x002B: // Prophet 64 case 0x002B: // Prophet 64
result = new Mapper002B(chipAddress, chipBank, chipData); result = new Mapper002B(chipAddress, chipBank, chipData);
@ -119,6 +119,16 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
return result; return result;
} }
private static List<CartridgeChip> BuildChipList(IList<int> addresses, IList<int> banks, IList<int[]> 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) private static int ReadCRTShort(BinaryReader reader)
{ {
return (reader.ReadByte() << 8) | return (reader.ReadByte() << 8) |
@ -256,6 +266,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
{ {
} }
public virtual IEnumerable<MemoryDomain> CreateMemoryDomains() =>
Array.Empty<MemoryDomain>();
private bool _driveLightEnabled; private bool _driveLightEnabled;
private bool _driveLightOn; private bool _driveLightOn;

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -133,6 +134,15 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
ser.EndSection(); ser.EndSection();
} }
public ISaveRam SaveRam => _cartridgeDevice as ISaveRam;
public IEnumerable<MemoryDomain> CreateMemoryDomains()
{
if (_connected)
return _cartridgeDevice.CreateMemoryDomains();
return Array.Empty<MemoryDomain>();
}
public bool DriveLightEnabled => _connected && _cartridgeDevice.DriveLightEnabled; public bool DriveLightEnabled => _connected && _cartridgeDevice.DriveLightEnabled;
public bool DriveLightOn => _connected && _cartridgeDevice.DriveLightOn; public bool DriveLightOn => _connected && _cartridgeDevice.DriveLightOn;

View File

@ -1,125 +1,154 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
{ {
// EasyFlash cartridge /// <summary>
// No official games came on one of these but there /// Implements the EasyFlash cartridge format.
// are a few dumps from GameBase64 that use this mapper ///
/// The most common EasyFlash implementation uses 2x AM29F040 programmable ROMs
// There are 64 banks total, DE00 is bank select. /// and a 256-byte memory.
// Selecing a bank will select both Lo and Hi ROM. ///
// DE02 will switch exrom/game bits: bit 0=game, /// The address bus is 19 bits wide. Bits 18-13 are set by the "bank"
// bit 1=exrom, bit 2=for our cases, always set true. /// register (implemented as a separate bank of flip-flops on the board) and
// These two registers are write only. /// 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
// This cartridge always starts up in Ultimax mode, /// that address range $8000-$9FFF will correspond to one flash ROM, and $A000-$BFFF
// with Game set high and ExRom set low. /// (or $E000-$FFFF in UltiMax configuration) will correspond to the other.
///
// There is also 256 bytes RAM at DF00-DFFF. /// Control registers are mapped to $DE00 and $DE02. The 256-byte RAM is mapped to $DF00-$DFFF.
/// </summary>
// We emulate having the AM29F040 chip. /// <remarks>
internal sealed class Mapper0020 : CartridgeDevice /// 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
/// </remarks>
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 byte[] _deltaA; // 8000
private int[] _banksB = new int[64 << 13]; // A000 private byte[] _deltaB; // A000
private readonly int[] _originalMediaA; // 8000 private readonly Am29F040B _chipA = new();
private readonly int[] _originalMediaB; // A000 private readonly Am29F040B _chipB = new();
private bool _saveRamDirty;
private bool _boardLed; private bool _boardLed;
private bool _jumper; private bool _jumper;
private int _stateBits; private int _stateBits;
private int[] _ram = new int[256]; private byte[] _ram = new byte[256];
private int _bankNumber;
private bool _commandLatch55; public Mapper0020(IReadOnlyList<CartridgeChip> chips)
private bool _commandLatchAa;
private int _internalRomState;
public Mapper0020(IList<int> newAddresses, IList<int> newBanks, IList<int[]> newData)
{ {
DriveLightEnabled = true; DriveLightEnabled = true;
var count = newAddresses.Count; var count = chips.Count;
// force ultimax mode (the cart SHOULD set this // force ultimax mode (the cart SHOULD set this
// otherwise on load, according to the docs) // otherwise on load, according to the docs)
pinGame = false; pinGame = false;
pinExRom = true; 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 // load in all banks
for (var i = 0; i < count; i++) foreach (var chip in chips)
{ {
switch (newAddresses[i]) switch (chip.Address)
{ {
case 0x8000: 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; break;
case 0xA000: case 0xA000:
case 0xE000: 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; break;
} }
} }
// default to bank 0 // default to bank 0
BankSet(0); _bankNumber = 0;
// internal operation settings
_commandLatch55 = false;
_commandLatchAa = false;
_internalRomState = 0;
// back up original media // back up original media
_originalMediaA = _banksA.Select(d => d).ToArray(); _originalMediaA = _chipA.Data.ToArray();
_originalMediaB = _banksB.Select(d => d).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<byte>(_originalMediaA, _chipA.Data).ToArray();
if (_chipB.CheckDataDirty() || _deltaB == null)
_deltaB = DeltaSerializer.GetDelta<byte>(_originalMediaB, _chipB.Data).ToArray();
_saveRamDirty = false;
} }
protected override void SyncStateInternal(Serializer ser) 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("BoardLed", ref _boardLed);
ser.Sync("Jumper", ref _jumper); ser.Sync("Jumper", ref _jumper);
ser.Sync("StateBits", ref _stateBits); ser.Sync("StateBits", ref _stateBits);
ser.Sync("RAM", ref _ram, useNull: false); ser.Sync("RAM", ref _ram, useNull: false);
ser.Sync("CommandLatch55", ref _commandLatchAa); ser.Sync("MediaStateA", ref _deltaA, useNull: false);
ser.Sync("CommandLatchAA", ref _commandLatchAa); ser.Sync("MediaStateB", ref _deltaB, useNull: false);
ser.Sync("InternalROMState", ref _internalRomState);
ser.SyncDelta("MediaStateA", _originalMediaA, _banksA); ser.BeginSection("FlashA");
ser.SyncDelta("MediaStateB", _originalMediaB, _banksB); _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; DriveLightOn = _boardLed;
} }
private void BankSet(int index) private int CalculateBankOffset(int addr) =>
{ (addr & 0x1FFF) | (_bankNumber << 13);
_bankOffset = (index & 0x3F) << 13;
} public override int Peek8000(int addr) =>
_chipA.Peek(CalculateBankOffset(addr));
public override int Peek8000(int addr) public override int PeekA000(int addr) =>
{ _chipB.Peek(CalculateBankOffset(addr));
addr &= 0x1FFF;
return _banksA[addr | _bankOffset];
}
public override int PeekA000(int addr)
{
addr &= 0x1FFF;
return _banksB[addr | _bankOffset];
}
public override int PeekDE00(int 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 // but Peek is provided here for debug reasons
// and may not stay around // and may not stay around
addr &= 0x02; 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) public override int PeekDF00(int addr)
{ {
addr &= 0xFF; addr &= 0xFF;
@ -141,7 +176,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
addr &= 0x02; addr &= 0x02;
if (addr == 0x00) if (addr == 0x00)
{ {
BankSet(val); _bankNumber = val & 0x3F;
} }
else else
{ {
@ -152,18 +187,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
public override void PokeDF00(int addr, int val) public override void PokeDF00(int addr, int val)
{ {
addr &= 0xFF; addr &= 0xFF;
_ram[addr] = val & 0xFF; _ram[addr] = unchecked((byte)val);
} }
public override int Read8000(int addr) public override int Read8000(int addr) =>
{ _chipA.Read(CalculateBankOffset(addr));
return ReadInternal(addr & 0x1FFF, _banksA);
}
public override int ReadA000(int addr) public override int ReadA000(int addr) =>
{ _chipB.Read(CalculateBankOffset(addr));
return ReadInternal(addr & 0x1FFF, _banksB);
}
public override int ReadDF00(int addr) public override int ReadDF00(int addr)
{ {
@ -171,33 +202,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
return _ram[addr]; 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) private void StateSet(int val)
{ {
_stateBits = val &= 0x87; _stateBits = val &= 0x87;
@ -212,83 +216,23 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
pinExRom = (val & 0x02) == 0; pinExRom = (val & 0x02) == 0;
_boardLed = (val & 0x80) != 0; _boardLed = (val & 0x80) != 0;
_internalRomState = 0;
DriveLightOn = _boardLed; DriveLightOn = _boardLed;
} }
public override void Write8000(int addr, int val) 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) public override void WriteA000(int addr, int val)
{
WriteInternal(addr | 0x2000, val);
}
private void WriteInternal(int addr, int val)
{ {
if (pinGame || !pinExRom) if (pinGame || !pinExRom)
{
return; return;
}
if (val == 0xF0) // any address, resets flash _chipB.Write(CalculateBankOffset(addr), val);
{
_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;
}
} }
public override void WriteDE00(int addr, int val) public override void WriteDE00(int addr, int val)
@ -296,7 +240,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
addr &= 0x02; addr &= 0x02;
if (addr == 0x00) if (addr == 0x00)
{ {
BankSet(val); _bankNumber = val & 0x3F;
} }
else else
{ {
@ -306,7 +250,65 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge
public override void WriteDF00(int addr, int val) 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<MemoryDomain> 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();
}
/// <summary>
/// Applies a SaveRam block to the flash memory.
/// </summary>
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;
} }
} }