diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 8a7b8d0ed6..758600cf4e 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -2119,7 +2119,7 @@ namespace BizHawk.Client.EmuHawk "Virtual Boy", "*.vb;%ARCH%", "Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%", "Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;*.pzx;*.csw;*.wav;%ARCH%", - "Amstrad CPC", "*.cdt;%ARCH%", + "Amstrad CPC", "*.cdt;*.dsk;%ARCH%", "All Files", "*.*"); } diff --git a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj index d6d61bbf3b..0a8aa6ec09 100644 --- a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj +++ b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj @@ -79,6 +79,7 @@ + @@ -143,4 +144,4 @@ --> - + \ No newline at end of file diff --git a/BizHawk.Emulation.Common/DSKIdentifier.cs b/BizHawk.Emulation.Common/DSKIdentifier.cs new file mode 100644 index 0000000000..b40c6ffdf5 --- /dev/null +++ b/BizHawk.Emulation.Common/DSKIdentifier.cs @@ -0,0 +1,421 @@ + +using System.Linq; +using System.Text; + +namespace BizHawk.Emulation.Common +{ + /// + /// A slightly convoluted way of determining the required System based on a *.dsk file + /// This is here because (for probably good reason) there does not appear to be a route + /// to BizHawk.Emulation.Cores from Bizhawk.Emultion.Common + /// + public class DSKIdentifier + { + /// + /// Default fallthrough to AppleII + /// + public string IdentifiedSystem = "AppleII"; + private string PossibleIdent = ""; + + private byte[] data; + + // dsk header + public byte NumberOfTracks { get; set; } + public byte NumberOfSides { get; set; } + public int[] TrackSizes { get; set; } + + // state + public int CylinderCount; + public int SideCount; + public int BytesPerTrack; + + public Track[] Tracks = null; + + public DSKIdentifier(byte[] imageData) + { + data = imageData; + ParseDskImage(); + } + + private void ParseDskImage() + { + string ident = Encoding.ASCII.GetString(data, 0, 16).ToUpper(); + if (ident.Contains("MV - CPC")) + { + ParseDSK(); + } + else if (ident.Contains("EXTENDED CPC DSK")) + { + ParseEDSK(); + } + else + { + // fall through + return; + } + + CalculateFormat(); + } + + private void CalculateFormat() + { + // uses some of the work done here: https://github.com/damieng/DiskImageManager + var trk = Tracks[0]; + + // look for standard speccy bootstart + if (trk.Sectors[0].SectorData != null && trk.Sectors[0].SectorData.Length > 0) + { + if (trk.Sectors[0].SectorData[0] == 0 && trk.Sectors[0].SectorData[1] == 0 + && trk.Sectors[0].SectorData[2] == 40) + { + PossibleIdent = "ZXSpectrum"; + } + } + + // search for PLUS3DOS string + foreach (var t in Tracks) + { + foreach (var s in t.Sectors) + { + if (s.SectorData == null || s.SectorData.Length == 0) + continue; + + string str = Encoding.ASCII.GetString(s.SectorData, 0, s.SectorData.Length).ToUpper(); + if (str.Contains("PLUS3DOS")) + { + IdentifiedSystem = "ZXSpectrum"; + return; + } + } + } + + // check for bootable status + if (trk.Sectors[0].SectorData != null && trk.Sectors[0].SectorData.Length > 0) + { + int chksm = trk.Sectors[0].GetChecksum256(); + + switch(chksm) + { + case 3: + IdentifiedSystem = "ZXSpectrum"; + return; + case 1: + case 255: + // different Amstrad PCW boot records + // just return CPC for now + IdentifiedSystem = "AmstradCPC"; + return; + } + + switch(trk.GetLowestSectorID()) + { + case 65: + case 193: + IdentifiedSystem = "AmstradCPC"; + return; + } + } + + // at this point the disk is not standard bootable + // try format analysis + if (trk.Sectors.Length == 9 && trk.Sectors[0].SectorSize == 2) + { + switch(trk.GetLowestSectorID()) + { + case 1: + switch(trk.Sectors[0].GetChecksum256()) + { + case 3: + IdentifiedSystem = "ZXSpectrum"; + return; + case 1: + case 255: + // different Amstrad PCW checksums + // just return CPC for now + IdentifiedSystem = "AmstradCPC"; + return; + } + break; + case 65: + case 193: + IdentifiedSystem = "AmstradCPC"; + return; + } + } + + // could be an odd format disk + switch (trk.GetLowestSectorID()) + { + case 1: + if (trk.Sectors.Length == 8) + { + // CPC IBM + IdentifiedSystem = "AmstradCPC"; + return; + } + break; + case 65: + case 193: + // possible CPC custom + PossibleIdent = "AmstradCPC"; + break; + } + + // other custom ZX Spectrum formats + if (NumberOfSides == 1 && trk.Sectors.Length == 10) + { + if (trk.Sectors[0].SectorData.Length > 10) + { + if (trk.Sectors[0].SectorData[2] == 42 && trk.Sectors[0].SectorData[8] == 12) + { + switch (trk.Sectors[0].SectorData[5]) + { + case 0: + if (trk.Sectors[1].SectorID == 8) + { + switch (Tracks[1].Sectors[0].SectorID) + { + case 7: + IdentifiedSystem = "ZXSpectrum"; + return; + default: + PossibleIdent = "ZXSpectrum"; + break; + } + } + else + { + PossibleIdent = "ZXSpectrum"; + } + break; + case 1: + if (trk.Sectors[1].SectorID == 8) + { + switch (Tracks[1].Sectors[0].SectorID) + { + case 7: + case 1: + IdentifiedSystem = "ZXSpectrum"; + return; + } + } + else + { + PossibleIdent = "ZXSpectrum"; + } + break; + } + } + + if (trk.Sectors[0].SectorData[7] == 3 && + trk.Sectors[0].SectorData[9] == 23 && + trk.Sectors[0].SectorData[2] == 40) + { + IdentifiedSystem = "ZXSpectrum"; + return; + } + } + } + + // last chance. use the possible value + if (IdentifiedSystem == "AppleII" && PossibleIdent != "") + { + IdentifiedSystem = "ZXSpectrum"; + } + } + + private void ParseDSK() + { + NumberOfTracks = data[0x30]; + NumberOfSides = data[0x31]; + TrackSizes = new int[NumberOfTracks * NumberOfSides]; + Tracks = new Track[NumberOfTracks * NumberOfSides]; + int pos = 0x32; + for (int i = 0; i < NumberOfTracks * NumberOfSides; i++) + { + TrackSizes[i] = (ushort)(data[pos] | data[pos + 1] << 8); + } + pos = 0x100; + for (int i = 0; i < NumberOfTracks * NumberOfSides; i++) + { + if (TrackSizes[i] == 0) + { + Tracks[i] = new Track(); + Tracks[i].Sectors = new Sector[0]; + continue; + } + int p = pos; + Tracks[i] = new Track(); + Tracks[i].TrackIdent = Encoding.ASCII.GetString(data, p, 12); + p += 16; + Tracks[i].TrackNumber = data[p++]; + Tracks[i].SideNumber = data[p++]; + p += 2; + Tracks[i].SectorSize = data[p++]; + Tracks[i].NumberOfSectors = data[p++]; + Tracks[i].GAP3Length = data[p++]; + Tracks[i].FillerByte = data[p++]; + int dpos = pos + 0x100; + Tracks[i].Sectors = new Sector[Tracks[i].NumberOfSectors]; + for (int s = 0; s < Tracks[i].NumberOfSectors; s++) + { + Tracks[i].Sectors[s] = new Sector(); + + Tracks[i].Sectors[s].TrackNumber = data[p++]; + Tracks[i].Sectors[s].SideNumber = data[p++]; + Tracks[i].Sectors[s].SectorID = data[p++]; + Tracks[i].Sectors[s].SectorSize = data[p++]; + Tracks[i].Sectors[s].Status1 = data[p++]; + Tracks[i].Sectors[s].Status2 = data[p++]; + Tracks[i].Sectors[s].ActualDataByteLength = (ushort)(data[p] | data[p + 1] << 8); + p += 2; + if (Tracks[i].Sectors[s].SectorSize == 0) + { + Tracks[i].Sectors[s].ActualDataByteLength = TrackSizes[i]; + } + else if (Tracks[i].Sectors[s].SectorSize > 6) + { + Tracks[i].Sectors[s].ActualDataByteLength = TrackSizes[i]; + } + else if (Tracks[i].Sectors[s].SectorSize == 6) + { + Tracks[i].Sectors[s].ActualDataByteLength = 0x1800; + } + else + { + Tracks[i].Sectors[s].ActualDataByteLength = 0x80 << Tracks[i].Sectors[s].SectorSize; + } + Tracks[i].Sectors[s].SectorData = new byte[Tracks[i].Sectors[s].ActualDataByteLength]; + for (int b = 0; b < Tracks[i].Sectors[s].ActualDataByteLength; b++) + { + Tracks[i].Sectors[s].SectorData[b] = data[dpos + b]; + } + dpos += Tracks[i].Sectors[s].ActualDataByteLength; + } + pos += TrackSizes[i]; + } + } + + private void ParseEDSK() + { + NumberOfTracks = data[0x30]; + NumberOfSides = data[0x31]; + TrackSizes = new int[NumberOfTracks * NumberOfSides]; + Tracks = new Track[NumberOfTracks * NumberOfSides]; + int pos = 0x34; + for (int i = 0; i < NumberOfTracks * NumberOfSides; i++) + { + TrackSizes[i] = data[pos++] * 256; + } + pos = 0x100; + for (int i = 0; i < NumberOfTracks * NumberOfSides; i++) + { + if (TrackSizes[i] == 0) + { + Tracks[i] = new Track(); + Tracks[i].Sectors = new Sector[0]; + continue; + } + int p = pos; + Tracks[i] = new Track(); + Tracks[i].TrackIdent = Encoding.ASCII.GetString(data, p, 12); + p += 16; + Tracks[i].TrackNumber = data[p++]; + Tracks[i].SideNumber = data[p++]; + Tracks[i].DataRate = data[p++]; + Tracks[i].RecordingMode = data[p++]; + Tracks[i].SectorSize = data[p++]; + Tracks[i].NumberOfSectors = data[p++]; + Tracks[i].GAP3Length = data[p++]; + Tracks[i].FillerByte = data[p++]; + int dpos = pos + 0x100; + Tracks[i].Sectors = new Sector[Tracks[i].NumberOfSectors]; + for (int s = 0; s < Tracks[i].NumberOfSectors; s++) + { + Tracks[i].Sectors[s] = new Sector(); + + Tracks[i].Sectors[s].TrackNumber = data[p++]; + Tracks[i].Sectors[s].SideNumber = data[p++]; + Tracks[i].Sectors[s].SectorID = data[p++]; + Tracks[i].Sectors[s].SectorSize = data[p++]; + Tracks[i].Sectors[s].Status1 = data[p++]; + Tracks[i].Sectors[s].Status2 = data[p++]; + Tracks[i].Sectors[s].ActualDataByteLength = (ushort)(data[p] | data[p + 1] << 8); + p += 2; + Tracks[i].Sectors[s].SectorData = new byte[Tracks[i].Sectors[s].ActualDataByteLength]; + for (int b = 0; b < Tracks[i].Sectors[s].ActualDataByteLength; b++) + { + Tracks[i].Sectors[s].SectorData[b] = data[dpos + b]; + } + if (Tracks[i].Sectors[s].SectorSize <= 7) + { + int specifiedSize = 0x80 << Tracks[i].Sectors[s].SectorSize; + if (specifiedSize < Tracks[i].Sectors[s].ActualDataByteLength) + { + if (Tracks[i].Sectors[s].ActualDataByteLength % specifiedSize != 0) + { + Tracks[i].Sectors[s].ContainsMultipleWeakSectors = true; + } + } + } + dpos += Tracks[i].Sectors[s].ActualDataByteLength; + } + pos += TrackSizes[i]; + } + } + + #region Internal Classes + + 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; } + + public byte GetLowestSectorID() + { + byte res = 0xFF; + foreach (var s in Sectors) + { + if (s.SectorID < res) + res = s.SectorID; + } + return res; + } + } + + 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 GetChecksum256() + { + int res = 0; + for (int i = 0; i < SectorData.Length; i++) + { + res += SectorData[i] % 256; + } + return res; + } + } + + #endregion + + + } +} diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index 6b9026b489..1cd73d701e 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -314,7 +314,7 @@ namespace BizHawk.Emulation.Common game.System = "AmstradCPC"; break; - case ".TAP": + case ".TAP": byte[] head = romData.Take(8).ToArray(); if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE")) game.System = "C64"; @@ -346,14 +346,9 @@ namespace BizHawk.Emulation.Common break; case ".DSK": - byte[] head2 = romData.Take(20).ToArray(); - string ident = System.Text.Encoding.Default.GetString(head2); - if (ident.ToUpper().Contains("EXTENDED CPC DSK") || - ident.ToUpper().Contains("MV - CPC")) - game.System = "ZXSpectrum"; - else - game.System = "AppleII"; - break; + var dId = new DSKIdentifier(romData); + game.System = dId.IdentifiedSystem; + break; case ".PO": case ".DO":