ZXHawk: +3 disk ready for testing

This commit is contained in:
Asnivor 2018-04-26 12:54:10 +01:00
parent 0d3f8f16ed
commit 7aaa5e8a52
34 changed files with 8967 additions and 76 deletions

View File

@ -2109,7 +2109,7 @@ namespace BizHawk.Client.EmuHawk
"Apple II", "*.dsk;*.do;*.po;%ARCH%",
"Virtual Boy", "*.vb;%ARCH%",
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
"Sinclair ZX Spectrum", "*.tzx;*.tap;%ARCH%",
"Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;%ARCH%",
"All Files", "*.*");
}

View File

@ -340,6 +340,14 @@ namespace BizHawk.Emulation.Common
break;
case ".DSK":
byte[] head2 = romData.Take(20).ToArray();
if (System.Text.Encoding.Default.GetString(head2).ToUpper().Contains("EXTENDED CPC DSK") ||
System.Text.Encoding.Default.GetString(head2).ToUpper().Contains("MV - CPCEMU"))
game.System = "ZXSpectrum";
else
game.System = "AppleII";
break;
case ".PO":
case ".DO":
game.System = "AppleII";

View File

@ -258,9 +258,20 @@
<Compile Include="Computers\Commodore64\MOS\Vic.VideoProvider.cs" />
<Compile Include="Computers\Commodore64\SaveState.cs" />
<Compile Include="Computers\Commodore64\User\UserPortDevice.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IFDDHost.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\FloppyDisk.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IJoystick.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IPortIODevice.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IPSG.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\CHRN.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPD765.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPD765.Definitions.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPD765.FDC.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPD765.FDD.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPD765.IPortIODevice.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPD765.Registers.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPD765.Timing.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Disk\NECUPS765.Static.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\CursorJoystick.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\SinclairJoystick2.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\SinclairJoystick1.cs" />
@ -280,12 +291,20 @@
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum128K\ZX128.ULA.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum16K\ZX16.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.ULA.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\MediaSerializationType.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\CPCExtendedFloppyDisk.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\CPCFloppyDisk.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\DiskImage.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\DiskType.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\DskConverter.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\Sector.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\Track.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\MediaConverter.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\MediaConverterType.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\MediaSerializer.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapeCommand.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapeDataBlock.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapSerializer.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TzxSerializer.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TapConverter.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Tape\TzxConverter.cs" />
<Compile Include="Computers\SinclairSpectrum\SoundProviderMixer.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\MachineType.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.cs" />

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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>
/// <param name="diskData"></param>
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; }
}
}

View File

