From c76e2f35a009da053972443637d49b8b2f30061f Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 13 Sep 2018 10:44:48 +0100 Subject: [PATCH] ZXHawk: Starting on UDI and IPF disk image support (although neither are fully working or hooked up yet) --- .../BizHawk.Emulation.Cores.csproj | 6 +- .../Hardware/Disk/NECUPD765.FDD.cs | 8 + .../Machine/SpectrumBase.Media.cs | 23 + .../{ => CPCFormat}/CPCExtendedFloppyDisk.cs | 0 .../Disk/{ => CPCFormat}/CPCFloppyDisk.cs | 0 .../SinclairSpectrum/Media/Disk/DiskType.cs | 17 +- .../SinclairSpectrum/Media/Disk/FloppyDisk.cs | 31 +- .../Media/Disk/IPFFormat/IPFFloppyDisk.cs | 461 ++++++++++++++++++ .../Media/Disk/UDIFormat/UDI1_0FloppyDisk.cs | 217 +++++++++ .../SinclairSpectrum/Media/MediaConverter.cs | 48 ++ 10 files changed, 797 insertions(+), 14 deletions(-) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/{ => CPCFormat}/CPCExtendedFloppyDisk.cs (100%) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/{ => CPCFormat}/CPCFloppyDisk.cs (100%) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/IPFFormat/IPFFloppyDisk.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/UDIFormat/UDI1_0FloppyDisk.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 3f9c29fb35..7c5b552b65 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -288,9 +288,11 @@ - - + + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Disk/NECUPD765.FDD.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Disk/NECUPD765.FDD.cs index 0d79ee1bed..272930e904 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Disk/NECUPD765.FDD.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Disk/NECUPD765.FDD.cs @@ -816,6 +816,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum fdd = new CPCFloppyDisk(); found = fdd.ParseDisk(diskData); break; + case DiskType.IPF: + fdd = new IPFFloppyDisk(); + found = fdd.ParseDisk(diskData); + break; + case DiskType.UDI: + fdd = new UDI1_0FloppyDisk(); + found = fdd.ParseDisk(diskData); + break; } if (found) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs index 826b2394d0..57525f6028 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs @@ -158,6 +158,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case DiskType.CPC: found = CPCFloppyDisk.SplitDoubleSided(m, working); break; + case DiskType.UDI: + found = UDI1_0FloppyDisk.SplitDoubleSided(m, working); + break; } if (found) @@ -262,6 +265,26 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // spectrum .fdi disk file return SpectrumMediaType.Disk; } + if (hdr.ToUpper().StartsWith("CAPS")) + { + // IPF format file + return SpectrumMediaType.Disk; + } + if (hdr.ToUpper().StartsWith("UDI!") && data[0x08] == 0) + { + // UDI v1.0 + if (hdr.StartsWith("udi!")) + { + throw new NotSupportedException("ZXHawk currently does not supported UDIv1.0 with compression."); + } + else + { + if (data[0x0A] == 0x01) + return SpectrumMediaType.DiskDoubleSided; + else + return SpectrumMediaType.Disk; + } + } // tape checking if (hdr.ToUpper().StartsWith("ZXTAPE!")) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCExtendedFloppyDisk.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCFormat/CPCExtendedFloppyDisk.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCExtendedFloppyDisk.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCFormat/CPCExtendedFloppyDisk.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCFloppyDisk.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCFormat/CPCFloppyDisk.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCFloppyDisk.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/CPCFormat/CPCFloppyDisk.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/DiskType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/DiskType.cs index 51ee9f75f4..0adbffd649 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/DiskType.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/DiskType.cs @@ -14,6 +14,21 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Extended CPCEMU disk format (used in the built-in +3 disk drive) /// - CPCExtended + CPCExtended, + + /// + /// Interchangeable Preservation Format + /// + IPF, + + /// + /// Ultra Disk Image Format (v1.0) + /// + UDI, + + /// + /// Ultra Disk Image Format (v1.1) + /// + UDIv1_1 } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs index 6aa6dc8d06..656609fe19 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/FloppyDisk.cs @@ -602,13 +602,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public byte NumberOfSectors { get; set; } public byte GAP3Length { get; set; } public byte FillerByte { get; set; } - public Sector[] Sectors { get; set; } + public virtual Sector[] Sectors { get; set; } + + #region UDI + + public virtual byte TrackType { get; set; } + public virtual int TLEN { get; set; } + public virtual int CLEN { get { return TLEN / 8 + (TLEN % 8 / 7) / 8; } } + public virtual byte[] TrackData { get; set; } + + #endregion /// /// Presents a contiguous byte array of all sector data for this track /// (including any multiple weak/random data) /// - public byte[] TrackSectorData + public virtual byte[] TrackSectorData { get { @@ -626,15 +635,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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 virtual byte TrackNumber { get; set; } + public virtual byte SideNumber { get; set; } + public virtual byte SectorID { get; set; } + public virtual byte SectorSize { get; set; } + public virtual byte Status1 { get; set; } + public virtual byte Status2 { get; set; } + public virtual int ActualDataByteLength { get; set; } + public virtual byte[] SectorData { get; set; } + public virtual bool ContainsMultipleWeakSectors { get; set; } public int WeakReadIndex = 0; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/IPFFormat/IPFFloppyDisk.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/IPFFormat/IPFFloppyDisk.cs new file mode 100644 index 0000000000..f99c0ae179 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/IPFFormat/IPFFloppyDisk.cs @@ -0,0 +1,461 @@ +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 +{ + public class IPFFloppyDisk : FloppyDisk + { + /// + /// The format type + /// + public override DiskType DiskFormatType => DiskType.IPF; + + /// + /// Attempts to parse incoming disk data + /// + /// + /// + /// TRUE: disk parsed + /// FALSE: unable to parse disk + /// + public override bool ParseDisk(byte[] data) + { + // look for standard magic string + string ident = Encoding.ASCII.GetString(data, 0, 16); + + if (!ident.ToUpper().Contains("CAPS")) + { + // incorrect format + return false; + } + + int pos = 0; + + List blocks = new List(); + + while (pos < data.Length) + { + try + { + var block = IPFBlock.ParseNextBlock(ref pos, this, data, blocks); + + if (block == null) + { + // EOF + break; + } + + if (block.RecordType == RecordHeaderType.None) + { + // unknown block + } + + blocks.Add(block); + } + catch (Exception ex) + { + + } + } + + // now process the blocks + var infoBlock = blocks.Where(a => a.RecordType == RecordHeaderType.INFO).FirstOrDefault(); + var IMGEblocks = blocks.Where(a => a.RecordType == RecordHeaderType.IMGE).ToList(); + var DATAblocks = blocks.Where(a => a.RecordType == RecordHeaderType.DATA).ToList(); + + DiskHeader.NumberOfTracks = (byte)(IMGEblocks.Count()); + DiskHeader.NumberOfSides = (byte)(infoBlock.INFOmaxSide + 1); + DiskTracks = new Track[DiskHeader.NumberOfTracks]; + + for (int t = 0; t < DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides; t++) + { + // each imge block represents one track + var img = IMGEblocks[t]; + DiskTracks[t] = new Track(); + var trk = DiskTracks[t]; + + var blockCount = img.IMGEblockCount; + var dataBlock = DATAblocks.Where(a => a.DATAdataKey == img.IMGEdataKey).FirstOrDefault(); + + trk.SideNumber = (byte)img.IMGEside; + trk.TrackNumber = (byte)img.IMGEtrack; + + trk.Sectors = new Sector[blockCount]; + + // process data block descriptors + int p = 0; + for (int d = 0; d < blockCount; d++) + { + var extraDataAreaStart = 32 * blockCount; + trk.Sectors[d] = new Sector(); + var sector = trk.Sectors[d]; + + int dataBits = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + int gapBits = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + int dataBytes; + int gapBytes; + int gapOffset; + int cellType; + if (infoBlock.INFOencoderType == 1) + { + dataBytes = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + gapBytes = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + } + else if (infoBlock.INFOencoderType == 2) + { + gapOffset = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + cellType = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + } + int encoderType = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + int? blockFlags = null; + if (infoBlock.INFOencoderType == 2) + { + blockFlags = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); + } + p += 4; + + int gapDefault = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + int dataOffset = MediaConverter.GetBEInt32(dataBlock.DATAextraDataRaw, p); p += 4; + + // gap stream elements + if (infoBlock.INFOencoderType == 2 && gapBits != 0 && blockFlags != null) + { + if (!blockFlags.Value.Bit(1) && !blockFlags.Value.Bit(0)) + { + // no gap stream + } + if (!blockFlags.Value.Bit(1) && blockFlags.Value.Bit(0)) + { + // Forward gap stream list only + } + if (blockFlags.Value.Bit(1) && !blockFlags.Value.Bit(0)) + { + // Backward gap stream list only + } + if (blockFlags.Value.Bit(1) && blockFlags.Value.Bit(0)) + { + // Forward and Backward stream lists + } + } + + // data stream elements + if (dataBits != 0) + { + var dsLocation = dataOffset; + + for (;;) + { + byte dataHead = dataBlock.DATAextraDataRaw[dsLocation++]; + if (dataHead == 0) + { + // end of data stream list + break; + } + + var sampleSize = ((dataHead & 0xE0) >> 5); + var dataType = dataHead & 0x1F; + byte[] dSize = new byte[sampleSize]; + Array.Copy(dataBlock.DATAextraDataRaw, dsLocation, dSize, 0, sampleSize); + var dataSize = MediaConverter.GetBEInt32FromByteArray(dSize); + dsLocation += dSize.Length; + int dataLen; + byte[] dataStream = new byte[0]; + + if (blockFlags != null && blockFlags.Value.Bit(2)) + { + // bits + if (dataType != 5) + { + dataLen = dataSize / 8; + if (dataSize % 8 != 0) + { + // bits left over + } + dataStream = new byte[dataLen]; + Array.Copy(dataBlock.DATAextraDataRaw, dsLocation, dataStream, 0, dataLen); + } + } + else + { + // bytes + if (dataType != 5) + { + dataStream = new byte[dataSize]; + Array.Copy(dataBlock.DATAextraDataRaw, dsLocation, dataStream, 0, dataSize); + } + } + + // dataStream[] now contains the data + switch (dataType) + { + // SYNC + case 1: + break; + // DATA + case 2: + if (dataStream.Length == 7) + { + // ID + // first byte IAM + sector.TrackNumber = dataStream[1]; + sector.SideNumber = dataStream[2]; + sector.SectorID = dataStream[3]; + sector.SectorSize = dataStream[4]; + } + else if (dataStream.Length > 255) + { + // DATA + // first byte DAM + if (dataStream[0] == 0xF8) + { + // deleted address mark + //sector.Status1 + } + sector.SectorData = new byte[dataStream.Length - 1 - 2]; + Array.Copy(dataStream, 1, sector.SectorData, 0, dataStream.Length - 1 - 2); + } + break; + // GAP + case 3: + break; + // RAW + case 4: + break; + // FUZZY + case 5: + break; + default: + break; + } + + + dsLocation += dataStream.Length; + } + } + } + } + + return true; + } + + public class IPFBlock + { + public RecordHeaderType RecordType; + public int BlockLength; + public int CRC; + public byte[] RawBlockData; + public int StartPos; + + #region INFO + + public int INFOmediaType; + public int INFOencoderType; + public int INFOencoderRev; + public int INFOfileKey; + public int INFOfileRev; + public int INFOorigin; + public int INFOminTrack; + public int INFOmaxTrack; + public int INFOminSide; + public int INFOmaxSide; + public int INFOcreationDate; + public int INFOcreationTime; + public int INFOplatform1; + public int INFOplatform2; + public int INFOplatform3; + public int INFOplatform4; + public int INFOdiskNumber; + public int INFOcreatorId; + + #endregion + + #region IMGE + + public int IMGEtrack; + public int IMGEside; + public int IMGEdensity; + public int IMGEsignalType; + public int IMGEtrackBytes; + public int IMGEstartBytePos; + public int IMGEstartBitPos; + public int IMGEdataBits; + public int IMGEgapBits; + public int IMGEtrackBits; + public int IMGEblockCount; + public int IMGEencoderProcess; + public int IMGEtrackFlags; + public int IMGEdataKey; + + #endregion + + #region DATA + + public int DATAlength; + public int DATAbitSize; + public int DATAcrc; + public int DATAdataKey; + public byte[] DATAextraDataRaw; + + #endregion + + public static IPFBlock ParseNextBlock(ref int startPos, FloppyDisk disk, byte[] data, List blockCollection) + { + IPFBlock ipf = new IPFBlock(); + ipf.StartPos = startPos; + + if (startPos >= data.Length) + { + // EOF + return null; + } + + // assume the startPos passed in is actually the start of a new block + // look for record header ident + string ident = Encoding.ASCII.GetString(data, startPos, 4); + startPos += 4; + try + { + ipf.RecordType = (RecordHeaderType)Enum.Parse(typeof(RecordHeaderType), ident); + } + catch + { + ipf.RecordType = RecordHeaderType.None; + } + + // setup for actual block size + ipf.BlockLength = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.CRC = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.RawBlockData = new byte[ipf.BlockLength]; + Array.Copy(data, ipf.StartPos, ipf.RawBlockData, 0, ipf.BlockLength); + + switch (ipf.RecordType) + { + // Nothing to process / unknown + // just move ahead + case RecordHeaderType.CAPS: + case RecordHeaderType.TRCK: + case RecordHeaderType.DUMP: + case RecordHeaderType.CTEI: + case RecordHeaderType.CTEX: + default: + startPos = ipf.StartPos + ipf.BlockLength; + break; + + // INFO block + case RecordHeaderType.INFO: + // INFO header is followed immediately by an INFO block + ipf.INFOmediaType = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOencoderType = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOencoderRev = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOfileKey = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOfileRev = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOorigin = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOminTrack = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOmaxTrack = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOminSide = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOmaxSide = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOcreationDate = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOcreationTime = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOplatform1 = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOplatform2 = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOplatform3 = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOplatform4 = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOdiskNumber = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.INFOcreatorId = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + startPos += 12; // reserved + break; + + case RecordHeaderType.IMGE: + ipf.IMGEtrack = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEside = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEdensity = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEsignalType = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEtrackBytes = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEstartBytePos = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEstartBitPos = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEdataBits = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEgapBits = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEtrackBits = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEblockCount = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEencoderProcess = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEtrackFlags = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.IMGEdataKey = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + startPos += 12; // reserved + break; + + case RecordHeaderType.DATA: + ipf.DATAlength = MediaConverter.GetBEInt32(data, startPos); + if (ipf.DATAlength == 0) + { + ipf.DATAextraDataRaw = new byte[0]; + ipf.DATAlength = 0; + } + else + { + ipf.DATAextraDataRaw = new byte[ipf.DATAlength]; + } + startPos += 4; + ipf.DATAbitSize = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.DATAcrc = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + ipf.DATAdataKey = MediaConverter.GetBEInt32(data, startPos); startPos += 4; + + if (ipf.DATAlength != 0) + { + Array.Copy(data, startPos, ipf.DATAextraDataRaw, 0, ipf.DATAlength); + } + + startPos += ipf.DATAlength; + break; + } + + return ipf; + } + } + + public enum RecordHeaderType + { + None, + CAPS, + DUMP, + DATA, + TRCK, + INFO, + IMGE, + CTEI, + CTEX, + } + + + /// + /// State serlialization + /// + /// + 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(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/UDIFormat/UDI1_0FloppyDisk.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/UDIFormat/UDI1_0FloppyDisk.cs new file mode 100644 index 0000000000..d46908a73d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Disk/UDIFormat/UDI1_0FloppyDisk.cs @@ -0,0 +1,217 @@ +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 +{ + public class UDI1_0FloppyDisk : FloppyDisk + { + /// + /// The format type + /// + public override DiskType DiskFormatType => DiskType.UDI; + + /// + /// Attempts to parse incoming disk data + /// + /// + /// + /// TRUE: disk parsed + /// FALSE: unable to parse disk + /// + public override bool ParseDisk(byte[] data) + { + // look for standard magic string + string ident = Encoding.ASCII.GetString(data, 0, 4); + + if (!ident.StartsWith("UDI!") && !ident.StartsWith("udi!")) + { + // incorrect format + return false; + } + + if (data[0x08] != 0) + { + // wrong version + return false; + } + + if (ident == "udi!") + { + // cant handle compression yet + return false; + } + + DiskHeader.DiskIdent = ident; + DiskHeader.NumberOfTracks = (byte)(data[0x09] + 1); + DiskHeader.NumberOfSides = (byte)(data[0x0A] + 1); + + DiskTracks = new Track[DiskHeader.NumberOfTracks * DiskHeader.NumberOfSides]; + + int fileSize = MediaConverter.GetInt32(data, 4); // not including the final 4-byte checksum + + // ignore extended header + var extHdrSize = MediaConverter.GetInt32(data, 0x0C); + int pos = 0x10 + extHdrSize; + + // process track information + for (int t = 0; t < DiskHeader.NumberOfTracks; t++) + { + DiskTracks[t] = new UDIv1Track(); + DiskTracks[t].TrackNumber = (byte)t; + DiskTracks[t].SideNumber = 0; + DiskTracks[t].TrackType = data[pos++]; + DiskTracks[t].TLEN = MediaConverter.GetWordValue(data, pos); pos += 2; + DiskTracks[t].TrackData = new byte[DiskTracks[t].TLEN + DiskTracks[t].CLEN]; + Array.Copy(data, pos, DiskTracks[t].TrackData, 0, DiskTracks[t].TLEN + DiskTracks[t].CLEN); + pos += DiskTracks[t].TLEN + DiskTracks[t].CLEN; + } + + return true; + } + + /// + /// Takes a double-sided disk byte array and converts into 2 single-sided arrays + /// + /// + /// + /// + public static bool SplitDoubleSided(byte[] data, List results) + { + // look for standard magic string + string ident = Encoding.ASCII.GetString(data, 0, 4); + + if (!ident.StartsWith("UDI!") && !ident.StartsWith("udi!")) + { + // incorrect format + return false; + } + + if (data[0x08] != 0) + { + // wrong version + return false; + } + + if (ident == "udi!") + { + // cant handle compression yet + return false; + } + + byte[] S0 = new byte[data.Length]; + byte[] S1 = new byte[data.Length]; + + // header + var extHdr = MediaConverter.GetInt32(data, 0x0C); + Array.Copy(data, 0, S0, 0, 0x10 + extHdr); + Array.Copy(data, 0, S1, 0, 0x10 + extHdr); + // change side number + S0[0x0A] = 0; + S1[0x0A] = 0; + + int pos = 0x10 + extHdr; + int fileSize = MediaConverter.GetInt32(data, 4); // not including the final 4-byte checksum + + int s0Pos = pos; + int s1Pos = pos; + + // process track information + for (int t = 0; t < (data[0x09] + 1) * 2; t++) + { + var TLEN = MediaConverter.GetWordValue(data, pos + 1); + var CLEN = TLEN / 8 + (TLEN % 8 / 7) / 8; + var blockSize = TLEN + CLEN + 3; + + // 2 sided image: side 0 tracks will all have t as an even number + try + { + if (t == 0 || t % 2 == 0) + { + Array.Copy(data, pos, S0, s0Pos, blockSize); + s0Pos += blockSize; + } + else + { + Array.Copy(data, pos, S1, s1Pos, blockSize); + s1Pos += blockSize; + } + } + catch (Exception ex) + { + + } + + + pos += blockSize; + } + + // skip checkum bytes for now + + byte[] s0final = new byte[s0Pos]; + byte[] s1final = new byte[s1Pos]; + Array.Copy(S0, 0, s0final, 0, s0Pos); + Array.Copy(S1, 0, s1final, 0, s1Pos); + + results.Add(s0final); + results.Add(s1final); + + return true; + } + + public class UDIv1Track : Track + { + /// + /// Parse the UDI TrackData byte[] array into sector objects + /// + public override Sector[] Sectors + { + get + { + List secs = new List(); + var datas = TrackData.Skip(3).Take(TLEN).ToArray(); + var clocks = new BitArray(TrackData.Skip(3 + TLEN).Take(CLEN).ToArray()); + + return secs.ToArray(); + } + } + } + + public class UDIv1Sector : Sector + { + + } + + + /// + /// State serlialization + /// + /// + 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(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverter.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverter.cs index fde6bdf130..74ae9b5c57 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverter.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaConverter.cs @@ -2,6 +2,7 @@ using System.IO; using System.IO.Compression; using System.Runtime.InteropServices; +using System.Linq; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { @@ -95,6 +96,53 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return buf[offsetIndex] | buf[offsetIndex + 1] << 8 | buf[offsetIndex + 2] << 16 | buf[offsetIndex + 3] << 24; } + /// + /// Returns an int32 from a byte array based on offset (in BIG ENDIAN format) + /// + /// + /// + /// + public static int GetBEInt32(byte[] buf, int offsetIndex) + { + byte[] b = new byte[4]; + Array.Copy(buf, offsetIndex, b, 0, 4); + byte[] buffer = b.Reverse().ToArray(); + int pos = 0; + return buffer[pos++] | buffer[pos++] << 8 | buffer[pos++] << 16 | buffer[pos++] << 24; + } + + /// + /// Returns an int32 from a byte array based on the length of the byte array (in BIG ENDIAN format) + /// + /// + /// + public static int GetBEInt32FromByteArray(byte[] buf) + { + byte[] b = buf.Reverse().ToArray(); + if (b.Length == 0) + return 0; + int res = b[0]; + int pos = 1; + switch (b.Length) + { + case 1: + default: + return res; + case 2: + return res | b[pos] << (8 * pos++); + case 3: + return res | b[pos] << (8 * pos++) | b[pos] << (8 * pos++); + case 4: + return res | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++); + case 5: + return res | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++); + case 6: + return res | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++); + case 7: + return res | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++) | b[pos] << (8 * pos++); + } + } + /// /// Returns an int32 from a byte array based on offset ///