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%",
|
"Virtual Boy", "*.vb;%ARCH%",
|
||||||
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
|
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
|
||||||
"Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;*.pzx;*.csw;*.wav;%ARCH%",
|
"Sinclair ZX Spectrum", "*.tzx;*.tap;*.dsk;*.pzx;*.csw;*.wav;%ARCH%",
|
||||||
"Amstrad CPC", "*.cdt;%ARCH%",
|
"Amstrad CPC", "*.cdt;*.dsk;%ARCH%",
|
||||||
"All Files", "*.*");
|
"All Files", "*.*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
<Compile Include="CoreComms.cs" />
|
<Compile Include="CoreComms.cs" />
|
||||||
<Compile Include="Database\CRC32.cs" />
|
<Compile Include="Database\CRC32.cs" />
|
||||||
<Compile Include="Database\Database.cs" />
|
<Compile Include="Database\Database.cs" />
|
||||||
|
<Compile Include="DSKIdentifier.cs" />
|
||||||
<Compile Include="Database\FirmwareDatabase.cs" />
|
<Compile Include="Database\FirmwareDatabase.cs" />
|
||||||
<Compile Include="Database\GameInfo.cs" />
|
<Compile Include="Database\GameInfo.cs" />
|
||||||
<Compile Include="EmulationExceptions.cs" />
|
<Compile Include="EmulationExceptions.cs" />
|
||||||
|
@ -143,4 +144,4 @@
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</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";
|
game.System = "AmstradCPC";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ".TAP":
|
case ".TAP":
|
||||||
byte[] head = romData.Take(8).ToArray();
|
byte[] head = romData.Take(8).ToArray();
|
||||||
if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE"))
|
if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE"))
|
||||||
game.System = "C64";
|
game.System = "C64";
|
||||||
|
@ -346,14 +346,9 @@ namespace BizHawk.Emulation.Common
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ".DSK":
|
case ".DSK":
|
||||||
byte[] head2 = romData.Take(20).ToArray();
|
var dId = new DSKIdentifier(romData);
|
||||||
string ident = System.Text.Encoding.Default.GetString(head2);
|
game.System = dId.IdentifiedSystem;
|
||||||
if (ident.ToUpper().Contains("EXTENDED CPC DSK") ||
|
break;
|
||||||
ident.ToUpper().Contains("MV - CPC"))
|
|
||||||
game.System = "ZXSpectrum";
|
|
||||||
else
|
|
||||||
game.System = "AppleII";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ".PO":
|
case ".PO":
|
||||||
case ".DO":
|
case ".DO":
|
||||||
|
|
Loading…
Reference in New Issue