@ -324,13 +324,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void LoadTape(byte[] tapeData)
{
// check TZX first
TzxSerializer tzxSer = new TzxSerializer(this);
TzxConverter tzxSer = new TzxConverter(this);
if (tzxSer.CheckType(tapeData))
{
// this file has a tzx header - attempt serialization
try
{
tzxSer.DeSerialize(tapeData);
tzxSer.Read(tapeData);
return;
}
catch (Exception ex)
@ -343,10 +343,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
else
{
TapSerializer tapSer = new TapSerializer(this);
TapConverter tapSer = new TapConverter(this);
try
{
tapSer.DeSerialize(tapeData);
tapSer.Read(tapeData);
return;
}
catch (Exception ex)

View File

@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 { return _flag1; }
set { _flag1 = value; }
}
/// <summary>
/// Status register 2
/// </summary>
private byte _flag2;
public byte Flag2
{
get { return _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 { return 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 { return 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 { return 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 { return 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 { return 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 { return 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 { return 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 { return 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 { return NECUPD765.GetBit(6, _flag2); }
set
{
if (value) { NECUPD765.SetBit(6, ref _flag2); }
else { NECUPD765.UnSetBit(6, ref _flag2); }
}
}
#endregion
}
}

View File

@ -0,0 +1,830 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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("UnitSelect", ref UnitSelect);
ser.Sync("Side", ref Side);
ser.Sync("Cylinder", ref Cylinder);
ser.Sync("Head", ref Head);
ser.Sync("Sector", ref Sector);
ser.Sync("SectorSize", ref SectorSize);
ser.Sync("EOT", ref EOT);
ser.Sync("Gap3Length", ref Gap3Length);
ser.Sync("DTL", ref DTL);
ser.EndSection();
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,909 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 { return _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>
/// <returns></returns>
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>
/// <param name="tapeData"></param>
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[DiskDriveIndex].FDD_EjectDisk();
}
/// <summary>
/// Signs whether the current active drive has a disk inserted
/// </summary>
public bool FDD_IsDiskLoaded
{
get { return DriveStates[DiskDriveIndex].FDD_IsDiskLoaded; }
}
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 = true;
/// <summary>
/// Storage for recal steps
/// One step for each indexpulse (track index) until track 0
/// </summary>
//public int RecalibrationCounter;
/// <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 current command that the drive is processing
/// </summary>
//public DriveMainState CurrentState = DriveMainState.None;
/// <summary>
/// Current seek state
/// </summary>
//public SeekSubState SeekState = SeekSubState.Idle;
/// <summary>
/// Seek int state
/// </summary>
//public SeekIntStatus SeekIntState = SeekIntStatus.Normal;
/// <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>
/// <param name="direction"></param>
/// <param name="cylinderCount"></param>
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>
/// <param name="resBuffer"></param>
/// <param name=""></param>
/// <param name=""></param>
/// <returns></returns>
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>
/// <param name="resBuffer"></param>
/// <param name="chrn"></param>
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>
/// <returns></returns>
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>
/// <param name="tapeData"></param>
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("ID", ref ID);
ser.Sync("FLAG_WRITEPROTECT", ref FLAG_WRITEPROTECT);
//ser.Sync("FLAG_DISKCHANGED", ref FLAG_DISKCHANGED);
//ser.Sync("FLAG_RECALIBRATING", ref FLAG_RECALIBRATING);
//ser.Sync("FLAG_SEEK_INTERRUPT", ref FLAG_SEEK_INTERRUPT);
//ser.Sync("IntStatus", ref IntStatus);
//ser.Sync("ST0", ref ST0);
//ser.Sync("RecalibrationCounter", ref RecalibrationCounter);
ser.Sync("SeekCounter", ref SeekCounter);
ser.Sync("SeekStatus", ref SeekStatus);
ser.Sync("SeekAge", ref SeekAge);
ser.Sync("CurrentSide", ref CurrentSide);
//ser.Sync("CurrentTrack", ref CurrentTrack);
ser.Sync("TrackIndex", ref TrackIndex);
ser.Sync("SeekingTrack", ref SeekingTrack);
//ser.Sync("CurrentSector", ref CurrentSector);
ser.Sync("SectorIndex", ref SectorIndex);
//ser.Sync("RAngles", ref RAngles);
//ser.Sync("DataPointer", ref DataPointer);
//ser.SyncEnum("CurrentState", ref CurrentState);
//ser.SyncEnum("SeekState", ref SeekState);
//ser.SyncEnum("SeekIntState", ref SeekIntState);
}
#endregion
}
#endregion
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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
{
public string outputfile = @"D:\Dropbox\Dropbox\_Programming\TASVideos\BizHawk\output\zxhawkio-" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".csv";
public string outputString = "STATUS,WRITE,READ\r\n";
public bool writeDebug = false;
/// <summary>
/// Device responds to an IN instruction
/// </summary>
/// <param name="port"></param>
/// <param name="data"></param>
/// <returns></returns>
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)
outputString += ",," + data + "\r\n";
return true;
}
if (port == 0x2ffd)
{
// read main status register
// this can happen at any time
data = ReadMainStatus();
if (writeDebug)
outputString += data + ",,\r\n";
return true;
}
return false;
}
/// <summary>
/// Device responds to an OUT instruction
/// </summary>
/// <param name="port"></param>
/// <param name="data"></param>
/// <returns></returns>
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 + ",\r\n";
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;
}
}
}

View File

@ -0,0 +1,554 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Registers
/// </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 Main Status Register
/// <summary>
/// Main status register (accessed via reads to port 0x2ffd)
/// </summary>
private byte _RegMain;
/// <summary>
/// FDD0 Busy (seek/recalib active, until succesful sense intstat)
/// </summary>
private bool MainDB0
{
get { return GetBit(0, _RegMain); }
set
{
if (value) { SetBit(0, ref _RegMain); }
else { UnSetBit(0, ref _RegMain); }
}
}
/// <summary>
/// FDD1 Busy (seek/recalib active, until succesful sense intstat)
/// </summary>
private bool MainDB1
{
get { return GetBit(1, _RegMain); }
set
{
if (value) { SetBit(1, ref _RegMain); }
else { UnSetBit(1, ref _RegMain); }
}
}
/// <summary>
/// FDD2 Busy (seek/recalib active, until succesful sense intstat)
/// </summary>
private bool MainDB2
{
get { return GetBit(2, _RegMain); }
set
{
if (value) { SetBit(2, ref _RegMain); }
else { UnSetBit(2, ref _RegMain); }
}
}
/// <summary>
/// FDD3 Busy (seek/recalib active, until succesful sense intstat)
/// </summary>
private bool MainDB3
{
get { return GetBit(3, _RegMain); }
set
{
if (value) { SetBit(3, ref _RegMain); }
else { UnSetBit(3, ref _RegMain); }
}
}
/// <summary>
/// FDC Busy (still in command-, execution- or result-phase)
/// </summary>
private bool MainCB
{
get { return GetBit(4, _RegMain); }
set
{
if (value) { SetBit(4, ref _RegMain); }
else { UnSetBit(4, ref _RegMain); }
}
}
/// <summary>
/// Execution Mode (still in execution-phase, non_DMA_only)
/// </summary>
private bool MainEXM
{
get { return GetBit(5, _RegMain); }
set
{
if (value) { SetBit(5, ref _RegMain); }
else { UnSetBit(5, ref _RegMain); }
}
}
/// <summary>
/// Data Input/Output (0=CPU->FDC, 1=FDC->CPU) (see b7)
/// </summary>
private bool MainDIO
{
get { return GetBit(6, _RegMain); }
set
{
if (value) { SetBit(6, ref _RegMain); }
else { UnSetBit(6, ref _RegMain); }
}
}
/// <summary>
/// Request For Master (1=ready for next byte) (see b6 for direction)
/// </summary>
private bool MainRQM
{
get { return GetBit(7, _RegMain); }
set
{
if (value) { SetBit(7, ref _RegMain); }
else { UnSetBit(7, ref _RegMain); }
}
}
#endregion
#region Status Register 0
/// <summary>
/// Status Register 0
/// </summary>
private byte _Reg0;
/// <summary>
/// Unit Select (driveno during interrupt)
/// </summary>
private bool ST0US0
{
get { return GetBit(0, _Reg0); }
set
{
if (value) { SetBit(0, ref _Reg0); }
else { UnSetBit(0, ref _Reg0); }
}
}
/// <summary>
/// Unit Select (driveno during interrupt)
/// </summary>
private bool ST0US1
{
get { return GetBit(1, _Reg0); }
set
{
if (value) { SetBit(1, ref _Reg0); }
else { UnSetBit(1, ref _Reg0); }
}
}
/// <summary>
/// Head Address (head during interrupt)
/// </summary>
private bool ST0HD
{
get { return GetBit(2, _Reg0); }
set
{
if (value) { SetBit(2, ref _Reg0); }
else { UnSetBit(2, ref _Reg0); }
}
}
/// <summary>
/// Not Ready (drive not ready or non-existing 2nd head selected)
/// </summary>
private bool ST0NR
{
get { return GetBit(3, _Reg0); }
set
{
if (value) { SetBit(3, ref _Reg0); }
else { UnSetBit(3, ref _Reg0); }
}
}
/// <summary>
/// Equipment Check (drive failure or recalibrate failed (retry))
/// </summary>
private bool ST0EC
{
get { return GetBit(4, _Reg0); }
set
{
if (value) { SetBit(4, ref _Reg0); }
else { UnSetBit(4, ref _Reg0); }
}
}
/// <summary>
/// Seek End (Set if seek-command completed)
/// </summary>
private bool ST0SE
{
get { return GetBit(5, _Reg0); }
set
{
if (value) { SetBit(5, ref _Reg0); }
else { UnSetBit(5, ref _Reg0); }
}
}
/// <summary>
/// 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>
private bool ST0IC0
{
get { return GetBit(6, _Reg0); }
set
{
if (value) { SetBit(6, ref _Reg0); }
else { UnSetBit(6, ref _Reg0); }
}
}
/// <summary>
/// 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>
private bool ST0IC1
{
get { return GetBit(7, _Reg0); }
set
{
if (value) { SetBit(7, ref _Reg0); }
else { UnSetBit(7, ref _Reg0); }
}
}
#endregion
#region Status Register 1
/// <summary>
/// Status Register 1
/// </summary>
private byte _Reg1;
/// <summary>
/// Missing Address Mark (Sector_ID or DAM not found)
/// </summary>
private bool ST1MA
{
get { return GetBit(0, _Reg1); }
set
{
if (value) { SetBit(0, ref _Reg1); }
else { UnSetBit(0, ref _Reg1); }
}
}
/// <summary>
/// Not Writeable (tried to write/format disc with wprot_tab=on)
/// </summary>
private bool ST1NW
{
get { return GetBit(1, _Reg1); }
set
{
if (value) { SetBit(1, ref _Reg1); }
else { UnSetBit(1, ref _Reg1); }
}
}
/// <summary>
/// No Data (Sector_ID not found, CRC fail in ID_field)
/// </summary>
private bool ST1ND
{
get { return GetBit(2, _Reg1); }
set
{
if (value) { SetBit(2, ref _Reg1); }
else { UnSetBit(2, ref _Reg1); }
}
}
/// <summary>
/// Over Run (CPU too slow in execution-phase (ca. 26us/Byte))
/// </summary>
private bool ST1OR
{
get { return GetBit(4, _Reg1); }
set
{
if (value) { SetBit(4, ref _Reg1); }
else { UnSetBit(4, ref _Reg1); }
}
}
/// <summary>
/// Data Error (CRC-fail in ID- or Data-Field)
/// </summary>
private bool ST1DE
{
get { return GetBit(5, _Reg1); }
set
{
if (value) { SetBit(5, ref _Reg1); }
else { UnSetBit(5, ref _Reg1); }
}
}
/// <summary>
/// End of Track (set past most read/write commands) (see IC)
/// </summary>
private bool ST1EN
{
get { return GetBit(7, _Reg1); }
set
{
if (value) { SetBit(7, ref _Reg1); }
else { UnSetBit(7, ref _Reg1); }
}
}
#endregion
#region Status Register 2
/// <summary>
/// Status Register 2
/// </summary>
private byte _Reg2;
/// <summary>
/// Missing Address Mark in Data Field (DAM not found)
/// </summary>
private bool ST2MD
{
get { return GetBit(0, _Reg2); }
set
{
if (value) { SetBit(0, ref _Reg2); }
else { UnSetBit(0, ref _Reg2); }
}
}
/// <summary>
/// Bad Cylinder (read/programmed track-ID different and read-ID = FF)
/// </summary>
private bool ST2BC
{
get { return GetBit(1, _Reg2); }
set
{
if (value) { SetBit(1, ref _Reg2); }
else { UnSetBit(1, ref _Reg2); }
}
}
/// <summary>
/// Scan Not Satisfied (no fitting sector found)
/// </summary>
private bool ST2SN
{
get { return GetBit(2, _Reg2); }
set
{
if (value) { SetBit(2, ref _Reg2); }
else { UnSetBit(2, ref _Reg2); }
}
}
/// <summary>
/// Scan Equal Hit (equal)
/// </summary>
private bool ST2SH
{
get { return GetBit(3, _Reg2); }
set
{
if (value) { SetBit(3, ref _Reg2); }
else { UnSetBit(3, ref _Reg2); }
}
}
/// <summary>
/// Wrong Cylinder (read/programmed track-ID different) (see b1)
/// </summary>
private bool ST2WC
{
get { return GetBit(4, _Reg2); }
set
{
if (value) { SetBit(4, ref _Reg2); }
else { UnSetBit(4, ref _Reg2); }
}
}
/// <summary>
/// Data Error in Data Field (CRC-fail in data-field)
/// </summary>
private bool ST2DD
{
get { return GetBit(5, _Reg2); }
set
{
if (value) { SetBit(5, ref _Reg2); }
else { UnSetBit(5, ref _Reg2); }
}
}
/// <summary>
/// Control Mark (read/scan command found sector with deleted DAM)
/// </summary>
private bool ST2CM
{
get { return GetBit(6, _Reg2); }
set
{
if (value) { SetBit(6, ref _Reg2); }
else { UnSetBit(6, ref _Reg2); }
}
}
#endregion
#region Status Register 3
/// <summary>
/// Status Register 3
/// </summary>
private byte _Reg3;
/// <summary>
/// Unit Select (pin 28,29 of FDC)
/// </summary>
private bool ST3US0
{
get { return GetBit(0, _Reg3); }
set
{
if (value) { SetBit(0, ref _Reg3); }
else { UnSetBit(0, ref _Reg3); }
}
}
/// <summary>
/// Unit Select (pin 28,29 of FDC)
/// </summary>
private bool ST3US1
{
get { return GetBit(1, _Reg3); }
set
{
if (value) { SetBit(1, ref _Reg3); }
else { UnSetBit(1, ref _Reg3); }
}
}
/// <summary>
/// Head Address (pin 27 of FDC)
/// </summary>
private bool ST3HD
{
get { return GetBit(2, _Reg3); }
set
{
if (value) { SetBit(2, ref _Reg3); }
else { UnSetBit(2, ref _Reg3); }
}
}
/// <summary>
/// Two Side (0=yes, 1=no (!))
/// </summary>
private bool ST3TS
{
get { return GetBit(3, _Reg3); }
set
{
if (value) { SetBit(3, ref _Reg3); }
else { UnSetBit(3, ref _Reg3); }
}
}
/// <summary>
/// Track 0 (on track 0 we are)
/// </summary>
private bool ST3T0
{
get { return GetBit(4, _Reg3); }
set
{
if (value) { SetBit(4, ref _Reg3); }
else { UnSetBit(4, ref _Reg3); }
}
}
/// <summary>
/// Ready (drive ready signal)
/// </summary>
private bool ST3RY
{
get { return GetBit(5, _Reg3); }
set
{
if (value) { SetBit(5, ref _Reg3); }
else { UnSetBit(5, ref _Reg3); }
}
}
/// <summary>
/// Write Protected (write protected)
/// </summary>
private bool ST3WP
{
get { return GetBit(6, _Reg3); }
set
{
if (value) { SetBit(6, ref _Reg3); }
else { UnSetBit(6, ref _Reg3); }
}
}
/// <summary>
/// Fault (if supported: 1=Drive failure)
/// </summary>
private bool ST3FT
{
get { return GetBit(7, _Reg3); }
set
{
if (value) { SetBit(7, ref _Reg3); }
else { UnSetBit(7, ref _Reg3); }
}
}
#endregion
*/
}
}

