Emulation.Common: Additional method added to inspect *.dsk images in order to work out whether they are ZX, CPC or AppleII
This commit is contained in:
parent
facbdd3630
commit
1268b09849
|
@ -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", "*.*");
|
||||
}
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
<Compile Include="CoreComms.cs" />
|
||||
<Compile Include="Database\CRC32.cs" />
|
||||
<Compile Include="Database\Database.cs" />
|
||||
<Compile Include="DSKIdentifier.cs" />
|
||||
<Compile Include="Database\FirmwareDatabase.cs" />
|
||||
<Compile Include="Database\GameInfo.cs" />
|
||||
<Compile Include="EmulationExceptions.cs" />
|
||||
|
@ -143,4 +144,4 @@
|
|||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
|
@ -0,0 +1,421 @@
|
|||
|
||||
using System.Linq;
|
||||
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.Emultion.Common
|
||||
/// </summary>
|
||||
public class DSKIdentifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Default fallthrough to AppleII
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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":
|
||||
|
|
Loading…
Reference in New Issue