BizHawk/BizHawk.Emulation.Common/DSKIdentifier.cs

418 lines
11 KiB
C#

using System.Text;
namespace BizHawk.Emulation.Common
{
/// <summary>
/// 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.Emulation.Common
/// </summary>
public class DSKIdentifier
{
/// <summary>
/// Default fallthrough to AppleII
/// </summary>
public string IdentifiedSystem = "AppleII";
private string PossibleIdent = "";
private readonly 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
}
}