View File

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 every cycle by the emulated machine
/// Simulates the floppy drive and updates execution phase bits
/// </summary>
public void RunCPUCycle()
{
// decrement tick counter
TickCounter--;
if (TickCounter > 0)
{
// not ready to emulate a floppy drive cycle yet
return;
}
// time to emulate a floppy drive cycle
// reset the tick counter
TickCounter = StatesPerDriveTick;
//RunDriveCycle();
}
/// <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>
/// <returns></returns>
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;
}
}
}
}

View File

@ -0,0 +1,251 @@
using BizHawk.Common;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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>
/// <param name="machine"></param>
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)
{
ser.BeginSection("NEC-UPD765");
#region FDD
ser.Sync("FDD_FLAG_MOTOR", ref FDD_FLAG_MOTOR);
for (int i = 0; i < 4; i++)
{
ser.BeginSection("HITDrive_" + i);
DriveStates[i].SyncState(ser);
ser.EndSection();
}
ser.Sync("DiskDriveIndex", ref _diskDriveIndex);
// set active drive
DiskDriveIndex = _diskDriveIndex;
#endregion
#region Registers
ser.Sync("_RegMain", ref StatusMain);
ser.Sync("_Reg0", ref Status0);
ser.Sync("_Reg1", ref Status1);
ser.Sync("_Reg2", ref Status2);
ser.Sync("_Reg3", ref Status3);
#endregion
#region Controller state
ser.Sync("DriveLight", ref DriveLight);
ser.SyncEnum("ActivePhase", ref ActivePhase);
//ser.SyncEnum("ActiveDirection", ref ActiveDirection);
ser.SyncEnum("ActiveInterrupt", ref ActiveInterrupt);
ser.Sync("CommBuffer", ref CommBuffer, false);
ser.Sync("CommCounter", ref CommCounter);
ser.Sync("ResBuffer", ref ResBuffer, false);
ser.Sync("ExecBuffer", ref ExecBuffer, false);
ser.Sync("ExecCounter", ref ExecCounter);
ser.Sync("ExecLength", ref ExecLength);
ser.Sync("InterruptResultBuffer", ref InterruptResultBuffer, false);
ser.Sync("ResCounter", ref ResCounter);
ser.Sync("ResLength", ref ResLength);
ser.Sync("LastSectorDataWriteByte", ref LastSectorDataWriteByte);
ser.Sync("LastSectorDataReadByte", ref LastSectorDataReadByte);
ser.Sync("LastByteReceived", ref LastByteReceived);
ser.Sync("_cmdIndex", ref _cmdIndex);
// resync the ActiveCommand
CMDIndex = _cmdIndex;
ActiveCommandParams.SyncState(ser);
ser.Sync("IndexPulseCounter", ref IndexPulseCounter);
//ser.SyncEnum("_activeStatus", ref _activeStatus);
//ser.SyncEnum("_statusRaised", ref _statusRaised);
ser.Sync("CMD_FLAG_MT", ref CMD_FLAG_MT);
ser.Sync("CMD_FLAG_MF", ref CMD_FLAG_MF);
ser.Sync("CMD_FLAG_SK", ref CMD_FLAG_SK);
ser.Sync("SRT", ref SRT);
ser.Sync("HUT", ref HUT);
ser.Sync("HLT", ref HLT);
ser.Sync("ND", ref ND);
ser.Sync("SRT_Counter", ref SRT_Counter);
ser.Sync("HUT_Counter", ref HUT_Counter);
ser.Sync("HLT_Counter", ref HLT_Counter);
ser.Sync("SectorDelayCounter", ref SectorDelayCounter);
ser.Sync("SectorID", ref SectorID);
#endregion
#region Timing
ser.Sync("LastCPUCycle", ref LastCPUCycle);
ser.Sync("StatusDelay", ref StatusDelay);
ser.Sync("TickCounter", ref TickCounter);
ser.Sync("DriveCycleCounter", ref DriveCycleCounter);
#endregion
ser.EndSection();
}
#endregion
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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>
/// <param name="bitNumber"></param>
/// <param name="dataByte"></param>
/// <returns></returns>
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>
/// <param name="bitNumber"></param>
/// <param name=""></param>
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>
/// <param name="bitNumber"></param>
/// <param name=""></param>
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>
/// <param name="dataByte"></param>
/// <returns></returns>
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>
/// <param name="driveNumber"></param>
/// <param name="dataByte"></param>
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;
}
}
}
}

View File

@ -8,8 +8,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public abstract partial class SpectrumBase
{
// until +3 disk drive is emulated, we assume that incoming files are tape images
/// <summary>
/// The tape or disk image(s) that are passed in from the main ZXSpectrum class
/// </summary>
@ -18,12 +16,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Tape images
/// </summary>
protected List<byte[]> tapeImages { get; set; }
public List<byte[]> tapeImages { get; set; }
/// <summary>
/// Disk images
/// </summary>
protected List<byte[]> diskImages { get; set; }
public List<byte[]> diskImages { get; set; }
/// <summary>
/// The index of the currently 'loaded' tape image
@ -93,6 +91,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// load the media into the disk device
diskMediaIndex = result;
// fire osd message
Spectrum.OSD_DiskInserted();
LoadDiskMedia();
}
}
@ -105,7 +107,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
mediaImages = files;
LoadAllMedia();
Spectrum.OSD_TapeInit();
}
/// <summary>
@ -116,17 +117,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
tapeImages = new List<byte[]>();
diskImages = new List<byte[]>();
foreach (var m in mediaImages)
int cnt = 0;
foreach (var m in mediaImages)
{
switch (IdentifyMedia(m))
switch (IdentifyMedia(m))
{
case SpectrumMediaType.Tape:
tapeImages.Add(m);
Spectrum._tapeInfo.Add(Spectrum._gameInfo[cnt]);
break;
case SpectrumMediaType.Disk:
diskImages.Add(m);
Spectrum._diskInfo.Add(Spectrum._gameInfo[cnt]);
break;
}
cnt++;
}
if (tapeImages.Count > 0)
@ -149,7 +155,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
protected void LoadDiskMedia()
{
throw new NotImplementedException("+3 disk drive device not yet implemented");
if (this.GetType() != typeof(ZX128Plus3))
{
Spectrum.CoreComm.ShowMessage("You are trying to load one of more disk images.\n\n Please select ZX Spectrum +3 emulation immediately and reboot the core");
return;
}
else
{
//Spectrum.CoreComm.ShowMessage("You are attempting to load a disk into the +3 disk drive.\n\nThis DOES NOT currently work properly but IS under active development.");
}
UPDDiskDevice.FDD_LoadDisk(diskImages[diskMediaIndex]);
}
/// <summary>

View File

@ -59,6 +59,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public virtual DatacorderDevice TapeDevice { get; set; }
/// <summary>
/// The +3 built-in disk drive
/// </summary>
public virtual NECUPD765 UPDDiskDevice { get; set; }
/// <summary>
/// Holds the currently selected joysticks
/// </summary>
@ -67,7 +72,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Signs whether the disk motor is on or off
/// </summary>
protected bool DiskMotorState;
//protected bool DiskMotorState;
/// <summary>
/// +3/2a printer port strobe
@ -349,6 +354,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
((AYChip)AYDevice as AYChip).PanningConfiguration = Spectrum.Settings.AYPanConfig;
}
if (UPDDiskDevice != null)
{
UPDDiskDevice.SyncState(ser);
}
ser.Sync("tapeMediaIndex", ref tapeMediaIndex);
TapeMediaIndex = tapeMediaIndex;

View File

@ -223,9 +223,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
}
// bit 3 controls the disk motor (1=on, 0=off)
DiskMotorState = bits[3];
// bit 4 is the printer port strobe
PrinterPortStrobe = bits[4];
}

View File

@ -36,6 +36,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
InputRead = true;
}
else if (UPDDiskDevice.ReadPort(port, ref result))
{
return (byte)result;
}
else
{
if (KeyboardDevice.ReadPort(port, ref result))
@ -97,6 +101,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
AYDevice.WritePort(port, value);
UPDDiskDevice.WritePort(port, value);
// port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set
if (port == 0x7ffd)
{
@ -150,9 +156,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
}
// bit 3 controls the disk motor (1=on, 0=off)
DiskMotorState = bits[3];
// bit 4 is the printer port strobe
PrinterPortStrobe = bits[4];
}

View File

@ -44,6 +44,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
TapeDevice = new DatacorderDevice(spectrum.SyncSettings.AutoLoadTape);
TapeDevice.Init(this);
UPDDiskDevice = new NECUPD765();
UPDDiskDevice.Init(this);
InitializeMedia(files);
}

View File

