[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:
parent
83ea4bfb1c
commit
99ca3be22a
|
@ -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<MemoryDomain>
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,12 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
}
|
||||
|
||||
InitMedia(_roms[_currentDisk]);
|
||||
|
||||
if (_board.CartPort.SaveRam is { } cartSaveRam)
|
||||
{
|
||||
ser.Register<ISaveRam>(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:
|
||||
|
|
|
@ -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 = 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace BizHawk.Emulation.Cores.Computers.Commodore64.Cartridge;
|
||||
|
||||
public class CartridgeChip
|
||||
{
|
||||
public int Address;
|
||||
public int Bank;
|
||||
public int[] Data;
|
||||
}
|
|
@ -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<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)
|
||||
{
|
||||
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 _driveLightOn;
|
||||
|
||||
|
|
|
@ -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<MemoryDomain> CreateMemoryDomains()
|
||||
{
|
||||
if (_connected)
|
||||
return _cartridgeDevice.CreateMemoryDomains();
|
||||
return Array.Empty<MemoryDomain>();
|
||||
}
|
||||
|
||||
public bool DriveLightEnabled => _connected && _cartridgeDevice.DriveLightEnabled;
|
||||
public bool DriveLightOn => _connected && _cartridgeDevice.DriveLightOn;
|
||||
|
||||
|
|
|
@ -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
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 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<int> newAddresses, IList<int> newBanks, IList<int[]> newData)
|
||||
public Mapper0020(IReadOnlyList<CartridgeChip> 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<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)
|
||||
{
|
||||
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<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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue