Deduplicate code common to CPCHawk and ZXHawk
Also fixed indentation and replaced some block comments with #if false
This commit is contained in:
parent
7456d81a9c
commit
c7c26cd946
|
@ -1,25 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a beeper/buzzer device
|
||||
/// </summary>
|
||||
public interface IBeeperDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialization
|
||||
/// </summary>
|
||||
void Init(int sampleRate, int tStatesPerFrame);
|
||||
|
||||
/// <summary>
|
||||
/// Processes an incoming pulse value and adds it to the blipbuffer
|
||||
/// </summary>
|
||||
void ProcessPulseValue(bool pulse);
|
||||
|
||||
/// <summary>
|
||||
/// State serialization
|
||||
/// </summary>
|
||||
void SyncState(Serializer ser);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for the sector CHRN structure
|
||||
/// </summary>
|
||||
public class CHRN
|
||||
{
|
||||
/// <summary>
|
||||
/// Track
|
||||
/// </summary>
|
||||
public byte C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Side
|
||||
/// </summary>
|
||||
public byte H { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector ID
|
||||
/// </summary>
|
||||
public byte R { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector Size
|
||||
/// </summary>
|
||||
public byte N { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Status register 1
|
||||
/// </summary>
|
||||
private byte _flag1;
|
||||
public byte Flag1
|
||||
{
|
||||
get => _flag1;
|
||||
set => _flag1 = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status register 2
|
||||
/// </summary>
|
||||
private byte _flag2;
|
||||
public byte Flag2
|
||||
{
|
||||
get => _flag2;
|
||||
set => _flag2 = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the last transmitted/received data bytes
|
||||
/// </summary>
|
||||
public byte[] DataBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID for the read/write data command
|
||||
/// </summary>
|
||||
public int DataID { get; set; }
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark (Sector_ID or DAM not found)
|
||||
/// </summary>
|
||||
public bool ST1MA
|
||||
{
|
||||
get => NECUPD765.GetBit(0, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(0, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(0, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
/// </summary>
|
||||
public bool ST1ND
|
||||
{
|
||||
get => NECUPD765.GetBit(2, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(2, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(2, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error (CRC-fail in ID- or Data-Field)
|
||||
/// </summary>
|
||||
public bool ST1DE
|
||||
{
|
||||
get => NECUPD765.GetBit(5, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(5, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(5, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End of Track (set past most read/write commands) (see IC)
|
||||
/// </summary>
|
||||
public bool ST1EN
|
||||
{
|
||||
get => NECUPD765.GetBit(7, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(7, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(7, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark in Data Field (DAM not found)
|
||||
/// </summary>
|
||||
public bool ST2MD
|
||||
{
|
||||
get => NECUPD765.GetBit(0, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(0, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(0, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
/// </summary>
|
||||
public bool ST2BC
|
||||
{
|
||||
get => NECUPD765.GetBit(1, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(1, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(1, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
/// </summary>
|
||||
public bool ST2WC
|
||||
{
|
||||
get => NECUPD765.GetBit(4, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(4, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(4, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error in Data Field (CRC-fail in data-field)
|
||||
/// </summary>
|
||||
public bool ST2DD
|
||||
{
|
||||
get => NECUPD765.GetBit(5, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(5, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(5, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control Mark (read/scan command found sector with deleted DAM)
|
||||
/// </summary>
|
||||
public bool ST2CM
|
||||
{
|
||||
get => NECUPD765.GetBit(6, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(6, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(6, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,875 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Floppy drive related stuff
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765 : IFDDHost
|
||||
{
|
||||
#region Drive State
|
||||
|
||||
/// <summary>
|
||||
/// FDD Flag - motor on/off
|
||||
/// </summary>
|
||||
public bool FDD_FLAG_MOTOR;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently active disk drive
|
||||
/// </summary>
|
||||
public int DiskDriveIndex
|
||||
{
|
||||
get => _diskDriveIndex;
|
||||
set
|
||||
{
|
||||
// when index is changed update the ActiveDrive
|
||||
_diskDriveIndex = value;
|
||||
ActiveDrive = DriveStates[_diskDriveIndex];
|
||||
}
|
||||
}
|
||||
private int _diskDriveIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The currently active drive
|
||||
/// </summary>
|
||||
private DriveState ActiveDrive;
|
||||
|
||||
/// <summary>
|
||||
/// Array that holds state information for each possible drive
|
||||
/// </summary>
|
||||
private DriveState[] DriveStates = new DriveState[4];
|
||||
|
||||
#endregion
|
||||
|
||||
#region FDD Methods
|
||||
|
||||
/// <summary>
|
||||
/// Initialization / reset of the floppy drive subsystem
|
||||
/// </summary>
|
||||
private void FDD_Init()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
DriveState ds = new DriveState(i, this);
|
||||
DriveStates[i] = ds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the requested sector
|
||||
/// </summary>
|
||||
private FloppyDisk.Sector GetSector()
|
||||
{
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
// get the current track
|
||||
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
|
||||
|
||||
// get the current sector index
|
||||
int index = ActiveDrive.SectorIndex;
|
||||
|
||||
// make sure this index exists
|
||||
if (index > trk.Sectors.Length)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// index hole count
|
||||
int iHole = 0;
|
||||
|
||||
// loop through the sectors in a track
|
||||
// the loop ends with either the sector being found
|
||||
// or the index hole being passed twice
|
||||
while (iHole <= 2)
|
||||
{
|
||||
// does the requested sector match the current sector
|
||||
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
|
||||
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
|
||||
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
|
||||
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
|
||||
{
|
||||
// sector has been found
|
||||
sector = trk.Sectors[index];
|
||||
|
||||
UnSetBit(SR2_BC, ref Status2);
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
break;
|
||||
}
|
||||
|
||||
// check for bad cylinder
|
||||
if (trk.Sectors[index].SectorIDInfo.C == 255)
|
||||
{
|
||||
SetBit(SR2_BC, ref Status2);
|
||||
}
|
||||
// check for no cylinder
|
||||
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
|
||||
{
|
||||
SetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// incrememnt sector index
|
||||
index++;
|
||||
|
||||
// have we reached the index hole?
|
||||
if (trk.Sectors.Length <= index)
|
||||
{
|
||||
// wrap around
|
||||
index = 0;
|
||||
iHole++;
|
||||
}
|
||||
}
|
||||
|
||||
// search loop has completed and the sector may or may not have been found
|
||||
|
||||
// bad cylinder detected?
|
||||
if (Status2.Bit(SR2_BC))
|
||||
{
|
||||
// remove WC
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// update sectorindex on drive
|
||||
ActiveDrive.SectorIndex = index;
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
// IFDDHost methods that fall through to the currently active drive
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
public void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// we are only going to load into the first drive
|
||||
DriveStates[0].FDD_LoadDisk(diskData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
DriveStates[0].FDD_EjectDisk();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded => DriveStates[DiskDriveIndex].FDD_IsDiskLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the disk object from drive 0
|
||||
/// </summary>
|
||||
public FloppyDisk DiskPointer => DriveStates[0].Disk;
|
||||
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Status Class
|
||||
|
||||
/// <summary>
|
||||
/// Holds specfic state information about a drive
|
||||
/// </summary>
|
||||
private class DriveState : IFDDHost
|
||||
{
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// The drive ID from an FDC perspective
|
||||
/// </summary>
|
||||
public int ID;
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this drive ready
|
||||
/// TRUE if both drive exists and has a disk inserted
|
||||
/// </summary>
|
||||
public bool FLAG_READY
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disk is write protected (TRUE BY DEFAULT)
|
||||
/// </summary>
|
||||
public bool FLAG_WRITEPROTECT = false;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for seek steps
|
||||
/// One step for each indexpulse (track index) until seeked track
|
||||
/// </summary>
|
||||
public int SeekCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Seek status
|
||||
/// </summary>
|
||||
public int SeekStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Age counter
|
||||
/// </summary>
|
||||
public int SeekAge;
|
||||
|
||||
/// <summary>
|
||||
/// The current side
|
||||
/// </summary>
|
||||
public byte CurrentSide;
|
||||
|
||||
/// <summary>
|
||||
/// The current track index in the DiskTracks array
|
||||
/// </summary>
|
||||
public byte TrackIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The track ID of the current cylinder
|
||||
/// </summary>
|
||||
public byte CurrentTrackID
|
||||
{
|
||||
get
|
||||
{
|
||||
// default invalid track
|
||||
int id = 0xff;
|
||||
|
||||
if (Disk == null)
|
||||
return (byte)id;
|
||||
|
||||
if (Disk.DiskTracks.Count() == 0)
|
||||
return (byte)id;
|
||||
|
||||
if (TrackIndex >= Disk.GetTrackCount())
|
||||
TrackIndex = 0;
|
||||
else if (TrackIndex < 0)
|
||||
TrackIndex = 0;
|
||||
|
||||
var track = Disk.DiskTracks[TrackIndex];
|
||||
|
||||
id = track.TrackNumber;
|
||||
|
||||
return (byte)id;
|
||||
}
|
||||
set
|
||||
{
|
||||
for (int i = 0; i < Disk.GetTrackCount(); i++)
|
||||
{
|
||||
if (Disk.DiskTracks[i].TrackNumber == value)
|
||||
{
|
||||
TrackIndex = (byte)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The new track that the drive is seeking to
|
||||
/// (used in seek operations)
|
||||
/// </summary>
|
||||
public int SeekingTrack;
|
||||
|
||||
/// <summary>
|
||||
/// The current sector index in the Sectors array
|
||||
/// </summary>
|
||||
public int SectorIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The currently loaded floppy disk
|
||||
/// </summary>
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent controller
|
||||
/// </summary>
|
||||
private NECUPD765 FDC;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lookups
|
||||
|
||||
/// <summary>
|
||||
/// TRUE if we are on track 0
|
||||
/// </summary>
|
||||
public bool FLAG_TRACK0
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TrackIndex == 0) { return true; }
|
||||
else { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/*
|
||||
/// <summary>
|
||||
/// Moves the head across the disk cylinders
|
||||
/// </summary>
|
||||
public void MoveHead(SkipDirection direction, int cylinderCount)
|
||||
{
|
||||
// get total tracks
|
||||
int trackCount = Disk.DiskTracks.Count();
|
||||
|
||||
int trk = 0;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case SkipDirection.Increment:
|
||||
trk = (int)CurrentTrack + cylinderCount;
|
||||
if (trk >= trackCount)
|
||||
{
|
||||
// past the last track
|
||||
trk = trackCount - 1;
|
||||
}
|
||||
else if (trk < 0)
|
||||
trk = 0;
|
||||
break;
|
||||
case SkipDirection.Decrement:
|
||||
trk = (int)CurrentTrack - cylinderCount;
|
||||
if (trk < 0)
|
||||
{
|
||||
// before the first track
|
||||
trk = 0;
|
||||
}
|
||||
else if (trk >= trackCount)
|
||||
trk = trackCount - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// move the head
|
||||
CurrentTrack = (byte)trk;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
/// <summary>
|
||||
/// Finds a supplied sector
|
||||
/// </summary>
|
||||
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
|
||||
{
|
||||
int index =CurrentSector;
|
||||
int lc = 0;
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
bool found = false;
|
||||
|
||||
do
|
||||
{
|
||||
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
|
||||
if (sector != null && sector.SectorID == prms.Sector)
|
||||
{
|
||||
// sector found
|
||||
// check for data errors
|
||||
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
|
||||
{
|
||||
// data errors found
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// sector doesnt match
|
||||
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
|
||||
if (c == 255)
|
||||
{
|
||||
// bad cylinder
|
||||
resBuffer[RS_ST2] |= 0x02;
|
||||
}
|
||||
else if (prms.Cylinder != c)
|
||||
{
|
||||
// cylinder mismatch
|
||||
resBuffer[RS_ST2] |= 0x10;
|
||||
}
|
||||
|
||||
// increment index
|
||||
index++;
|
||||
|
||||
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
|
||||
{
|
||||
// out of bounds
|
||||
index = 0;
|
||||
lc++;
|
||||
}
|
||||
|
||||
} while (lc < 2);
|
||||
|
||||
if ((resBuffer[RS_ST2] & 0x02) != 0)
|
||||
{
|
||||
// bad cylinder set - remove no cylinder
|
||||
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
|
||||
}
|
||||
|
||||
// update current sector
|
||||
CurrentSector = index;
|
||||
|
||||
if (found)
|
||||
return sector;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates a result buffer
|
||||
/// </summary>
|
||||
public void FillResult(ref byte[] resBuffer, CHRN chrn)
|
||||
{
|
||||
// clear results
|
||||
resBuffer[RS_ST0] = 0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
|
||||
if (chrn == null)
|
||||
{
|
||||
// no chrn supplied
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates the result buffer with ReadID data
|
||||
/// </summary>
|
||||
public void ReadID(ref byte[] resBuffer)
|
||||
{
|
||||
if (CheckDriveStatus() == false)
|
||||
{
|
||||
// drive not ready
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
return;
|
||||
}
|
||||
|
||||
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
|
||||
|
||||
if (track != null && track.NumberOfSectors > 0)
|
||||
{
|
||||
// formatted track
|
||||
|
||||
// get the current sector
|
||||
int index = CurrentSector;
|
||||
|
||||
// is the index out of bounds?
|
||||
if (index >= track.NumberOfSectors)
|
||||
{
|
||||
// reset the index
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// read the sector data
|
||||
var data = track.Sectors[index];
|
||||
resBuffer[RS_C] = data.TrackNumber;
|
||||
resBuffer[RS_H] = data.SideNumber;
|
||||
resBuffer[RS_R] = data.SectorID;
|
||||
resBuffer[RS_N] = data.SectorSize;
|
||||
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
|
||||
// increment the current sector
|
||||
CurrentSector = index + 1;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// unformatted track?
|
||||
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
|
||||
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
|
||||
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
|
||||
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
|
||||
|
||||
SetBit(SR0_IC0, ref ST0);
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0x01;
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
/// <summary>
|
||||
/// The drive performs a seek operation if necessary
|
||||
/// Return value TRUE indicates seek complete
|
||||
/// </summary>
|
||||
public void DoSeek()
|
||||
{
|
||||
if (CurrentState != DriveMainState.Recalibrate &&
|
||||
CurrentState != DriveMainState.Seek)
|
||||
{
|
||||
// no seek/recalibrate has been asked for
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetBit(ID, FDC.StatusMain))
|
||||
{
|
||||
// drive is already seeking
|
||||
return;
|
||||
}
|
||||
|
||||
RunSeekCycle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a seek cycle
|
||||
/// </summary>
|
||||
public void RunSeekCycle()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
switch (SeekState)
|
||||
{
|
||||
// seek or recalibrate has been requested
|
||||
case SeekSubState.Idle:
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
// recalibrate always seeks to track 0
|
||||
SeekingTrack = 0;
|
||||
}
|
||||
SeekState = SeekSubState.MoveInit;
|
||||
|
||||
// mark drive as busy
|
||||
// this should be cleared by SIS command
|
||||
SetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
break;
|
||||
|
||||
// setup for the head move
|
||||
case SeekSubState.MoveInit:
|
||||
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
// we are already at the required track
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
!FLAG_TRACK0)
|
||||
{
|
||||
// recalibration fail
|
||||
SeekIntState = SeekIntStatus.Abnormal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
// equipment check
|
||||
SetBit(SR0_EC, ref FDC.Status0);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
FLAG_TRACK0)
|
||||
{
|
||||
// recalibration success
|
||||
SeekIntState = SeekIntStatus.Normal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
|
||||
{
|
||||
// drive not ready
|
||||
FLAG_READY = false;
|
||||
|
||||
// drive not ready
|
||||
SeekIntState = SeekIntStatus.DriveNotReady;
|
||||
|
||||
// cancel any interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.None;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (SeekCounter > 1)
|
||||
{
|
||||
// not ready to seek yet
|
||||
SeekCounter--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.MoveImmediate;
|
||||
break;
|
||||
}
|
||||
|
||||
// head move
|
||||
SeekState = SeekSubState.HeadMove;
|
||||
|
||||
break;
|
||||
|
||||
case SeekSubState.HeadMove:
|
||||
|
||||
// do the seek
|
||||
SeekCounter = FDC.SRT;
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, 1);
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, 1);
|
||||
}
|
||||
|
||||
// should the seek be completed now?
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
// seek not finished yet
|
||||
return;
|
||||
|
||||
// seek emulation processed immediately
|
||||
case SeekSubState.MoveImmediate:
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, delta);
|
||||
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, delta);
|
||||
}
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
|
||||
case SeekSubState.PerformCompletion:
|
||||
SeekDone();
|
||||
SeekState = SeekSubState.SeekCompleted;
|
||||
break;
|
||||
|
||||
case SeekSubState.SeekCompleted:
|
||||
// seek has already completed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a seek operation has completed
|
||||
/// </summary>
|
||||
public void SeekDone()
|
||||
{
|
||||
SeekCounter = 0;
|
||||
SeekingTrack = CurrentTrack;
|
||||
|
||||
// generate ST0 register data
|
||||
|
||||
// get only the IC bits
|
||||
IntStatus &= IC_ABORTED_DISCREMOVED;
|
||||
|
||||
// drive ready?
|
||||
if (!FLAG_READY)
|
||||
{
|
||||
SetBit(SR0_NR, ref IntStatus);
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
|
||||
// are we recalibrating?
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// set seek end
|
||||
SetBit(SR0_SE, ref IntStatus);
|
||||
/*
|
||||
// head address
|
||||
if (CurrentSide > 0)
|
||||
{
|
||||
SetBit(SR0_HD, ref IntStatus);
|
||||
|
||||
// drive only supports 1 head
|
||||
// set the EC bit
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
*/
|
||||
/*
|
||||
// UnitSelect
|
||||
SetUnitSelect(ID, ref IntStatus);
|
||||
|
||||
// move to none state
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
//SeekState = SeekSubState.SeekCompleted;
|
||||
|
||||
// set the seek interrupt flag for this drive
|
||||
// this will be cleared at the next successful senseint
|
||||
FLAG_SEEK_INTERRUPT = true;
|
||||
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public DriveState(int driveID, NECUPD765 fdc)
|
||||
{
|
||||
ID = driveID;
|
||||
FDC = fdc;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
public void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// try dsk first
|
||||
FloppyDisk fdd = null;
|
||||
bool found = false;
|
||||
|
||||
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DiskType.CPCExtended:
|
||||
fdd = new CPCExtendedFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.CPC:
|
||||
fdd = new CPCFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
Disk = fdd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
Disk = null;
|
||||
//FLAG_READY = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Disk != null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StateSerialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync(nameof(ID), ref ID);
|
||||
ser.Sync(nameof(FLAG_WRITEPROTECT), ref FLAG_WRITEPROTECT);
|
||||
//ser.Sync(nameof(FLAG_DISKCHANGED), ref FLAG_DISKCHANGED);
|
||||
//ser.Sync(nameof(FLAG_RECALIBRATING), ref FLAG_RECALIBRATING);
|
||||
//ser.Sync(nameof(FLAG_SEEK_INTERRUPT), ref FLAG_SEEK_INTERRUPT);
|
||||
//ser.Sync(nameof(IntStatus), ref IntStatus);
|
||||
//ser.Sync(nameof(ST0), ref ST0);
|
||||
//ser.Sync(nameof(RecalibrationCounter), ref RecalibrationCounter);
|
||||
ser.Sync(nameof(SeekCounter), ref SeekCounter);
|
||||
ser.Sync(nameof(SeekStatus), ref SeekStatus);
|
||||
ser.Sync(nameof(SeekAge), ref SeekAge);
|
||||
ser.Sync(nameof(CurrentSide), ref CurrentSide);
|
||||
//ser.Sync(nameof(CurrentTrack), ref CurrentTrack);
|
||||
ser.Sync(nameof(TrackIndex), ref TrackIndex);
|
||||
ser.Sync(nameof(SeekingTrack), ref SeekingTrack);
|
||||
//ser.Sync(nameof(CurrentSector), ref CurrentSector);
|
||||
ser.Sync(nameof(SectorIndex), ref SectorIndex);
|
||||
//ser.Sync(nameof(RAngles), ref RAngles);
|
||||
//ser.Sync(nameof(DataPointer), ref DataPointer);
|
||||
//ser.SyncEnum(nameof(CurrentState), ref CurrentState);
|
||||
//ser.SyncEnum(nameof(SeekState), ref SeekState);
|
||||
//ser.SyncEnum(nameof(SeekIntState), ref SeekIntState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
public class NECUPD765CPC : NECUPD765<CPCBase, NECUPD765CPC.CPCDriveState>
|
||||
{
|
||||
protected override CPCDriveState ConstructDriveState(int driveID, NECUPD765<CPCBase, CPCDriveState> fdc) => new CPCDriveState(driveID, fdc);
|
||||
|
||||
protected override void TimingInit()
|
||||
{
|
||||
// z80 timing
|
||||
double frameSize = _machine.GateArray.FrameLength;
|
||||
double rRate = _machine.GateArray.Z80ClockSpeed / frameSize;
|
||||
long tPerSecond = (long)(frameSize * rRate);
|
||||
CPUCyclesPerMs = tPerSecond / 1000;
|
||||
|
||||
// drive timing
|
||||
double dRate = DriveClock / frameSize;
|
||||
long dPerSecond = (long)(frameSize * dRate);
|
||||
DriveCyclesPerMs = dPerSecond / 1000;
|
||||
|
||||
long TStatesPerDriveCycle = (long)((double)_machine.GateArray.Z80ClockSpeed / DriveClock);
|
||||
StatesPerDriveTick = TStatesPerDriveCycle;
|
||||
|
||||
}
|
||||
|
||||
public class CPCDriveState : NECUPD765DriveState
|
||||
{
|
||||
public CPCDriveState(int driveID, NECUPD765<CPCBase, CPCDriveState> fdc) : base(driveID, fdc) {}
|
||||
|
||||
public override void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// try dsk first
|
||||
FloppyDisk fdd = null;
|
||||
bool found = false;
|
||||
|
||||
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DiskType.CPCExtended:
|
||||
fdd = new CPCExtendedFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.CPC:
|
||||
fdd = new CPCFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
Disk = fdd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -23,7 +25,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
private CRCT_6845 CRCT => _machine.CRCT;
|
||||
//private CRTDevice CRT => _machine.CRT;
|
||||
private IPSG PSG => _machine.AYDevice;
|
||||
private NECUPD765 FDC => _machine.UPDDiskDevice;
|
||||
private NECUPD765CPC FDC => _machine.UPDDiskDevice;
|
||||
private DatacorderDevice DATACORDER => _machine.TapeDevice;
|
||||
private ushort BUSRQ => CPU.MEMRQ[CPU.bus_pntr];
|
||||
public const ushort PCh = 1;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
TapeDevice = new DatacorderDevice(autoTape);
|
||||
TapeDevice.Init(this);
|
||||
|
||||
UPDDiskDevice = new NECUPD765();
|
||||
UPDDiskDevice = new NECUPD765CPC();
|
||||
UPDDiskDevice.Init(this);
|
||||
|
||||
InitializeMedia(files);
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
|
@ -7,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Main properties / fields / contruction*
|
||||
/// </summary>
|
||||
public abstract partial class CPCBase
|
||||
public abstract partial class CPCBase : CPCSpectrumBase.CPCSpectrumBase
|
||||
{
|
||||
#region Devices
|
||||
|
||||
|
@ -50,7 +51,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
/// <summary>
|
||||
/// The Amstrad disk drive
|
||||
/// </summary>
|
||||
public virtual NECUPD765 UPDDiskDevice { get; set; }
|
||||
public virtual NECUPD765CPC UPDDiskDevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Cathode Ray Tube Controller chip
|
||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
/// The different disk formats ZXHawk currently supports
|
||||
/// </summary>
|
||||
public enum DiskType
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard CPCEMU disk format (used in the built-in +3 disk drive)
|
||||
/// </summary>
|
||||
CPC,
|
||||
|
||||
/// <summary>
|
||||
/// Extended CPCEMU disk format (used in the built-in +3 disk drive)
|
||||
/// </summary>
|
||||
CPCExtended
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
public interface CPCSpectrumBase
|
||||
{
|
||||
Z80A CPU { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a beeper/buzzer device
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an object that can load a floppy disk image
|
|
@ -1,15 +1,17 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a spectrum joystick
|
||||
/// </summary>
|
||||
public interface IJoystick
|
||||
public interface IJoystick<T> where T : Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of joystick
|
||||
/// </summary>
|
||||
JoystickType JoyType { get; }
|
||||
T JoyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of all the possibly button press names
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a device that utilizes port IN & OUT
|
|
@ -0,0 +1,179 @@
|
|||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for the sector CHRN structure
|
||||
/// </summary>
|
||||
public class CHRN
|
||||
{
|
||||
/// <summary>
|
||||
/// Track
|
||||
/// </summary>
|
||||
public byte C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Side
|
||||
/// </summary>
|
||||
public byte H { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector ID
|
||||
/// </summary>
|
||||
public byte R { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector Size
|
||||
/// </summary>
|
||||
public byte N { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Status register 1
|
||||
/// </summary>
|
||||
private byte _flag1;
|
||||
public byte Flag1
|
||||
{
|
||||
get => _flag1;
|
||||
set => _flag1 = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status register 2
|
||||
/// </summary>
|
||||
private byte _flag2;
|
||||
public byte Flag2
|
||||
{
|
||||
get => _flag2;
|
||||
set => _flag2 = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the last transmitted/received data bytes
|
||||
/// </summary>
|
||||
public byte[] DataBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID for the read/write data command
|
||||
/// </summary>
|
||||
public int DataID { get; set; }
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark (Sector_ID or DAM not found)
|
||||
/// </summary>
|
||||
public bool ST1MA
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(0, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(0, ref _flag1); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(0, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
/// </summary>
|
||||
public bool ST1ND
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(2, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(2, ref _flag1); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(2, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error (CRC-fail in ID- or Data-Field)
|
||||
/// </summary>
|
||||
public bool ST1DE
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(5, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(5, ref _flag1); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(5, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End of Track (set past most read/write commands) (see IC)
|
||||
/// </summary>
|
||||
public bool ST1EN
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(7, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(7, ref _flag1); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(7, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark in Data Field (DAM not found)
|
||||
/// </summary>
|
||||
public bool ST2MD
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(0, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(0, ref _flag2); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(0, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
/// </summary>
|
||||
public bool ST2BC
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(1, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(1, ref _flag2); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(1, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
/// </summary>
|
||||
public bool ST2WC
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(4, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(4, ref _flag2); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(4, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error in Data Field (CRC-fail in data-field)
|
||||
/// </summary>
|
||||
public bool ST2DD
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(5, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(5, ref _flag2); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(5, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control Mark (read/scan command found sector with deleted DAM)
|
||||
/// </summary>
|
||||
public bool ST2CM
|
||||
{
|
||||
get => NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.GetBit(6, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.SetBit(6, ref _flag2); }
|
||||
else { NECUPD765<CPCSpectrumBase, NECUPD765DriveState>.UnSetBit(6, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
public interface INECUPD765
|
||||
{
|
||||
bool FDD_FLAG_MOTOR { get; }
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Definitions
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||
{
|
||||
#region Enums
|
||||
|
||||
|
@ -692,10 +692,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
/// </summary>
|
||||
private class Command
|
||||
{
|
||||
// /// <summary>
|
||||
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
|
||||
// /// </summary>
|
||||
// public int BitMask { get; set; }
|
||||
// /// <summary>
|
||||
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
|
||||
// /// </summary>
|
||||
// public int BitMask { get; set; }
|
||||
/// <summary>
|
||||
/// The command code after bitmask has been applied
|
||||
/// </summary>
|
|
@ -3,20 +3,20 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// FDC State and Methods
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||
{
|
||||
#region Controller State
|
||||
|
||||
|
@ -210,69 +210,69 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
/// Main status register (accessed via reads to port 0x2ffd)
|
||||
/// </summary>
|
||||
/*
|
||||
b0..3 DB FDD0..3 Busy (seek/recalib active, until succesful sense intstat)
|
||||
b4 CB FDC Busy (still in command-, execution- or result-phase)
|
||||
b5 EXM Execution Mode (still in execution-phase, non_DMA_only)
|
||||
b6 DIO Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
|
||||
b7 RQM Request For Master (1=ready for next byte) (see b6 for direction)
|
||||
*/
|
||||
b0..3 DB FDD0..3 Busy (seek/recalib active, until succesful sense intstat)
|
||||
b4 CB FDC Busy (still in command-, execution- or result-phase)
|
||||
b5 EXM Execution Mode (still in execution-phase, non_DMA_only)
|
||||
b6 DIO Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
|
||||
b7 RQM Request For Master (1=ready for next byte) (see b6 for direction)
|
||||
*/
|
||||
private byte StatusMain;
|
||||
|
||||
/// <summary>
|
||||
/// Status Register 0
|
||||
/// </summary>
|
||||
/*
|
||||
b0,1 US Unit Select (driveno during interrupt)
|
||||
b2 HD Head Address (head during interrupt)
|
||||
b3 NR Not Ready (drive not ready or non-existing 2nd head selected)
|
||||
b4 EC Equipment Check (drive failure or recalibrate failed (retry))
|
||||
b5 SE Seek End (Set if seek-command completed)
|
||||
b6,7 IC Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
|
||||
or senseint with no int occured, 3=aborted:disc removed etc.)
|
||||
*/
|
||||
b0,1 US Unit Select (driveno during interrupt)
|
||||
b2 HD Head Address (head during interrupt)
|
||||
b3 NR Not Ready (drive not ready or non-existing 2nd head selected)
|
||||
b4 EC Equipment Check (drive failure or recalibrate failed (retry))
|
||||
b5 SE Seek End (Set if seek-command completed)
|
||||
b6,7 IC Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
|
||||
or senseint with no int occured, 3=aborted:disc removed etc.)
|
||||
*/
|
||||
private byte Status0;
|
||||
|
||||
/// <summary>
|
||||
/// Status Register 1
|
||||
/// </summary>
|
||||
/*
|
||||
b0 MA Missing Address Mark (Sector_ID or DAM not found)
|
||||
b1 NW Not Writeable (tried to write/format disc with wprot_tab=on)
|
||||
b2 ND No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
b3,6 0 Not used
|
||||
b4 OR Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
|
||||
b5 DE Data Error (CRC-fail in ID- or Data-Field)
|
||||
b7 EN End of Track (set past most read/write commands) (see IC)
|
||||
*/
|
||||
b0 MA Missing Address Mark (Sector_ID or DAM not found)
|
||||
b1 NW Not Writeable (tried to write/format disc with wprot_tab=on)
|
||||
b2 ND No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
b3,6 0 Not used
|
||||
b4 OR Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
|
||||
b5 DE Data Error (CRC-fail in ID- or Data-Field)
|
||||
b7 EN End of Track (set past most read/write commands) (see IC)
|
||||
*/
|
||||
private byte Status1;
|
||||
|
||||
/// <summary>
|
||||
/// Status Register 2
|
||||
/// </summary>
|
||||
/*
|
||||
b0 MD Missing Address Mark in Data Field (DAM not found)
|
||||
b1 BC Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
b2 SN Scan Not Satisfied (no fitting sector found)
|
||||
b3 SH Scan Equal Hit (equal)
|
||||
b4 WC Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
b5 DD Data Error in Data Field (CRC-fail in data-field)
|
||||
b6 CM Control Mark (read/scan command found sector with deleted DAM)
|
||||
b7 0 Not Used
|
||||
*/
|
||||
b0 MD Missing Address Mark in Data Field (DAM not found)
|
||||
b1 BC Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
b2 SN Scan Not Satisfied (no fitting sector found)
|
||||
b3 SH Scan Equal Hit (equal)
|
||||
b4 WC Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
b5 DD Data Error in Data Field (CRC-fail in data-field)
|
||||
b6 CM Control Mark (read/scan command found sector with deleted DAM)
|
||||
b7 0 Not Used
|
||||
*/
|
||||
private byte Status2;
|
||||
|
||||
/// <summary>
|
||||
/// Status Register 3
|
||||
/// </summary>
|
||||
/*
|
||||
b0,1 US Unit Select (pin 28,29 of FDC)
|
||||
b2 HD Head Address (pin 27 of FDC)
|
||||
b3 TS Two Side (0=yes, 1=no (!))
|
||||
b4 T0 Track 0 (on track 0 we are)
|
||||
b5 RY Ready (drive ready signal)
|
||||
b6 WP Write Protected (write protected)
|
||||
b7 FT Fault (if supported: 1=Drive failure)
|
||||
*/
|
||||
b0,1 US Unit Select (pin 28,29 of FDC)
|
||||
b2 HD Head Address (pin 27 of FDC)
|
||||
b3 TS Two Side (0=yes, 1=no (!))
|
||||
b4 T0 Track 0 (on track 0 we are)
|
||||
b5 RY Ready (drive ready signal)
|
||||
b6 WP Write Protected (write protected)
|
||||
b7 FT Fault (if supported: 1=Drive failure)
|
||||
*/
|
||||
private byte Status3;
|
||||
|
||||
#endregion
|
||||
|
@ -385,8 +385,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
sectorSize = 0x80 << ActiveCommandParams.SectorSize;
|
||||
}
|
||||
|
||||
var mtc = maxTransferCap;
|
||||
|
||||
// get the current track
|
||||
var track = ActiveDrive.Disk.DiskTracks.FirstOrDefault(a => a.TrackNumber == ActiveDrive.CurrentTrackID);
|
||||
|
||||
|
@ -672,22 +670,13 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
switch (ActiveCommandParams.SectorSize)
|
||||
{
|
||||
case 1:
|
||||
if (CMD_FLAG_MF)
|
||||
maxTransferCap = 6656;
|
||||
else
|
||||
maxTransferCap = 3840;
|
||||
maxTransferCap = CMD_FLAG_MF ? 6656 : 3840;
|
||||
break;
|
||||
case 2:
|
||||
if (CMD_FLAG_MF)
|
||||
maxTransferCap = 7680;
|
||||
else
|
||||
maxTransferCap = 4096;
|
||||
maxTransferCap = CMD_FLAG_MF ? 7680 : 4096;
|
||||
break;
|
||||
case 3:
|
||||
if (CMD_FLAG_MF)
|
||||
maxTransferCap = 8192;
|
||||
else
|
||||
maxTransferCap = 4096;
|
||||
maxTransferCap = CMD_FLAG_MF ? 8192 : 4096;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2148,17 +2137,17 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
// second byte is the current track id
|
||||
ResBuffer[1] = ActiveDrive.CurrentTrackID;
|
||||
}
|
||||
/*
|
||||
else if (ActiveDrive.SeekStatus == SEEK_INTACKNOWLEDGED)
|
||||
{
|
||||
// DriveA interrupt has already been acknowledged
|
||||
ActiveDrive.SeekStatus = SEEK_IDLE;
|
||||
#if false
|
||||
else if (ActiveDrive.SeekStatus == SEEK_INTACKNOWLEDGED)
|
||||
{
|
||||
// DriveA interrupt has already been acknowledged
|
||||
ActiveDrive.SeekStatus = SEEK_IDLE;
|
||||
|
||||
ResLength = 1;
|
||||
Status0 = 192;
|
||||
ResBuffer[0] = Status0;
|
||||
}
|
||||
*/
|
||||
ResLength = 1;
|
||||
Status0 = 192;
|
||||
ResBuffer[0] = Status0;
|
||||
}
|
||||
#endif
|
||||
else if (ActiveDrive.SeekStatus == SEEK_IDLE)
|
||||
{
|
||||
// SIS with no interrupt
|
||||
|
@ -2363,7 +2352,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
SetBit(MSR_EXM, ref StatusMain);
|
||||
SetBit(MSR_CB, ref StatusMain);
|
||||
|
||||
// overrun detection
|
||||
// overrun detection
|
||||
OverrunCounter++;
|
||||
if (OverrunCounter >= 64)
|
||||
{
|
||||
|
@ -2383,6 +2372,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
break;
|
||||
}
|
||||
|
||||
// if (!CheckTiming()) UnSetBit(MSR_EXM, ref StatusMain);
|
||||
|
||||
return StatusMain;
|
||||
}
|
||||
|
||||
|
@ -2474,7 +2465,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
//// we are in command phase
|
||||
case Phase.Command:
|
||||
// attempt to process this parameter byte
|
||||
//ProcessCommand(data);
|
||||
//ProcessCommand(data);
|
||||
ActiveCommand.CommandDelegate();
|
||||
break;
|
||||
//// we are in execution phase
|
||||
|
@ -2553,15 +2544,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
CMDIndex = CommandList.Count() - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
if ((CMD_FLAG_MF && !ActiveCommand.MF) ||
|
||||
(CMD_FLAG_MT && !ActiveCommand.MT) ||
|
||||
(CMD_FLAG_SK && !ActiveCommand.SK))
|
||||
{
|
||||
// command byte included spurious bit 5,6 or 7 flags
|
||||
CMDIndex = CommandList.Count() - 1;
|
||||
}
|
||||
*/
|
||||
#if false
|
||||
if ((CMD_FLAG_MF && !ActiveCommand.MF) ||
|
||||
(CMD_FLAG_MT && !ActiveCommand.MT) ||
|
||||
(CMD_FLAG_SK && !ActiveCommand.SK))
|
||||
{
|
||||
// command byte included spurious bit 5,6 or 7 flags
|
||||
CMDIndex = CommandList.Count() - 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
CommCounter = 0;
|
||||
|
@ -2571,14 +2562,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
// move to command phase
|
||||
ActivePhase = Phase.Command;
|
||||
|
||||
/*
|
||||
// check for invalid SIS
|
||||
if (ActiveInterrupt == InterruptState.None && CMDIndex == CC_SENSE_INTSTATUS)
|
||||
{
|
||||
CMDIndex = CC_INVALID;
|
||||
//ActiveCommand.CommandDelegate(InstructionState.StartResult);
|
||||
}
|
||||
*/
|
||||
#if false
|
||||
// check for invalid SIS
|
||||
if (ActiveInterrupt == InterruptState.None && CMDIndex == CC_SENSE_INTSTATUS)
|
||||
{
|
||||
CMDIndex = CC_INVALID;
|
||||
//ActiveCommand.CommandDelegate(InstructionState.StartResult);
|
||||
}
|
||||
#endif
|
||||
|
||||
// set reslength
|
||||
ResLength = ActiveCommand.ResultByteCount;
|
|
@ -0,0 +1,189 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Floppy drive related stuff
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public abstract partial class NECUPD765<TMachine, TDriveState> : IFDDHost, INECUPD765
|
||||
{
|
||||
#region Drive State
|
||||
|
||||
/// <summary>
|
||||
/// FDD Flag - motor on/off
|
||||
/// </summary>
|
||||
public bool FDD_FLAG_MOTOR;
|
||||
|
||||
bool INECUPD765.FDD_FLAG_MOTOR => FDD_FLAG_MOTOR;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently active disk drive
|
||||
/// </summary>
|
||||
public int DiskDriveIndex
|
||||
{
|
||||
get => _diskDriveIndex;
|
||||
set
|
||||
{
|
||||
// when index is changed update the ActiveDrive
|
||||
_diskDriveIndex = value;
|
||||
ActiveDrive = DriveStates[_diskDriveIndex];
|
||||
}
|
||||
}
|
||||
private int _diskDriveIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The currently active drive
|
||||
/// </summary>
|
||||
private NECUPD765DriveState ActiveDrive;
|
||||
|
||||
/// <summary>
|
||||
/// Array that holds state information for each possible drive
|
||||
/// </summary>
|
||||
private NECUPD765DriveState[] DriveStates = new NECUPD765DriveState[4];
|
||||
|
||||
#endregion
|
||||
|
||||
#region FDD Methods
|
||||
|
||||
protected abstract TDriveState ConstructDriveState(int driveID, NECUPD765<TMachine, TDriveState> fdc);
|
||||
|
||||
/// <summary>
|
||||
/// Initialization / reset of the floppy drive subsystem
|
||||
/// </summary>
|
||||
private void FDD_Init()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
NECUPD765DriveState ds = ConstructDriveState(i, this);
|
||||
DriveStates[i] = ds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the requested sector
|
||||
/// </summary>
|
||||
private FloppyDisk.Sector GetSector()
|
||||
{
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
// get the current track
|
||||
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
|
||||
|
||||
// get the current sector index
|
||||
int index = ActiveDrive.SectorIndex;
|
||||
|
||||
// make sure this index exists
|
||||
if (index > trk.Sectors.Length)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// index hole count
|
||||
int iHole = 0;
|
||||
|
||||
// loop through the sectors in a track
|
||||
// the loop ends with either the sector being found
|
||||
// or the index hole being passed twice
|
||||
while (iHole <= 2)
|
||||
{
|
||||
// does the requested sector match the current sector
|
||||
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
|
||||
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
|
||||
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
|
||||
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
|
||||
{
|
||||
// sector has been found
|
||||
sector = trk.Sectors[index];
|
||||
|
||||
UnSetBit(SR2_BC, ref Status2);
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
break;
|
||||
}
|
||||
|
||||
// check for bad cylinder
|
||||
if (trk.Sectors[index].SectorIDInfo.C == 255)
|
||||
{
|
||||
SetBit(SR2_BC, ref Status2);
|
||||
}
|
||||
// check for no cylinder
|
||||
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
|
||||
{
|
||||
SetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// incrememnt sector index
|
||||
index++;
|
||||
|
||||
// have we reached the index hole?
|
||||
if (trk.Sectors.Length <= index)
|
||||
{
|
||||
// wrap around
|
||||
index = 0;
|
||||
iHole++;
|
||||
}
|
||||
}
|
||||
|
||||
// search loop has completed and the sector may or may not have been found
|
||||
|
||||
// bad cylinder detected?
|
||||
if (Status2.Bit(SR2_BC))
|
||||
{
|
||||
// remove WC
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// update sectorindex on drive
|
||||
ActiveDrive.SectorIndex = index;
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
// IFDDHost methods that fall through to the currently active drive
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
public void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// we are only going to load into the first drive
|
||||
DriveStates[0].FDD_LoadDisk(diskData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
DriveStates[0].FDD_EjectDisk();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded => DriveStates[DiskDriveIndex].FDD_IsDiskLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the disk object from drive 0
|
||||
/// </summary>
|
||||
public FloppyDisk DiskPointer => DriveStates[0].Disk;
|
||||
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -3,20 +3,20 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// IPortIODevice
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765 : IPortIODevice
|
||||
public abstract partial class NECUPD765<TMachine, TDriveState> : IPortIODevice
|
||||
{
|
||||
#region Dev Logging
|
||||
|
||||
|
@ -31,15 +31,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
|
||||
|
||||
/*
|
||||
* Status read
|
||||
* Data write
|
||||
* Data read
|
||||
* CMD code
|
||||
* CMD string
|
||||
* MT flag
|
||||
* MK flag
|
||||
* SK flag
|
||||
* */
|
||||
Status read
|
||||
Data write
|
||||
Data read
|
||||
CMD code
|
||||
CMD string
|
||||
MT flag
|
||||
MK flag
|
||||
SK flag
|
||||
*/
|
||||
private string[] workingArr = new string[3];
|
||||
|
||||
private void BuildCSVLine()
|
|
@ -1,19 +1,19 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper methods
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the specified bit value from supplied byte
|
|
@ -1,18 +1,18 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Timimng
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||
{
|
||||
/// <summary>
|
||||
/// The current Z80 cycle
|
||||
|
@ -43,7 +43,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
/// <summary>
|
||||
/// Defines the numbers of Z80 cycles per MS
|
||||
/// </summary>
|
||||
private long CPUCyclesPerMs;
|
||||
protected long CPUCyclesPerMs;
|
||||
|
||||
/// <summary>
|
||||
/// The floppy drive emulated clock speed
|
||||
|
@ -73,23 +73,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
/// <summary>
|
||||
/// Initializes the timing routines
|
||||
/// </summary>
|
||||
private void TimingInit()
|
||||
{
|
||||
// z80 timing
|
||||
double frameSize = _machine.GateArray.FrameLength;
|
||||
double rRate = _machine.GateArray.Z80ClockSpeed / frameSize;
|
||||
long tPerSecond = (long)(frameSize * rRate);
|
||||
CPUCyclesPerMs = tPerSecond / 1000;
|
||||
|
||||
// drive timing
|
||||
double dRate = DriveClock / frameSize;
|
||||
long dPerSecond = (long)(frameSize * dRate);
|
||||
DriveCyclesPerMs = dPerSecond / 1000;
|
||||
|
||||
long TStatesPerDriveCycle = (long)((double)_machine.GateArray.Z80ClockSpeed / DriveClock);
|
||||
StatesPerDriveTick = TStatesPerDriveCycle;
|
||||
|
||||
}
|
||||
protected abstract void TimingInit();
|
||||
|
||||
/// <summary>
|
||||
/// Called by reads to the main status register
|
|
@ -1,27 +1,29 @@
|
|||
using BizHawk.Common;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The NEC floppy disk controller (and floppy drive) found in the +3
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||
where TMachine : CPCSpectrumBase
|
||||
where TDriveState : NECUPD765DriveState
|
||||
{
|
||||
#region Devices
|
||||
|
||||
/// <summary>
|
||||
/// The emulated spectrum machine
|
||||
/// The emulated CPC or Spectrum machine
|
||||
/// </summary>
|
||||
private CPCBase _machine;
|
||||
protected TMachine _machine;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -38,7 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
/// <summary>
|
||||
/// Initialization routine
|
||||
/// </summary>
|
||||
public void Init(CPCBase machine)
|
||||
public void Init(TMachine machine)
|
||||
{
|
||||
_machine = machine;
|
||||
FDD_Init();
|
||||
|
@ -94,56 +96,56 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
{
|
||||
CommandList = new List<Command>
|
||||
{
|
||||
// read data
|
||||
new Command { CommandDelegate = UPD_ReadData, CommandCode = 0x06, MT = true, MF = true, SK = true, IsRead = true,
|
||||
// read data
|
||||
new Command { CommandDelegate = UPD_ReadData, CommandCode = 0x06, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// read id
|
||||
new Command { CommandDelegate = UPD_ReadID, CommandCode = 0x0a, MF = true, IsRead = true,
|
||||
// read id
|
||||
new Command { CommandDelegate = UPD_ReadID, CommandCode = 0x0a, MF = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 7 },
|
||||
// specify
|
||||
new Command { CommandDelegate = UPD_Specify, CommandCode = 0x03,
|
||||
// specify
|
||||
new Command { CommandDelegate = UPD_Specify, CommandCode = 0x03,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
|
||||
// read diagnostic
|
||||
new Command { CommandDelegate = UPD_ReadDiagnostic, CommandCode = 0x02, MF = true, SK = true, IsRead = true,
|
||||
// read diagnostic
|
||||
new Command { CommandDelegate = UPD_ReadDiagnostic, CommandCode = 0x02, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan equal
|
||||
new Command { CommandDelegate = UPD_ScanEqual, CommandCode = 0x11, MT = true, MF = true, SK = true, IsRead = true,
|
||||
// scan equal
|
||||
new Command { CommandDelegate = UPD_ScanEqual, CommandCode = 0x11, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan high or equal
|
||||
new Command { CommandDelegate = UPD_ScanHighOrEqual, CommandCode = 0x1d, MT = true, MF = true, SK = true, IsRead = true,
|
||||
// scan high or equal
|
||||
new Command { CommandDelegate = UPD_ScanHighOrEqual, CommandCode = 0x1d, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan low or equal
|
||||
new Command { CommandDelegate = UPD_ScanLowOrEqual, CommandCode = 0x19, MT = true, MF = true, SK = true, IsRead = true,
|
||||
// scan low or equal
|
||||
new Command { CommandDelegate = UPD_ScanLowOrEqual, CommandCode = 0x19, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// read deleted data
|
||||
new Command { CommandDelegate = UPD_ReadDeletedData, CommandCode = 0x0c, MT = true, MF = true, SK = true, IsRead = true,
|
||||
// read deleted data
|
||||
new Command { CommandDelegate = UPD_ReadDeletedData, CommandCode = 0x0c, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// write data
|
||||
new Command { CommandDelegate = UPD_WriteData, CommandCode = 0x05, MT = true, MF = true, IsWrite = true,
|
||||
// write data
|
||||
new Command { CommandDelegate = UPD_WriteData, CommandCode = 0x05, MT = true, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// write id
|
||||
new Command { CommandDelegate = UPD_WriteID, CommandCode = 0x0d, MF = true, IsWrite = true,
|
||||
// write id
|
||||
new Command { CommandDelegate = UPD_WriteID, CommandCode = 0x0d, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 5, ResultByteCount = 7 },
|
||||
// write deleted data
|
||||
new Command { CommandDelegate = UPD_WriteDeletedData, CommandCode = 0x09, MT = true, MF = true, IsWrite = true,
|
||||
// write deleted data
|
||||
new Command { CommandDelegate = UPD_WriteDeletedData, CommandCode = 0x09, MT = true, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// seek
|
||||
new Command { CommandDelegate = UPD_Seek, CommandCode = 0x0f,
|
||||
// seek
|
||||
new Command { CommandDelegate = UPD_Seek, CommandCode = 0x0f,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
|
||||
// recalibrate (seek track00)
|
||||
new Command { CommandDelegate = UPD_Recalibrate, CommandCode = 0x07,
|
||||
// recalibrate (seek track00)
|
||||
new Command { CommandDelegate = UPD_Recalibrate, CommandCode = 0x07,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 0 },
|
||||
// sense interrupt status
|
||||
new Command { CommandDelegate = UPD_SenseInterruptStatus, CommandCode = 0x08,
|
||||
// sense interrupt status
|
||||
new Command { CommandDelegate = UPD_SenseInterruptStatus, CommandCode = 0x08,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 2 },
|
||||
// sense drive status
|
||||
new Command { CommandDelegate = UPD_SenseDriveStatus, CommandCode = 0x04,
|
||||
// sense drive status
|
||||
new Command { CommandDelegate = UPD_SenseDriveStatus, CommandCode = 0x04,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 1 },
|
||||
// version
|
||||
new Command { CommandDelegate = UPD_Version, CommandCode = 0x10,
|
||||
// version
|
||||
new Command { CommandDelegate = UPD_Version, CommandCode = 0x10,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
|
||||
// invalid
|
||||
new Command { CommandDelegate = UPD_Invalid, CommandCode = 0x00,
|
||||
// invalid
|
||||
new Command { CommandDelegate = UPD_Invalid, CommandCode = 0x00,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
|
||||
};
|
||||
}
|
|
@ -0,0 +1,651 @@
|
|||
using System.Linq;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds specfic state information about a drive
|
||||
/// </summary>
|
||||
public abstract class NECUPD765DriveState : IFDDHost
|
||||
{
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// The drive ID from an FDC perspective
|
||||
/// </summary>
|
||||
public int ID;
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this drive ready
|
||||
/// TRUE if both drive exists and has a disk inserted
|
||||
/// </summary>
|
||||
public bool FLAG_READY
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disk is write protected (TRUE BY DEFAULT)
|
||||
/// </summary>
|
||||
public bool FLAG_WRITEPROTECT = false;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for seek steps
|
||||
/// One step for each indexpulse (track index) until seeked track
|
||||
/// </summary>
|
||||
public int SeekCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Seek status
|
||||
/// </summary>
|
||||
public int SeekStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Age counter
|
||||
/// </summary>
|
||||
public int SeekAge;
|
||||
|
||||
/// <summary>
|
||||
/// The current side
|
||||
/// </summary>
|
||||
public byte CurrentSide;
|
||||
|
||||
/// <summary>
|
||||
/// The current track index in the DiskTracks array
|
||||
/// </summary>
|
||||
public byte TrackIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The track ID of the current cylinder
|
||||
/// </summary>
|
||||
public byte CurrentTrackID
|
||||
{
|
||||
get
|
||||
{
|
||||
// default invalid track
|
||||
int id = 0xff;
|
||||
|
||||
if (Disk == null)
|
||||
return (byte)id;
|
||||
|
||||
if (Disk.DiskTracks.Count() == 0)
|
||||
return (byte)id;
|
||||
|
||||
if (TrackIndex >= Disk.GetTrackCount())
|
||||
TrackIndex = 0;
|
||||
else if (TrackIndex < 0)
|
||||
TrackIndex = 0;
|
||||
|
||||
var track = Disk.DiskTracks[TrackIndex];
|
||||
|
||||
id = track.TrackNumber;
|
||||
|
||||
return (byte)id;
|
||||
}
|
||||
set
|
||||
{
|
||||
for (int i = 0; i < Disk.GetTrackCount(); i++)
|
||||
{
|
||||
if (Disk.DiskTracks[i].TrackNumber == value)
|
||||
{
|
||||
TrackIndex = (byte)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The new track that the drive is seeking to
|
||||
/// (used in seek operations)
|
||||
/// </summary>
|
||||
public int SeekingTrack;
|
||||
|
||||
/// <summary>
|
||||
/// The current sector index in the Sectors array
|
||||
/// </summary>
|
||||
public int SectorIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The currently loaded floppy disk
|
||||
/// </summary>
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent controller
|
||||
/// </summary>
|
||||
private INECUPD765 FDC;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lookups
|
||||
|
||||
/// <summary>
|
||||
/// TRUE if we are on track 0
|
||||
/// </summary>
|
||||
public bool FLAG_TRACK0 => TrackIndex == 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Moves the head across the disk cylinders
|
||||
/// </summary>
|
||||
public void MoveHead(SkipDirection direction, int cylinderCount)
|
||||
{
|
||||
// get total tracks
|
||||
int trackCount = Disk.DiskTracks.Count();
|
||||
|
||||
int trk = 0;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case SkipDirection.Increment:
|
||||
trk = (int)CurrentTrack + cylinderCount;
|
||||
if (trk >= trackCount)
|
||||
{
|
||||
// past the last track
|
||||
trk = trackCount - 1;
|
||||
}
|
||||
else if (trk < 0)
|
||||
trk = 0;
|
||||
break;
|
||||
case SkipDirection.Decrement:
|
||||
trk = (int)CurrentTrack - cylinderCount;
|
||||
if (trk < 0)
|
||||
{
|
||||
// before the first track
|
||||
trk = 0;
|
||||
}
|
||||
else if (trk >= trackCount)
|
||||
trk = trackCount - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// move the head
|
||||
CurrentTrack = (byte)trk;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Finds a supplied sector
|
||||
/// </summary>
|
||||
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
|
||||
{
|
||||
int index =CurrentSector;
|
||||
int lc = 0;
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
bool found = false;
|
||||
|
||||
do
|
||||
{
|
||||
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
|
||||
if (sector != null && sector.SectorID == prms.Sector)
|
||||
{
|
||||
// sector found
|
||||
// check for data errors
|
||||
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
|
||||
{
|
||||
// data errors found
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// sector doesnt match
|
||||
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
|
||||
if (c == 255)
|
||||
{
|
||||
// bad cylinder
|
||||
resBuffer[RS_ST2] |= 0x02;
|
||||
}
|
||||
else if (prms.Cylinder != c)
|
||||
{
|
||||
// cylinder mismatch
|
||||
resBuffer[RS_ST2] |= 0x10;
|
||||
}
|
||||
|
||||
// increment index
|
||||
index++;
|
||||
|
||||
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
|
||||
{
|
||||
// out of bounds
|
||||
index = 0;
|
||||
lc++;
|
||||
}
|
||||
|
||||
} while (lc < 2);
|
||||
|
||||
if ((resBuffer[RS_ST2] & 0x02) != 0)
|
||||
{
|
||||
// bad cylinder set - remove no cylinder
|
||||
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
|
||||
}
|
||||
|
||||
// update current sector
|
||||
CurrentSector = index;
|
||||
|
||||
if (found)
|
||||
return sector;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates a result buffer
|
||||
/// </summary>
|
||||
public void FillResult(ref byte[] resBuffer, CHRN chrn)
|
||||
{
|
||||
// clear results
|
||||
resBuffer[RS_ST0] = 0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
|
||||
if (chrn == null)
|
||||
{
|
||||
// no chrn supplied
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates the result buffer with ReadID data
|
||||
/// </summary>
|
||||
public void ReadID(ref byte[] resBuffer)
|
||||
{
|
||||
if (CheckDriveStatus() == false)
|
||||
{
|
||||
// drive not ready
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
return;
|
||||
}
|
||||
|
||||
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
|
||||
|
||||
if (track != null && track.NumberOfSectors > 0)
|
||||
{
|
||||
// formatted track
|
||||
|
||||
// get the current sector
|
||||
int index = CurrentSector;
|
||||
|
||||
// is the index out of bounds?
|
||||
if (index >= track.NumberOfSectors)
|
||||
{
|
||||
// reset the index
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// read the sector data
|
||||
var data = track.Sectors[index];
|
||||
resBuffer[RS_C] = data.TrackNumber;
|
||||
resBuffer[RS_H] = data.SideNumber;
|
||||
resBuffer[RS_R] = data.SectorID;
|
||||
resBuffer[RS_N] = data.SectorSize;
|
||||
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
|
||||
// increment the current sector
|
||||
CurrentSector = index + 1;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// unformatted track?
|
||||
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
|
||||
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
|
||||
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
|
||||
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
|
||||
|
||||
SetBit(SR0_IC0, ref ST0);
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0x01;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// The drive performs a seek operation if necessary
|
||||
/// Return value TRUE indicates seek complete
|
||||
/// </summary>
|
||||
public void DoSeek()
|
||||
{
|
||||
if (CurrentState != DriveMainState.Recalibrate &&
|
||||
CurrentState != DriveMainState.Seek)
|
||||
{
|
||||
// no seek/recalibrate has been asked for
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetBit(ID, FDC.StatusMain))
|
||||
{
|
||||
// drive is already seeking
|
||||
return;
|
||||
}
|
||||
|
||||
RunSeekCycle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a seek cycle
|
||||
/// </summary>
|
||||
public void RunSeekCycle()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
switch (SeekState)
|
||||
{
|
||||
// seek or recalibrate has been requested
|
||||
case SeekSubState.Idle:
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
// recalibrate always seeks to track 0
|
||||
SeekingTrack = 0;
|
||||
}
|
||||
SeekState = SeekSubState.MoveInit;
|
||||
|
||||
// mark drive as busy
|
||||
// this should be cleared by SIS command
|
||||
SetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
break;
|
||||
|
||||
// setup for the head move
|
||||
case SeekSubState.MoveInit:
|
||||
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
// we are already at the required track
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
!FLAG_TRACK0)
|
||||
{
|
||||
// recalibration fail
|
||||
SeekIntState = SeekIntStatus.Abnormal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
// equipment check
|
||||
SetBit(SR0_EC, ref FDC.Status0);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
FLAG_TRACK0)
|
||||
{
|
||||
// recalibration success
|
||||
SeekIntState = SeekIntStatus.Normal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
|
||||
{
|
||||
// drive not ready
|
||||
FLAG_READY = false;
|
||||
|
||||
// drive not ready
|
||||
SeekIntState = SeekIntStatus.DriveNotReady;
|
||||
|
||||
// cancel any interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.None;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (SeekCounter > 1)
|
||||
{
|
||||
// not ready to seek yet
|
||||
SeekCounter--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.MoveImmediate;
|
||||
break;
|
||||
}
|
||||
|
||||
// head move
|
||||
SeekState = SeekSubState.HeadMove;
|
||||
|
||||
break;
|
||||
|
||||
case SeekSubState.HeadMove:
|
||||
|
||||
// do the seek
|
||||
SeekCounter = FDC.SRT;
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, 1);
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, 1);
|
||||
}
|
||||
|
||||
// should the seek be completed now?
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
// seek not finished yet
|
||||
return;
|
||||
|
||||
// seek emulation processed immediately
|
||||
case SeekSubState.MoveImmediate:
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, delta);
|
||||
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, delta);
|
||||
}
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
|
||||
case SeekSubState.PerformCompletion:
|
||||
SeekDone();
|
||||
SeekState = SeekSubState.SeekCompleted;
|
||||
break;
|
||||
|
||||
case SeekSubState.SeekCompleted:
|
||||
// seek has already completed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a seek operation has completed
|
||||
/// </summary>
|
||||
public void SeekDone()
|
||||
{
|
||||
SeekCounter = 0;
|
||||
SeekingTrack = CurrentTrack;
|
||||
|
||||
// generate ST0 register data
|
||||
|
||||
// get only the IC bits
|
||||
IntStatus &= IC_ABORTED_DISCREMOVED;
|
||||
|
||||
// drive ready?
|
||||
if (!FLAG_READY)
|
||||
{
|
||||
SetBit(SR0_NR, ref IntStatus);
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
|
||||
// are we recalibrating?
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// set seek end
|
||||
SetBit(SR0_SE, ref IntStatus);
|
||||
/*
|
||||
// head address
|
||||
if (CurrentSide > 0)
|
||||
{
|
||||
SetBit(SR0_HD, ref IntStatus);
|
||||
|
||||
// drive only supports 1 head
|
||||
// set the EC bit
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
*/
|
||||
// UnitSelect
|
||||
SetUnitSelect(ID, ref IntStatus);
|
||||
|
||||
// move to none state
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
//SeekState = SeekSubState.SeekCompleted;
|
||||
|
||||
// set the seek interrupt flag for this drive
|
||||
// this will be cleared at the next successful senseint
|
||||
FLAG_SEEK_INTERRUPT = true;
|
||||
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public NECUPD765DriveState(int driveID, INECUPD765 fdc)
|
||||
{
|
||||
ID = driveID;
|
||||
FDC = fdc;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
public abstract void FDD_LoadDisk(byte[] diskData);
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
Disk = null;
|
||||
//FLAG_READY = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Disk != null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StateSerialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync(nameof(ID), ref ID);
|
||||
ser.Sync(nameof(FLAG_WRITEPROTECT), ref FLAG_WRITEPROTECT);
|
||||
//ser.Sync(nameof(FLAG_DISKCHANGED), ref FLAG_DISKCHANGED);
|
||||
//ser.Sync(nameof(FLAG_RECALIBRATING), ref FLAG_RECALIBRATING);
|
||||
//ser.Sync(nameof(FLAG_SEEK_INTERRUPT), ref FLAG_SEEK_INTERRUPT);
|
||||
//ser.Sync(nameof(IntStatus), ref IntStatus);
|
||||
//ser.Sync(nameof(ST0), ref ST0);
|
||||
//ser.Sync(nameof(RecalibrationCounter), ref RecalibrationCounter);
|
||||
ser.Sync(nameof(SeekCounter), ref SeekCounter);
|
||||
ser.Sync(nameof(SeekStatus), ref SeekStatus);
|
||||
ser.Sync(nameof(SeekAge), ref SeekAge);
|
||||
ser.Sync(nameof(CurrentSide), ref CurrentSide);
|
||||
//ser.Sync(nameof(CurrentTrack), ref CurrentTrack);
|
||||
ser.Sync(nameof(TrackIndex), ref TrackIndex);
|
||||
ser.Sync(nameof(SeekingTrack), ref SeekingTrack);
|
||||
//ser.Sync(nameof(CurrentSector), ref CurrentSector);
|
||||
ser.Sync(nameof(SectorIndex), ref SectorIndex);
|
||||
//ser.Sync(nameof(RAngles), ref RAngles);
|
||||
//ser.Sync(nameof(DataPointer), ref DataPointer);
|
||||
//ser.SyncEnum(nameof(CurrentState), ref CurrentState);
|
||||
//ser.SyncEnum(nameof(SeekState), ref SeekState);
|
||||
//ser.SyncEnum(nameof(SeekIntState), ref SeekIntState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The different disk formats ZXHawk currently supports
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// This abstract class defines a logical floppy disk
|
||||
|
@ -307,7 +307,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
// TrackID is consistent between the sectors although is usually high (233, 237 etc)
|
||||
// SideID is fairly random looking but with all IDs being even
|
||||
// SectorID is also fairly random looking but contains both odd and even numbers
|
||||
//
|
||||
//
|
||||
// There doesnt appear to be any CRC errors in this track, but the sector size is always 1 (256 bytes)
|
||||
// Each sector contains different filler byte
|
||||
// Once track 0 is loaded the CPU completely reads all the sectors in this track one-by-one.
|
||||
|
@ -402,104 +402,104 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Should be run at the end of the ParseDisk process
|
||||
/// If speedlock is detected the flag is set in the disk image
|
||||
/// </summary>
|
||||
protected virtual void SpeedlockDetection()
|
||||
{
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Should be run at the end of the ParseDisk process
|
||||
/// If speedlock is detected the flag is set in the disk image
|
||||
/// </summary>
|
||||
protected virtual void SpeedlockDetection()
|
||||
{
|
||||
|
||||
if (DiskTracks.Length == 0)
|
||||
return;
|
||||
if (DiskTracks.Length == 0)
|
||||
return;
|
||||
|
||||
// check for speedlock copyright notice
|
||||
string ident = Encoding.ASCII.GetString(DiskData, 0x100, 0x1400);
|
||||
if (!ident.ToUpper().Contains("SPEEDLOCK"))
|
||||
{
|
||||
// speedlock not found
|
||||
return;
|
||||
}
|
||||
// check for speedlock copyright notice
|
||||
string ident = Encoding.ASCII.GetString(DiskData, 0x100, 0x1400);
|
||||
if (!ident.ToUpper().Contains("SPEEDLOCK"))
|
||||
{
|
||||
// speedlock not found
|
||||
return;
|
||||
}
|
||||
|
||||
// get cylinder 0
|
||||
var cyl = DiskTracks[0];
|
||||
// get cylinder 0
|
||||
var cyl = DiskTracks[0];
|
||||
|
||||
// get sector with ID=2
|
||||
var sec = cyl.Sectors.Where(a => a.SectorID == 2).FirstOrDefault();
|
||||
// get sector with ID=2
|
||||
var sec = cyl.Sectors.Where(a => a.SectorID == 2).FirstOrDefault();
|
||||
|
||||
if (sec == null)
|
||||
return;
|
||||
if (sec == null)
|
||||
return;
|
||||
|
||||
// check for already multiple weak copies
|
||||
if (sec.ContainsMultipleWeakSectors || sec.SectorData.Length != 0x80 << sec.SectorSize)
|
||||
return;
|
||||
// check for already multiple weak copies
|
||||
if (sec.ContainsMultipleWeakSectors || sec.SectorData.Length != 0x80 << sec.SectorSize)
|
||||
return;
|
||||
|
||||
// check for invalid crcs in sector 2
|
||||
if (sec.Status1.Bit(5) || sec.Status2.Bit(5))
|
||||
{
|
||||
Protection = ProtectionType.Speedlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
// check for invalid crcs in sector 2
|
||||
if (sec.Status1.Bit(5) || sec.Status2.Bit(5))
|
||||
{
|
||||
Protection = ProtectionType.Speedlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we are going to create a total of 5 weak sector copies
|
||||
// keeping the original copy
|
||||
byte[] origData = sec.SectorData.ToArray();
|
||||
List<byte> data = new List<byte>();
|
||||
//Random rnd = new Random();
|
||||
// we are going to create a total of 5 weak sector copies
|
||||
// keeping the original copy
|
||||
byte[] origData = sec.SectorData.ToArray();
|
||||
List<byte> data = new List<byte>();
|
||||
//Random rnd = new Random();
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
for (int s = 0; s < origData.Length; s++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
data.Add(origData[s]);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
for (int s = 0; s < origData.Length; s++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
data.Add(origData[s]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// deterministic 'random' implementation
|
||||
int n = origData[s] + i + 1;
|
||||
if (n > 0xff)
|
||||
n = n - 0xff;
|
||||
else if (n < 0)
|
||||
n = 0xff + n;
|
||||
// deterministic 'random' implementation
|
||||
int n = origData[s] + i + 1;
|
||||
if (n > 0xff)
|
||||
n = n - 0xff;
|
||||
else if (n < 0)
|
||||
n = 0xff + n;
|
||||
|
||||
byte nByte = (byte)n;
|
||||
byte nByte = (byte)n;
|
||||
|
||||
if (s < 336)
|
||||
{
|
||||
// non weak data
|
||||
data.Add(origData[s]);
|
||||
}
|
||||
else if (s < 511)
|
||||
{
|
||||
// weak data
|
||||
data.Add(nByte);
|
||||
}
|
||||
else if (s == 511)
|
||||
{
|
||||
// final sector byte
|
||||
data.Add(nByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
// speedlock sector should not be more than 512 bytes
|
||||
// but in case it is just do non weak
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s < 336)
|
||||
{
|
||||
// non weak data
|
||||
data.Add(origData[s]);
|
||||
}
|
||||
else if (s < 511)
|
||||
{
|
||||
// weak data
|
||||
data.Add(nByte);
|
||||
}
|
||||
else if (s == 511)
|
||||
{
|
||||
// final sector byte
|
||||
data.Add(nByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
// speedlock sector should not be more than 512 bytes
|
||||
// but in case it is just do non weak
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// commit the sector data
|
||||
sec.SectorData = data.ToArray();
|
||||
sec.ContainsMultipleWeakSectors = true;
|
||||
sec.ActualDataByteLength = data.Count();
|
||||
// commit the sector data
|
||||
sec.SectorData = data.ToArray();
|
||||
sec.ContainsMultipleWeakSectors = true;
|
||||
sec.ActualDataByteLength = data.Count();
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns the track count for the disk
|
||||
|
@ -587,8 +587,18 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
public byte NumberOfSectors { get; set; }
|
||||
public byte GAP3Length { get; set; }
|
||||
public byte FillerByte { get; set; }
|
||||
public Sector[] Sectors { get; set; }
|
||||
public virtual Sector[] Sectors { get; set; }
|
||||
|
||||
#region UDI
|
||||
|
||||
public byte TrackType { get; set; }
|
||||
public int TLEN { get; set; }
|
||||
public int CLEN => TLEN / 8 + (TLEN % 8 / 7) / 8;
|
||||
public byte[] TrackData { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Presents a contiguous byte array of all sector data for this track
|
||||
/// (including any multiple weak/random data)
|
||||
|
@ -607,6 +617,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public class Sector
|
||||
|
@ -637,10 +648,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
{
|
||||
return ActualDataByteLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
|
||||
}
|
||||
|
||||
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -667,10 +676,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
|
||||
return l.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SectorData;
|
||||
}
|
||||
|
||||
return SectorData;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -688,14 +695,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
return res;
|
||||
|
||||
/*
|
||||
int copies = ActualDataByteLength / (0x80 << SectorSize);
|
||||
Random rnd = new Random();
|
||||
int r = rnd.Next(0, copies - 1);
|
||||
int step = r * (0x80 << SectorSize);
|
||||
byte[] res = new byte[(0x80 << SectorSize)];
|
||||
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
|
||||
return res;
|
||||
*/
|
||||
int copies = ActualDataByteLength / (0x80 << SectorSize);
|
||||
Random rnd = new Random();
|
||||
int r = rnd.Next(0, copies - 1);
|
||||
int step = r * (0x80 << SectorSize);
|
||||
byte[] res = new byte[(0x80 << SectorSize)];
|
||||
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
|
||||
return res;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the possible commands that can be raised from each tape block
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a tape block
|
||||
|
@ -52,31 +52,31 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|||
set => _blockData = value;
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
|
||||
/// Its basically tape information
|
||||
/// </summary>
|
||||
private byte[][] _tapeDescriptionData;
|
||||
#if false
|
||||
/// <summary>
|
||||
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
|
||||
/// Its basically tape information
|
||||
/// </summary>
|
||||
private byte[][] _tapeDescriptionData;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Tape Description Data in a human readable format
|
||||
/// </summary>
|
||||
public List<string> TapeDescriptionData
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> data = new List<string>();
|
||||
/// <summary>
|
||||
/// Returns the Tape Description Data in a human readable format
|
||||
/// </summary>
|
||||
public List<string> TapeDescriptionData
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> data = new List<string>();
|
||||
|
||||
foreach (byte[] b in _tapeDescriptionData)
|
||||
{
|
||||
data.Add(Encoding.ASCII.GetString(b));
|
||||
}
|
||||
foreach (byte[] b in _tapeDescriptionData)
|
||||
{
|
||||
data.Add(Encoding.ASCII.GetString(b));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
*/
|
||||
return data;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#region Block Meta Data
|
|
@ -1,29 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an object that can load a floppy disk image
|
||||
/// </summary>
|
||||
public interface IFDDHost
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently inserted diskimage
|
||||
/// </summary>
|
||||
FloppyDisk Disk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
void FDD_LoadDisk(byte[] diskData);
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
void FDD_EjectDisk();
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
bool FDD_IsDiskLoaded { get; }
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a spectrum joystick
|
||||
/// </summary>
|
||||
public interface IJoystick
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of joystick
|
||||
/// </summary>
|
||||
JoystickType JoyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of all the possibly button press names
|
||||
/// </summary>
|
||||
string[] ButtonCollection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The player number that this controller is currently assigned to
|
||||
/// </summary>
|
||||
int PlayerNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the joystick line based on key pressed
|
||||
/// </summary>
|
||||
void SetJoyInput(string key, bool isPressed);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of a particular joystick binding
|
||||
/// </summary>
|
||||
bool GetJoyInput(string key);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a device that utilizes port IN & OUT
|
||||
/// </summary>
|
||||
public interface IPortIODevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
bool ReadPort(ushort port, ref int result);
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
bool WritePort(ushort port, int result);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
using BizHawk.Emulation.Cores.Sound;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for the sector CHRN structure
|
||||
/// </summary>
|
||||
public class CHRN
|
||||
{
|
||||
/// <summary>
|
||||
/// Track
|
||||
/// </summary>
|
||||
public byte C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Side
|
||||
/// </summary>
|
||||
public byte H { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector ID
|
||||
/// </summary>
|
||||
public byte R { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector Size
|
||||
/// </summary>
|
||||
public byte N { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Status register 1
|
||||
/// </summary>
|
||||
private byte _flag1;
|
||||
public byte Flag1
|
||||
{
|
||||
get => _flag1;
|
||||
set => _flag1 = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status register 2
|
||||
/// </summary>
|
||||
private byte _flag2;
|
||||
public byte Flag2
|
||||
{
|
||||
get => _flag2;
|
||||
set => _flag2 = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the last transmitted/received data bytes
|
||||
/// </summary>
|
||||
public byte[] DataBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID for the read/write data command
|
||||
/// </summary>
|
||||
public int DataID { get; set; }
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark (Sector_ID or DAM not found)
|
||||
/// </summary>
|
||||
public bool ST1MA
|
||||
{
|
||||
get => NECUPD765.GetBit(0, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(0, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(0, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
/// </summary>
|
||||
public bool ST1ND
|
||||
{
|
||||
get => NECUPD765.GetBit(2, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(2, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(2, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error (CRC-fail in ID- or Data-Field)
|
||||
/// </summary>
|
||||
public bool ST1DE
|
||||
{
|
||||
get => NECUPD765.GetBit(5, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(5, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(5, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End of Track (set past most read/write commands) (see IC)
|
||||
/// </summary>
|
||||
public bool ST1EN
|
||||
{
|
||||
get => NECUPD765.GetBit(7, _flag1);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(7, ref _flag1); }
|
||||
else { NECUPD765.UnSetBit(7, ref _flag1); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark in Data Field (DAM not found)
|
||||
/// </summary>
|
||||
public bool ST2MD
|
||||
{
|
||||
get => NECUPD765.GetBit(0, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(0, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(0, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
/// </summary>
|
||||
public bool ST2BC
|
||||
{
|
||||
get => NECUPD765.GetBit(1, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(1, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(1, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
/// </summary>
|
||||
public bool ST2WC
|
||||
{
|
||||
get => NECUPD765.GetBit(4, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(4, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(4, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Error in Data Field (CRC-fail in data-field)
|
||||
/// </summary>
|
||||
public bool ST2DD
|
||||
{
|
||||
get => NECUPD765.GetBit(5, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(5, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(5, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control Mark (read/scan command found sector with deleted DAM)
|
||||
/// </summary>
|
||||
public bool ST2CM
|
||||
{
|
||||
get => NECUPD765.GetBit(6, _flag2);
|
||||
set
|
||||
{
|
||||
if (value) { NECUPD765.SetBit(6, ref _flag2); }
|
||||
else { NECUPD765.UnSetBit(6, ref _flag2); }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,826 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Definitions
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the current phase of the controller
|
||||
/// </summary>
|
||||
private enum Phase
|
||||
{
|
||||
/// <summary>
|
||||
/// FDC is in an idle state, awaiting the next initial command byte
|
||||
/// </summary>
|
||||
Idle,
|
||||
|
||||
/// <summary>
|
||||
/// FDC is in a state waiting for the next command instruction
|
||||
/// A command consists of a command byte (eventually including the MF, MK, SK bits), and up to eight parameter bytes
|
||||
/// </summary>
|
||||
Command,
|
||||
|
||||
/// <summary>
|
||||
/// During this phase, the actual data is transferred (if any). Usually that are the data bytes for the read/written sector(s), except for the Format Track Command,
|
||||
/// in that case four bytes for each sector are transferred
|
||||
/// </summary>
|
||||
Execution,
|
||||
|
||||
/// <summary>
|
||||
/// Returns up to seven result bytes (depending on the command) that are containing status information. The Recalibrate and Seek Track commands do not return result bytes directly,
|
||||
/// instead the program must wait until the Main Status Register signalizes that the command has been completed, and then it must (!) send a
|
||||
/// Sense Interrupt State command to 'terminate' the Seek/Recalibrate command.
|
||||
/// </summary>
|
||||
Result
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The lifecycle of an instruction
|
||||
/// Similar to phase, this describes the current 'sub-phase' we are in when dealing with an instruction
|
||||
/// </summary>
|
||||
private enum InstructionState
|
||||
{
|
||||
/// <summary>
|
||||
/// FDC has received a command byte and is currently reading parameter bytes from the data bus
|
||||
/// </summary>
|
||||
ReceivingParameters,
|
||||
|
||||
/// <summary>
|
||||
/// All parameter bytes have been received. This phase allows any neccessary setup before instruction execution starts
|
||||
/// </summary>
|
||||
PreExecution,
|
||||
|
||||
/// <summary>
|
||||
/// The start of instruction execution. This may end up with the FDC moving into result phase,
|
||||
/// but also may also prepare the way for further processing to occur later in execution phase
|
||||
/// </summary>
|
||||
StartExecute,
|
||||
|
||||
/// <summary>
|
||||
/// Data is read or written in execution phase
|
||||
/// </summary>
|
||||
ExecutionReadWrite,
|
||||
|
||||
/// <summary>
|
||||
/// Execution phase is well under way. This state primarily deals with data transfer between CPU and FDC
|
||||
/// </summary>
|
||||
ExecutionWrite,
|
||||
|
||||
/// <summary>
|
||||
/// Execution phase is well under way. This state primarily deals with data transfer between FDC and CPU
|
||||
/// </summary>
|
||||
ExecutionRead,
|
||||
|
||||
/// <summary>
|
||||
/// Execution has finished and results bytes are ready to be read by the CPU
|
||||
/// Initial result setup
|
||||
/// </summary>
|
||||
StartResult,
|
||||
|
||||
/// <summary>
|
||||
/// Result processing
|
||||
/// </summary>
|
||||
ProcessResult,
|
||||
|
||||
/// <summary>
|
||||
/// Results are being sent
|
||||
/// </summary>
|
||||
SendingResults,
|
||||
|
||||
/// <summary>
|
||||
/// Final cleanup tasks when the instruction has fully completed
|
||||
/// </summary>
|
||||
Completed
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents internal interrupt state of the FDC
|
||||
/// </summary>
|
||||
public enum InterruptState
|
||||
{
|
||||
/// <summary>
|
||||
/// There is no interrupt
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Execution interrupt
|
||||
/// </summary>
|
||||
Execution,
|
||||
/// <summary>
|
||||
/// Result interrupt
|
||||
/// </summary>
|
||||
Result,
|
||||
/// <summary>
|
||||
/// Ready interrupt
|
||||
/// </summary>
|
||||
Ready,
|
||||
/// <summary>
|
||||
/// Seek interrupt
|
||||
/// </summary>
|
||||
Seek
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible main states that each drive can be in
|
||||
/// </summary>
|
||||
public enum DriveMainState
|
||||
{
|
||||
/// <summary>
|
||||
/// Drive is not doing anything
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Seek operation is in progress
|
||||
/// </summary>
|
||||
Seek,
|
||||
/// <summary>
|
||||
/// Recalibrate operation is in progress
|
||||
/// </summary>
|
||||
Recalibrate,
|
||||
/// <summary>
|
||||
/// A scan data operation is in progress
|
||||
/// </summary>
|
||||
Scan,
|
||||
/// <summary>
|
||||
/// A read ID operation is in progress
|
||||
/// </summary>
|
||||
ReadID,
|
||||
/// <summary>
|
||||
/// A read data operation is in progress
|
||||
/// </summary>
|
||||
ReadData,
|
||||
/// <summary>
|
||||
/// A read diagnostic (read track) operation is in progress
|
||||
/// </summary>
|
||||
ReadDiagnostic,
|
||||
/// <summary>
|
||||
/// A write id (format track) operation is in progress
|
||||
/// </summary>
|
||||
WriteID,
|
||||
/// <summary>
|
||||
/// A write data operation is in progress
|
||||
/// </summary>
|
||||
WriteData,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State information during a seek/recalibration operation
|
||||
/// </summary>
|
||||
public enum SeekSubState
|
||||
{
|
||||
/// <summary>
|
||||
/// Seek hasnt started yet
|
||||
/// </summary>
|
||||
Idle,
|
||||
/// <summary>
|
||||
/// Delayed
|
||||
/// </summary>
|
||||
Wait,
|
||||
/// <summary>
|
||||
/// Setup for head move
|
||||
/// </summary>
|
||||
MoveInit,
|
||||
/// <summary>
|
||||
/// Seek is currently happening
|
||||
/// </summary>
|
||||
HeadMove,
|
||||
/// <summary>
|
||||
/// Head move with no delay
|
||||
/// </summary>
|
||||
MoveImmediate,
|
||||
/// <summary>
|
||||
/// Ready to complete
|
||||
/// </summary>
|
||||
PerformCompletion,
|
||||
/// <summary>
|
||||
/// Seek operation has completed
|
||||
/// </summary>
|
||||
SeekCompleted
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seek int code
|
||||
/// </summary>
|
||||
public enum SeekIntStatus
|
||||
{
|
||||
Normal,
|
||||
Abnormal,
|
||||
DriveNotReady,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The direction of a specific command
|
||||
/// </summary>
|
||||
private enum CommandDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// Data flows from UPD765A to Z80
|
||||
/// </summary>
|
||||
OUT,
|
||||
/// <summary>
|
||||
/// Data flows from Z80 to UPD765A
|
||||
/// </summary>
|
||||
IN
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum defining the different types of result that can be returned
|
||||
/// </summary>
|
||||
private enum ResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard 7 result bytes are returned
|
||||
/// </summary>
|
||||
Standard,
|
||||
/// <summary>
|
||||
/// 1 byte returned - ST3
|
||||
/// (used for SenseDriveStatus)
|
||||
/// </summary>
|
||||
ST3,
|
||||
/// <summary>
|
||||
/// 1 byte returned - ST0
|
||||
/// (used for version & invalid)
|
||||
/// </summary>
|
||||
ST0,
|
||||
/// <summary>
|
||||
/// 2 bytes returned for sense interrupt status command
|
||||
/// ST0
|
||||
/// CurrentCylinder
|
||||
/// </summary>
|
||||
Interrupt
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible list of encountered drive status errors
|
||||
/// </summary>
|
||||
public enum Status
|
||||
{
|
||||
/// <summary>
|
||||
/// No error detected
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// An undefined error has been detected
|
||||
/// </summary>
|
||||
Undefined,
|
||||
/// <summary>
|
||||
/// Drive is not ready
|
||||
/// </summary>
|
||||
DriveNotReady,
|
||||
/// <summary>
|
||||
/// Invalid command received
|
||||
/// </summary>
|
||||
Invalid,
|
||||
/// <summary>
|
||||
/// The disk has its write protection tab enabled
|
||||
/// </summary>
|
||||
WriteProtected,
|
||||
/// <summary>
|
||||
/// The requested sector has not been found
|
||||
/// </summary>
|
||||
SectorNotFound
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the direction that the head is moving over the cylinders
|
||||
/// Increment: Track number increasing (head moving from outside of disk inwards)
|
||||
/// Decrement: Track number decreasing (head moving from inside of disk outwards)
|
||||
/// </summary>
|
||||
public enum SkipDirection
|
||||
{
|
||||
Increment,
|
||||
Decrement
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
// Command Instruction Constants
|
||||
// Designates the default postitions within the cmdbuffer array
|
||||
|
||||
public const int CM_HEAD = 0;
|
||||
/// <summary>
|
||||
/// C - Track
|
||||
/// </summary>
|
||||
public const int CM_C = 1;
|
||||
/// <summary>
|
||||
/// H - Side
|
||||
/// </summary>
|
||||
public const int CM_H = 2;
|
||||
/// <summary>
|
||||
/// R - Sector ID
|
||||
/// </summary>
|
||||
public const int CM_R = 3;
|
||||
/// <summary>
|
||||
/// N - Sector size
|
||||
/// </summary>
|
||||
public const int CM_N = 4;
|
||||
/// <summary>
|
||||
/// EOT - End of track
|
||||
/// </summary>
|
||||
public const int CM_EOT = 5;
|
||||
/// <summary>
|
||||
/// GPL - Gap length
|
||||
/// </summary>
|
||||
public const int CM_GPL = 6;
|
||||
/// <summary>
|
||||
/// DTL - Data length
|
||||
/// </summary>
|
||||
public const int CM_DTL = 7;
|
||||
/// <summary>
|
||||
/// STP - Step
|
||||
/// </summary>
|
||||
public const int CM_STP = 7;
|
||||
|
||||
// Result Instruction Constants
|
||||
// Designates the default postitions within the cmdbuffer array
|
||||
|
||||
/// <summary>
|
||||
/// Status register 0
|
||||
/// </summary>
|
||||
public const int RS_ST0 = 0;
|
||||
/// <summary>
|
||||
/// Status register 1
|
||||
/// </summary>
|
||||
public const int RS_ST1 = 1;
|
||||
/// <summary>
|
||||
/// Status register 2
|
||||
/// </summary>
|
||||
public const int RS_ST2 = 2;
|
||||
/// <summary>
|
||||
/// C - Track
|
||||
/// </summary>
|
||||
public const int RS_C = 3;
|
||||
/// <summary>
|
||||
/// H - Side
|
||||
/// </summary>
|
||||
public const int RS_H = 4;
|
||||
/// <summary>
|
||||
/// R - Sector ID
|
||||
/// </summary>
|
||||
public const int RS_R = 5;
|
||||
/// <summary>
|
||||
/// N - Sector size
|
||||
/// </summary>
|
||||
public const int RS_N = 6;
|
||||
|
||||
// Main Status Register Constants
|
||||
// Designates the bit positions within the Main status register
|
||||
|
||||
/// <summary>
|
||||
/// FDD0 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 0 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D0B = 0;
|
||||
/// <summary>
|
||||
/// FDD1 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 1 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D1B = 1;
|
||||
/// <summary>
|
||||
/// FDD2 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 2 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D2B = 2;
|
||||
/// <summary>
|
||||
/// FDD3 Busy (seek/recalib active, until succesful sense intstat)
|
||||
/// FDD number 3 is in the seek mode. If any of the DnB bits IS set FDC will not accept read or write command.
|
||||
/// </summary>
|
||||
public const int MSR_D3B = 3;
|
||||
/// <summary>
|
||||
/// FDC Busy (still in command-, execution- or result-phase)
|
||||
/// A Read or Write command is in orocess. (FDC Busy) FDC will not accept any other command
|
||||
/// </summary>
|
||||
public const int MSR_CB = 4;
|
||||
/// <summary>
|
||||
/// Execution Mode (still in execution-phase, non_DMA_only)
|
||||
/// This bit is set only during execution ohase (Execution Mode) in non-DMA mode When DB5 goes low, execution phase has ended and result phase has started.It operates only during
|
||||
/// non-DMA mode of operation
|
||||
/// </summary>
|
||||
public const int MSR_EXM = 5;
|
||||
/// <summary>
|
||||
/// Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
|
||||
/// Indicates direction of data transfer between FDC and data regrster If DIO = 1, then transfer is from data register to the
|
||||
/// processor.If DIO = 0, then transfer is from the processor to data register
|
||||
/// </summary>
|
||||
public const int MSR_DIO = 6;
|
||||
/// <summary>
|
||||
/// Request For Master (1=ready for next byte) (see b6 for direction)
|
||||
/// ndicates data register IS ready to send or receive data to or from the processor Both bits DIO and RQM should be
|
||||
/// used to perform the hand-shaking functions of “ready” and “directron” to the processor
|
||||
/// </summary>
|
||||
public const int MSR_RQM = 7;
|
||||
|
||||
// Status Register 0 Constants
|
||||
// Designates the bit positions within the status register 0
|
||||
|
||||
/// <summary>
|
||||
/// Unit Select (driveno during interrupt)
|
||||
/// This flag IS used to indicate a drive unit number at interrupt
|
||||
/// </summary>
|
||||
public const int SR0_US0 = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Unit Select (driveno during interrupt)
|
||||
/// This flag IS used to indicate a drive unit number at interrupt
|
||||
/// </summary>
|
||||
public const int SR0_US1 = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Head Address (head during interrupt)
|
||||
/// State of the head at interrupt
|
||||
/// </summary>
|
||||
public const int SR0_HD = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Not Ready (drive not ready or non-existing 2nd head selected)
|
||||
/// Not Ready - When the FDD IS in the not-ready state and a Read or Write command IS Issued, this
|
||||
/// flag IS set If a Read or Write command is issued to side 1 of a single-sided drive, then this flag IS set
|
||||
/// </summary>
|
||||
public const int SR0_NR = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Equipment Check (drive failure or recalibrate failed (retry))
|
||||
/// Equipment check - If a fault srgnal IS received from the FDD, or if the track 0 srgnal fails to occur after 77
|
||||
/// step pulses(Recalibrate Command) then this flag is set
|
||||
/// </summary>
|
||||
public const int SR0_EC = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Seek End (Set if seek-command completed)
|
||||
/// Seek end - When the FDC completes the Seek command, this flag IS set lo 1 (high)
|
||||
/// </summary>
|
||||
public const int SR0_SE = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Interrupt Code (low byte)
|
||||
/// Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
|
||||
/// or senseint with no int occured, 3=aborted:disc removed etc.)
|
||||
/// </summary>
|
||||
public const int SR0_IC0 = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Interrupt Code (high byte)
|
||||
/// Interrupt Code (0=OK, 1=aborted:readfail/OK if EN, 2=unknown cmd
|
||||
/// or senseint with no int occured, 3=aborted:disc removed etc.)
|
||||
/// </summary>
|
||||
public const int SR0_IC1 = 7;
|
||||
|
||||
// Status Register 1 Constants
|
||||
// Designates the bit positions within the status register 1
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark (Sector_ID or DAM not found)
|
||||
/// Missing address mark - This bit is set i f the FDC does not detect the IDAM before 2 index pulses It is also set if
|
||||
/// the FDC cannot find the DAM or DDAM after the IDAM i s found.MD bit of ST2 is also set at this time
|
||||
/// </summary>
|
||||
public const int SR1_MA = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Not Writeable (tried to write/format disc with wprot_tab=on)
|
||||
/// Not writable (write protect) - During execution of Write Data, Write Deleted Data or Write ID command. if the FDC
|
||||
/// detect: a write protect srgnal from the FDD.then this flag is Set
|
||||
/// </summary>
|
||||
public const int SR1_NW = 1;
|
||||
|
||||
/// <summary>
|
||||
/// No Data
|
||||
/// No Data (Sector_ID not found, CRC fail in ID_field)
|
||||
///
|
||||
/// During execution of Read Data. Read Deleted Data Write Data.Write Deleted Data or Scan command, if the FDC cannot find
|
||||
/// the sector specified in the IDR(2)Register, this flag i s set.
|
||||
///
|
||||
/// During execution of the Read ID command. if the FDC cannot read the ID field without an error, then this flag IS set
|
||||
///
|
||||
/// During execution of the Read Diagnostic command. if the starting sector cannot be found, then this flag is set
|
||||
/// </summary>
|
||||
public const int SR1_ND = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
|
||||
/// Overrun - If the FDC i s not serviced by the host system during data transfers within a certain time interval.this flaa i s set
|
||||
/// </summary>
|
||||
public const int SR1_OR = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Data Error (CRC-fail in ID- or Data-Field)
|
||||
/// Data error - When the FDC detects a CRC(1) error in either the ID field or the data field, this flag is set
|
||||
/// </summary>
|
||||
public const int SR1_DE = 5;
|
||||
|
||||
/// <summary>
|
||||
/// End of Track (set past most read/write commands) (see IC)
|
||||
/// End of cylinder - When the FDC tries to access a sector beyond the final sector of a cylinder, this flag I S set
|
||||
/// </summary>
|
||||
public const int SR1_EN = 7;
|
||||
|
||||
// Status Register 2 Constants
|
||||
// Designates the bit positions within the status register 2
|
||||
|
||||
/// <summary>
|
||||
/// Missing Address Mark in Data Field (DAM not found)
|
||||
/// Missing address mark - When data IS read from the medium, i f the FDC cannot find a data address mark or deleted
|
||||
/// data address mark, then this flag is set
|
||||
/// </summary>
|
||||
public const int SR2_MD = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
|
||||
/// Bad cylinder - This bit i s related to the ND bit. and when the contents of C on the medium is different
|
||||
/// from that stored i n the IDR and the contents of C IS FFH.then this flag IS set
|
||||
/// </summary>
|
||||
public const int SR2_BC = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Scan Not Satisfied (no fitting sector found)
|
||||
/// Scan not satisfied - During execution of the Scan command, i f the F D cannot find a sector on the cylinder
|
||||
/// which meets the condition.then this flag i s set
|
||||
/// </summary>
|
||||
public const int SR2_SN = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Scan Equal Hit (equal)
|
||||
/// Scan equal hit - During execution of the Scan command. i f the condition of “equal” is satisfied, this flag i s set
|
||||
/// </summary>
|
||||
public const int SR2_SH = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
|
||||
/// Wrong cylinder - This bit IS related to the ND bit, and when the contents of C(3) on the medium is different
|
||||
/// from that stored i n the IDR.this flag is set
|
||||
/// </summary>
|
||||
public const int SR2_WC = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Data Error in Data Field (CRC-fail in data-field)
|
||||
/// Data error in data field - If the FDC detects a CRC error i n the data field then this flag is set
|
||||
/// </summary>
|
||||
public const int SR2_DD = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Control Mark (read/scan command found sector with deleted DAM)
|
||||
/// Control mark - During execution of the Read Data or Scan command, if the FDC encounters a sector
|
||||
/// which contains a deleted data address mark, this flag is set Also set if DAM is
|
||||
/// found during Read Deleted Data
|
||||
/// </summary>
|
||||
public const int SR2_CM = 6;
|
||||
|
||||
// Status Register 3 Constants
|
||||
// Designates the bit positions within the status register 3
|
||||
|
||||
/// <summary>
|
||||
/// Unit select 0
|
||||
/// Unit Select (pin 28,29 of FDC)
|
||||
/// </summary>
|
||||
public const int SR3_US0 = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Unit select 1
|
||||
/// Unit Select (pin 28,29 of FDC)
|
||||
/// </summary>
|
||||
public const int SR3_US1 = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Head address (side select)
|
||||
/// Head Address (pin 27 of FDC)
|
||||
/// </summary>
|
||||
public const int SR3_HD = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Two Side (0=yes, 1=no (!))
|
||||
/// Two-side - This bit IS used to indicate the status of the two-side signal from the FDD
|
||||
/// </summary>
|
||||
public const int SR3_TS = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Track 0 (on track 0 we are)
|
||||
/// Track 0 - This bit IS used to indicate the status of the track 0 signal from the FDD
|
||||
/// </summary>
|
||||
public const int SR3_T0 = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Ready - status of the ready signal from the fdd
|
||||
/// Ready (drive ready signal)
|
||||
/// </summary>
|
||||
public const int SR3_RY = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Write Protected (write protected)
|
||||
/// Write protect - status of the wp signal from the fdd
|
||||
/// </summary>
|
||||
public const int SR3_WP = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Fault - This bit is used to indicate the status of the fault signal from the FDD
|
||||
/// Fault (if supported: 1=Drive failure)
|
||||
/// </summary>
|
||||
public const int SR3_FT = 7;
|
||||
|
||||
// Interrupt Code Masks
|
||||
|
||||
/// <summary>
|
||||
/// 1 = aborted:readfail / OK if EN (end of track)
|
||||
/// </summary>
|
||||
public const byte IC_OK = 0x00;
|
||||
|
||||
/// <summary>
|
||||
/// 1 = aborted:readfail / OK if EN (end of track)
|
||||
/// </summary>
|
||||
public const byte IC_ABORTED_RF_OKEN = 0x40;
|
||||
|
||||
/// <summary>
|
||||
/// 2 = unknown cmd or senseint with no int occured
|
||||
/// </summary>
|
||||
public const byte IC_NO_INT_OCCURED = 0x80;
|
||||
|
||||
/// <summary>
|
||||
/// 3 = aborted:disc removed etc
|
||||
/// </summary>
|
||||
public const byte IC_ABORTED_DISCREMOVED = 0xC0;
|
||||
|
||||
// command code constants
|
||||
public const int CC_READ_DATA = 0x06;
|
||||
public const int CC_READ_ID = 0x0a;
|
||||
public const int CC_SPECIFY = 0x03;
|
||||
public const int CC_READ_DIAGNOSTIC = 0x02;
|
||||
public const int CC_SCAN_EQUAL = 0x11;
|
||||
public const int CC_SCAN_HIGHOREQUAL = 0x1d;
|
||||
public const int CC_SCAN_LOWOREQUAL = 0x19;
|
||||
public const int CC_READ_DELETEDDATA = 0x0c;
|
||||
public const int CC_WRITE_DATA = 0x05;
|
||||
public const int CC_WRITE_ID = 0x0d;
|
||||
public const int CC_WRITE_DELETEDDATA = 0x09;
|
||||
public const int CC_SEEK = 0x0f;
|
||||
public const int CC_RECALIBRATE = 0x07;
|
||||
public const int CC_SENSE_INTSTATUS = 0x08;
|
||||
public const int CC_SENSE_DRIVESTATUS = 0x04;
|
||||
public const int CC_VERSION = 0x10;
|
||||
public const int CC_INVALID = 0x00;
|
||||
|
||||
// drive seek state constants
|
||||
public const int SEEK_IDLE = 0;
|
||||
public const int SEEK_SEEK = 1;
|
||||
public const int SEEK_RECALIBRATE = 2;
|
||||
// seek interrupt
|
||||
public const int SEEK_INTACKNOWLEDGED = 3;
|
||||
public const int SEEK_NORMALTERM = 4;
|
||||
public const int SEEK_ABNORMALTERM = 5;
|
||||
public const int SEEK_DRIVENOTREADY = 6;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Classes & Structs
|
||||
|
||||
/// <summary>
|
||||
/// Class that holds information about a specific command
|
||||
/// </summary>
|
||||
private class Command
|
||||
{
|
||||
// /// <summary>
|
||||
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
|
||||
// /// </summary>
|
||||
// public int BitMask { get; set; }
|
||||
/// <summary>
|
||||
/// The command code after bitmask has been applied
|
||||
/// </summary>
|
||||
public int CommandCode { get; set; }
|
||||
/// <summary>
|
||||
/// The number of bytes that make up the full command
|
||||
/// </summary>
|
||||
public int ParameterByteCount { get; set; }
|
||||
/// <summary>
|
||||
/// The number of result bytes that will be generated from the command
|
||||
/// </summary>
|
||||
public int ResultByteCount { get; set; }
|
||||
/// <summary>
|
||||
/// The command direction
|
||||
/// IN - Z80 to UPD765A
|
||||
/// OUT - UPD765A to Z80
|
||||
/// </summary>
|
||||
public CommandDirection Direction { get; set; }
|
||||
/// <summary>
|
||||
/// Command makes use of the MT bit
|
||||
/// </summary>
|
||||
public bool MT;
|
||||
/// <summary>
|
||||
/// Command makes use of the MF bit
|
||||
/// </summary>
|
||||
public bool MF;
|
||||
/// <summary>
|
||||
/// Command makes use of the SK bit
|
||||
/// </summary>
|
||||
public bool SK;
|
||||
/// <summary>
|
||||
/// Read/Write command that is READ
|
||||
/// </summary>
|
||||
public bool IsRead;
|
||||
/// <summary>
|
||||
/// Read/Write command that is WRITE
|
||||
/// </summary>
|
||||
public bool IsWrite;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate function that is called by this command
|
||||
/// bool 1: EXECUTE - if TRUE the command will be executed. if FALSE the method will instead parse commmand parameter bytes
|
||||
/// bool 2: RESULT - if TRUE
|
||||
/// </summary>
|
||||
public Action CommandDelegate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage for command parameters
|
||||
/// </summary>
|
||||
public class CommandParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The requested drive
|
||||
/// </summary>
|
||||
public byte UnitSelect;
|
||||
/// <summary>
|
||||
/// The requested physical side
|
||||
/// </summary>
|
||||
public byte Side;
|
||||
/// <summary>
|
||||
/// The requested track (C)
|
||||
/// </summary>
|
||||
public byte Cylinder;
|
||||
/// <summary>
|
||||
/// The requested head (H)
|
||||
/// </summary>
|
||||
public byte Head;
|
||||
/// <summary>
|
||||
/// The requested sector (R)
|
||||
/// </summary>
|
||||
public byte Sector;
|
||||
/// <summary>
|
||||
/// The specified sector size (N)
|
||||
/// </summary>
|
||||
public byte SectorSize;
|
||||
/// <summary>
|
||||
/// The end of track or last sector value (EOT)
|
||||
/// </summary>
|
||||
public byte EOT;
|
||||
/// <summary>
|
||||
/// Gap3 length (GPL)
|
||||
/// </summary>
|
||||
public byte Gap3Length;
|
||||
/// <summary>
|
||||
/// Data length (DTL) - When N is defined as 00, DTL stands for the data length
|
||||
/// which users are going to read out or write into the sector
|
||||
/// </summary>
|
||||
public byte DTL;
|
||||
|
||||
/// <summary>
|
||||
/// Clear down
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
UnitSelect = 0;
|
||||
Side = 0;
|
||||
Cylinder = 0;
|
||||
Head = 0;
|
||||
Sector = 0;
|
||||
SectorSize = 0;
|
||||
EOT = 0;
|
||||
Gap3Length = 0;
|
||||
DTL = 0;
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("ActiveCmdParams");
|
||||
|
||||
ser.Sync(nameof(UnitSelect), ref UnitSelect);
|
||||
ser.Sync(nameof(Side), ref Side);
|
||||
ser.Sync(nameof(Cylinder), ref Cylinder);
|
||||
ser.Sync(nameof(Head), ref Head);
|
||||
ser.Sync(nameof(Sector), ref Sector);
|
||||
ser.Sync(nameof(SectorSize), ref SectorSize);
|
||||
ser.Sync(nameof(EOT), ref EOT);
|
||||
ser.Sync(nameof(Gap3Length), ref Gap3Length);
|
||||
ser.Sync(nameof(DTL), ref DTL);
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,876 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Floppy drive related stuff
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765 : IFDDHost
|
||||
{
|
||||
#region Drive State
|
||||
|
||||
/// <summary>
|
||||
/// FDD Flag - motor on/off
|
||||
/// </summary>
|
||||
public bool FDD_FLAG_MOTOR;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently active disk drive
|
||||
/// </summary>
|
||||
public int DiskDriveIndex
|
||||
{
|
||||
get => _diskDriveIndex;
|
||||
set
|
||||
{
|
||||
// when index is changed update the ActiveDrive
|
||||
_diskDriveIndex = value;
|
||||
ActiveDrive = DriveStates[_diskDriveIndex];
|
||||
}
|
||||
}
|
||||
private int _diskDriveIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The currently active drive
|
||||
/// </summary>
|
||||
private DriveState ActiveDrive;
|
||||
|
||||
/// <summary>
|
||||
/// Array that holds state information for each possible drive
|
||||
/// </summary>
|
||||
private DriveState[] DriveStates = new DriveState[4];
|
||||
|
||||
#endregion
|
||||
|
||||
#region FDD Methods
|
||||
|
||||
/// <summary>
|
||||
/// Initialization / reset of the floppy drive subsystem
|
||||
/// </summary>
|
||||
private void FDD_Init()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
DriveState ds = new DriveState(i, this);
|
||||
DriveStates[i] = ds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the requested sector
|
||||
/// </summary>
|
||||
private FloppyDisk.Sector GetSector()
|
||||
{
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
// get the current track
|
||||
var trk = ActiveDrive.Disk.DiskTracks[ActiveDrive.TrackIndex];
|
||||
|
||||
// get the current sector index
|
||||
int index = ActiveDrive.SectorIndex;
|
||||
|
||||
// make sure this index exists
|
||||
if (index > trk.Sectors.Length)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// index hole count
|
||||
int iHole = 0;
|
||||
|
||||
// loop through the sectors in a track
|
||||
// the loop ends with either the sector being found
|
||||
// or the index hole being passed twice
|
||||
while (iHole <= 2)
|
||||
{
|
||||
// does the requested sector match the current sector
|
||||
if (trk.Sectors[index].SectorIDInfo.C == ActiveCommandParams.Cylinder &&
|
||||
trk.Sectors[index].SectorIDInfo.H == ActiveCommandParams.Head &&
|
||||
trk.Sectors[index].SectorIDInfo.R == ActiveCommandParams.Sector &&
|
||||
trk.Sectors[index].SectorIDInfo.N == ActiveCommandParams.SectorSize)
|
||||
{
|
||||
// sector has been found
|
||||
sector = trk.Sectors[index];
|
||||
|
||||
UnSetBit(SR2_BC, ref Status2);
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
break;
|
||||
}
|
||||
|
||||
// check for bad cylinder
|
||||
if (trk.Sectors[index].SectorIDInfo.C == 255)
|
||||
{
|
||||
SetBit(SR2_BC, ref Status2);
|
||||
}
|
||||
// check for no cylinder
|
||||
else if (trk.Sectors[index].SectorIDInfo.C != ActiveCommandParams.Cylinder)
|
||||
{
|
||||
SetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// incrememnt sector index
|
||||
index++;
|
||||
|
||||
// have we reached the index hole?
|
||||
if (trk.Sectors.Length <= index)
|
||||
{
|
||||
// wrap around
|
||||
index = 0;
|
||||
iHole++;
|
||||
}
|
||||
}
|
||||
|
||||
// search loop has completed and the sector may or may not have been found
|
||||
|
||||
// bad cylinder detected?
|
||||
if (Status2.Bit(SR2_BC))
|
||||
{
|
||||
// remove WC
|
||||
UnSetBit(SR2_WC, ref Status2);
|
||||
}
|
||||
|
||||
// update sectorindex on drive
|
||||
ActiveDrive.SectorIndex = index;
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
// IFDDHost methods that fall through to the currently active drive
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
public void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// we are only going to load into the first drive
|
||||
DriveStates[0].FDD_LoadDisk(diskData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
DriveStates[0].FDD_EjectDisk();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded => DriveStates[DiskDriveIndex].FDD_IsDiskLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the disk object from drive 0
|
||||
/// </summary>
|
||||
public FloppyDisk DiskPointer => DriveStates[0].Disk;
|
||||
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Status Class
|
||||
|
||||
/// <summary>
|
||||
/// Holds specfic state information about a drive
|
||||
/// </summary>
|
||||
private class DriveState : IFDDHost
|
||||
{
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// The drive ID from an FDC perspective
|
||||
/// </summary>
|
||||
public int ID;
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether this drive ready
|
||||
/// TRUE if both drive exists and has a disk inserted
|
||||
/// </summary>
|
||||
public bool FLAG_READY
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!FDD_IsDiskLoaded || Disk.GetTrackCount() == 0 || !FDC.FDD_FLAG_MOTOR)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disk is write protected (TRUE BY DEFAULT)
|
||||
/// </summary>
|
||||
public bool FLAG_WRITEPROTECT = false;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for seek steps
|
||||
/// One step for each indexpulse (track index) until seeked track
|
||||
/// </summary>
|
||||
public int SeekCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Seek status
|
||||
/// </summary>
|
||||
public int SeekStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Age counter
|
||||
/// </summary>
|
||||
public int SeekAge;
|
||||
|
||||
/// <summary>
|
||||
/// The current side
|
||||
/// </summary>
|
||||
public byte CurrentSide;
|
||||
|
||||
/// <summary>
|
||||
/// The current track index in the DiskTracks array
|
||||
/// </summary>
|
||||
public byte TrackIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The track ID of the current cylinder
|
||||
/// </summary>
|
||||
public byte CurrentTrackID
|
||||
{
|
||||
get
|
||||
{
|
||||
// default invalid track
|
||||
int id = 0xff;
|
||||
|
||||
if (Disk == null)
|
||||
return (byte)id;
|
||||
|
||||
if (Disk.DiskTracks.Count() == 0)
|
||||
return (byte)id;
|
||||
|
||||
if (TrackIndex >= Disk.GetTrackCount())
|
||||
TrackIndex = 0;
|
||||
else if (TrackIndex < 0)
|
||||
TrackIndex = 0;
|
||||
|
||||
var track = Disk.DiskTracks[TrackIndex];
|
||||
|
||||
id = track.TrackNumber;
|
||||
|
||||
return (byte)id;
|
||||
}
|
||||
set
|
||||
{
|
||||
for (int i = 0; i < Disk.GetTrackCount(); i++)
|
||||
{
|
||||
if (Disk.DiskTracks[i].TrackNumber == value)
|
||||
{
|
||||
TrackIndex = (byte)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The new track that the drive is seeking to
|
||||
/// (used in seek operations)
|
||||
/// </summary>
|
||||
public int SeekingTrack;
|
||||
|
||||
/// <summary>
|
||||
/// The current sector index in the Sectors array
|
||||
/// </summary>
|
||||
public int SectorIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The currently loaded floppy disk
|
||||
/// </summary>
|
||||
public FloppyDisk Disk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent controller
|
||||
/// </summary>
|
||||
private NECUPD765 FDC;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lookups
|
||||
|
||||
/// <summary>
|
||||
/// TRUE if we are on track 0
|
||||
/// </summary>
|
||||
public bool FLAG_TRACK0 => TrackIndex == 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/*
|
||||
/// <summary>
|
||||
/// Moves the head across the disk cylinders
|
||||
/// </summary>
|
||||
public void MoveHead(SkipDirection direction, int cylinderCount)
|
||||
{
|
||||
// get total tracks
|
||||
int trackCount = Disk.DiskTracks.Count();
|
||||
|
||||
int trk = 0;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case SkipDirection.Increment:
|
||||
trk = (int)CurrentTrack + cylinderCount;
|
||||
if (trk >= trackCount)
|
||||
{
|
||||
// past the last track
|
||||
trk = trackCount - 1;
|
||||
}
|
||||
else if (trk < 0)
|
||||
trk = 0;
|
||||
break;
|
||||
case SkipDirection.Decrement:
|
||||
trk = (int)CurrentTrack - cylinderCount;
|
||||
if (trk < 0)
|
||||
{
|
||||
// before the first track
|
||||
trk = 0;
|
||||
}
|
||||
else if (trk >= trackCount)
|
||||
trk = trackCount - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// move the head
|
||||
CurrentTrack = (byte)trk;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
/// <summary>
|
||||
/// Finds a supplied sector
|
||||
/// </summary>
|
||||
public FloppyDisk.Sector FindSector(ref byte[] resBuffer, CommandParameters prms)
|
||||
{
|
||||
int index =CurrentSector;
|
||||
int lc = 0;
|
||||
FloppyDisk.Sector sector = null;
|
||||
|
||||
bool found = false;
|
||||
|
||||
do
|
||||
{
|
||||
sector = Disk.DiskTracks[CurrentTrack].Sectors[index];
|
||||
if (sector != null && sector.SectorID == prms.Sector)
|
||||
{
|
||||
// sector found
|
||||
// check for data errors
|
||||
if ((sector.Status1 & 0x20) != 0 || (sector.Status2 & 0x20) != 0)
|
||||
{
|
||||
// data errors found
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// sector doesnt match
|
||||
var c = Disk.DiskTracks[CurrentTrack].Sectors[index].TrackNumber;
|
||||
if (c == 255)
|
||||
{
|
||||
// bad cylinder
|
||||
resBuffer[RS_ST2] |= 0x02;
|
||||
}
|
||||
else if (prms.Cylinder != c)
|
||||
{
|
||||
// cylinder mismatch
|
||||
resBuffer[RS_ST2] |= 0x10;
|
||||
}
|
||||
|
||||
// increment index
|
||||
index++;
|
||||
|
||||
if (index >= Disk.DiskTracks[CurrentTrack].NumberOfSectors)
|
||||
{
|
||||
// out of bounds
|
||||
index = 0;
|
||||
lc++;
|
||||
}
|
||||
|
||||
} while (lc < 2);
|
||||
|
||||
if ((resBuffer[RS_ST2] & 0x02) != 0)
|
||||
{
|
||||
// bad cylinder set - remove no cylinder
|
||||
UnSetBit(SR2_WC, ref resBuffer[RS_ST2]);
|
||||
}
|
||||
|
||||
// update current sector
|
||||
CurrentSector = index;
|
||||
|
||||
if (found)
|
||||
return sector;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates a result buffer
|
||||
/// </summary>
|
||||
public void FillResult(ref byte[] resBuffer, CHRN chrn)
|
||||
{
|
||||
// clear results
|
||||
resBuffer[RS_ST0] = 0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
|
||||
if (chrn == null)
|
||||
{
|
||||
// no chrn supplied
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0;
|
||||
resBuffer[RS_ST2] = 0;
|
||||
resBuffer[RS_C] = 0;
|
||||
resBuffer[RS_H] = 0;
|
||||
resBuffer[RS_R] = 0;
|
||||
resBuffer[RS_N] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates the result buffer with ReadID data
|
||||
/// </summary>
|
||||
public void ReadID(ref byte[] resBuffer)
|
||||
{
|
||||
if (CheckDriveStatus() == false)
|
||||
{
|
||||
// drive not ready
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
return;
|
||||
}
|
||||
|
||||
var track = Disk.DiskTracks.Where(a => a.TrackNumber == CurrentTrack).FirstOrDefault();
|
||||
|
||||
if (track != null && track.NumberOfSectors > 0)
|
||||
{
|
||||
// formatted track
|
||||
|
||||
// get the current sector
|
||||
int index = CurrentSector;
|
||||
|
||||
// is the index out of bounds?
|
||||
if (index >= track.NumberOfSectors)
|
||||
{
|
||||
// reset the index
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// read the sector data
|
||||
var data = track.Sectors[index];
|
||||
resBuffer[RS_C] = data.TrackNumber;
|
||||
resBuffer[RS_H] = data.SideNumber;
|
||||
resBuffer[RS_R] = data.SectorID;
|
||||
resBuffer[RS_N] = data.SectorSize;
|
||||
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
|
||||
// increment the current sector
|
||||
CurrentSector = index + 1;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// unformatted track?
|
||||
resBuffer[RS_C] = FDC.CommBuffer[CM_C];
|
||||
resBuffer[RS_H] = FDC.CommBuffer[CM_H];
|
||||
resBuffer[RS_R] = FDC.CommBuffer[CM_R];
|
||||
resBuffer[RS_N] = FDC.CommBuffer[CM_N];
|
||||
|
||||
SetBit(SR0_IC0, ref ST0);
|
||||
resBuffer[RS_ST0] = ST0;
|
||||
resBuffer[RS_ST1] = 0x01;
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
/// <summary>
|
||||
/// The drive performs a seek operation if necessary
|
||||
/// Return value TRUE indicates seek complete
|
||||
/// </summary>
|
||||
public void DoSeek()
|
||||
{
|
||||
if (CurrentState != DriveMainState.Recalibrate &&
|
||||
CurrentState != DriveMainState.Seek)
|
||||
{
|
||||
// no seek/recalibrate has been asked for
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetBit(ID, FDC.StatusMain))
|
||||
{
|
||||
// drive is already seeking
|
||||
return;
|
||||
}
|
||||
|
||||
RunSeekCycle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a seek cycle
|
||||
/// </summary>
|
||||
public void RunSeekCycle()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
switch (SeekState)
|
||||
{
|
||||
// seek or recalibrate has been requested
|
||||
case SeekSubState.Idle:
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
// recalibrate always seeks to track 0
|
||||
SeekingTrack = 0;
|
||||
}
|
||||
SeekState = SeekSubState.MoveInit;
|
||||
|
||||
// mark drive as busy
|
||||
// this should be cleared by SIS command
|
||||
SetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
break;
|
||||
|
||||
// setup for the head move
|
||||
case SeekSubState.MoveInit:
|
||||
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
// we are already at the required track
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
!FLAG_TRACK0)
|
||||
{
|
||||
// recalibration fail
|
||||
SeekIntState = SeekIntStatus.Abnormal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
// equipment check
|
||||
SetBit(SR0_EC, ref FDC.Status0);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (CurrentState == DriveMainState.Recalibrate &&
|
||||
FLAG_TRACK0)
|
||||
{
|
||||
// recalibration success
|
||||
SeekIntState = SeekIntStatus.Normal;
|
||||
|
||||
// raise seek interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.Seek;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for error
|
||||
if (IntStatus >= IC_ABORTED_DISCREMOVED || Disk == null)
|
||||
{
|
||||
// drive not ready
|
||||
FLAG_READY = false;
|
||||
|
||||
// drive not ready
|
||||
SeekIntState = SeekIntStatus.DriveNotReady;
|
||||
|
||||
// cancel any interrupt
|
||||
FDC.ActiveInterrupt = InterruptState.None;
|
||||
|
||||
// unset DB bit
|
||||
UnSetBit(ID, ref FDC.StatusMain);
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
if (SeekCounter > 1)
|
||||
{
|
||||
// not ready to seek yet
|
||||
SeekCounter--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FDC.SRT < 1 && CurrentTrack != SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.MoveImmediate;
|
||||
break;
|
||||
}
|
||||
|
||||
// head move
|
||||
SeekState = SeekSubState.HeadMove;
|
||||
|
||||
break;
|
||||
|
||||
case SeekSubState.HeadMove:
|
||||
|
||||
// do the seek
|
||||
SeekCounter = FDC.SRT;
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, 1);
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, 1);
|
||||
}
|
||||
|
||||
// should the seek be completed now?
|
||||
if (CurrentTrack == SeekingTrack)
|
||||
{
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
}
|
||||
|
||||
// seek not finished yet
|
||||
return;
|
||||
|
||||
// seek emulation processed immediately
|
||||
case SeekSubState.MoveImmediate:
|
||||
|
||||
if (CurrentTrack < SeekingTrack)
|
||||
{
|
||||
// we are seeking forward
|
||||
var delta = SeekingTrack - CurrentTrack;
|
||||
MoveHead(SkipDirection.Increment, delta);
|
||||
|
||||
}
|
||||
else if (CurrentTrack > SeekingTrack)
|
||||
{
|
||||
// we are seeking backward
|
||||
var delta = CurrentTrack - SeekingTrack;
|
||||
MoveHead(SkipDirection.Decrement, delta);
|
||||
}
|
||||
|
||||
SeekState = SeekSubState.PerformCompletion;
|
||||
break;
|
||||
|
||||
case SeekSubState.PerformCompletion:
|
||||
SeekDone();
|
||||
SeekState = SeekSubState.SeekCompleted;
|
||||
break;
|
||||
|
||||
case SeekSubState.SeekCompleted:
|
||||
// seek has already completed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a seek operation has completed
|
||||
/// </summary>
|
||||
public void SeekDone()
|
||||
{
|
||||
SeekCounter = 0;
|
||||
SeekingTrack = CurrentTrack;
|
||||
|
||||
// generate ST0 register data
|
||||
|
||||
// get only the IC bits
|
||||
IntStatus &= IC_ABORTED_DISCREMOVED;
|
||||
|
||||
// drive ready?
|
||||
if (!FLAG_READY)
|
||||
{
|
||||
SetBit(SR0_NR, ref IntStatus);
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
|
||||
// are we recalibrating?
|
||||
if (CurrentState == DriveMainState.Recalibrate)
|
||||
{
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// set seek end
|
||||
SetBit(SR0_SE, ref IntStatus);
|
||||
/*
|
||||
// head address
|
||||
if (CurrentSide > 0)
|
||||
{
|
||||
SetBit(SR0_HD, ref IntStatus);
|
||||
|
||||
// drive only supports 1 head
|
||||
// set the EC bit
|
||||
SetBit(SR0_EC, ref IntStatus);
|
||||
}
|
||||
*/
|
||||
/*
|
||||
// UnitSelect
|
||||
SetUnitSelect(ID, ref IntStatus);
|
||||
|
||||
// move to none state
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
//SeekState = SeekSubState.SeekCompleted;
|
||||
|
||||
// set the seek interrupt flag for this drive
|
||||
// this will be cleared at the next successful senseint
|
||||
FLAG_SEEK_INTERRUPT = true;
|
||||
|
||||
//CurrentState = DriveMainState.None;
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
public DriveState(int driveID, NECUPD765 fdc)
|
||||
{
|
||||
ID = driveID;
|
||||
FDC = fdc;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFDDHost
|
||||
|
||||
/// <summary>
|
||||
/// Parses a new disk image and loads it into this floppy drive
|
||||
/// </summary>
|
||||
public void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// try dsk first
|
||||
FloppyDisk fdd = null;
|
||||
bool found = false;
|
||||
|
||||
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DiskType.CPCExtended:
|
||||
fdd = new CPCExtendedFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.CPC:
|
||||
fdd = new CPCFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.IPF:
|
||||
fdd = new IPFFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.UDI:
|
||||
fdd = new UDI1_0FloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
Disk = fdd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the current disk
|
||||
/// </summary>
|
||||
public void FDD_EjectDisk()
|
||||
{
|
||||
Disk = null;
|
||||
//FLAG_READY = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether the current active drive has a disk inserted
|
||||
/// </summary>
|
||||
public bool FDD_IsDiskLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Disk != null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StateSerialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync(nameof(ID), ref ID);
|
||||
ser.Sync(nameof(FLAG_WRITEPROTECT), ref FLAG_WRITEPROTECT);
|
||||
//ser.Sync(nameof(FLAG_DISKCHANGED), ref FLAG_DISKCHANGED);
|
||||
//ser.Sync(nameof(FLAG_RECALIBRATING), ref FLAG_RECALIBRATING);
|
||||
//ser.Sync(nameof(FLAG_SEEK_INTERRUPT), ref FLAG_SEEK_INTERRUPT);
|
||||
//ser.Sync(nameof(IntStatus), ref IntStatus);
|
||||
//ser.Sync(nameof(ST0), ref ST0);
|
||||
//ser.Sync(nameof(RecalibrationCounter), ref RecalibrationCounter);
|
||||
ser.Sync(nameof(SeekCounter), ref SeekCounter);
|
||||
ser.Sync(nameof(SeekStatus), ref SeekStatus);
|
||||
ser.Sync(nameof(SeekAge), ref SeekAge);
|
||||
ser.Sync(nameof(CurrentSide), ref CurrentSide);
|
||||
//ser.Sync(nameof(CurrentTrack), ref CurrentTrack);
|
||||
ser.Sync(nameof(TrackIndex), ref TrackIndex);
|
||||
ser.Sync(nameof(SeekingTrack), ref SeekingTrack);
|
||||
//ser.Sync(nameof(CurrentSector), ref CurrentSector);
|
||||
ser.Sync(nameof(SectorIndex), ref SectorIndex);
|
||||
//ser.Sync(nameof(RAngles), ref RAngles);
|
||||
//ser.Sync(nameof(DataPointer), ref DataPointer);
|
||||
//ser.SyncEnum(nameof(CurrentState), ref CurrentState);
|
||||
//ser.SyncEnum(nameof(SeekState), ref SeekState);
|
||||
//ser.SyncEnum(nameof(SeekIntState), ref SeekIntState);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// IPortIODevice
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765 : IPortIODevice
|
||||
{
|
||||
#region Dev Logging
|
||||
|
||||
public string outputfile = @"D:\Dropbox\Dropbox\_Programming\TASVideos\BizHawk\output\zxhawkio-" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".csv";
|
||||
public string outputString = "STATUS,WRITE,READ,CODE,MT,MF,SK,CMDCNT,RESCNT,EXECCNT,EXECLEN\r\n";
|
||||
public bool writeDebug = false;
|
||||
|
||||
public List<string> dLog = new List<string>
|
||||
{
|
||||
"STATUS,WRITE,READ,CODE,MT,MF,SK,CMDCNT,RESCNT,EXECCNT,EXECLEN"
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Status read
|
||||
* Data write
|
||||
* Data read
|
||||
* CMD code
|
||||
* CMD string
|
||||
* MT flag
|
||||
* MK flag
|
||||
* SK flag
|
||||
* */
|
||||
private string[] workingArr = new string[3];
|
||||
|
||||
private void BuildCSVLine()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
sb.Append(workingArr[i]);
|
||||
sb.Append(",");
|
||||
workingArr[i] = "";
|
||||
}
|
||||
|
||||
sb.Append(ActiveCommand.CommandCode).Append(",");
|
||||
|
||||
sb.Append(CMD_FLAG_MT).Append(",");
|
||||
sb.Append(CMD_FLAG_MF).Append(",");
|
||||
sb.Append(CMD_FLAG_SK).Append(",");
|
||||
|
||||
sb.Append(CommCounter).Append(",");
|
||||
sb.Append(ResCounter).Append(",");
|
||||
sb.Append(ExecCounter).Append(",");
|
||||
sb.Append(ExecLength);
|
||||
|
||||
//sb.Append("\r\n");
|
||||
|
||||
//outputString += sb.ToString();
|
||||
dLog.Add(sb.ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an IN instruction
|
||||
/// </summary>
|
||||
public bool ReadPort(ushort port, ref int data)
|
||||
{
|
||||
BitArray bits = new BitArray(new byte[] { (byte)data });
|
||||
|
||||
if (port == 0x3ffd)
|
||||
{
|
||||
// Z80 is trying to read from the data register
|
||||
data = ReadDataRegister();
|
||||
if (writeDebug)
|
||||
{
|
||||
workingArr[2] = data.ToString();
|
||||
//outputString += ",," + data + "," + ActiveCommand.CommandCode + "\r\n";
|
||||
BuildCSVLine();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (port == 0x2ffd)
|
||||
{
|
||||
// read main status register
|
||||
// this can happen at any time
|
||||
data = ReadMainStatus();
|
||||
if (writeDebug)
|
||||
{
|
||||
//outputString += data + ",,," + ActiveCommand.CommandCode + "\r\n";
|
||||
workingArr[0] = data.ToString();
|
||||
BuildCSVLine();
|
||||
//System.IO.File.WriteAllText(outputfile, outputString);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Device responds to an OUT instruction
|
||||
/// </summary>
|
||||
public bool WritePort(ushort port, int data)
|
||||
{
|
||||
BitArray bits = new BitArray(new byte[] { (byte)data });
|
||||
|
||||
if (port == 0x3ffd)
|
||||
{
|
||||
// Z80 is attempting to write to the data register
|
||||
WriteDataRegister((byte)data);
|
||||
if (writeDebug)
|
||||
{
|
||||
//outputString += "," + data + ",," + ActiveCommand.CommandCode + "\r\n";
|
||||
workingArr[1] = data.ToString();
|
||||
BuildCSVLine();
|
||||
//System.IO.File.WriteAllText(outputfile, outputString);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (port == 0x1ffd)
|
||||
{
|
||||
// set disk motor on/off
|
||||
FDD_FLAG_MOTOR = bits[3];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Timimng
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
/// <summary>
|
||||
/// The current Z80 cycle
|
||||
/// </summary>
|
||||
private long CurrentCPUCycle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_machine == null)
|
||||
return 0;
|
||||
else
|
||||
return _machine.CPU.TotalExecutedCycles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The last CPU cycle when the FDC accepted an IO read/write
|
||||
/// </summary>
|
||||
private long LastCPUCycle;
|
||||
|
||||
/// <summary>
|
||||
/// The current delay figure (in Z80 t-states)
|
||||
/// This implementation only introduces delay upon main status register reads
|
||||
/// All timing calculations should be done during the other read/write operations
|
||||
/// </summary>
|
||||
private long StatusDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the numbers of Z80 cycles per MS
|
||||
/// </summary>
|
||||
private long CPUCyclesPerMs;
|
||||
|
||||
/// <summary>
|
||||
/// The floppy drive emulated clock speed
|
||||
/// </summary>
|
||||
public const double DriveClock = 31250;
|
||||
|
||||
/// <summary>
|
||||
/// The number of floppy drive cycles per MS
|
||||
/// </summary>
|
||||
public long DriveCyclesPerMs;
|
||||
|
||||
/// <summary>
|
||||
/// The number of T-States in one floppy drive clock tick
|
||||
/// </summary>
|
||||
public long StatesPerDriveTick;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for measuring when the floppy drive is ready to run a cycle
|
||||
/// </summary>
|
||||
private long TickCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Internal drive cycle counter
|
||||
/// </summary>
|
||||
private int DriveCycleCounter = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the timing routines
|
||||
/// </summary>
|
||||
private void TimingInit()
|
||||
{
|
||||
// z80 timing
|
||||
double frameSize = _machine.ULADevice.FrameLength;
|
||||
double rRate = _machine.ULADevice.ClockSpeed / frameSize;
|
||||
long tPerSecond = (long)(frameSize * rRate);
|
||||
CPUCyclesPerMs = tPerSecond / 1000;
|
||||
|
||||
// drive timing
|
||||
double dRate = DriveClock / frameSize;
|
||||
long dPerSecond = (long)(frameSize * dRate);
|
||||
DriveCyclesPerMs = dPerSecond / 1000;
|
||||
|
||||
long TStatesPerDriveCycle = (long)((double)_machine.ULADevice.ClockSpeed / DriveClock);
|
||||
StatesPerDriveTick = TStatesPerDriveCycle;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by reads to the main status register
|
||||
/// Returns true if there is no delay
|
||||
/// Returns false if read is to be deferred
|
||||
/// </summary>
|
||||
private bool CheckTiming()
|
||||
{
|
||||
// get delta
|
||||
long delta = CurrentCPUCycle - LastCPUCycle;
|
||||
|
||||
if (StatusDelay >= delta)
|
||||
{
|
||||
// there is still delay remaining
|
||||
StatusDelay -= delta;
|
||||
LastCPUCycle = CurrentCPUCycle;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no delay remaining
|
||||
StatusDelay = 0;
|
||||
LastCPUCycle = CurrentCPUCycle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// The NEC floppy disk controller (and floppy drive) found in the +3
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
#region Devices
|
||||
|
||||
/// <summary>
|
||||
/// The emulated spectrum machine
|
||||
/// </summary>
|
||||
private SpectrumBase _machine;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction & Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Main constructor
|
||||
/// </summary>
|
||||
public NECUPD765()
|
||||
{
|
||||
InitCommandList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialization routine
|
||||
/// </summary>
|
||||
public void Init(SpectrumBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
FDD_Init();
|
||||
TimingInit();
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the FDC
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
// setup main status
|
||||
StatusMain = 0;
|
||||
|
||||
Status0 = 0;
|
||||
Status1 = 0;
|
||||
Status2 = 0;
|
||||
Status3 = 0;
|
||||
|
||||
SetBit(MSR_RQM, ref StatusMain);
|
||||
|
||||
SetPhase_Idle();
|
||||
|
||||
//FDC_FLAG_RQM = true;
|
||||
//ActiveDirection = CommandDirection.IN;
|
||||
SRT = 6;
|
||||
HUT = 16;
|
||||
HLT = 2;
|
||||
HLT_Counter = 0;
|
||||
HUT_Counter = 0;
|
||||
IndexPulseCounter = 0;
|
||||
CMD_FLAG_MF = false;
|
||||
|
||||
foreach (var d in DriveStates)
|
||||
{
|
||||
//d.SeekingTrack = d.CurrentTrack;
|
||||
////d.SeekCounter = 0;
|
||||
//d.FLAG_SEEK_INTERRUPT = false;
|
||||
//d.IntStatus = 0;
|
||||
//d.SeekState = SeekSubState.Idle;
|
||||
//d.SeekIntState = SeekIntStatus.Normal;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the command structure
|
||||
/// Each command represents one of the internal UPD765 commands
|
||||
/// </summary>
|
||||
private void InitCommandList()
|
||||
{
|
||||
CommandList = new List<Command>
|
||||
{
|
||||
// read data
|
||||
new Command { CommandDelegate = UPD_ReadData, CommandCode = 0x06, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// read id
|
||||
new Command { CommandDelegate = UPD_ReadID, CommandCode = 0x0a, MF = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 7 },
|
||||
// specify
|
||||
new Command { CommandDelegate = UPD_Specify, CommandCode = 0x03,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
|
||||
// read diagnostic
|
||||
new Command { CommandDelegate = UPD_ReadDiagnostic, CommandCode = 0x02, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan equal
|
||||
new Command { CommandDelegate = UPD_ScanEqual, CommandCode = 0x11, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan high or equal
|
||||
new Command { CommandDelegate = UPD_ScanHighOrEqual, CommandCode = 0x1d, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// scan low or equal
|
||||
new Command { CommandDelegate = UPD_ScanLowOrEqual, CommandCode = 0x19, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// read deleted data
|
||||
new Command { CommandDelegate = UPD_ReadDeletedData, CommandCode = 0x0c, MT = true, MF = true, SK = true, IsRead = true,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// write data
|
||||
new Command { CommandDelegate = UPD_WriteData, CommandCode = 0x05, MT = true, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// write id
|
||||
new Command { CommandDelegate = UPD_WriteID, CommandCode = 0x0d, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 5, ResultByteCount = 7 },
|
||||
// write deleted data
|
||||
new Command { CommandDelegate = UPD_WriteDeletedData, CommandCode = 0x09, MT = true, MF = true, IsWrite = true,
|
||||
Direction = CommandDirection.IN, ParameterByteCount = 8, ResultByteCount = 7 },
|
||||
// seek
|
||||
new Command { CommandDelegate = UPD_Seek, CommandCode = 0x0f,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 2, ResultByteCount = 0 },
|
||||
// recalibrate (seek track00)
|
||||
new Command { CommandDelegate = UPD_Recalibrate, CommandCode = 0x07,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 0 },
|
||||
// sense interrupt status
|
||||
new Command { CommandDelegate = UPD_SenseInterruptStatus, CommandCode = 0x08,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 2 },
|
||||
// sense drive status
|
||||
new Command { CommandDelegate = UPD_SenseDriveStatus, CommandCode = 0x04,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 1, ResultByteCount = 1 },
|
||||
// version
|
||||
new Command { CommandDelegate = UPD_Version, CommandCode = 0x10,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
|
||||
// invalid
|
||||
new Command { CommandDelegate = UPD_Invalid, CommandCode = 0x00,
|
||||
Direction = CommandDirection.OUT, ParameterByteCount = 0, ResultByteCount = 1 },
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Serialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
void SyncFDDState(Serializer ser1)
|
||||
{
|
||||
ser1.Sync(nameof(FDD_FLAG_MOTOR), ref FDD_FLAG_MOTOR);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
ser1.BeginSection("HITDrive_" + i);
|
||||
DriveStates[i].SyncState(ser1);
|
||||
ser1.EndSection();
|
||||
}
|
||||
|
||||
ser1.Sync(nameof(DiskDriveIndex), ref _diskDriveIndex);
|
||||
// set active drive
|
||||
DiskDriveIndex = _diskDriveIndex;
|
||||
}
|
||||
|
||||
void SyncRegisterState(Serializer ser1)
|
||||
{
|
||||
ser1.Sync("_RegMain", ref StatusMain);
|
||||
ser1.Sync("_Reg0", ref Status0);
|
||||
ser1.Sync("_Reg1", ref Status1);
|
||||
ser1.Sync("_Reg2", ref Status2);
|
||||
ser1.Sync("_Reg3", ref Status3);
|
||||
}
|
||||
|
||||
void SyncControllerState(Serializer ser1)
|
||||
{
|
||||
ser1.Sync(nameof(DriveLight), ref DriveLight);
|
||||
ser1.SyncEnum(nameof(ActivePhase), ref ActivePhase);
|
||||
#if false
|
||||
ser1.SyncEnum(nameof(ActiveDirection), ref ActiveDirection);
|
||||
#endif
|
||||
ser1.SyncEnum(nameof(ActiveInterrupt), ref ActiveInterrupt);
|
||||
ser1.Sync(nameof(CommBuffer), ref CommBuffer, false);
|
||||
ser1.Sync(nameof(CommCounter), ref CommCounter);
|
||||
ser1.Sync(nameof(ResBuffer), ref ResBuffer, false);
|
||||
ser1.Sync(nameof(ExecBuffer), ref ExecBuffer, false);
|
||||
ser1.Sync(nameof(ExecCounter), ref ExecCounter);
|
||||
ser1.Sync(nameof(ExecLength), ref ExecLength);
|
||||
ser1.Sync(nameof(InterruptResultBuffer), ref InterruptResultBuffer, false);
|
||||
ser1.Sync(nameof(ResCounter), ref ResCounter);
|
||||
ser1.Sync(nameof(ResLength), ref ResLength);
|
||||
ser1.Sync(nameof(LastSectorDataWriteByte), ref LastSectorDataWriteByte);
|
||||
ser1.Sync(nameof(LastSectorDataReadByte), ref LastSectorDataReadByte);
|
||||
ser1.Sync(nameof(LastByteReceived), ref LastByteReceived);
|
||||
|
||||
ser1.Sync(nameof(_cmdIndex), ref _cmdIndex);
|
||||
// resync the ActiveCommand
|
||||
CMDIndex = _cmdIndex;
|
||||
|
||||
ActiveCommandParams.SyncState(ser1);
|
||||
|
||||
ser1.Sync(nameof(IndexPulseCounter), ref IndexPulseCounter);
|
||||
#if false
|
||||
ser1.SyncEnum(nameof(_activeStatus), ref _activeStatus);
|
||||
ser1.SyncEnum(nameof(_statusRaised), ref _statusRaised);
|
||||
#endif
|
||||
|
||||
ser1.Sync(nameof(CMD_FLAG_MT), ref CMD_FLAG_MT);
|
||||
ser1.Sync(nameof(CMD_FLAG_MF), ref CMD_FLAG_MF);
|
||||
ser1.Sync(nameof(CMD_FLAG_SK), ref CMD_FLAG_SK);
|
||||
ser1.Sync(nameof(SRT), ref SRT);
|
||||
ser1.Sync(nameof(HUT), ref HUT);
|
||||
ser1.Sync(nameof(HLT), ref HLT);
|
||||
ser1.Sync(nameof(ND), ref ND);
|
||||
ser1.Sync(nameof(SRT_Counter), ref SRT_Counter);
|
||||
ser1.Sync(nameof(HUT_Counter), ref HUT_Counter);
|
||||
ser1.Sync(nameof(HLT_Counter), ref HLT_Counter);
|
||||
|
||||
ser1.Sync(nameof(SectorDelayCounter), ref SectorDelayCounter);
|
||||
ser1.Sync(nameof(SectorID), ref SectorID);
|
||||
}
|
||||
|
||||
void SyncTimingState(Serializer ser1)
|
||||
{
|
||||
ser1.Sync(nameof(LastCPUCycle), ref LastCPUCycle);
|
||||
ser1.Sync(nameof(StatusDelay), ref StatusDelay);
|
||||
ser1.Sync(nameof(TickCounter), ref TickCounter);
|
||||
ser1.Sync(nameof(DriveCycleCounter), ref DriveCycleCounter);
|
||||
}
|
||||
|
||||
ser.BeginSection("NEC-UPD765");
|
||||
SyncFDDState(ser);
|
||||
SyncRegisterState(ser);
|
||||
SyncControllerState(ser);
|
||||
SyncTimingState(ser);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
public class NECUPD765Spectrum : NECUPD765<SpectrumBase, NECUPD765Spectrum.ZXDriveState>
|
||||
{
|
||||
protected override ZXDriveState ConstructDriveState(int driveID, NECUPD765<SpectrumBase, ZXDriveState> fdc) => new ZXDriveState(driveID, fdc);
|
||||
|
||||
protected override void TimingInit()
|
||||
{
|
||||
// z80 timing
|
||||
double frameSize = _machine.ULADevice.FrameLength;
|
||||
double rRate = _machine.ULADevice.ClockSpeed / frameSize;
|
||||
long tPerSecond = (long)(frameSize * rRate);
|
||||
CPUCyclesPerMs = tPerSecond / 1000;
|
||||
|
||||
// drive timing
|
||||
double dRate = DriveClock / frameSize;
|
||||
long dPerSecond = (long)(frameSize * dRate);
|
||||
DriveCyclesPerMs = dPerSecond / 1000;
|
||||
|
||||
long TStatesPerDriveCycle = (long)((double)_machine.ULADevice.ClockSpeed / DriveClock);
|
||||
StatesPerDriveTick = TStatesPerDriveCycle;
|
||||
}
|
||||
|
||||
public class ZXDriveState : NECUPD765DriveState
|
||||
{
|
||||
public ZXDriveState(int driveID, NECUPD765<SpectrumBase, ZXDriveState> fdc) : base(driveID, fdc) {}
|
||||
|
||||
public override void FDD_LoadDisk(byte[] diskData)
|
||||
{
|
||||
// try dsk first
|
||||
FloppyDisk fdd = null;
|
||||
bool found = false;
|
||||
|
||||
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DiskType.CPCExtended:
|
||||
fdd = new CPCExtendedFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.CPC:
|
||||
fdd = new CPCFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.IPF:
|
||||
fdd = new IPFFloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
case DiskType.UDI:
|
||||
fdd = new UDI1_0FloppyDisk();
|
||||
found = fdd.ParseDisk(diskData);
|
||||
break;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
Disk = fdd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
throw new Exception(this.GetType().ToString() +
|
||||
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper methods
|
||||
/// </summary>
|
||||
#region Attribution
|
||||
/*
|
||||
Implementation based on the information contained here:
|
||||
http://www.cpcwiki.eu/index.php/765_FDC
|
||||
and here:
|
||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||
*/
|
||||
#endregion
|
||||
public partial class NECUPD765
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the specified bit value from supplied byte
|
||||
/// </summary>
|
||||
public static bool GetBit(int bitNumber, byte dataByte)
|
||||
{
|
||||
if (bitNumber < 0 || bitNumber > 7)
|
||||
return false;
|
||||
|
||||
BitArray bi = new BitArray(new byte[] { dataByte });
|
||||
|
||||
return bi[bitNumber];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified bit of the supplied byte to 1
|
||||
/// </summary>
|
||||
public static void SetBit(int bitNumber, ref byte dataByte)
|
||||
{
|
||||
if (bitNumber < 0 || bitNumber > 7)
|
||||
return;
|
||||
|
||||
int db = (int)dataByte;
|
||||
|
||||
db |= 1 << bitNumber;
|
||||
|
||||
dataByte = (byte)db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified bit of the supplied byte to 0
|
||||
/// </summary>
|
||||
public static void UnSetBit(int bitNumber, ref byte dataByte)
|
||||
{
|
||||
if (bitNumber < 0 || bitNumber > 7)
|
||||
return;
|
||||
|
||||
int db = (int)dataByte;
|
||||
|
||||
db &= ~(1 << bitNumber);
|
||||
|
||||
dataByte = (byte)db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a drive number (0-3) based on the first two bits of the supplied byte
|
||||
/// </summary>
|
||||
public static int GetUnitSelect(byte dataByte)
|
||||
{
|
||||
int driveNumber = dataByte & 0x03;
|
||||
return driveNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the first two bits of a byte based on the supplied drive number (0-3)
|
||||
/// </summary>
|
||||
public static void SetUnitSelect(int driveNumber, ref byte dataByte)
|
||||
{
|
||||
switch (driveNumber)
|
||||
{
|
||||
case 0:
|
||||
UnSetBit(SR0_US0, ref dataByte);
|
||||
UnSetBit(SR0_US1, ref dataByte);
|
||||
break;
|
||||
case 1:
|
||||
SetBit(SR0_US0, ref dataByte);
|
||||
UnSetBit(SR0_US1, ref dataByte);
|
||||
break;
|
||||
case 2:
|
||||
SetBit(SR0_US1, ref dataByte);
|
||||
UnSetBit(SR0_US0, ref dataByte);
|
||||
break;
|
||||
case 3:
|
||||
SetBit(SR0_US0, ref dataByte);
|
||||
SetBit(SR0_US1, ref dataByte);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Cursor joystick
|
||||
/// Maps to a combination of 0xf7fe and 0xeffe
|
||||
/// </summary>
|
||||
public class CursorJoystick : IJoystick
|
||||
public class CursorJoystick : IJoystick<JoystickType>
|
||||
{
|
||||
//private int _joyLine;
|
||||
private SpectrumBase _machine;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
public class KempstonJoystick : IJoystick
|
||||
public class KempstonJoystick : IJoystick<JoystickType>
|
||||
{
|
||||
private int _joyLine;
|
||||
private SpectrumBase _machine;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// A null joystick object
|
||||
/// </summary>
|
||||
public class NullJoystick : IJoystick
|
||||
public class NullJoystick : IJoystick<JoystickType>
|
||||
{
|
||||
private int _joyLine;
|
||||
private SpectrumBase _machine;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Sinclair Joystick LEFT
|
||||
/// Just maps to the standard keyboard and is read the same (from port 0xf7fe)
|
||||
/// </summary>
|
||||
public class SinclairJoystick1 : IJoystick
|
||||
public class SinclairJoystick1 : IJoystick<JoystickType>
|
||||
{
|
||||
//private int _joyLine;
|
||||
private SpectrumBase _machine;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Sinclair Joystick RIGHT
|
||||
/// Just maps to the standard keyboard and is read the same (from port 0xeffe)
|
||||
/// </summary>
|
||||
public class SinclairJoystick2 : IJoystick
|
||||
public class SinclairJoystick2 : IJoystick<JoystickType>
|
||||
{
|
||||
//private int _joyLine;
|
||||
private SpectrumBase _machine;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -280,7 +282,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// </summary>
|
||||
protected void InitJoysticks(List<JoystickType> joys)
|
||||
{
|
||||
var jCollection = new List<IJoystick>();
|
||||
var jCollection = new List<IJoystick<JoystickType>>();
|
||||
|
||||
for (int i = 0; i < joys.Count(); i++)
|
||||
{
|
||||
|
@ -298,7 +300,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// <summary>
|
||||
/// Instantiates a new IJoystick object
|
||||
/// </summary>
|
||||
public IJoystick InstantiateJoystick(JoystickType type, int playerNumber)
|
||||
public IJoystick<JoystickType> InstantiateJoystick(JoystickType type, int playerNumber)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
@ -320,7 +322,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// <summary>
|
||||
/// Returns a IJoystick object depending on the type (or null if not found)
|
||||
/// </summary>
|
||||
protected IJoystick LocateUniqueJoystick(JoystickType type)
|
||||
protected IJoystick<JoystickType> LocateUniqueJoystick(JoystickType type)
|
||||
{
|
||||
return JoystickCollection.FirstOrDefault(a => a.JoyType == type);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
using BizHawk.Emulation.Cores.Sound;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
|
@ -8,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// The abstract class that all emulated models will inherit from
|
||||
/// * Main properties / fields / contruction*
|
||||
/// </summary>
|
||||
public abstract partial class SpectrumBase
|
||||
public abstract partial class SpectrumBase : CPCSpectrumBase.CPCSpectrumBase
|
||||
{
|
||||
#region Devices
|
||||
|
||||
|
@ -66,12 +67,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// <summary>
|
||||
/// The +3 built-in disk drive
|
||||
/// </summary>
|
||||
public virtual NECUPD765 UPDDiskDevice { get; set; }
|
||||
public virtual NECUPD765Spectrum UPDDiskDevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the currently selected joysticks
|
||||
/// </summary>
|
||||
public virtual IJoystick[] JoystickCollection { get; set; }
|
||||
public virtual IJoystick<JoystickType>[] JoystickCollection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// +3/2a printer port strobe
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
TapeDevice = new DatacorderDevice(spectrum.SyncSettings.AutoLoadTape);
|
||||
TapeDevice.Init(this);
|
||||
|
||||
UPDDiskDevice = new NECUPD765();
|
||||
UPDDiskDevice = new NECUPD765Spectrum();
|
||||
UPDDiskDevice.Init(this);
|
||||
|
||||
InitializeMedia(files);
|
||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,735 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// This abstract class defines a logical floppy disk
|
||||
/// </summary>
|
||||
public abstract class FloppyDisk
|
||||
{
|
||||
/// <summary>
|
||||
/// The disk format type
|
||||
/// </summary>
|
||||
public abstract DiskType DiskFormatType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disk information header
|
||||
/// </summary>
|
||||
public Header DiskHeader = new Header();
|
||||
|
||||
/// <summary>
|
||||
/// Track array
|
||||
/// </summary>
|
||||
public Track[] DiskTracks = null;
|
||||
|
||||
/// <summary>
|
||||
/// No. of tracks per side
|
||||
/// </summary>
|
||||
public int CylinderCount;
|
||||
|
||||
/// <summary>
|
||||
/// The number of physical sides
|
||||
/// </summary>
|
||||
public int SideCount;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes per track
|
||||
/// </summary>
|
||||
public int BytesPerTrack;
|
||||
|
||||
/// <summary>
|
||||
/// The write-protect tab on the disk
|
||||
/// </summary>
|
||||
public bool WriteProtected;
|
||||
|
||||
/// <summary>
|
||||
/// The detected protection scheme (if any)
|
||||
/// </summary>
|
||||
public ProtectionType Protection;
|
||||
|
||||
/// <summary>
|
||||
/// The actual disk image data
|
||||
/// </summary>
|
||||
public byte[] DiskData;
|
||||
|
||||
/// <summary>
|
||||
/// If TRUE then data on the disk has changed (been written to)
|
||||
/// This will be used to determine whether the disk data needs to be included
|
||||
/// in any SyncState operations
|
||||
/// </summary>
|
||||
protected bool DirtyData = false;
|
||||
|
||||
/// <summary>
|
||||
/// Used to deterministically choose a 'random' sector when dealing with weak reads
|
||||
/// </summary>
|
||||
public int RandomCounter
|
||||
{
|
||||
get => _randomCounter;
|
||||
set
|
||||
{
|
||||
_randomCounter = value;
|
||||
|
||||
foreach (var trk in DiskTracks)
|
||||
{
|
||||
foreach (var sec in trk.Sectors)
|
||||
{
|
||||
sec.RandSecCounter = _randomCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected int _randomCounter;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse incoming disk data
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// TRUE: disk parsed
|
||||
/// FALSE: unable to parse disk
|
||||
/// </returns>
|
||||
public virtual bool ParseDisk(byte[] diskData)
|
||||
{
|
||||
// default result
|
||||
// override in inheriting class
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Examines the floppydisk data to work out what protection (if any) is present
|
||||
/// If possible it will also fix the disk data for this protection
|
||||
/// This should be run at the end of the ParseDisk() method
|
||||
/// </summary>
|
||||
public virtual void ParseProtection()
|
||||
{
|
||||
int[] weakArr = new int[2];
|
||||
|
||||
// speedlock
|
||||
if (DetectSpeedlock(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.Speedlock;
|
||||
|
||||
Sector sec = DiskTracks[0].Sectors[1];
|
||||
if (!sec.ContainsMultipleWeakSectors)
|
||||
{
|
||||
byte[] origData = sec.SectorData.ToArray();
|
||||
List<byte> data = new List<byte>();
|
||||
for (int m = 0; m < 3; m++)
|
||||
{
|
||||
for (int i = 0; i < 512; i++)
|
||||
{
|
||||
// deterministic 'random' implementation
|
||||
int n = origData[i] + m + 1;
|
||||
if (n > 0xff)
|
||||
n = n - 0xff;
|
||||
else if (n < 0)
|
||||
n = 0xff + n;
|
||||
|
||||
byte nByte = (byte)n;
|
||||
|
||||
if (m == 0)
|
||||
{
|
||||
data.Add(origData[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < weakArr[0])
|
||||
{
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
|
||||
else if (weakArr[1] > 0)
|
||||
{
|
||||
data.Add(nByte);
|
||||
weakArr[1]--;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sec.SectorData = data.ToArray();
|
||||
sec.ActualDataByteLength = data.Count();
|
||||
sec.ContainsMultipleWeakSectors = true;
|
||||
}
|
||||
}
|
||||
else if (DetectAlkatraz(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.Alkatraz;
|
||||
}
|
||||
else if (DetectPaulOwens(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.PaulOwens;
|
||||
}
|
||||
else if (DetectHexagon(ref weakArr))
|
||||
{
|
||||
Protection = ProtectionType.Hexagon;
|
||||
}
|
||||
else if (DetectShadowOfTheBeast())
|
||||
{
|
||||
Protection = ProtectionType.ShadowOfTheBeast;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detection routine for shadow of the beast game
|
||||
/// Still cannot get this to work, but at least the game is detected
|
||||
/// </summary>
|
||||
public bool DetectShadowOfTheBeast()
|
||||
{
|
||||
if (DiskTracks[0].Sectors.Length != 9)
|
||||
return false;
|
||||
|
||||
var zeroSecs = DiskTracks[0].Sectors;
|
||||
if (zeroSecs[0].SectorID != 65 ||
|
||||
zeroSecs[1].SectorID != 66 ||
|
||||
zeroSecs[2].SectorID != 67 ||
|
||||
zeroSecs[3].SectorID != 68 ||
|
||||
zeroSecs[4].SectorID != 69 ||
|
||||
zeroSecs[5].SectorID != 70 ||
|
||||
zeroSecs[6].SectorID != 71 ||
|
||||
zeroSecs[7].SectorID != 72 ||
|
||||
zeroSecs[8].SectorID != 73)
|
||||
return false;
|
||||
|
||||
var oneSecs = DiskTracks[1].Sectors;
|
||||
|
||||
if (oneSecs.Length != 8)
|
||||
return false;
|
||||
|
||||
if (oneSecs[0].SectorID != 17 ||
|
||||
oneSecs[1].SectorID != 18 ||
|
||||
oneSecs[2].SectorID != 19 ||
|
||||
oneSecs[3].SectorID != 20 ||
|
||||
oneSecs[4].SectorID != 21 ||
|
||||
oneSecs[5].SectorID != 22 ||
|
||||
oneSecs[6].SectorID != 23 ||
|
||||
oneSecs[7].SectorID != 24)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect speedlock weak sector
|
||||
/// </summary>
|
||||
public bool DetectSpeedlock(ref int[] weak)
|
||||
{
|
||||
// SPEEDLOCK NOTES (-asni 2018-05-01)
|
||||
// ---------------------------------
|
||||
// Speedlock is one of the more common +3 disk protections and there are a few different versions
|
||||
// Usually, track 0 sector 1 (ID 2) has data CRC errors that result in certain bytes returning a different value every time they are read
|
||||
// Speedlock will generally read this track a number of times during the load process
|
||||
// and if the correct bytes are not different between reads, the load fails
|
||||
|
||||
// always must have track 0 containing 9 sectors
|
||||
if (DiskTracks[0].Sectors.Length != 9)
|
||||
return false;
|
||||
|
||||
// check for SPEEDLOCK ident in sector 0
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[0].SectorData, 0, DiskTracks[0].Sectors[0].SectorData.Length);
|
||||
if (!ident.ToUpper().Contains("SPEEDLOCK"))
|
||||
return false;
|
||||
|
||||
// check for correct sector 0 lengths
|
||||
if (DiskTracks[0].Sectors[0].SectorSize != 2 ||
|
||||
DiskTracks[0].Sectors[0].SectorData.Length < 0x200)
|
||||
return false;
|
||||
|
||||
// sector[1] (SectorID 2) contains the weak sectors
|
||||
Sector sec = DiskTracks[0].Sectors[1];
|
||||
|
||||
// check for correct sector 1 lengths
|
||||
if (sec.SectorSize != 2 ||
|
||||
sec.SectorData.Length < 0x200)
|
||||
return false;
|
||||
|
||||
// secID 2 needs a CRC error
|
||||
//if (!(sec.Status1.Bit(5) || sec.Status2.Bit(5)))
|
||||
//return false;
|
||||
|
||||
// check for filler
|
||||
bool startFillerFound = true;
|
||||
for (int i = 0; i < 250; i++)
|
||||
{
|
||||
if (sec.SectorData[i] != sec.SectorData[i + 1])
|
||||
{
|
||||
startFillerFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!startFillerFound)
|
||||
{
|
||||
weak[0] = 0;
|
||||
weak[1] = 0x200;
|
||||
}
|
||||
else
|
||||
{
|
||||
weak[0] = 0x150;
|
||||
weak[1] = 0x20;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Alkatraz
|
||||
/// </summary>
|
||||
public bool DetectAlkatraz(ref int[] weak)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data1 = DiskTracks[0].Sectors[0].SectorData;
|
||||
var data2 = DiskTracks[0].Sectors[0].SectorData.Length;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for ALKATRAZ ident in sector 0
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[0].SectorData, 0, DiskTracks[0].Sectors[0].SectorData.Length);
|
||||
if (!ident.ToUpper().Contains("ALKATRAZ PROTECTION SYSTEM"))
|
||||
return false;
|
||||
|
||||
// ALKATRAZ NOTES (-asni 2018-05-01)
|
||||
// ---------------------------------
|
||||
// Alkatraz protection appears to revolve around a track on the disk with 18 sectors,
|
||||
// (track position is not consistent) with the sector ID info being incorrect:
|
||||
// TrackID is consistent between the sectors although is usually high (233, 237 etc)
|
||||
// SideID is fairly random looking but with all IDs being even
|
||||
// SectorID is also fairly random looking but contains both odd and even numbers
|
||||
//
|
||||
// There doesnt appear to be any CRC errors in this track, but the sector size is always 1 (256 bytes)
|
||||
// Each sector contains different filler byte
|
||||
// Once track 0 is loaded the CPU completely reads all the sectors in this track one-by-one.
|
||||
// Data transferred during execution must be correct, also result ST0, ST1 and ST2 must be 64, 128 and 0 respectively
|
||||
|
||||
// Immediately following this track are a number of tracks and sectors with a DAM set.
|
||||
// These are all read in sector by sector
|
||||
// Again, Alkatraz appears to require that ST0, ST1, and ST2 result bytes are set to 64, 128 and 0 respectively
|
||||
// (so the CM in ST2 needs to be reset)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Paul Owens
|
||||
/// </summary>
|
||||
public bool DetectPaulOwens(ref int[] weak)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data1 = DiskTracks[0].Sectors[2].SectorData;
|
||||
var data2 = DiskTracks[0].Sectors[2].SectorData.Length;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for PAUL OWENS ident in sector 2
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[2].SectorData, 0, DiskTracks[0].Sectors[2].SectorData.Length);
|
||||
if (!ident.ToUpper().Contains("PAUL OWENS"))
|
||||
return false;
|
||||
|
||||
// Paul Owens Disk Protection Notes (-asni 2018-05-01)
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
// This scheme looks a little similar to Alkatraz with incorrect sector ID info in many places
|
||||
// and deleted address marks (although these do not seem to show the strict relience on removing the CM mark from ST2 result that Alkatraz does)
|
||||
// There are also data CRC errors but these don't look to be read more than once/checked for changes during load
|
||||
// Main identifiers:
|
||||
//
|
||||
// * There are more than 10 cylinders
|
||||
// * Cylinder 1 has no sector data
|
||||
// * The sector ID infomation in most cases contains incorrect track IDs
|
||||
// * Tracks 0 (boot) and 5 appear to be pretty much the only tracks that do not have incorrect sector ID marks
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detect Hexagon copy protection
|
||||
/// </summary>
|
||||
public bool DetectHexagon(ref int[] weak)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data1 = DiskTracks[0].Sectors.Length;
|
||||
var data2 = DiskTracks[0].Sectors[8].ActualDataByteLength;
|
||||
var data3 = DiskTracks[0].Sectors[8].SectorData;
|
||||
var data4 = DiskTracks[0].Sectors[8].SectorData.Length;
|
||||
var data5 = DiskTracks[1].Sectors[0];
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DiskTracks[0].Sectors.Length != 10 || DiskTracks[0].Sectors[8].ActualDataByteLength != 512)
|
||||
return false;
|
||||
|
||||
// check for Hexagon ident in sector 8
|
||||
string ident = Encoding.ASCII.GetString(DiskTracks[0].Sectors[8].SectorData, 0, DiskTracks[0].Sectors[8].SectorData.Length);
|
||||
if (ident.ToUpper().Contains("GON DISK PROT"))
|
||||
return true;
|
||||
|
||||
// hexagon protection may not be labelled as such
|
||||
var track = DiskTracks[1];
|
||||
var sector = track.Sectors[0];
|
||||
|
||||
if (sector.SectorSize == 6 && sector.Status1 == 0x20 && sector.Status2 == 0x60)
|
||||
{
|
||||
if (track.Sectors.Length == 1)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Hexagon Copy Protection Notes (-asni 2018-05-01)
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
//
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Should be run at the end of the ParseDisk process
|
||||
/// If speedlock is detected the flag is set in the disk image
|
||||
/// </summary>
|
||||
protected virtual void SpeedlockDetection()
|
||||
{
|
||||
|
||||
if (DiskTracks.Length == 0)
|
||||
return;
|
||||
|
||||
// check for speedlock copyright notice
|
||||
string ident = Encoding.ASCII.GetString(DiskData, 0x100, 0x1400);
|
||||
if (!ident.ToUpper().Contains("SPEEDLOCK"))
|
||||
{
|
||||
// speedlock not found
|
||||
return;
|
||||
}
|
||||
|
||||
// get cylinder 0
|
||||
var cyl = DiskTracks[0];
|
||||
|
||||
// get sector with ID=2
|
||||
var sec = cyl.Sectors.Where(a => a.SectorID == 2).FirstOrDefault();
|
||||
|
||||
if (sec == null)
|
||||
return;
|
||||
|
||||
// check for already multiple weak copies
|
||||
if (sec.ContainsMultipleWeakSectors || sec.SectorData.Length != 0x80 << sec.SectorSize)
|
||||
return;
|
||||
|
||||
// check for invalid crcs in sector 2
|
||||
if (sec.Status1.Bit(5) || sec.Status2.Bit(5))
|
||||
{
|
||||
Protection = ProtectionType.Speedlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// we are going to create a total of 5 weak sector copies
|
||||
// keeping the original copy
|
||||
byte[] origData = sec.SectorData.ToArray();
|
||||
List<byte> data = new List<byte>();
|
||||
//Random rnd = new Random();
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
for (int s = 0; s < origData.Length; s++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
data.Add(origData[s]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// deterministic 'random' implementation
|
||||
int n = origData[s] + i + 1;
|
||||
if (n > 0xff)
|
||||
n = n - 0xff;
|
||||
else if (n < 0)
|
||||
n = 0xff + n;
|
||||
|
||||
byte nByte = (byte)n;
|
||||
|
||||
if (s < 336)
|
||||
{
|
||||
// non weak data
|
||||
data.Add(origData[s]);
|
||||
}
|
||||
else if (s < 511)
|
||||
{
|
||||
// weak data
|
||||
data.Add(nByte);
|
||||
}
|
||||
else if (s == 511)
|
||||
{
|
||||
// final sector byte
|
||||
data.Add(nByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
// speedlock sector should not be more than 512 bytes
|
||||
// but in case it is just do non weak
|
||||
data.Add(origData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// commit the sector data
|
||||
sec.SectorData = data.ToArray();
|
||||
sec.ContainsMultipleWeakSectors = true;
|
||||
sec.ActualDataByteLength = data.Count();
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the track count for the disk
|
||||
/// </summary>
|
||||
public virtual int GetTrackCount()
|
||||
{
|
||||
return DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the current sector ID info
|
||||
/// </summary>
|
||||
public virtual CHRN ReadID(byte trackIndex, byte side, int sectorIndex)
|
||||
{
|
||||
if (side != 0)
|
||||
return null;
|
||||
|
||||
if (DiskTracks.Length <= trackIndex || trackIndex < 0)
|
||||
{
|
||||
// invalid track - wrap around
|
||||
trackIndex = 0;
|
||||
}
|
||||
|
||||
var track = DiskTracks[trackIndex];
|
||||
|
||||
if (track.NumberOfSectors <= sectorIndex)
|
||||
{
|
||||
// invalid sector - wrap around
|
||||
sectorIndex = 0;
|
||||
}
|
||||
|
||||
var sector = track.Sectors[sectorIndex];
|
||||
|
||||
CHRN chrn = new CHRN();
|
||||
|
||||
chrn.C = sector.TrackNumber;
|
||||
chrn.H = sector.SideNumber;
|
||||
chrn.R = sector.SectorID;
|
||||
|
||||
// wrap around for N > 7
|
||||
if (sector.SectorSize > 7)
|
||||
{
|
||||
chrn.N = (byte)(sector.SectorSize - 7);
|
||||
}
|
||||
else if (sector.SectorSize < 0)
|
||||
{
|
||||
chrn.N = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
chrn.N = sector.SectorSize;
|
||||
}
|
||||
|
||||
chrn.Flag1 = (byte)(sector.Status1 & 0x25);
|
||||
chrn.Flag2 = (byte)(sector.Status2 & 0x61);
|
||||
|
||||
chrn.DataBytes = sector.ActualData;
|
||||
|
||||
return chrn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State serialization routines
|
||||
/// </summary>
|
||||
public abstract void SyncState(Serializer ser);
|
||||
|
||||
|
||||
public class Header
|
||||
{
|
||||
public string DiskIdent { get; set; }
|
||||
public string DiskCreatorString { get; set; }
|
||||
public byte NumberOfTracks { get; set; }
|
||||
public byte NumberOfSides { get; set; }
|
||||
public int[] TrackSizes { get; set; }
|
||||
}
|
||||
|
||||
public class Track
|
||||
{
|
||||
public string TrackIdent { get; set; }
|
||||
public byte TrackNumber { get; set; }
|
||||
public byte SideNumber { get; set; }
|
||||
public byte DataRate { get; set; }
|
||||
public byte RecordingMode { get; set; }
|
||||
public byte SectorSize { get; set; }
|
||||
public byte NumberOfSectors { get; set; }
|
||||
public byte GAP3Length { get; set; }
|
||||
public byte FillerByte { get; set; }
|
||||
public virtual Sector[] Sectors { get; set; }
|
||||
|
||||
#region UDI
|
||||
|
||||
public virtual byte TrackType { get; set; }
|
||||
public virtual int TLEN { get; set; }
|
||||
public virtual int CLEN => TLEN / 8 + (TLEN % 8 / 7) / 8;
|
||||
public virtual byte[] TrackData { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Presents a contiguous byte array of all sector data for this track
|
||||
/// (including any multiple weak/random data)
|
||||
/// </summary>
|
||||
public virtual byte[] TrackSectorData
|
||||
{
|
||||
get
|
||||
{
|
||||
List<byte> list = new List<byte>();
|
||||
|
||||
foreach (var sec in Sectors)
|
||||
{
|
||||
list.AddRange(sec.ActualData);
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Sector
|
||||
{
|
||||
public virtual byte TrackNumber { get; set; }
|
||||
public virtual byte SideNumber { get; set; }
|
||||
public virtual byte SectorID { get; set; }
|
||||
public virtual byte SectorSize { get; set; }
|
||||
public virtual byte Status1 { get; set; }
|
||||
public virtual byte Status2 { get; set; }
|
||||
public virtual int ActualDataByteLength { get; set; }
|
||||
public virtual byte[] SectorData { get; set; }
|
||||
public virtual bool ContainsMultipleWeakSectors { get; set; }
|
||||
|
||||
public int WeakReadIndex = 0;
|
||||
|
||||
public void SectorReadCompleted()
|
||||
{
|
||||
if (ContainsMultipleWeakSectors)
|
||||
WeakReadIndex++;
|
||||
}
|
||||
|
||||
public int DataLen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ContainsMultipleWeakSectors)
|
||||
{
|
||||
return ActualDataByteLength;
|
||||
}
|
||||
|
||||
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int RandSecCounter = 0;
|
||||
|
||||
public byte[] ActualData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ContainsMultipleWeakSectors)
|
||||
{
|
||||
// check whether filler bytes are needed
|
||||
int size = 0x80 << SectorSize;
|
||||
if (size > ActualDataByteLength)
|
||||
{
|
||||
List<byte> l = new List<byte>();
|
||||
l.AddRange(SectorData);
|
||||
for (int i = 0; i < size - ActualDataByteLength; i++)
|
||||
{
|
||||
//l.Add(SectorData[i]);
|
||||
l.Add(SectorData.Last());
|
||||
}
|
||||
|
||||
return l.ToArray();
|
||||
}
|
||||
|
||||
return SectorData;
|
||||
}
|
||||
else
|
||||
{
|
||||
// weak read neccessary
|
||||
int copies = ActualDataByteLength / (0x80 << SectorSize);
|
||||
|
||||
// handle index wrap-around
|
||||
if (WeakReadIndex > copies - 1)
|
||||
WeakReadIndex = copies - 1;
|
||||
|
||||
// get the sector data based on the current weakreadindex
|
||||
int step = WeakReadIndex * (0x80 << SectorSize);
|
||||
byte[] res = new byte[(0x80 << SectorSize)];
|
||||
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
|
||||
return res;
|
||||
|
||||
/*
|
||||
int copies = ActualDataByteLength / (0x80 << SectorSize);
|
||||
Random rnd = new Random();
|
||||
int r = rnd.Next(0, copies - 1);
|
||||
int step = r * (0x80 << SectorSize);
|
||||
byte[] res = new byte[(0x80 << SectorSize)];
|
||||
Array.Copy(SectorData, step, res, 0, 0x80 << SectorSize);
|
||||
return res;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CHRN SectorIDInfo =>
|
||||
new CHRN
|
||||
{
|
||||
C = TrackNumber,
|
||||
H = SideNumber,
|
||||
R = SectorID,
|
||||
N = SectorSize,
|
||||
Flag1 = Status1,
|
||||
Flag2 = Status2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the type of speedlock detection found
|
||||
/// </summary>
|
||||
public enum ProtectionType
|
||||
{
|
||||
None,
|
||||
Speedlock,
|
||||
Alkatraz,
|
||||
Hexagon,
|
||||
Frontier,
|
||||
PaulOwens,
|
||||
ShadowOfTheBeast
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
public class IPFFloppyDisk : FloppyDisk
|
||||
|
|
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
public class UDI1_0FloppyDisk : FloppyDisk
|
||||
|
|
|
@ -3,6 +3,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -3,6 +3,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -4,6 +4,8 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the possible commands that can be raised from each tape block
|
||||
/// </summary>
|
||||
public enum TapeCommand
|
||||
{
|
||||
NONE,
|
||||
STOP_THE_TAPE,
|
||||
STOP_THE_TAPE_48K,
|
||||
BEGIN_GROUP,
|
||||
END_GROUP,
|
||||
SHOW_MESSAGE,
|
||||
}
|
||||
}
|
|
@ -1,282 +0,0 @@
|
|||
using BizHawk.Common;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a tape block
|
||||
/// </summary>
|
||||
public class TapeDataBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Either the TZX block ID, or -1 in the case of non-tzx blocks
|
||||
/// </summary>
|
||||
private int _blockID = -1;
|
||||
public int BlockID
|
||||
{
|
||||
get => _blockID;
|
||||
set
|
||||
{
|
||||
_blockID = value;
|
||||
|
||||
if (MetaData == null)
|
||||
MetaData = new Dictionary<BlockDescriptorTitle, string>();
|
||||
|
||||
AddMetaData(BlockDescriptorTitle.Block_ID, value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The block type
|
||||
/// </summary>
|
||||
private BlockType _blockType;
|
||||
public BlockType BlockDescription
|
||||
{
|
||||
get => _blockType;
|
||||
set
|
||||
{
|
||||
_blockType = value;
|
||||
if (MetaData == null)
|
||||
MetaData = new Dictionary<BlockDescriptorTitle, string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Byte array containing the raw block data
|
||||
/// </summary>
|
||||
private byte[] _blockData;
|
||||
public byte[] BlockData
|
||||
{
|
||||
get => _blockData;
|
||||
set => _blockData = value;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
/// <summary>
|
||||
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
|
||||
/// Its basically tape information
|
||||
/// </summary>
|
||||
private byte[][] _tapeDescriptionData;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Tape Description Data in a human readable format
|
||||
/// </summary>
|
||||
public List<string> TapeDescriptionData
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> data = new List<string>();
|
||||
|
||||
foreach (byte[] b in _tapeDescriptionData)
|
||||
{
|
||||
data.Add(Encoding.ASCII.GetString(b));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#region Block Meta Data
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of block related data
|
||||
/// </summary>
|
||||
public Dictionary<BlockDescriptorTitle, string> MetaData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single metadata item to the Dictionary
|
||||
/// </summary>
|
||||
public void AddMetaData(BlockDescriptorTitle descriptor, string data)
|
||||
{
|
||||
// check whether entry already exists
|
||||
bool check = MetaData.ContainsKey(descriptor);
|
||||
if (check)
|
||||
{
|
||||
// already exists - update
|
||||
MetaData[descriptor] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
// create new
|
||||
MetaData.Add(descriptor, data);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List containing the pulse timing values
|
||||
/// </summary>
|
||||
public List<int> DataPeriods = new List<int>();
|
||||
|
||||
public bool InitialPulseLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Command that is raised by this data block
|
||||
/// (that may or may not need to be acted on)
|
||||
/// </summary>
|
||||
private TapeCommand _command = TapeCommand.NONE;
|
||||
public TapeCommand Command
|
||||
{
|
||||
get => _command;
|
||||
set => _command = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The defined post-block pause
|
||||
/// </summary>
|
||||
private int _pauseInMS;
|
||||
public int PauseInMS
|
||||
{
|
||||
get => _pauseInMS;
|
||||
set => _pauseInMS = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the data periods as an array
|
||||
/// (primarily to aid in bizhawk state serialization)
|
||||
/// </summary>
|
||||
public int[] GetDataPeriodsArray()
|
||||
{
|
||||
return DataPeriods.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts an array of data periods and updates the DataPeriods list accordingly
|
||||
/// (primarily to aid in bizhawk state serialization)
|
||||
/// </summary>
|
||||
public void SetDataPeriodsArray(int[] periodArray)
|
||||
{
|
||||
DataPeriods = periodArray?.ToList() ?? new List<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bizhawk state serialization
|
||||
/// </summary>
|
||||
public void SyncState(Serializer ser, int blockPosition)
|
||||
{
|
||||
ser.BeginSection("DataBlock" + blockPosition);
|
||||
|
||||
ser.Sync(nameof(_blockID), ref _blockID);
|
||||
//ser.SyncFixedString(nameof(_blockDescription), ref _blockDescription, 200);
|
||||
ser.SyncEnum(nameof(_blockType), ref _blockType);
|
||||
ser.Sync(nameof(_blockData), ref _blockData, true);
|
||||
ser.SyncEnum(nameof(_command), ref _command);
|
||||
|
||||
int[] tempArray = null;
|
||||
|
||||
if (ser.IsWriter)
|
||||
{
|
||||
tempArray = GetDataPeriodsArray();
|
||||
ser.Sync("_periods", ref tempArray, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ser.Sync("_periods", ref tempArray, true);
|
||||
SetDataPeriodsArray(tempArray);
|
||||
}
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The types of TZX blocks
|
||||
/// </summary>
|
||||
public enum BlockType
|
||||
{
|
||||
Standard_Speed_Data_Block = 0x10,
|
||||
Turbo_Speed_Data_Block = 0x11,
|
||||
Pure_Tone = 0x12,
|
||||
Pulse_Sequence = 0x13,
|
||||
Pure_Data_Block = 0x14,
|
||||
Direct_Recording = 0x15,
|
||||
CSW_Recording = 0x18,
|
||||
Generalized_Data_Block = 0x19,
|
||||
Pause_or_Stop_the_Tape = 0x20,
|
||||
Group_Start = 0x21,
|
||||
Group_End = 0x22,
|
||||
Jump_to_Block = 0x23,
|
||||
Loop_Start = 0x24,
|
||||
Loop_End = 0x25,
|
||||
Call_Sequence = 0x26,
|
||||
Return_From_Sequence = 0x27,
|
||||
Select_Block = 0x28,
|
||||
Stop_the_Tape_48K = 0x2A,
|
||||
Set_Signal_Level = 0x2B,
|
||||
Text_Description = 0x30,
|
||||
Message_Block = 0x31,
|
||||
Archive_Info = 0x32,
|
||||
Hardware_Type = 0x33,
|
||||
Custom_Info_Block = 0x35,
|
||||
Glue_Block = 0x5A,
|
||||
|
||||
// depreciated blocks
|
||||
C64_ROM_Type_Data_Block = 0x16,
|
||||
C64_Turbo_Tape_Data_Block = 0x17,
|
||||
Emulation_Info = 0x34,
|
||||
Snapshot_Block = 0x40,
|
||||
|
||||
// unsupported / undetected
|
||||
Unsupported,
|
||||
|
||||
// PZX blocks
|
||||
PZXT,
|
||||
PULS,
|
||||
DATA,
|
||||
BRWS,
|
||||
PAUS,
|
||||
|
||||
// zxhawk proprietry
|
||||
PAUSE_BLOCK,
|
||||
|
||||
WAV_Recording
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Different title possibilities
|
||||
/// </summary>
|
||||
public enum BlockDescriptorTitle
|
||||
{
|
||||
Undefined,
|
||||
Block_ID,
|
||||
Program,
|
||||
Data_Bytes,
|
||||
Bytes,
|
||||
|
||||
Pilot_Pulse_Length,
|
||||
Pilot_Pulse_Count,
|
||||
First_Sync_Length,
|
||||
Second_Sync_Length,
|
||||
Zero_Bit_Length,
|
||||
One_Bit_Length,
|
||||
Data_Length,
|
||||
Bits_In_Last_Byte,
|
||||
Pause_After_Data,
|
||||
|
||||
Pulse_Length,
|
||||
Pulse_Count,
|
||||
|
||||
Text_Description,
|
||||
Title,
|
||||
Publisher,
|
||||
Author,
|
||||
Year,
|
||||
Language,
|
||||
Type,
|
||||
Price,
|
||||
Protection,
|
||||
Origin,
|
||||
Comments,
|
||||
|
||||
Needs_Parsing
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
|
|
Loading…
Reference in New Issue