@ -0,0 +1,167 @@
using BizHawk.Common.NumberExtensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Logical object representing a standard +3 disk image
/// </summary>
public class CPCExtendedFloppyDisk : FloppyDisk
{
/// <summary>
/// The format type
/// </summary>
public override DiskType DiskFormatType => DiskType.CPCExtended;
/// <summary>
/// Attempts to parse incoming disk data
/// </summary>
/// <param name="diskData"></param>
/// <returns>
/// TRUE: disk parsed
/// FALSE: unable to parse disk
/// </returns>
public override bool ParseDisk(byte[] data)
{
// look for standard magic string
string ident = Encoding.ASCII.GetString(data, 0, 16);
if (!ident.ToUpper().Contains("EXTENDED CPC DSK"))
{
// incorrect format
return false;
}
// read the disk information block
DiskHeader.DiskIdent = ident;
DiskHeader.DiskCreatorString = Encoding.ASCII.GetString(data, 0x22, 14);
DiskHeader.NumberOfTracks = data[0x30];
DiskHeader.NumberOfSides = data[0x31];
DiskHeader.TrackSizes = new int[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
DiskTracks = new Track[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
DiskData = data;
int pos = 0x34;
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
{
DiskHeader.TrackSizes[i] = data[pos++] * 256;
}
// move to first track information block
pos = 0x100;
// parse each track
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
{
// check for unformatted track
if (DiskHeader.TrackSizes[i] == 0)
{
DiskTracks[i] = new Track();
DiskTracks[i].Sectors = new Sector[0];
continue;
}
int p = pos;
DiskTracks[i] = new Track();
// track info block
DiskTracks[i].TrackIdent = Encoding.ASCII.GetString(data, p, 12);
p += 16;
DiskTracks[i].TrackNumber = data[p++];
DiskTracks[i].SideNumber = data[p++];
DiskTracks[i].DataRate = data[p++];
DiskTracks[i].RecordingMode = data[p++];
DiskTracks[i].SectorSize = data[p++];
DiskTracks[i].NumberOfSectors = data[p++];
DiskTracks[i].GAP3Length = data[p++];
DiskTracks[i].FillerByte = data[p++];
int dpos = pos + 0x100;
// sector info list
DiskTracks[i].Sectors = new Sector[DiskTracks[i].NumberOfSectors];
for (int s = 0; s < DiskTracks[i].NumberOfSectors; s++)
{
DiskTracks[i].Sectors[s] = new Sector();
DiskTracks[i].Sectors[s].TrackNumber = data[p++];
DiskTracks[i].Sectors[s].SideNumber = data[p++];
DiskTracks[i].Sectors[s].SectorID = data[p++];
DiskTracks[i].Sectors[s].SectorSize = data[p++];
DiskTracks[i].Sectors[s].Status1 = data[p++];
DiskTracks[i].Sectors[s].Status2 = data[p++];
DiskTracks[i].Sectors[s].ActualDataByteLength = MediaConverter.GetWordValue(data, p);
p += 2;
// sector data - begins at 0x100 offset from the start of the track info block (in this case dpos)
DiskTracks[i].Sectors[s].SectorData = new byte[DiskTracks[i].Sectors[s].ActualDataByteLength];
// copy the data
for (int b = 0; b < DiskTracks[i].Sectors[s].ActualDataByteLength; b++)
{
DiskTracks[i].Sectors[s].SectorData[b] = data[dpos + b];
}
// check for multiple weak/random sectors stored
if (DiskTracks[i].Sectors[s].SectorSize <= 7)
{
// sectorsize n=8 is equivilent to n=0 - FDC will use DTL for length
int specifiedSize = 0x80 << DiskTracks[i].Sectors[s].SectorSize;
if (specifiedSize < DiskTracks[i].Sectors[s].ActualDataByteLength)
{
// more data stored than sectorsize defines
// check for multiple weak/random copies
if (DiskTracks[i].Sectors[s].ActualDataByteLength % specifiedSize != 0)
{
DiskTracks[i].Sectors[s].ContainsMultipleWeakSectors = true;
}
}
}
// move dpos to the next sector data postion
dpos += DiskTracks[i].Sectors[s].ActualDataByteLength;
}
// move to the next track info block
pos += DiskHeader.TrackSizes[i];
}
// run speedlock detector
SpeedlockDetection();
return true;
}
/// <summary>
/// State serlialization
/// </summary>
/// <param name="ser"></param>
public override void SyncState(Serializer ser)
{
ser.BeginSection("Plus3FloppyDisk");
ser.Sync("CylinderCount", ref CylinderCount);
ser.Sync("SideCount", ref SideCount);
ser.Sync("BytesPerTrack", ref BytesPerTrack);
ser.Sync("WriteProtected", ref WriteProtected);
ser.SyncEnum("Protection", ref Protection);
ser.Sync("DirtyData", ref DirtyData);
if (DirtyData)
{
}
// sync deterministic track and sector counters
ser.Sync(" _randomCounter", ref _randomCounter);
RandomCounter = _randomCounter;
ser.EndSection();
}
}
}

View File

@ -0,0 +1,169 @@
using BizHawk.Common.NumberExtensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Logical object representing a standard +3 disk image
/// </summary>
public class CPCFloppyDisk : FloppyDisk
{
/// <summary>
/// The format type
/// </summary>
public override DiskType DiskFormatType => DiskType.CPC;
/// <summary>
/// Attempts to parse incoming disk data
/// </summary>
/// <param name="diskData"></param>
/// <returns>
/// TRUE: disk parsed
/// FALSE: unable to parse disk
/// </returns>
public override bool ParseDisk(byte[] data)
{
// look for standard magic string
string ident = Encoding.ASCII.GetString(data, 0, 16);
if (!ident.ToUpper().Contains("MV - CPCEMU"))
{
// incorrect format
return false;
}
// read the disk information block
DiskHeader.DiskIdent = ident;
DiskHeader.DiskCreatorString = Encoding.ASCII.GetString(data, 0x22, 14);
DiskHeader.NumberOfTracks = data[0x30];
DiskHeader.NumberOfSides = data[0x31];
DiskHeader.TrackSizes = new int[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
DiskTracks = new Track[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides];
DiskData = data;
int pos = 0x32;
// standard CPC format all track sizes are the same in the image
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
{
DiskHeader.TrackSizes[i] = MediaConverter.GetWordValue(data, pos);
}
// move to first track information block
pos = 0x100;
// parse each track
for (int i = 0; i < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; i++)
{
// check for unformatted track
if (DiskHeader.TrackSizes[i] == 0)
{
DiskTracks[i] = new Track();
DiskTracks[i].Sectors = new Sector[0];
continue;
}
int p = pos;
DiskTracks[i] = new Track();
// track info block
DiskTracks[i].TrackIdent = Encoding.ASCII.GetString(data, p, 12);
p += 16;
DiskTracks[i].TrackNumber = data[p++];
DiskTracks[i].SideNumber = data[p++];
p += 2;
DiskTracks[i].SectorSize = data[p++];
DiskTracks[i].NumberOfSectors = data[p++];
DiskTracks[i].GAP3Length = data[p++];
DiskTracks[i].FillerByte = data[p++];
int dpos = pos + 0x100;
// sector info list
DiskTracks[i].Sectors = new Sector[DiskTracks[i].NumberOfSectors];
for (int s = 0; s < DiskTracks[i].NumberOfSectors; s++)
{
DiskTracks[i].Sectors[s] = new Sector();
DiskTracks[i].Sectors[s].TrackNumber = data[p++];
DiskTracks[i].Sectors[s].SideNumber = data[p++];
DiskTracks[i].Sectors[s].SectorID = data[p++];
DiskTracks[i].Sectors[s].SectorSize = data[p++];
DiskTracks[i].Sectors[s].Status1 = data[p++];
DiskTracks[i].Sectors[s].Status2 = data[p++];
DiskTracks[i].Sectors[s].ActualDataByteLength = MediaConverter.GetWordValue(data, p);
p += 2;
// actualdatabytelength value is calculated now
if (DiskTracks[i].Sectors[s].SectorSize == 0)
{
// no sectorsize specified - DTL will be used at runtime
DiskTracks[i].Sectors[s].ActualDataByteLength = DiskHeader.TrackSizes[i];
}
else if (DiskTracks[i].Sectors[s].SectorSize > 6)
{
// invalid - wrap around to 0
DiskTracks[i].Sectors[s].ActualDataByteLength = DiskHeader.TrackSizes[i];
}
else if (DiskTracks[i].Sectors[s].SectorSize == 6)
{
// only 0x1800 bytes are stored
DiskTracks[i].Sectors[s].ActualDataByteLength = 0x1800;
}
else
{
// valid sector size for this format
DiskTracks[i].Sectors[s].ActualDataByteLength = 0x80 << DiskTracks[i].Sectors[s].SectorSize;
}
// sector data - begins at 0x100 offset from the start of the track info block (in this case dpos)
DiskTracks[i].Sectors[s].SectorData = new byte[DiskTracks[i].Sectors[s].ActualDataByteLength];
// copy the data
for (int b = 0; b < DiskTracks[i].Sectors[s].ActualDataByteLength; b++)
{
DiskTracks[i].Sectors[s].SectorData[b] = data[dpos + b];
}
// move dpos to the next sector data postion
dpos += DiskTracks[i].Sectors[s].ActualDataByteLength;
}
// move to the next track info block
pos += DiskHeader.TrackSizes[i];
}
// run speedlock detector
SpeedlockDetection();
return true;
}
/// <summary>
/// State serlialization
/// </summary>
/// <param name="ser"></param>
public override void SyncState(Serializer ser)
{
ser.BeginSection("Plus3FloppyDisk");
ser.Sync("CylinderCount", ref CylinderCount);
ser.Sync("SideCount", ref SideCount);
ser.Sync("BytesPerTrack", ref BytesPerTrack);
ser.Sync("WriteProtected", ref WriteProtected);
ser.SyncEnum("Protection", ref Protection);
ser.Sync("DirtyData", ref DirtyData);
if (DirtyData)
{
}
ser.EndSection();
}
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Object that represents logical disk media once imported
/// </summary>
public class DiskImage
{
#region File Header Info
/// <summary>
/// The identified format
/// (in the case of .dsk files, this is probably "EXTENDED CPC DSK")
/// </summary>
public string Header_SystemIdent { get; set; }
/// <summary>
/// The software originally used to create thsi image
/// </summary>
public string Header_CreatorSoftware { get; set; }
/// <summary>
/// The number of tracks in the disk image (header specified)
/// </summary>
public int Header_TrackCount { get; set; }
/// <summary>
/// The number of sides on the disk (header specified)
/// </summary>
public int Header_SideCount { get; set; }
/// <summary>
/// Size of a track including 0x100 byte track information block
/// (in a .dsk all tracks will be the same size)
/// </summary>
public int Header_TrackSize { get; set; }
#endregion
public List<Track> Tracks = new List<Track>();
/// <summary>
/// Reads an entire sector
/// </summary>
/// <param name="side"></param>
/// <param name="track"></param>
/// <param name="sector"></param>
/// <returns></returns>
public CHRN ReadSector(int side, int track, int sector)
{
var t = Tracks.Where(a => a.TrackNumber == track).FirstOrDefault();
if (t == null)
return null;
var s = t.Sectors.Where(a => a.SectorID == sector).FirstOrDefault();
if (s == null)
return null;
var chrn = s.GetCHRN();
chrn.DataBytes = s.SectorData;
return chrn;
}
/// <summary>
/// Reads data from a sector based on offset and length
/// </summary>
/// <param name="side"></param>
/// <param name="track"></param>
/// <param name="sector"></param>
/// <returns></returns>
public CHRN ReadSector(int side, int track, int sector, int offset, int length)
{
var result = ReadSector(side, track, sector);
if (result == null)
return null;
if (offset > result.DataBytes.Length)
return null;
if (length + offset > result.DataBytes.Length)
return null;
// data is safe to read
var data = result.DataBytes.Skip(offset).Take(length).ToArray();
result.DataBytes = data;
return result;
}
/// <summary>
/// Reads an entire track
/// </summary>
/// <param name="side"></param>
/// <param name="track"></param>
/// <returns></returns>
public Track ReadTrack(int side, int track)
{
return Tracks.Where(a => a.TrackNumber == track).FirstOrDefault();
}
/// <summary>
/// Reads a track based on offset and length
/// </summary>
/// <param name="side"></param>
/// <param name="track"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
/// <returns></returns>
public Track ReadTrack(int side, int track, int offset, int length)
{
var t = ReadTrack(side, track);
if (t == null)
return null;
if (offset >= t.AllSectorBytes.Count() || offset + length >= t.AllSectorBytes.Count())
return null;
var data = t.AllSectorBytes.Skip(offset).Take(length).ToArray();
t.ResultBytes = data;
return t;
}
/// <summary>
/// Returns the number of tracks preset on the disk
/// </summary>
/// <returns></returns>
public int GetTrackCount()
{
return Tracks.Count();
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <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
}
}

View File

@ -0,0 +1,511 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Reponsible for DSK format serializaton
/// File format info taken from: http://www.cpcwiki.eu/index.php/Format:DSK_disk_image_file_format
/// </summary>
public class DskConverter : MediaConverter
{
/// <summary>
/// The type of serializer
/// </summary>
private MediaConverterType _formatType = MediaConverterType.DSK;
public override MediaConverterType FormatType
{
get
{
return _formatType;
}
}
/// <summary>
/// Signs whether this class can be used to read data
/// </summary>
public override bool IsReader { get { return true; } }
/// <summary>
/// Signs whether this class can be used to write data
/// </summary>
public override bool IsWriter { get { return false; } }
/// <summary>
/// The disk image that we will be populating
/// </summary>
private DiskImage _diskImage = new DiskImage();
/// <summary>
/// The current position whilst parsing the incoming data
/// </summary>
private int _position = 0;
#region Construction
private IFDDHost _diskDrive;
public DskConverter(IFDDHost diskDrive)
{
_diskDrive = diskDrive;
}
#endregion
/// <summary>
/// Returns TRUE if dsk header is detected
/// </summary>
/// <param name="data"></param>
public override bool CheckType(byte[] data)
{
// check whether this is a valid dsk format file by looking at the identifier in the header
// (first 16 bytes of the file)
string ident = Encoding.ASCII.GetString(data, 0, 16);
if (!ident.ToUpper().Contains("EXTENDED CPC DSK") && !ident.ToUpper().Contains("MV - CPCEMU"))
{
// this is not a valid DSK format file
return false;
}
else
{
return true;
}
}
/// <summary>
/// Read method
/// </summary>
/// <param name="data"></param>
public override void Read(byte[] data)
{
// populate dsk header info
string ident = Encoding.ASCII.GetString(data, 0, 34);
_diskImage.Header_SystemIdent = ident;
if (ident.ToUpper().Contains("EXTENDED CPC DSK"))
ReadExtendedDsk(data);
else if (ident.ToUpper().Contains("MV - CPCEMU"))
ReadDsk(data);
// load the disk 'into' the controller
//_diskDrive.Disk = _diskImage;
}
/// <summary>
/// Parses the extended disk format
/// The extended DSK image is a file designed to describe copy-protected floppy disk software
/// </summary>
/// <param name="data"></param>
private void ReadExtendedDsk(byte[] data)
{
/* DISK INFORMATION BLOCK
offset description bytes
00 - 21 "EXTENDED CPC DSK File\r\nDisk-Info\r\n" 34
22 - 2f name of creator (utility/emulator) 14
30 number of tracks 1
31 number of sides 1
32 - 33 unused 2
34 - xx track size table number of tracks*number of sides
*/
// name of creator
_position = 0x22;
_diskImage.Header_CreatorSoftware = Encoding.ASCII.GetString(data, _position, 14);
// number of tracks
_position = 0x30;
_diskImage.Header_TrackCount = data[_position++];
// number of sides
_diskImage.Header_SideCount = data[_position++];
_position += 2;
/* TRACK OFFSET TABLE
offset description bytes
01 high byte of track 0 length (equivalent to track length/256) 1
... ... ...
track lengths are stored in the same order as the tracks in the image e.g. In the case of a double sided disk: Track 0 side 0, Track 0 side 1, Track 1 side 0 etc...
A size of "0" indicates an unformatted track. In this case there is no data, and no track information block for this track in the image file!
Actual length of track data = (high byte of track length) * 256
Track length includes the size of the TRACK INFORMATION BLOCK (256 bytes)
The location of a Track Information Block for a chosen track is found by summing the sizes of all tracks up to the chosen track plus the size of the Disc Information Block (&100 bytes). The first track is at offset &100 in the disc image.
*/
// iterate through the track size table and do an initial setup of all the tracks
// (this is just new tracks with the offset value saved)
int trkPos = 0x100;
for (int i = 0; i < _diskImage.Header_TrackCount * _diskImage.Header_SideCount; i++)
{
Track track = new Track();
// calc actual track length (including track info block)
int ts = data[_position++];
int tLen = ts * 256;
track.TrackStartOffset = trkPos;
track.TrackByteLength = tLen;
_diskImage.Tracks.Add(track);
trkPos += tLen;
}
// iterate through each newly created track and parse it based on the offset
List<Track> tracks = new List<Track>();
for (int tr = 0; tr < _diskImage.Tracks.Count(); tr++)
{
// get the track we are interested in
var t = _diskImage.Tracks[tr];
// validity check
if (t.TrackByteLength == 0)
{
// unformatted track that is not present in the disk image
Track trU = new Track();
trU.TrackNumber = (byte)tr;
trU.TrackByteLength = 0;
tracks.Add(trU);
continue;
}
Track track = new Track();
track.TrackStartOffset = t.TrackStartOffset;
track.TrackByteLength = t.TrackByteLength;
// get data for this track block
byte[] tData = data.Skip(t.TrackStartOffset).Take(t.TrackByteLength).ToArray();
// start of data block
int pos = 0;
// seek past track info ident
pos += 0x10;
/* TRACK INFORMATION BLOCK
offset description bytes
00 - 0b "Track-Info\r\n" 12
0c - 0f unused 4
10 track number 1
11 side number 1
12 data rate 1
13 recording mode 1
14 sector size 1
15 number of sectors 1
16 GAP#3 length 1
17 filler byte 1
18 - xx Sector Information List xx
*/
/* Format extensions
-----------------
Date rate description
0 Unknown.
1 Single or double density
2 High Density
3 Extended density
Data rate defines the rate at which data was written to the track. This value applies to the entire track.
Recording mode description
0 Unknown.
1 FM
2 MFM
Recording mode is used to define how the data was written. It defines the encoding used to write the data to the disc and the structure of the data on the disc including the layout of the sectors.
This value applies to the entire track
The NEC765 floppy disc controller is supplied with a single clock. When reading from and writing to a disc using the NEC765 you can choose
FM or MFM recording modes. Use of these modes and the clock into the NEC765 define the final rate at which the data is written to the disc.
When FM recording mode is used, data is read from or written to at a rate which is double that of when MFM is used.
The time for each bit will be twice the time for MFM
NEC765 Clock FM/MFM Actual rate
4Mhz FM 4us per bit
4Mhz MFM 2us per bit
*/
// track number
track.TrackNumber = tData[pos++];
// side number
track.SideNumber = tData[pos++];
// data rate
track.DataRate = tData[pos++];
// recording mode
track.RecordingMode = tData[pos++];
// sector size
track.SectorSize = tData[pos++];
// number of sectors
track.SectorCount = tData[pos++];
// GAP#3 Length
track.GAP3Length = tData[pos++];
// filler byte
track.FillerByte = tData[pos++];
/* SECTOR INFORMATION LIST
offset description bytes
00 track (equivalent to C parameter in NEC765 commands) 1
01 side (equivalent to H parameter in NEC765 commands) 1
02 sector ID (equivalent to R parameter in NEC765 commands) 1
03 sector size (equivalent to N parameter in NEC765 commands) 1
04 FDC status register 1 (equivalent to NEC765 ST1 status register) 1
05 FDC status register 2 (equivalent to NEC765 ST2 status register) 1
06 - 07 actual data length in bytes 2
*/
// parse sector information list
for (int s = 0; s < track.SectorCount; s++)
{
Sector sector = new Sector();
sector.Track = tData[pos++];
sector.Side = tData[pos++];
sector.SectorID = tData[pos++];
sector.SectorSize = tData[pos++];
sector.ST1 = tData[pos++];
sector.ST2 = tData[pos++];
sector.TotalDataLength = GetWordValue(tData, pos);
pos += 2;
// get the sector data - lives directly after the 256byte track info block
int secDataPos = 0x100;
/* Format extension
---------------_
It has been found that many protections using 8K Sectors (N="6") do store more than &1800 bytes of useable data.
It was thought that &1800 was the maximum useable limit, but this has proved wrong. So you should support 8K of data to ensure
this data is read correctly. The size of the sector will be reported in the SECTOR INFORMATION LIST as described above.
For sector size N="7" the full 16K will be stored. It is assumed that sector sizes are defined as 3 bits only,
so that a sector size of N="8" is equivalent to N="0".
*/
int bps = 0x80 << sector.SectorSize;
if (sector.SectorSize == 8 || sector.SectorSize == 0)
{
// no sector data
bps = 0x80 << 0;
//continue;
}
/* Format extension
----------------
Storing Multiple Versions of Weak/Random Sectors.
Some copy protections have what is described as 'weak/random' data. Each time the sector is read one or more bytes will change,
the value may be random between consecutive reads of the same sector.
To support these formats the following extension has been proposed.
Where a sector has weak/random data, there are multiple copies stored. The actual sector size field in the SECTOR INFORMATION LIST
describes the size of all the copies. To determine if a sector has multiple copies then compare the actual sector size field to the
size defined by the N parameter. For multiple copies the actual sector size field will have a value which is a multiple of the size
defined by the N parameter. The emulator should then choose which copy of the sector it should return on each read.
*/
int sizeOfAllCopies = sector.TotalDataLength;
if (sizeOfAllCopies % bps == 0)
{
// sector size is the same as total data length (indicating 1 sector data)
// or a factor of the total data length (indicating multiple copies)
for (int sd = 0; sd < sizeOfAllCopies / bps; sd++)
{
byte[] sData = new byte[bps];
for (int c = 0; c < bps; c++)
{
sData[c] = data[secDataPos++];
}
sector.AddSectorData(sd, sData);
}
}
else
{
// assume that there is one sector copy, but the total data length does not match
if (sector.TotalDataLength <= sizeOfAllCopies)
{
byte[] sData = new byte[bps];
for (int c = 0; c < bps; c++)
{
sData[c] = data[secDataPos++];
}
sector.AddSectorData(0, sData);
}
else
{
byte[] sData = new byte[sizeOfAllCopies];
for (int c = 0; c < sizeOfAllCopies; c++)
{
sData[c] = data[secDataPos++];
}
sector.AddSectorData(0, sData);
}
}
track.Sectors.Add(sector);
}
// add track to working list
tracks.Add(track);
}
// replace the tracks collection
_diskImage.Tracks = tracks;
}
/// <summary>
/// Parses the standard disk image format
/// </summary>
/// <param name="data"></param>
private void ReadDsk(byte[] data)
{
/* DISK INFORMATION BLOCK
offset description bytes
00-21 "MV - CPCEMU Disk-File\r\nDisk-Info\r\n" 34
22-2f name of creator 14
30 number of tracks 1
31 number of sides 1
32-33 size of a track (little endian; low byte followed by high byte) 2
34-ff not used (0) 204
*/
// name of creator
_position = 0x22;
_diskImage.Header_CreatorSoftware = Encoding.ASCII.GetString(data, _position, 14);
// number of tracks
_position = 0x30;
_diskImage.Header_TrackCount = data[_position++];
// number of sides
_diskImage.Header_SideCount = data[_position++];
// size of a track (little endian)
_diskImage.Header_TrackSize = GetWordValue(data, _position);
// 34-ff not used
// move to start of first track info block
_position = 0x100;
int tmpPos = _position;
// iterate through each track
for (int t = 0; t < _diskImage.Header_TrackCount; t++)
{
/* TRACK INFORMATION BLOCK
offset description bytes
00 - 0b "Track-Info\r\n" 12
0c - 0f unused 4
10 track number 1
11 side number 1
12 - 13 unused 2
14 sector size 1
15 number of sectors 1
16 GAP#3 length 1
17 filler byte 1
18 - xx Sector Information List xx
*/
Track track = new Track();
_position += 0x10;
track.TrackNumber = data[_position++];
track.SideNumber = data[_position++];
_position += 2;
/*
BPS (bytes per sector)
0 = 128 0x80
1 = 256 0x100
2 = 512 0x200
3 = 1024 0x400
4 = 2048 0x800
5 = 4096 0x1000
6 = 8192 0x2000
*/
track.SectorSize = data[_position++];
track.SectorCount = data[_position++];
track.GAP3Length = data[_position++];
track.FillerByte = data[_position++];
/* SECTOR INFORMATION LIST
offset description bytes
00 track (equivalent to C parameter in NEC765 commands) 1
01 side (equivalent to H parameter in NEC765 commands) 1
02 sector ID (equivalent to R parameter in NEC765 commands) 1
03 sector size (equivalent to N parameter in NEC765 commands) 1
04 FDC status register 1 (equivalent to NEC765 ST1 status register) 1
05 FDC status register 2 (equivalent to NEC765 ST2 status register) 1
06 - 07 notused (0) 2
*/
// get the sector info first
for (int s = 0; s < track.SectorCount; s++)
{
Sector sector = new Sector();
sector.Track = data[_position++];
sector.Side = data[_position++];
sector.SectorID = data[_position++];
sector.SectorSize = data[_position++];
sector.ST1 = data[_position++];
sector.ST2 = data[_position++];
track.Sectors.Add(sector);
_position += 2;
}
// now process the sector data - always offset 0x100 from the start of the track information block
tmpPos += 0x100;
_position = tmpPos;
// sectorsize in the track information list implies the boundaries of the sector data
// sectorsize in the sector info list gives us the actual size of each sector to read
for (int s = 0; s < track.SectorCount; s++)
{
var sec = track.Sectors[s];
List<byte> tmpData = new List<byte>();
// read the sector data bytes
int bps = 0x80 << sec.SectorSize;
if (bps > 0x1800)
bps = 0x1800;
for (int d = 0; d < bps; d++)
{
tmpData.Add(data[_position + d]);
}
sec.AddSectorData(0, tmpData.ToArray());
track.Sectors[s] = sec;
// sector data parsed - move to next sector data block
int trackBps = 0x80 << track.SectorSize;
if (trackBps > 0x1800)
trackBps = 0x1800;
_position += track.SectorSize;
}
// add the track to the disk image
_diskImage.Tracks.Add(track);
}
}
}
}

View File

@ -0,0 +1,396 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// This interface 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 { return _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>
/// <param name="diskData"></param>
/// <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>
/// Should be run at the end of the ParseDisk process
/// If speedlock is detected the flag is set in the disk image
/// </summary>
/// <returns></returns>
protected virtual void SpeedlockDetection()
{
/*
Based on the information here: http://simonowen.com/samdisk/plus3/
*/
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>
/// <returns></returns>
public virtual int GetTrackCount()
{
return DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides;
}
/// <summary>
/// Reads the current sector ID info
/// </summary>
/// <param name="track"></param>
/// <returns></returns>
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>
/// <param name="ser"></param>
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 Sector[] Sectors { get; set; }
/// <summary>
/// Presents a contiguous byte array of all sector data for this track
/// (including any multiple weak/random data)
/// </summary>
public 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 byte TrackNumber { get; set; }
public byte SideNumber { get; set; }
public byte SectorID { get; set; }
public byte SectorSize { get; set; }
public byte Status1 { get; set; }
public byte Status2 { get; set; }
public int ActualDataByteLength { get; set; }
public byte[] SectorData { get; set; }
public bool ContainsMultipleWeakSectors { get; set; }
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(0xe5);
}
return l.ToArray();
}
else
{
return SectorData;
}
}
else
{
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
{
get
{
return 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
}
}

View File

@ -0,0 +1,228 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents a disk sector
/// </summary>
public class Sector
{
/// <summary>
/// Used when the FDD returns the sector
/// False means the sector does not exist
/// </summary>
public bool IsValid = true;
/// <summary>
/// track (equivalent to C parameter in NEC765 commands)
/// </summary>
private byte _track;
public byte Track
{
get { return _track; }
set { _track = value; }
}
/// <summary>
/// side (equivalent to H parameter in NEC765 commands)
/// </summary>
private byte _side;
public byte Side
{
get { return _side; }
set { _side = value; }
}
/// <summary>
/// sector ID (equivalent to R parameter in NEC765 commands)
/// </summary>
private byte _sectorID;
public byte SectorID
{
get { return _sectorID; }
set { _sectorID = value; }
}
/// <summary>
/// sector size (equivalent to N parameter in NEC765 commands)
/// </summary>
private byte _sectorSize;
public byte SectorSize
{
get { return _sectorSize; }
set { _sectorSize = value; }
}
/// <summary>
/// FDC status register 1 (equivalent to NEC765 ST1 status register)
/// </summary>
private byte _sT1;
public byte ST1
{
get { return _sT1; }
set { _sT1 = value; }
}
/// <summary>
/// FDC status register 2 (equivalent to NEC765 ST2 status register)
/// </summary>
private byte _sT2;
public byte ST2
{
get { return _sT2; }
set { _sT2 = value; }
}
/// <summary>
/// Actual total data length in bytes
/// If this is greater than the sector size specified by N, then multiple copies
/// of the sector data are present (representing weak/random copy protection data)
/// </summary>
private int _totalDataLength;
public int TotalDataLength
{
get { return _totalDataLength; }
set { _totalDataLength = value; }
}
/// <summary>
/// The sector data entry point
/// If multiple sector data is found this should return a randomly chosen data for this sector
/// </summary>
public byte[] SectorData
{
get
{
// number of sector data copies found
int count = _sectorDatas.Count();
if (count <= 0)
{
// no sectors present
return new byte[0];
}
if (count == 1)
{
// only one copy of data found - return this
return _sectorDatas.First();
}
if (count > 1)
{
// there is more than one copy of sector data stored
// return at random to simulate weak/random copy protection sectors
Random rnd = new Random();
int pnt = rnd.Next(0, count - 1);
return _sectorDatas[pnt];
}
// probably shouldnt ever get this far
return null;
}
}
/// <summary>
/// Adds sector data based on the copy index
/// </summary>
/// <param name="position"></param>
public void AddSectorData(int position, byte[] data)
{
if (_sectorDatas.ElementAtOrDefault(position) == null)
{
// create byte arrays for uninstantiated indexes
for (int i = 0; i < position + 1; i++)
{
if (_sectorDatas.ElementAtOrDefault(i) == null)
{
_sectorDatas.Add(new byte[0]);
}
}
}
_sectorDatas[position] = data;
}
/// <summary>
/// Internal storage for sector data
/// </summary>
private List<byte[]> _sectorDatas = new List<byte[]>();
/// <summary>
/// Returns a CHRN object for this sector
/// </summary>
/// <returns></returns>
public CHRN GetCHRN()
{
return new CHRN
{
C = Track,
H = Side,
R = SectorID,
N = SectorSize,
Flag1 = ST1,
Flag2 = ST2,
DataBytes = SectorData
};
}
/// <summary>
/// State serialization
/// Should only be called for the ActiveSector object in the floppy drive
/// </summary>
/// <param name="ser"></param>
public void SyncState(Serializer ser)
{
ser.BeginSection("ActiveSector");
ser.Sync("IsValid", ref IsValid);
ser.Sync("_track", ref _track);
ser.Sync("_side", ref _side);
ser.Sync("_sectorID", ref _sectorID);
ser.Sync("_sectorSize", ref _sectorSize);
ser.Sync("_sT1", ref _sT1);
ser.Sync("_sT2", ref _sT2);
ser.Sync("_totalDataLength", ref _totalDataLength);
if (ser.IsReader)
{
ser.Sync("SecCopySize", ref SecCopySize);
byte[][] sds = new byte[SecCopySize][];
for (int i = 0; i < SecCopySize; i++)
{
ser.Sync("sec" + i, ref sds[i], false);
}
_sectorDatas = sds.ToList();
}
if (ser.IsWriter)
{
SecCopySize = _sectorDatas.Count();
ser.Sync("SecCopySize", ref SecCopySize);
byte[][] sds = _sectorDatas.ToArray();
for (int i = 0; i < SecCopySize; i++)
{
ser.Sync("sec" + i, ref sds[i], false);
}
}
ser.EndSection();
}
// Sector array size (for state serialization)
private int SecCopySize;
}
}

