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.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -23,7 +25,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
private CRCT_6845 CRCT => _machine.CRCT;
|
private CRCT_6845 CRCT => _machine.CRCT;
|
||||||
//private CRTDevice CRT => _machine.CRT;
|
//private CRTDevice CRT => _machine.CRT;
|
||||||
private IPSG PSG => _machine.AYDevice;
|
private IPSG PSG => _machine.AYDevice;
|
||||||
private NECUPD765 FDC => _machine.UPDDiskDevice;
|
private NECUPD765CPC FDC => _machine.UPDDiskDevice;
|
||||||
private DatacorderDevice DATACORDER => _machine.TapeDevice;
|
private DatacorderDevice DATACORDER => _machine.TapeDevice;
|
||||||
private ushort BUSRQ => CPU.MEMRQ[CPU.bus_pntr];
|
private ushort BUSRQ => CPU.MEMRQ[CPU.bus_pntr];
|
||||||
public const ushort PCh = 1;
|
public const ushort PCh = 1;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using BizHawk.Common.NumberExtensions;
|
using BizHawk.Common.NumberExtensions;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using BizHawk.Common.NumberExtensions;
|
using BizHawk.Common.NumberExtensions;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
TapeDevice = new DatacorderDevice(autoTape);
|
TapeDevice = new DatacorderDevice(autoTape);
|
||||||
TapeDevice.Init(this);
|
TapeDevice.Init(this);
|
||||||
|
|
||||||
UPDDiskDevice = new NECUPD765();
|
UPDDiskDevice = new NECUPD765CPC();
|
||||||
UPDDiskDevice.Init(this);
|
UPDDiskDevice.Init(this);
|
||||||
|
|
||||||
InitializeMedia(files);
|
InitializeMedia(files);
|
||||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
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
|
/// The abstract class that all emulated models will inherit from
|
||||||
/// * Main properties / fields / contruction*
|
/// * Main properties / fields / contruction*
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class CPCBase
|
public abstract partial class CPCBase : CPCSpectrumBase.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
#region Devices
|
#region Devices
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Amstrad disk drive
|
/// The Amstrad disk drive
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual NECUPD765 UPDDiskDevice { get; set; }
|
public virtual NECUPD765CPC UPDDiskDevice { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Cathode Ray Tube Controller chip
|
/// The Cathode Ray Tube Controller chip
|
||||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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;
|
using BizHawk.Common;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a beeper/buzzer device
|
/// Represents a beeper/buzzer device
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines an object that can load a floppy disk image
|
/// 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>
|
/// <summary>
|
||||||
/// Represents a spectrum joystick
|
/// Represents a spectrum joystick
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IJoystick
|
public interface IJoystick<T> where T : Enum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of joystick
|
/// The type of joystick
|
||||||
/// </summary>
|
/// </summary>
|
||||||
JoystickType JoyType { get; }
|
T JoyType { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Array of all the possibly button press names
|
/// 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>
|
/// <summary>
|
||||||
/// Represents a device that utilizes port IN & OUT
|
/// 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,7 +1,7 @@
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Definitions
|
/// Definitions
|
||||||
|
@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
public partial class NECUPD765
|
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||||
{
|
{
|
||||||
#region Enums
|
#region Enums
|
||||||
|
|
||||||
|
@ -692,10 +692,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private class Command
|
private class Command
|
||||||
{
|
{
|
||||||
// /// <summary>
|
// /// <summary>
|
||||||
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
|
// /// Mask to remove potential parameter bits (5,6, and or 7) in order to identify the command
|
||||||
// /// </summary>
|
// /// </summary>
|
||||||
// public int BitMask { get; set; }
|
// public int BitMask { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The command code after bitmask has been applied
|
/// The command code after bitmask has been applied
|
||||||
/// </summary>
|
/// </summary>
|
|
@ -3,7 +3,7 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// FDC State and Methods
|
/// FDC State and Methods
|
||||||
|
@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
public partial class NECUPD765
|
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||||
{
|
{
|
||||||
#region Controller State
|
#region Controller State
|
||||||
|
|
||||||
|
@ -385,8 +385,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
sectorSize = 0x80 << ActiveCommandParams.SectorSize;
|
sectorSize = 0x80 << ActiveCommandParams.SectorSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mtc = maxTransferCap;
|
|
||||||
|
|
||||||
// get the current track
|
// get the current track
|
||||||
var track = ActiveDrive.Disk.DiskTracks.FirstOrDefault(a => a.TrackNumber == ActiveDrive.CurrentTrackID);
|
var track = ActiveDrive.Disk.DiskTracks.FirstOrDefault(a => a.TrackNumber == ActiveDrive.CurrentTrackID);
|
||||||
|
|
||||||
|
@ -672,22 +670,13 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
switch (ActiveCommandParams.SectorSize)
|
switch (ActiveCommandParams.SectorSize)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
if (CMD_FLAG_MF)
|
maxTransferCap = CMD_FLAG_MF ? 6656 : 3840;
|
||||||
maxTransferCap = 6656;
|
|
||||||
else
|
|
||||||
maxTransferCap = 3840;
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (CMD_FLAG_MF)
|
maxTransferCap = CMD_FLAG_MF ? 7680 : 4096;
|
||||||
maxTransferCap = 7680;
|
|
||||||
else
|
|
||||||
maxTransferCap = 4096;
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (CMD_FLAG_MF)
|
maxTransferCap = CMD_FLAG_MF ? 8192 : 4096;
|
||||||
maxTransferCap = 8192;
|
|
||||||
else
|
|
||||||
maxTransferCap = 4096;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2148,7 +2137,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
// second byte is the current track id
|
// second byte is the current track id
|
||||||
ResBuffer[1] = ActiveDrive.CurrentTrackID;
|
ResBuffer[1] = ActiveDrive.CurrentTrackID;
|
||||||
}
|
}
|
||||||
/*
|
#if false
|
||||||
else if (ActiveDrive.SeekStatus == SEEK_INTACKNOWLEDGED)
|
else if (ActiveDrive.SeekStatus == SEEK_INTACKNOWLEDGED)
|
||||||
{
|
{
|
||||||
// DriveA interrupt has already been acknowledged
|
// DriveA interrupt has already been acknowledged
|
||||||
|
@ -2158,7 +2147,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
Status0 = 192;
|
Status0 = 192;
|
||||||
ResBuffer[0] = Status0;
|
ResBuffer[0] = Status0;
|
||||||
}
|
}
|
||||||
*/
|
#endif
|
||||||
else if (ActiveDrive.SeekStatus == SEEK_IDLE)
|
else if (ActiveDrive.SeekStatus == SEEK_IDLE)
|
||||||
{
|
{
|
||||||
// SIS with no interrupt
|
// SIS with no interrupt
|
||||||
|
@ -2383,6 +2372,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (!CheckTiming()) UnSetBit(MSR_EXM, ref StatusMain);
|
||||||
|
|
||||||
return StatusMain;
|
return StatusMain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2553,7 +2544,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
CMDIndex = CommandList.Count() - 1;
|
CMDIndex = CommandList.Count() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
#if false
|
||||||
if ((CMD_FLAG_MF && !ActiveCommand.MF) ||
|
if ((CMD_FLAG_MF && !ActiveCommand.MF) ||
|
||||||
(CMD_FLAG_MT && !ActiveCommand.MT) ||
|
(CMD_FLAG_MT && !ActiveCommand.MT) ||
|
||||||
(CMD_FLAG_SK && !ActiveCommand.SK))
|
(CMD_FLAG_SK && !ActiveCommand.SK))
|
||||||
|
@ -2561,7 +2552,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
// command byte included spurious bit 5,6 or 7 flags
|
// command byte included spurious bit 5,6 or 7 flags
|
||||||
CMDIndex = CommandList.Count() - 1;
|
CMDIndex = CommandList.Count() - 1;
|
||||||
}
|
}
|
||||||
*/
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
CommCounter = 0;
|
CommCounter = 0;
|
||||||
|
@ -2571,14 +2562,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
// move to command phase
|
// move to command phase
|
||||||
ActivePhase = Phase.Command;
|
ActivePhase = Phase.Command;
|
||||||
|
|
||||||
/*
|
#if false
|
||||||
// check for invalid SIS
|
// check for invalid SIS
|
||||||
if (ActiveInterrupt == InterruptState.None && CMDIndex == CC_SENSE_INTSTATUS)
|
if (ActiveInterrupt == InterruptState.None && CMDIndex == CC_SENSE_INTSTATUS)
|
||||||
{
|
{
|
||||||
CMDIndex = CC_INVALID;
|
CMDIndex = CC_INVALID;
|
||||||
//ActiveCommand.CommandDelegate(InstructionState.StartResult);
|
//ActiveCommand.CommandDelegate(InstructionState.StartResult);
|
||||||
}
|
}
|
||||||
*/
|
#endif
|
||||||
|
|
||||||
// set reslength
|
// set reslength
|
||||||
ResLength = ActiveCommand.ResultByteCount;
|
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,7 +3,7 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IPortIODevice
|
/// IPortIODevice
|
||||||
|
@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
public partial class NECUPD765 : IPortIODevice
|
public abstract partial class NECUPD765<TMachine, TDriveState> : IPortIODevice
|
||||||
{
|
{
|
||||||
#region Dev Logging
|
#region Dev Logging
|
||||||
|
|
||||||
|
@ -31,15 +31,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Status read
|
Status read
|
||||||
* Data write
|
Data write
|
||||||
* Data read
|
Data read
|
||||||
* CMD code
|
CMD code
|
||||||
* CMD string
|
CMD string
|
||||||
* MT flag
|
MT flag
|
||||||
* MK flag
|
MK flag
|
||||||
* SK flag
|
SK flag
|
||||||
* */
|
*/
|
||||||
private string[] workingArr = new string[3];
|
private string[] workingArr = new string[3];
|
||||||
|
|
||||||
private void BuildCSVLine()
|
private void BuildCSVLine()
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Static helper methods
|
/// Static helper methods
|
||||||
|
@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
public partial class NECUPD765
|
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the specified bit value from supplied byte
|
/// Returns the specified bit value from supplied byte
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timimng
|
/// Timimng
|
||||||
|
@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
public partial class NECUPD765
|
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current Z80 cycle
|
/// The current Z80 cycle
|
||||||
|
@ -43,7 +43,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the numbers of Z80 cycles per MS
|
/// Defines the numbers of Z80 cycles per MS
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private long CPUCyclesPerMs;
|
protected long CPUCyclesPerMs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The floppy drive emulated clock speed
|
/// The floppy drive emulated clock speed
|
||||||
|
@ -73,23 +73,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the timing routines
|
/// Initializes the timing routines
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void TimingInit()
|
protected abstract 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called by reads to the main status register
|
/// Called by reads to the main status register
|
|
@ -1,7 +1,7 @@
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The NEC floppy disk controller (and floppy drive) found in the +3
|
/// The NEC floppy disk controller (and floppy drive) found in the +3
|
||||||
|
@ -14,14 +14,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
http://www.cpcwiki.eu/imgs/f/f3/UPD765_Datasheet_OCRed.pdf
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
public partial class NECUPD765
|
public abstract partial class NECUPD765<TMachine, TDriveState>
|
||||||
|
where TMachine : CPCSpectrumBase
|
||||||
|
where TDriveState : NECUPD765DriveState
|
||||||
{
|
{
|
||||||
#region Devices
|
#region Devices
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The emulated spectrum machine
|
/// The emulated CPC or Spectrum machine
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private CPCBase _machine;
|
protected TMachine _machine;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialization routine
|
/// Initialization routine
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Init(CPCBase machine)
|
public void Init(TMachine machine)
|
||||||
{
|
{
|
||||||
_machine = machine;
|
_machine = machine;
|
||||||
FDD_Init();
|
FDD_Init();
|
|
@ -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>
|
/// <summary>
|
||||||
/// The different disk formats ZXHawk currently supports
|
/// The different disk formats ZXHawk currently supports
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This abstract class defines a logical floppy disk
|
/// This abstract class defines a logical floppy disk
|
||||||
|
@ -402,7 +402,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
#if false
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should be run at the end of the ParseDisk process
|
/// Should be run at the end of the ParseDisk process
|
||||||
/// If speedlock is detected the flag is set in the disk image
|
/// If speedlock is detected the flag is set in the disk image
|
||||||
|
@ -499,7 +499,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
sec.ActualDataByteLength = data.Count();
|
sec.ActualDataByteLength = data.Count();
|
||||||
|
|
||||||
}
|
}
|
||||||
*/
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the track count for the disk
|
/// Returns the track count for the disk
|
||||||
|
@ -587,8 +587,18 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
public byte NumberOfSectors { get; set; }
|
public byte NumberOfSectors { get; set; }
|
||||||
public byte GAP3Length { get; set; }
|
public byte GAP3Length { get; set; }
|
||||||
public byte FillerByte { 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>
|
/// <summary>
|
||||||
/// Presents a contiguous byte array of all sector data for this track
|
/// Presents a contiguous byte array of all sector data for this track
|
||||||
/// (including any multiple weak/random data)
|
/// (including any multiple weak/random data)
|
||||||
|
@ -607,6 +617,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
return list.ToArray();
|
return list.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Sector
|
public class Sector
|
||||||
|
@ -637,12 +648,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
{
|
{
|
||||||
return ActualDataByteLength;
|
return ActualDataByteLength;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
|
return ActualDataByteLength / (ActualDataByteLength / (0x80 << SectorSize));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int RandSecCounter = 0;
|
public int RandSecCounter = 0;
|
||||||
|
@ -667,11 +676,9 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
|
|
||||||
return l.ToArray();
|
return l.ToArray();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return SectorData;
|
return SectorData;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// weak read neccessary
|
// weak read neccessary
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the possible commands that can be raised from each tape block
|
/// Represents the possible commands that can be raised from each tape block
|
|
@ -2,7 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
namespace BizHawk.Emulation.Cores.Computers.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a tape block
|
/// Represents a tape block
|
||||||
|
@ -52,7 +52,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
set => _blockData = value;
|
set => _blockData = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
#if false
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
|
/// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization)
|
||||||
/// Its basically tape information
|
/// Its basically tape information
|
||||||
|
@ -76,7 +76,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#region Block Meta Data
|
#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.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
using BizHawk.Emulation.Cores.Sound;
|
using BizHawk.Emulation.Cores.Sound;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cursor joystick
|
/// Cursor joystick
|
||||||
/// Maps to a combination of 0xf7fe and 0xeffe
|
/// Maps to a combination of 0xf7fe and 0xeffe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CursorJoystick : IJoystick
|
public class CursorJoystick : IJoystick<JoystickType>
|
||||||
{
|
{
|
||||||
//private int _joyLine;
|
//private int _joyLine;
|
||||||
private SpectrumBase _machine;
|
private SpectrumBase _machine;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
public class KempstonJoystick : IJoystick
|
public class KempstonJoystick : IJoystick<JoystickType>
|
||||||
{
|
{
|
||||||
private int _joyLine;
|
private int _joyLine;
|
||||||
private SpectrumBase _machine;
|
private SpectrumBase _machine;
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A null joystick object
|
/// A null joystick object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NullJoystick : IJoystick
|
public class NullJoystick : IJoystick<JoystickType>
|
||||||
{
|
{
|
||||||
private int _joyLine;
|
private int _joyLine;
|
||||||
private SpectrumBase _machine;
|
private SpectrumBase _machine;
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sinclair Joystick LEFT
|
/// Sinclair Joystick LEFT
|
||||||
/// Just maps to the standard keyboard and is read the same (from port 0xf7fe)
|
/// Just maps to the standard keyboard and is read the same (from port 0xf7fe)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SinclairJoystick1 : IJoystick
|
public class SinclairJoystick1 : IJoystick<JoystickType>
|
||||||
{
|
{
|
||||||
//private int _joyLine;
|
//private int _joyLine;
|
||||||
private SpectrumBase _machine;
|
private SpectrumBase _machine;
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sinclair Joystick RIGHT
|
/// Sinclair Joystick RIGHT
|
||||||
/// Just maps to the standard keyboard and is read the same (from port 0xeffe)
|
/// Just maps to the standard keyboard and is read the same (from port 0xeffe)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SinclairJoystick2 : IJoystick
|
public class SinclairJoystick2 : IJoystick<JoystickType>
|
||||||
{
|
{
|
||||||
//private int _joyLine;
|
//private int _joyLine;
|
||||||
private SpectrumBase _machine;
|
private SpectrumBase _machine;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -280,7 +282,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void InitJoysticks(List<JoystickType> joys)
|
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++)
|
for (int i = 0; i < joys.Count(); i++)
|
||||||
{
|
{
|
||||||
|
@ -298,7 +300,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instantiates a new IJoystick object
|
/// Instantiates a new IJoystick object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IJoystick InstantiateJoystick(JoystickType type, int playerNumber)
|
public IJoystick<JoystickType> InstantiateJoystick(JoystickType type, int playerNumber)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
|
@ -320,7 +322,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a IJoystick object depending on the type (or null if not found)
|
/// Returns a IJoystick object depending on the type (or null if not found)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected IJoystick LocateUniqueJoystick(JoystickType type)
|
protected IJoystick<JoystickType> LocateUniqueJoystick(JoystickType type)
|
||||||
{
|
{
|
||||||
return JoystickCollection.FirstOrDefault(a => a.JoyType == type);
|
return JoystickCollection.FirstOrDefault(a => a.JoyType == type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using BizHawk.Emulation.Cores.Components.Z80A;
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
using BizHawk.Emulation.Cores.Sound;
|
using BizHawk.Emulation.Cores.Sound;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
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
|
/// The abstract class that all emulated models will inherit from
|
||||||
/// * Main properties / fields / contruction*
|
/// * Main properties / fields / contruction*
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class SpectrumBase
|
public abstract partial class SpectrumBase : CPCSpectrumBase.CPCSpectrumBase
|
||||||
{
|
{
|
||||||
#region Devices
|
#region Devices
|
||||||
|
|
||||||
|
@ -66,12 +67,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The +3 built-in disk drive
|
/// The +3 built-in disk drive
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual NECUPD765 UPDDiskDevice { get; set; }
|
public virtual NECUPD765Spectrum UPDDiskDevice { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds the currently selected joysticks
|
/// Holds the currently selected joysticks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual IJoystick[] JoystickCollection { get; set; }
|
public virtual IJoystick<JoystickType>[] JoystickCollection { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// +3/2a printer port strobe
|
/// +3/2a printer port strobe
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
TapeDevice = new DatacorderDevice(spectrum.SyncSettings.AutoLoadTape);
|
TapeDevice = new DatacorderDevice(spectrum.SyncSettings.AutoLoadTape);
|
||||||
TapeDevice.Init(this);
|
TapeDevice.Init(this);
|
||||||
|
|
||||||
UPDDiskDevice = new NECUPD765();
|
UPDDiskDevice = new NECUPD765Spectrum();
|
||||||
UPDDiskDevice.Init(this);
|
UPDDiskDevice.Init(this);
|
||||||
|
|
||||||
InitializeMedia(files);
|
InitializeMedia(files);
|
||||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -3,6 +3,8 @@ using BizHawk.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
public class IPFFloppyDisk : FloppyDisk
|
public class IPFFloppyDisk : FloppyDisk
|
||||||
|
|
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
public class UDI1_0FloppyDisk : FloppyDisk
|
public class UDI1_0FloppyDisk : FloppyDisk
|
||||||
|
|
|
@ -3,6 +3,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -3,6 +3,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -4,6 +4,8 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Cores.Computers.CPCSpectrumBase;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
Loading…
Reference in New Issue