View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Represents a disk track
/// </summary>
public class Track
{
/// <summary>
/// The track number
/// </summary>
public byte TrackNumber { get; set; }
/// <summary>
/// The side number
/// </summary>
public byte SideNumber { get; set; }
/// <summary>
/// Data rate defines the rate at which data was written to the track. This value applies to the entire track
/// </summary>
public byte DataRate { get; set; }
/// <summary>
/// Recording mode is used to define how the data was written. It defines the encoding used to write the data to the disc and the structure of the data on the
/// disc including the layout of the sectors. This value applies to the entire track.
/// </summary>
public byte RecordingMode { get; set; }
/// <summary>
/// Used to calculate the location of each sector's data. Therefore, The data allocated for each sector must be the same
/// </summary>
public int SectorSize { get; set; }
/// <summary>
/// Identifies the number of valid entries in the sector information list
/// </summary>
public byte SectorCount { get; set; }
/// <summary>
/// The length of GAP3 data
/// </summary>
public byte GAP3Length { get; set; }
/// <summary>
/// Byte used as filler
/// </summary>
public byte FillerByte { get; set; }
/// <summary>
/// List containing all the sectors for this track
/// </summary>
public List<Sector> Sectors = new List<Sector>();
/// <summary>
/// Defines the offset in the disk image file at which the track information block starts
/// </summary>
public int TrackStartOffset { get; set; }
/// <summary>
/// The actual length of the track (including info block) in the disk image file
/// </summary>
public int TrackByteLength { get; set; }
/// <summary>
/// Returns all sector data for this track in one byte array
/// </summary>
public byte[] AllSectorBytes
{
get
{
int byteSize = 0;
foreach (var s in Sectors)
{
byteSize += s.SectorData.Count();
}
byte[] all = new byte[byteSize];
int _pos = 0;
foreach (var s in Sectors)
{
foreach (var d in s.SectorData)
{
all[_pos] = d;
_pos++;
}
}
return all;
}
}
public byte[] ResultBytes { get; set; }
}
}

View File

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/// <summary>
/// Abtract class that represents all Media Serializers
/// </summary>
public abstract class MediaConverter
{
/// <summary>
/// The type of serializer
/// </summary>
public abstract MediaConverterType FormatType { get; }
/// <summary>
/// Signs whether this class can be used to read the data format
/// </summary>
public virtual bool IsReader
{
get
{
return false;
}
}
/// <summary>
/// Signs whether this class can be used to write the data format
/// </summary>
public virtual bool IsWriter
{
get
{
return false;
}
}
/// <summary>
/// Serialization method
/// </summary>
/// <param name="data"></param>
public virtual void Read(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
"Read operation is not implemented for this converter");
}
/// <summary>
/// DeSerialization method
/// </summary>
/// <param name="data"></param>
public virtual void Write(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
"Write operation is not implemented for this converter");
}
/// <summary>
/// Serializer does a quick check, returns TRUE if file is detected as this type
/// </summary>
/// <param name="data"></param>
public virtual bool CheckType(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
"Check type operation is not implemented for this converter");
}
#region Static Tools
/// <summary>
/// Converts an int32 value into a byte array
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static byte[] GetBytes(int value)
{
byte[] buf = new byte[4];
buf[0] = (byte)value;
buf[1] = (byte)(value >> 8);
buf[2] = (byte)(value >> 16);
buf[3] = (byte)(value >> 24);
return buf;
}
/// <summary>
/// Returns an int32 from a byte array based on offset
/// </summary>
/// <param name="buf"></param>
/// <param name="offsetIndex"></param>
/// <returns></returns>
public static int GetInt32(byte[] buf, int offsetIndex)
{
return buf[offsetIndex] | buf[offsetIndex + 1] << 8 | buf[offsetIndex + 2] << 16 | buf[offsetIndex + 3] << 24;
}
/// <summary>
/// Returns an uint16 from a byte array based on offset
/// </summary>
/// <param name="buf"></param>
/// <param name="offsetIndex"></param>
/// <returns></returns>
public static ushort GetWordValue(byte[] buf, int offsetIndex)
{
return (ushort)(buf[offsetIndex] | buf[offsetIndex + 1] << 8);
}
/// <summary>
/// Updates a byte array with a uint16 value based on offset
/// </summary>
/// <param name="buf"></param>
/// <param name="offsetIndex"></param>
/// <param name="value"></param>
public static void SetWordValue(byte[] buf, int offsetIndex, ushort value)
{
buf[offsetIndex] = (byte)value;
buf[offsetIndex + 1] = (byte)(value >> 8);
}
#endregion
}
}

View File

@ -9,10 +9,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Represents the different types of media serializer avaiable
/// </summary>
public enum MediaSerializationType
public enum MediaConverterType
{
NONE,
TZX,
TAP
TAP,
DSK
}
}

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/*
/// <summary>
/// Abtract class that represents all Media Serializers
/// </summary>
@ -121,4 +122,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
#endregion
}
*/
}

View File

@ -10,13 +10,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Reponsible for TAP format serializaton
/// </summary>
public class TapSerializer : MediaSerializer
public class TapConverter : MediaConverter
{
/// <summary>
/// The type of serializer
/// </summary>
private MediaSerializationType _formatType = MediaSerializationType.TAP;
public override MediaSerializationType FormatType
private MediaConverterType _formatType = MediaConverterType.TAP;
public override MediaConverterType FormatType
{
get
{
@ -25,20 +25,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
/// <summary>
/// Signs whether this class can be used to serialize
/// Signs whether this class can be used to read the data format
/// </summary>
public override bool IsSerializer { get { return false; } }
public override bool IsReader { get { return true; } }
/// <summary>
/// Signs whether this class can be used to de-serialize
/// Signs whether this class can be used to write the data format
/// </summary>
public override bool IsDeSerializer { get { return true; } }
public override bool IsWriter { get { return false; } }
#region Construction
private DatacorderDevice _datacorder;
public TapSerializer(DatacorderDevice _tapeDevice)
public TapConverter(DatacorderDevice _tapeDevice)
{
_datacorder = _tapeDevice;
}
@ -104,7 +104,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// DeSerialization method
/// </summary>
/// <param name="data"></param>
public override void DeSerialize(byte[] data)
public override void Read(byte[] data)
{
/*
The .TAP files contain blocks of tape-saved data. All blocks start with two bytes specifying how many bytes will follow (not counting the two length bytes). Then raw tape data follows, including the flag and checksum bytes. The checksum is the bitwise XOR of all bytes including the flag byte. For example, when you execute the line SAVE "ROM" CODE 0,2 this will result:

View File

@ -10,13 +10,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Reponsible for TZX format serializaton
/// </summary>
public class TzxSerializer : MediaSerializer
public class TzxConverter : MediaConverter
{
/// <summary>
/// The type of serializer
/// </summary>
private MediaSerializationType _formatType = MediaSerializationType.TZX;
public override MediaSerializationType FormatType
private MediaConverterType _formatType = MediaConverterType.TZX;
public override MediaConverterType FormatType
{
get
{
@ -25,14 +25,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
/// <summary>
/// Signs whether this class can be used to serialize
/// Signs whether this class can be used to read the data format
/// </summary>
public override bool IsSerializer { get { return false; } }
public override bool IsReader { get { return true; } }
/// <summary>
/// Signs whether this class can be used to de-serialize
/// Signs whether this class can be used to write the data format
/// </summary>
public override bool IsDeSerializer { get { return true; } }
public override bool IsWriter { get { return false; } }
/// <summary>
/// Working list of generated tape data blocks
@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
private DatacorderDevice _datacorder;
public TzxSerializer(DatacorderDevice _tapeDevice)
public TzxConverter(DatacorderDevice _tapeDevice)
{
_datacorder = _tapeDevice;
}
@ -101,7 +101,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// DeSerialization method
/// </summary>
/// <param name="data"></param>
public override void DeSerialize(byte[] data)
public override void Read(byte[] data)
{
// clear existing tape blocks
_datacorder.DataBlocks.Clear();

View File

@ -62,6 +62,33 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
#endregion
#region DiskDevice Message Methods
/// <summary>
/// Disk message that is fired on core init
/// </summary>
public void OSD_DiskInit()
{
StringBuilder sb = new StringBuilder();
if (_machine.diskImages != null)
{
sb.Append("Disk Media Imported (count: " + _machine.diskImages.Count() + ")");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator);
}
}
/// <summary>
/// Disk message that is fired when a new disk is inserted into the drive
/// </summary>
public void OSD_DiskInserted()
{
StringBuilder sb = new StringBuilder();
sb.Append("DISK INSERTED (" + _machine.DiskMediaIndex + ": " + _diskInfo[_machine.DiskMediaIndex].Name + ")");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
}
#endregion
#region TapeDevice Message Methods
/// <summary>
@ -69,12 +96,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public void OSD_TapeInit()
{
StringBuilder sb = new StringBuilder();
sb.Append("Tape Media Imported (count: " + _gameInfo.Count() + ")");
sb.Append("\n");
for (int i = 0; i < _gameInfo.Count(); i++)
sb.Append(i.ToString() + ": " + _gameInfo[i].Name + "\n");
if (_tapeInfo.Count == 0)
return;
StringBuilder sb = new StringBuilder();
sb.Append("Tape Media Imported (count: " + _tapeInfo.Count() + ")");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator);
}
@ -83,8 +109,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public void OSD_TapePlaying()
{
if (_tapeInfo.Count == 0)
return;
StringBuilder sb = new StringBuilder();
sb.Append("PLAYING (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")");
sb.Append("PLAYING (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
}
@ -94,8 +123,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public void OSD_TapeStopped()
{
if (_tapeInfo.Count == 0)
return;
StringBuilder sb = new StringBuilder();
sb.Append("STOPPED (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")");
sb.Append("STOPPED (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
}
@ -105,8 +137,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public void OSD_TapeRTZ()
{
if (_tapeInfo.Count == 0)
return;
StringBuilder sb = new StringBuilder();
sb.Append("REWOUND (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")");
sb.Append("REWOUND (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
}
@ -116,8 +151,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public void OSD_TapeInserted()
{
if (_tapeInfo.Count == 0)
return;
StringBuilder sb = new StringBuilder();
sb.Append("TAPE INSERTED (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")");
sb.Append("TAPE INSERTED (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
}
@ -129,6 +167,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_TapeStoppedAuto()
{
StringBuilder sb = new StringBuilder();
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("STOPPED (Auto Tape Trap Detected)");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
@ -140,6 +187,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_TapePlayingAuto()
{
StringBuilder sb = new StringBuilder();
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("PLAYING (Auto Tape Trap Detected)");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
@ -151,7 +207,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_TapePlayingBlockInfo(string blockinfo)
{
StringBuilder sb = new StringBuilder();
sb.Append("...Starting Block "+ blockinfo);
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("...Starting Block " + blockinfo);
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
}
@ -162,6 +227,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_TapePlayingSkipBlockInfo(string blockinfo)
{
StringBuilder sb = new StringBuilder();
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("...Skipping Empty Block " + blockinfo);
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
@ -173,6 +247,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_TapeEndDetected(string blockinfo)
{
StringBuilder sb = new StringBuilder();
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("...Skipping Empty Block " + blockinfo);
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
@ -184,6 +267,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_TapeNextBlock(string blockinfo)
{
StringBuilder sb = new StringBuilder();
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("Manual Skip Next " + blockinfo);
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
@ -195,6 +287,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_TapePrevBlock(string blockinfo)
{
StringBuilder sb = new StringBuilder();
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("Manual Skip Prev " + blockinfo);
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
@ -206,6 +307,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public void OSD_ShowTapeStatus()
{
StringBuilder sb = new StringBuilder();
if (_tapeInfo.Count == 0)
{
sb.Append("No Tape Loaded");
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
return;
}
sb.Append("Status: ");
if (_machine.TapeDevice.TapeIsPlaying)
@ -216,22 +325,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
sb.Clear();
sb.Append("Tape: " + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name);
sb.Append("Tape: " + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name);
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
sb.Clear();
sb.Append("Block: ");
sb.Append("(" + (_machine.TapeDevice.CurrentDataBlockIndex + 1) +
" of " + _machine.TapeDevice.DataBlocks.Count() + ") " +
sb.Append("(" + (_machine.TapeDevice.CurrentDataBlockIndex + 1) +
" of " + _machine.TapeDevice.DataBlocks.Count() + ") " +
_machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].BlockDescription);
SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
sb.Clear();
sb.Append("Block Pos: ");
int pos = _machine.TapeDevice.Position;
int end = _machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].DataPeriods.Count;
double p = 0;
double p = 0;
if (end != 0)
p = ((double)pos / (double)end) * (double)100;

View File

@ -18,9 +18,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public partial class ZXSpectrum : IRegionable, IDriveLight
{
public ZXSpectrum(CoreComm comm, IEnumerable<byte[]> files, List<GameInfo> game, object settings, object syncSettings)
{
{
var ser = new BasicServiceProvider(this);
ServiceProvider = ser;
ServiceProvider = ser;
InputCallbacks = new InputCallbackSystem();
MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
@ -43,7 +43,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
PutSyncSettings((ZXSpectrumSyncSettings)syncSettings ?? new ZXSpectrumSyncSettings());
PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings());
List <JoystickType> joysticks = new List<JoystickType>();
List<JoystickType> joysticks = new List<JoystickType>();
joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType1);
joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType2);
joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType3);
@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
Init(MachineType.ZXSpectrum16, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks);
break;
case MachineType.ZXSpectrum48:
ControllerDefinition = ZXSpectrumControllerDefinition;
ControllerDefinition = ZXSpectrumControllerDefinition;
Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks);
break;
case MachineType.ZXSpectrum128:
@ -78,8 +78,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
break;
default:
throw new InvalidOperationException("Machine not yet emulated");
}
}
_cpu.MemoryCallbacks = MemoryCallbacks;
HardReset = _machine.HardReset;
@ -90,7 +90,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
_cpu.WriteMemory = _machine.WriteMemory;
_cpu.ReadHardware = _machine.ReadPort;
_cpu.WriteHardware = _machine.WritePort;
_cpu.FetchDB = _machine.PushBus;
_cpu.FetchDB = _machine.PushBus;
ser.Register<ITraceable>(_tracer);
ser.Register<IDisassemblable>(_cpu);
@ -101,14 +101,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
SoundMixer.AddSource((ISoundProvider)_machine.TapeBuzzer);
if (_machine.AYDevice != null)
SoundMixer.AddSource(_machine.AYDevice);
// set audio device settings
if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip))
{
((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig;
_machine.AYDevice.Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYVolume;
}
if (_machine.BuzzerDevice != null)
{
((Buzzer)_machine.BuzzerDevice as Buzzer).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).EarVolume;
@ -120,24 +120,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
ser.Register<ISoundProvider>(SoundMixer);
HardReset();
SetupMemoryDomains();
SetupMemoryDomains();
}
public Action HardReset;
public Action SoftReset;
private readonly Z80A _cpu;
private readonly TraceBuffer _tracer;
public IController _controller = NullController.Instance;
public IController _controller;
public SpectrumBase _machine;
private List<GameInfo> _gameInfo;
public List<GameInfo> _gameInfo;
public List<GameInfo> _tapeInfo = new List<GameInfo>();
public List<GameInfo> _diskInfo = new List<GameInfo>();
private SoundProviderMixer SoundMixer;
private SoundProviderMixer SoundMixer;
private readonly List<byte[]> _files;
public bool DiagRom = false;
@ -269,8 +272,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
get
{
if (_machine != null &&
_machine.TapeDevice != null &&
_machine.TapeDevice.TapeIsPlaying)
(_machine.TapeDevice != null && _machine.TapeDevice.TapeIsPlaying) ||
(_machine.UPDDiskDevice != null && _machine.UPDDiskDevice.DriveLight))
return true;
return false;