Experiemental MDS/MDF Support
This commit is contained in:
parent
688015bde8
commit
6d87be1396
|
@ -470,7 +470,7 @@ namespace BizHawk.Client.Common
|
|||
System = "PSX"
|
||||
};
|
||||
}
|
||||
else if (ext == ".iso" || ext == ".cue" || ext == ".ccd")
|
||||
else if (ext == ".iso" || ext == ".cue" || ext == ".ccd" || ext == ".mds")
|
||||
{
|
||||
if (file.IsArchive)
|
||||
{
|
||||
|
@ -494,7 +494,7 @@ namespace BizHawk.Client.Common
|
|||
throw new InvalidOperationException("\r\n" + discMountJob.OUT_Log);
|
||||
}
|
||||
|
||||
var disc = discMountJob.OUT_Disc;
|
||||
var disc = discMountJob.OUT_Disc;
|
||||
|
||||
// -----------
|
||||
// TODO - use more sophisticated IDer
|
||||
|
|
|
@ -111,7 +111,7 @@ namespace BizHawk.Client.DiscoHawk
|
|||
foreach (string str in files)
|
||||
{
|
||||
string ext = Path.GetExtension(str).ToUpper();
|
||||
if(!ext.In(new string[]{".CUE",".ISO",".CCD"}))
|
||||
if(!ext.In(new string[]{".CUE",".ISO",".CCD", ".MDS"}))
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return new[]
|
||||
{
|
||||
".NES", ".FDS", ".UNF", ".SMS", ".GG", ".SG", ".GB", ".GBC", ".GBA", ".PCE", ".SGX", ".BIN", ".SMD", ".GEN", ".MD", ".SMC", ".SFC", ".A26", ".A78", ".LNX", ".COL", ".ROM", ".M3U", ".CUE", ".CCD", ".SGB", ".Z64", ".V64", ".N64", ".WS", ".WSC", ".XML", ".DSK", ".DO", ".PO", ".PSF", ".MINIPSF", ".NSF",
|
||||
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X"
|
||||
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2077,9 +2077,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (VersionInfo.DeveloperBuild)
|
||||
{
|
||||
return FormatFilter(
|
||||
"Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;%ARCH%",
|
||||
"Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.mds;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;%ARCH%",
|
||||
"Music Files", "*.psf;*.minipsf;*.sid;*.nsf",
|
||||
"Disc Images", "*.cue;*.ccd;*.m3u",
|
||||
"Disc Images", "*.cue;*.ccd;*.mds;*.m3u",
|
||||
"NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%",
|
||||
"Super NES", "*.smc;*.sfc;*.xml;%ARCH%",
|
||||
"Master System", "*.sms;*.gg;*.sg;%ARCH%",
|
||||
|
@ -2095,7 +2095,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
"Gameboy Advance", "*.gba;%ARCH%",
|
||||
"Colecovision", "*.col;%ARCH%",
|
||||
"Intellivision", "*.int;*.bin;*.rom;%ARCH%",
|
||||
"PlayStation", "*.cue;*.ccd;*.m3u",
|
||||
"PlayStation", "*.cue;*.ccd;*.mds;*.m3u",
|
||||
"PSX Executables (experimental)", "*.exe",
|
||||
"PSF Playstation Sound File", "*.psf;*.minipsf",
|
||||
"Commodore 64", "*.prg; *.d64, *.g64; *.crt; *.tap;%ARCH%",
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
<Compile Include="DiscFormats\Blobs\Blob_ZeroPadAdapter.cs" />
|
||||
<Compile Include="DiscFormats\Blobs\IBlob.cs" />
|
||||
<Compile Include="DiscFormats\Blobs\RiffMaster.cs" />
|
||||
<Compile Include="DiscFormats\MDS_Format.cs" />
|
||||
<Compile Include="DiscFormats\CCD_format.cs" />
|
||||
<Compile Include="DiscFormats\CUE\CueFileResolver.cs" />
|
||||
<Compile Include="DiscFormats\CUE\CUE_Compile.cs" />
|
||||
|
|
|
@ -0,0 +1,907 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Parsing Alcohol 120% files
|
||||
/// Info taken from:
|
||||
/// * http://forum.redump.org/post/41803/#p41803
|
||||
/// * Libmirage image-mds parser - https://sourceforge.net/projects/cdemu/files/libmirage/
|
||||
/// * DiscImageChef - https://github.com/claunia/DiscImageChef/blob/master/DiscImageChef.DiscImages/Alcohol120.cs
|
||||
/// </summary>
|
||||
public class MDS_Format
|
||||
{
|
||||
/// <summary>
|
||||
/// A loose representation of an Alcohol 120 .mds file (with a few extras)
|
||||
/// </summary>
|
||||
public class AFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Full path to the MDS file
|
||||
/// </summary>
|
||||
public string MDSPath;
|
||||
|
||||
/// <summary>
|
||||
/// MDS Header
|
||||
/// </summary>
|
||||
public AHeader Header = new AHeader();
|
||||
|
||||
/// <summary>
|
||||
/// List of MDS session blocks
|
||||
/// </summary>
|
||||
public List<ASession> Sessions = new List<ASession>();
|
||||
|
||||
/// <summary>
|
||||
/// List of track blocks
|
||||
/// </summary>
|
||||
public List<ATrack> Tracks = new List<ATrack>();
|
||||
|
||||
/// <summary>
|
||||
/// Current parsed session objects
|
||||
/// </summary>
|
||||
public List<Session> ParsedSession = new List<Session>();
|
||||
|
||||
/// <summary>
|
||||
/// Calculated MDS TOC entries (still to be parsed into BizHawk)
|
||||
/// </summary>
|
||||
public List<ATOCEntry> TOCEntries = new List<ATOCEntry>();
|
||||
|
||||
}
|
||||
|
||||
public class AHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard alcohol 120% signature - usually "MEDIA DESCRIPTOR"
|
||||
/// </summary>
|
||||
public string Signature; // 16 bytes
|
||||
|
||||
/// <summary>
|
||||
/// Alcohol version?
|
||||
/// </summary>
|
||||
public byte[] Version; // 2 bytes
|
||||
|
||||
/// <summary>
|
||||
/// The medium type
|
||||
/// * 0x00 - CD
|
||||
/// * 0x01 - CD-R
|
||||
/// * 0x02 - CD-RW
|
||||
/// * 0x10 - DVD
|
||||
/// * 0x12 - DVD-R
|
||||
/// </summary>
|
||||
public int Medium;
|
||||
|
||||
/// <summary>
|
||||
/// Number of sessions
|
||||
/// </summary>
|
||||
public int SessionCount;
|
||||
|
||||
/// <summary>
|
||||
/// Burst Cutting Area length
|
||||
/// </summary>
|
||||
public int BCALength;
|
||||
|
||||
/// <summary>
|
||||
/// Burst Cutting Area data offset
|
||||
/// </summary>
|
||||
public Int64 BCAOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to disc (DVD?) structures
|
||||
/// </summary>
|
||||
public Int64 StructureOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to the first session block
|
||||
/// </summary>
|
||||
public Int64 SessionOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Data Position Measurement offset
|
||||
/// </summary>
|
||||
public Int64 DPMOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Parse mds stream for the header
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <returns></returns>
|
||||
public AHeader Parse(Stream stream)
|
||||
{
|
||||
EndianBitConverter bc = EndianBitConverter.CreateForLittleEndian();
|
||||
EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
|
||||
|
||||
byte[] header = new byte[88];
|
||||
stream.Read(header, 0, 88);
|
||||
|
||||
this.Signature = Encoding.ASCII.GetString(header.Take(16).ToArray());
|
||||
this.Version = header.Skip(16).Take(2).ToArray();
|
||||
this.Medium = bc.ToInt16(header.Skip(18).Take(2).ToArray());
|
||||
this.SessionCount = bc.ToInt16(header.Skip(20).Take(2).ToArray());
|
||||
this.BCALength = bc.ToInt16(header.Skip(26).Take(2).ToArray());
|
||||
this.BCAOffset = bc.ToInt32(header.Skip(36).Take(4).ToArray());
|
||||
this.StructureOffset = bc.ToInt32(header.Skip(64).Take(4).ToArray());
|
||||
this.SessionOffset = bc.ToInt32(header.Skip(80).Take(4).ToArray());
|
||||
this.DPMOffset = bc.ToInt32(header.Skip(84).Take(4).ToArray());
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MDS session block representation
|
||||
/// </summary>
|
||||
public class ASession
|
||||
{
|
||||
public int SessionStart; /* Session's start address */
|
||||
public int SessionEnd; /* Session's end address */
|
||||
public int SessionNumber; /* Session number */
|
||||
public byte AllBlocks; /* Number of all data blocks. */
|
||||
public byte NonTrackBlocks; /* Number of lead-in data blocks */
|
||||
public int FirstTrack; /* First track in session */
|
||||
public int LastTrack; /* Last track in session */
|
||||
public Int64 TrackOffset; /* Offset of lead-in+regular track data blocks. */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation of an MDS track block
|
||||
/// For convenience (and extra confusion) this also holds the track extrablock, filename(footer) block infos
|
||||
/// as well as the calculated image filepath as specified in the MDS file
|
||||
/// </summary>
|
||||
public class ATrack
|
||||
{
|
||||
/// <summary>
|
||||
/// The specified data mode
|
||||
/// 0x00 - None (no data)
|
||||
/// 0x02 - DVD
|
||||
/// 0xA9 - Audio
|
||||
/// 0xAA - Mode1
|
||||
/// 0xAB - Mode2
|
||||
/// 0xAC - Mode2 Form1
|
||||
/// 0xAD - Mode2 Form2
|
||||
/// </summary>
|
||||
public byte Mode; /* Track mode */
|
||||
|
||||
/// <summary>
|
||||
/// Subchannel mode for the track (0x00 = None, 0x08 = Interleaved)
|
||||
/// </summary>
|
||||
public byte SubMode; /* Subchannel mode */
|
||||
|
||||
/* These are the fields from Sub-channel Q information, which are
|
||||
also returned in full TOC by READ TOC/PMA/ATIP command */
|
||||
public int ADR_Control; /* Adr/Ctl */
|
||||
public int TrackNo; /* Track number field */
|
||||
public int Point; /* Point field (= track number for track entries) */
|
||||
public int AMin; /* Min */
|
||||
public int ASec; /* Sec */
|
||||
public int AFrame; /* Frame */
|
||||
public int Zero; /* Zero */
|
||||
public int PMin; /* PMin */
|
||||
public int PSec; /* PSec */
|
||||
public int PFrame; /* PFrame */
|
||||
|
||||
public Int64 ExtraOffset; /* Start offset of this track's extra block. */
|
||||
public int SectorSize; /* Sector size. */
|
||||
public Int64 PLBA; /* Track start sector (PLBA). */
|
||||
public ulong StartOffset; /* Track start offset (from beginning of MDS file) */
|
||||
public Int64 Files; /* Number of filenames for this track */
|
||||
public Int64 FooterOffset; /* Start offset of footer (from beginning of MDS file) */
|
||||
|
||||
/// <summary>
|
||||
/// Track extra block
|
||||
/// </summary>
|
||||
public ATrackExtra ExtraBlock = new ATrackExtra();
|
||||
|
||||
/// <summary>
|
||||
/// List of footer(filename) blocks for this track
|
||||
/// </summary>
|
||||
public List<AFooter> FooterBlocks = new List<AFooter>();
|
||||
|
||||
/// <summary>
|
||||
/// List of the calculated full paths to this track's image file
|
||||
/// The MDS file itself may contain a filename, or just an *.extension
|
||||
/// </summary>
|
||||
public List<string> ImageFileNamePaths = new List<string>();
|
||||
|
||||
public int BlobIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extra track block
|
||||
/// </summary>
|
||||
public class ATrackExtra
|
||||
{
|
||||
public Int64 Pregap; /* Number of sectors in pregap. */
|
||||
public Int64 Sectors; /* Number of sectors in track. */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Footer (filename) block - potentially one for every track
|
||||
/// </summary>
|
||||
public class AFooter
|
||||
{
|
||||
public Int64 FilenameOffset; /* Start offset of image filename string (from beginning of mds file) */
|
||||
public Int64 WideChar; /* Seems to be set to 1 if widechar filename is used */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a parsed MDS TOC entry
|
||||
/// </summary>
|
||||
public class ATOCEntry
|
||||
{
|
||||
public ATOCEntry(int entryNum)
|
||||
{
|
||||
EntryNum = entryNum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// these should be 0-indexed
|
||||
/// </summary>
|
||||
public int EntryNum;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 1-indexed - the session that this entry belongs to
|
||||
/// </summary>
|
||||
public int Session;
|
||||
|
||||
/// <summary>
|
||||
/// this seems just to be the LBA corresponding to AMIN:ASEC:AFRAME (give or take 150). It's not stored on the disc, and it's redundant.
|
||||
/// </summary>
|
||||
//public int ALBA;
|
||||
|
||||
/// <summary>
|
||||
/// this seems just to be the LBA corresponding to PMIN:PSEC:PFRAME (give or take 150).
|
||||
/// </summary>
|
||||
public int PLBA;
|
||||
|
||||
//these correspond pretty directly to values in the Q subchannel fields
|
||||
//NOTE: they're specified as absolute MSF. That means, they're 2 seconds off from what they should be when viewed as final TOC values
|
||||
public int ADR_Control;
|
||||
public int TrackNo;
|
||||
public int Point;
|
||||
public int AMin;
|
||||
public int ASec;
|
||||
public int AFrame;
|
||||
public int Zero;
|
||||
public int PMin;
|
||||
public int PSec;
|
||||
public int PFrame;
|
||||
|
||||
|
||||
public int SectorSize;
|
||||
public long TrackOffset;
|
||||
|
||||
/// <summary>
|
||||
/// List of the calculated full paths to this track's image file
|
||||
/// The MDS file itself may contain a filename, or just an *.extension
|
||||
/// </summary>
|
||||
public List<string> ImageFileNamePaths = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Track extra block
|
||||
/// </summary>
|
||||
public ATrackExtra ExtraBlock = new ATrackExtra();
|
||||
|
||||
public int BlobIndex;
|
||||
}
|
||||
|
||||
public AFile Parse(Stream stream)
|
||||
{
|
||||
EndianBitConverter bc = EndianBitConverter.CreateForLittleEndian();
|
||||
EndianBitConverter bcBig = EndianBitConverter.CreateForBigEndian();
|
||||
bool isDvd = false;
|
||||
|
||||
AFile aFile = new AFile();
|
||||
|
||||
aFile.MDSPath = (stream as FileStream).Name;
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// check whether the header in the mds file is long enough
|
||||
if (stream.Length < 88) throw new MDSParseException("Malformed MDS format: The descriptor file does not appear to be long enough.");
|
||||
|
||||
// parse header
|
||||
aFile.Header = aFile.Header.Parse(stream);
|
||||
|
||||
// parse sessions
|
||||
Dictionary<int, ASession> aSessions = new Dictionary<int, ASession>();
|
||||
|
||||
stream.Seek(aFile.Header.SessionOffset, SeekOrigin.Begin);
|
||||
for (int se = 0; se < aFile.Header.SessionCount; se++)
|
||||
{
|
||||
byte[] sessionHeader = new byte[24];
|
||||
stream.Read(sessionHeader, 0, 24);
|
||||
//sessionHeader.Reverse().ToArray();
|
||||
|
||||
ASession session = new ASession();
|
||||
|
||||
session.SessionStart = bc.ToInt32(sessionHeader.Take(4).ToArray());
|
||||
session.SessionEnd = bc.ToInt32(sessionHeader.Skip(4).Take(4).ToArray());
|
||||
session.SessionNumber = bc.ToInt16(sessionHeader.Skip(8).Take(2).ToArray());
|
||||
session.AllBlocks = sessionHeader[10];
|
||||
session.NonTrackBlocks = sessionHeader[11];
|
||||
session.FirstTrack = bc.ToInt16(sessionHeader.Skip(12).Take(2).ToArray());
|
||||
session.LastTrack = bc.ToInt16(sessionHeader.Skip(14).Take(2).ToArray());
|
||||
session.TrackOffset = bc.ToInt32(sessionHeader.Skip(20).Take(4).ToArray());
|
||||
|
||||
|
||||
//mdsf.Sessions.Add(session);
|
||||
aSessions.Add(session.SessionNumber, session);
|
||||
}
|
||||
|
||||
long footerOffset = 0;
|
||||
|
||||
// parse track blocks
|
||||
Dictionary<int, ATrack> aTracks = new Dictionary<int, ATrack>();
|
||||
|
||||
// iterate through each session block
|
||||
foreach (ASession session in aSessions.Values)
|
||||
{
|
||||
stream.Seek(session.TrackOffset, SeekOrigin.Begin);
|
||||
//Dictionary<int, ATrack> sessionToc = new Dictionary<int, ATrack>();
|
||||
|
||||
// iterate through every block specified in each session
|
||||
for (int bl = 0; bl < session.AllBlocks; bl++)
|
||||
{
|
||||
byte[] trackHeader;
|
||||
ATrack track = new ATrack();
|
||||
|
||||
trackHeader = new byte[80];
|
||||
|
||||
stream.Read(trackHeader, 0, 80);
|
||||
|
||||
track.Mode = trackHeader[0];
|
||||
track.SubMode = trackHeader[1];
|
||||
track.ADR_Control = trackHeader[2];
|
||||
track.TrackNo = trackHeader[3];
|
||||
track.Point = trackHeader[4];
|
||||
track.AMin = trackHeader[5];
|
||||
track.ASec = trackHeader[6];
|
||||
track.AFrame = trackHeader[7];
|
||||
track.Zero = trackHeader[8];
|
||||
track.PMin = trackHeader[9];
|
||||
track.PSec = trackHeader[10];
|
||||
track.PFrame = trackHeader[11];
|
||||
track.ExtraOffset = bc.ToInt32(trackHeader.Skip(12).Take(4).ToArray());
|
||||
track.SectorSize = bc.ToInt16(trackHeader.Skip(16).Take(2).ToArray());
|
||||
track.PLBA = bc.ToInt32(trackHeader.Skip(36).Take(4).ToArray());
|
||||
track.StartOffset = BitConverter.ToUInt64(trackHeader.Skip(40).Take(8).ToArray(), 0);
|
||||
track.Files = bc.ToInt32(trackHeader.Skip(48).Take(4).ToArray());
|
||||
track.FooterOffset = bc.ToInt32(trackHeader.Skip(52).Take(4).ToArray());
|
||||
|
||||
if (track.Mode == 0x02)
|
||||
{
|
||||
isDvd = true;
|
||||
throw new MDSParseException("DVD Detected. Not currently supported!");
|
||||
}
|
||||
|
||||
|
||||
// check for track extra block - this can probably be handled in a separate loop,
|
||||
// but I'll just store the current stream position then seek forward to the extra block for this track
|
||||
Int64 currPos = stream.Position;
|
||||
|
||||
// Only CDs have extra blocks - for DVDs ExtraOffset = track length
|
||||
if (track.ExtraOffset > 0 && !isDvd)
|
||||
{
|
||||
byte[] extHeader = new byte[8];
|
||||
stream.Seek(track.ExtraOffset, SeekOrigin.Begin);
|
||||
stream.Read(extHeader, 0, 8);
|
||||
track.ExtraBlock.Pregap = bc.ToInt32(extHeader.Take(4).ToArray());
|
||||
track.ExtraBlock.Sectors = bc.ToInt32(extHeader.Skip(4).Take(4).ToArray());
|
||||
stream.Seek(currPos, SeekOrigin.Begin);
|
||||
}
|
||||
else if (isDvd == true)
|
||||
{
|
||||
track.ExtraBlock.Sectors = track.ExtraOffset;
|
||||
}
|
||||
|
||||
// read the footer/filename block for this track
|
||||
currPos = stream.Position;
|
||||
long numOfFilenames = track.Files;
|
||||
for (long fi = 1; fi <= numOfFilenames; fi++)
|
||||
{
|
||||
// skip leadin/out info tracks
|
||||
if (track.FooterOffset == 0)
|
||||
continue;
|
||||
|
||||
byte[] foot = new byte[16];
|
||||
stream.Seek(track.FooterOffset, SeekOrigin.Begin);
|
||||
stream.Read(foot, 0, 16);
|
||||
|
||||
AFooter f = new AFooter();
|
||||
f.FilenameOffset = bc.ToInt32(foot.Take(4).ToArray());
|
||||
f.WideChar = bc.ToInt32(foot.Skip(4).Take(4).ToArray());
|
||||
track.FooterBlocks.Add(f);
|
||||
track.FooterBlocks = track.FooterBlocks.Distinct().ToList();
|
||||
|
||||
// parse the filename string
|
||||
string fileName = "*.mdf";
|
||||
if (f.FilenameOffset > 0)
|
||||
{
|
||||
// filename offset is present
|
||||
stream.Seek(f.FilenameOffset, SeekOrigin.Begin);
|
||||
byte[] fname;
|
||||
|
||||
if (numOfFilenames == 1)
|
||||
{
|
||||
if (aFile.Header.DPMOffset == 0)
|
||||
{
|
||||
// filename is in the remaining space to EOF
|
||||
fname = new byte[stream.Length - stream.Position];
|
||||
}
|
||||
else
|
||||
{
|
||||
// filename is in the remaining space to EOF + dpm offset
|
||||
fname = new byte[aFile.Header.DPMOffset - stream.Position];
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// looks like each filename string is 6 bytes with a trailing \0
|
||||
fname = new byte[6];
|
||||
}
|
||||
|
||||
|
||||
// read the filename
|
||||
stream.Read(fname, 0, fname.Length);
|
||||
|
||||
// if widechar is 1 filename is stored using 16-bit, otherwise 8-bit is used
|
||||
if (f.WideChar == 1)
|
||||
fileName = Encoding.Unicode.GetString(fname).TrimEnd('\0');
|
||||
else
|
||||
fileName = Encoding.Default.GetString(fname).TrimEnd('\0');
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// assume an MDF file with the same name as the MDS
|
||||
}
|
||||
|
||||
string dir = Path.GetDirectoryName(aFile.MDSPath);
|
||||
|
||||
if (f.FilenameOffset == 0 ||
|
||||
string.Compare(fileName, "*.mdf", StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
fileName = dir + @"\" + Path.GetFileNameWithoutExtension(aFile.MDSPath) + ".mdf";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName = dir + @"\" + fileName;
|
||||
}
|
||||
|
||||
track.ImageFileNamePaths.Add(fileName);
|
||||
track.ImageFileNamePaths = track.ImageFileNamePaths.Distinct().ToList();
|
||||
}
|
||||
|
||||
stream.Position = currPos;
|
||||
|
||||
|
||||
aTracks.Add(track.Point, track);
|
||||
aFile.Tracks.Add(track);
|
||||
|
||||
if (footerOffset == 0)
|
||||
footerOffset = track.FooterOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// build custom session object
|
||||
aFile.ParsedSession = new List<Session>();
|
||||
foreach (var s in aSessions.Values)
|
||||
{
|
||||
Session session = new Session();
|
||||
ATrack startTrack;
|
||||
ATrack endTrack;
|
||||
|
||||
if (!aTracks.TryGetValue(s.FirstTrack, out startTrack))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!aTracks.TryGetValue(s.LastTrack, out endTrack))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
session.StartSector = startTrack.PLBA;
|
||||
session.StartTrack = s.FirstTrack;
|
||||
session.SessionSequence = s.SessionNumber;
|
||||
session.EndSector = endTrack.PLBA + endTrack.ExtraBlock.Sectors - 1;
|
||||
session.EndTrack = s.LastTrack;
|
||||
|
||||
aFile.ParsedSession.Add(session);
|
||||
}
|
||||
|
||||
// now build the TOC object
|
||||
foreach (var se in aFile.ParsedSession)
|
||||
{
|
||||
// get the first and last tracks
|
||||
int sTrack = se.StartTrack;
|
||||
int eTrack = se.EndTrack;
|
||||
|
||||
// get list of all tracks from aTracks for this session
|
||||
var tracks = (from a in aTracks.Values
|
||||
where a.TrackNo >= sTrack || a.TrackNo <= eTrack
|
||||
orderby a.TrackNo
|
||||
select a).ToList();
|
||||
|
||||
// create the TOC entries
|
||||
foreach (var t in tracks)
|
||||
{
|
||||
ATOCEntry toc = new ATOCEntry(t.Point);
|
||||
toc.ADR_Control = t.ADR_Control;
|
||||
toc.AFrame = t.AFrame;
|
||||
toc.AMin = t.AMin;
|
||||
toc.ASec = t.ASec;
|
||||
toc.EntryNum = t.TrackNo;
|
||||
toc.PFrame = t.PFrame;
|
||||
toc.PLBA = Convert.ToInt32(t.PLBA);
|
||||
toc.PMin = t.PMin;
|
||||
toc.Point = t.Point;
|
||||
toc.PSec = t.PSec;
|
||||
toc.SectorSize = t.SectorSize;
|
||||
toc.Zero = t.Zero;
|
||||
toc.TrackOffset = Convert.ToInt64(t.StartOffset);
|
||||
toc.Session = se.SessionSequence;
|
||||
toc.ImageFileNamePaths = t.ImageFileNamePaths;
|
||||
toc.ExtraBlock = t.ExtraBlock;
|
||||
toc.BlobIndex = t.BlobIndex;
|
||||
aFile.TOCEntries.Add(toc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return aFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom session object
|
||||
/// </summary>
|
||||
public class Session
|
||||
{
|
||||
public long StartSector;
|
||||
public int StartTrack;
|
||||
public int SessionSequence;
|
||||
public long EndSector;
|
||||
public int EndTrack;
|
||||
}
|
||||
|
||||
|
||||
public class MDSParseException : Exception
|
||||
{
|
||||
public MDSParseException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
|
||||
public class LoadResults
|
||||
{
|
||||
public List<RawTOCEntry> RawTOCEntries;
|
||||
public AFile ParsedMDSFile;
|
||||
public bool Valid;
|
||||
public Exception FailureException;
|
||||
public string MdsPath;
|
||||
}
|
||||
|
||||
public static LoadResults LoadMDSPath(string path)
|
||||
{
|
||||
LoadResults ret = new LoadResults();
|
||||
ret.MdsPath = path;
|
||||
//ret.MdfPath = Path.ChangeExtension(path, ".mdf");
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path)) throw new MDSParseException("Malformed MDS format: nonexistent MDS file!");
|
||||
|
||||
AFile mdsf;
|
||||
using (var infMDS = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
mdsf = new MDS_Format().Parse(infMDS);
|
||||
|
||||
ret.ParsedMDSFile = mdsf;
|
||||
|
||||
ret.Valid = true;
|
||||
}
|
||||
catch (MDSParseException ex)
|
||||
{
|
||||
ret.FailureException = ex;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Dictionary<int, IBlob> MountBlobs(AFile mdsf, Disc disc)
|
||||
{
|
||||
Dictionary<int, IBlob> BlobIndex = new Dictionary<int, IBlob>();
|
||||
|
||||
int count = 0;
|
||||
foreach (var track in mdsf.Tracks)
|
||||
{
|
||||
foreach (var file in track.ImageFileNamePaths.Distinct())
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
throw new MDSParseException("Malformed MDS format: nonexistent image file: " + file);
|
||||
|
||||
IBlob mdfBlob = null;
|
||||
long mdfLen = -1;
|
||||
|
||||
//mount the file
|
||||
if (mdfBlob == null)
|
||||
{
|
||||
var mdfFile = new Disc.Blob_RawFile() { PhysicalPath = file };
|
||||
mdfLen = mdfFile.Length;
|
||||
mdfBlob = mdfFile;
|
||||
}
|
||||
|
||||
bool dupe = false;
|
||||
foreach (var re in disc.DisposableResources)
|
||||
{
|
||||
if (re.ToString() == mdfBlob.ToString())
|
||||
dupe = true;
|
||||
}
|
||||
|
||||
if (!dupe)
|
||||
{
|
||||
// wrap in zeropadadapter
|
||||
disc.DisposableResources.Add(mdfBlob);
|
||||
BlobIndex[count] = mdfBlob;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BlobIndex;
|
||||
}
|
||||
|
||||
RawTOCEntry EmitRawTOCEntry(ATOCEntry entry)
|
||||
{
|
||||
BCD2 tno, ino;
|
||||
|
||||
//this should actually be zero. im not sure if this is stored as BCD2 or not
|
||||
tno = BCD2.FromDecimal(entry.TrackNo);
|
||||
|
||||
//these are special values.. I think, taken from this:
|
||||
//http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html
|
||||
//the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD.
|
||||
//Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing.
|
||||
ino = BCD2.FromDecimal(entry.Point);
|
||||
if (entry.Point == 0xA0) ino.BCDValue = 0xA0;
|
||||
else if (entry.Point == 0xA1) ino.BCDValue = 0xA1;
|
||||
else if (entry.Point == 0xA2) ino.BCDValue = 0xA2;
|
||||
|
||||
// get ADR & Control from ADR_Control byte
|
||||
byte adrc = Convert.ToByte(entry.ADR_Control);
|
||||
var Control = adrc & 0x0F;
|
||||
var ADR = adrc >> 4;
|
||||
|
||||
var q = new SubchannelQ
|
||||
{
|
||||
q_status = SubchannelQ.ComputeStatus(ADR, (EControlQ)(Control & 0xF)),
|
||||
q_tno = tno,
|
||||
q_index = ino,
|
||||
min = BCD2.FromDecimal(entry.AMin),
|
||||
sec = BCD2.FromDecimal(entry.ASec),
|
||||
frame = BCD2.FromDecimal(entry.AFrame),
|
||||
zero = (byte)entry.Zero,
|
||||
ap_min = BCD2.FromDecimal(entry.PMin),
|
||||
ap_sec = BCD2.FromDecimal(entry.PSec),
|
||||
ap_frame = BCD2.FromDecimal(entry.PFrame),
|
||||
q_crc = 0, //meaningless
|
||||
};
|
||||
|
||||
return new RawTOCEntry { QData = q };
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Loads a MDS at the specified path to a Disc object
|
||||
/// </summary>
|
||||
public Disc LoadMDSToDisc(string mdsPath, DiscMountPolicy IN_DiscMountPolicy)
|
||||
{
|
||||
var loadResults = LoadMDSPath(mdsPath);
|
||||
if (!loadResults.Valid)
|
||||
throw loadResults.FailureException;
|
||||
|
||||
Disc disc = new Disc();
|
||||
|
||||
// load all blobs
|
||||
Dictionary<int, IBlob> BlobIndex = MountBlobs(loadResults.ParsedMDSFile, disc);
|
||||
|
||||
var mdsf = loadResults.ParsedMDSFile;
|
||||
|
||||
//generate DiscTOCRaw items from the ones specified in the MDS file
|
||||
disc.RawTOCEntries = new List<RawTOCEntry>();
|
||||
foreach (var entry in mdsf.TOCEntries)
|
||||
{
|
||||
disc.RawTOCEntries.Add(EmitRawTOCEntry(entry));
|
||||
}
|
||||
|
||||
//analyze the RAWTocEntries to figure out what type of track track 1 is
|
||||
var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job() { Entries = disc.RawTOCEntries };
|
||||
tocSynth.Run();
|
||||
|
||||
// now build the sectors
|
||||
int currBlobIndex = 0;
|
||||
foreach (var session in mdsf.ParsedSession)
|
||||
{
|
||||
for (int i = session.StartTrack; i <= session.EndTrack; i++)
|
||||
{
|
||||
int relMSF = -1;
|
||||
|
||||
var track = mdsf.TOCEntries.Where(t => t.Point == i).FirstOrDefault();
|
||||
if (track == null)
|
||||
break;
|
||||
|
||||
// ignore the info entries
|
||||
if (track.Point == 0xA0 ||
|
||||
track.Point == 0xA1 ||
|
||||
track.Point == 0xA2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the blob(s) for this track
|
||||
// its probably a safe assumption that there will be only one blob per track,
|
||||
// but i'm still not 100% sure on this
|
||||
var tr = (from a in mdsf.TOCEntries
|
||||
where a.Point == i
|
||||
select a).FirstOrDefault();
|
||||
|
||||
if (tr == null)
|
||||
throw new MDSParseException("BLOB Error!");
|
||||
|
||||
List<string> blobstrings = new List<string>();
|
||||
foreach (var t in tr.ImageFileNamePaths)
|
||||
{
|
||||
if (!blobstrings.Contains(t))
|
||||
blobstrings.Add(t);
|
||||
}
|
||||
|
||||
var tBlobs = (from a in tr.ImageFileNamePaths
|
||||
select a).ToList();
|
||||
|
||||
if (tBlobs.Count < 1)
|
||||
throw new MDSParseException("BLOB Error!");
|
||||
|
||||
// is the currBlob valid for this track, or do we need to increment?
|
||||
string bString = tBlobs.First();
|
||||
|
||||
IBlob mdfBlob = null;
|
||||
|
||||
// check for track pregap and create if neccessary
|
||||
// this is specified in the track extras block
|
||||
if (track.ExtraBlock.Pregap > 0)
|
||||
{
|
||||
CUE.CueTrackType pregapTrackType = CUE.CueTrackType.Audio;
|
||||
if (tocSynth.Result.TOCItems[1].IsData)
|
||||
{
|
||||
if (tocSynth.Result.Session1Format == SessionFormat.Type20_CDXA)
|
||||
pregapTrackType = CUE.CueTrackType.Mode2_2352;
|
||||
else if (tocSynth.Result.Session1Format == SessionFormat.Type10_CDI)
|
||||
pregapTrackType = CUE.CueTrackType.CDI_2352;
|
||||
else if (tocSynth.Result.Session1Format == SessionFormat.Type00_CDROM_CDDA)
|
||||
pregapTrackType = CUE.CueTrackType.Mode1_2352;
|
||||
}
|
||||
for (int pre = 0; pre < track.ExtraBlock.Pregap; pre++)
|
||||
{
|
||||
relMSF++;
|
||||
|
||||
var ss_gap = new CUE.SS_Gap()
|
||||
{
|
||||
Policy = IN_DiscMountPolicy,
|
||||
TrackType = pregapTrackType
|
||||
};
|
||||
disc._Sectors.Add(ss_gap);
|
||||
|
||||
int qRelMSF = pre - Convert.ToInt32(track.ExtraBlock.Pregap);
|
||||
|
||||
//tweak relMSF due to ambiguity/contradiction in yellowbook docs
|
||||
if (!IN_DiscMountPolicy.CUE_PregapContradictionModeA)
|
||||
qRelMSF++;
|
||||
|
||||
//setup subQ
|
||||
byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption:
|
||||
ss_gap.sq.SetStatus(ADR, tocSynth.Result.TOCItems[1].Control);
|
||||
ss_gap.sq.q_tno = BCD2.FromDecimal(1);
|
||||
ss_gap.sq.q_index = BCD2.FromDecimal(0);
|
||||
ss_gap.sq.AP_Timestamp = pre;
|
||||
ss_gap.sq.Timestamp = qRelMSF;
|
||||
|
||||
//setup subP
|
||||
ss_gap.Pause = true;
|
||||
}
|
||||
// pregap processing completed
|
||||
}
|
||||
|
||||
|
||||
|
||||
// create track sectors
|
||||
long currBlobOffset = track.TrackOffset;
|
||||
for (long sector = session.StartSector; sector <= session.EndSector; sector++)
|
||||
{
|
||||
CUE.SS_Base sBase = null;
|
||||
|
||||
// get the current blob from the BlobIndex
|
||||
Disc.Blob_RawFile currBlob = BlobIndex[currBlobIndex] as Disc.Blob_RawFile;
|
||||
long currBlobLength = currBlob.Length;
|
||||
long currBlobPosition = sector;
|
||||
if (currBlobPosition == currBlobLength)
|
||||
currBlobIndex++;
|
||||
mdfBlob = disc.DisposableResources[currBlobIndex] as Disc.Blob_RawFile;
|
||||
|
||||
int userSector = 2048;
|
||||
switch (track.SectorSize)
|
||||
{
|
||||
case 2448:
|
||||
sBase = new CUE.SS_2352()
|
||||
{
|
||||
Policy = IN_DiscMountPolicy
|
||||
};
|
||||
userSector = 2352;
|
||||
break;
|
||||
case 2048:
|
||||
default:
|
||||
sBase = new CUE.SS_Mode1_2048()
|
||||
{
|
||||
Policy = IN_DiscMountPolicy
|
||||
};
|
||||
userSector = 2048;
|
||||
break;
|
||||
|
||||
//throw new Exception("Not supported: Sector Size " + track.SectorSize);
|
||||
}
|
||||
|
||||
// configure blob
|
||||
sBase.Blob = mdfBlob;
|
||||
sBase.BlobOffset = currBlobOffset;
|
||||
|
||||
currBlobOffset += track.SectorSize; // userSector;
|
||||
|
||||
// add subchannel data
|
||||
relMSF++;
|
||||
BCD2 tno, ino;
|
||||
|
||||
//this should actually be zero. im not sure if this is stored as BCD2 or not
|
||||
tno = BCD2.FromDecimal(track.TrackNo);
|
||||
|
||||
//these are special values.. I think, taken from this:
|
||||
//http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html
|
||||
//the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD.
|
||||
//Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing.
|
||||
ino = BCD2.FromDecimal(track.Point);
|
||||
if (track.Point == 0xA0) ino.BCDValue = 0xA0;
|
||||
else if (track.Point == 0xA1) ino.BCDValue = 0xA1;
|
||||
else if (track.Point == 0xA2) ino.BCDValue = 0xA2;
|
||||
|
||||
// get ADR & Control from ADR_Control byte
|
||||
byte adrc = Convert.ToByte(track.ADR_Control);
|
||||
var Control = adrc & 0x0F;
|
||||
var ADR = adrc >> 4;
|
||||
|
||||
var q = new SubchannelQ
|
||||
{
|
||||
q_status = SubchannelQ.ComputeStatus(ADR, (EControlQ)(Control & 0xF)),
|
||||
q_tno = BCD2.FromDecimal(track.Point),
|
||||
q_index = ino,
|
||||
AP_Timestamp = disc._Sectors.Count,
|
||||
Timestamp = relMSF - Convert.ToInt32(track.ExtraBlock.Pregap)
|
||||
};
|
||||
|
||||
sBase.sq = q;
|
||||
|
||||
disc._Sectors.Add(sBase);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return disc;
|
||||
}
|
||||
|
||||
} //class MDS_Format
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +187,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
CCD_Format ccdLoader = new CCD_Format();
|
||||
OUT_Disc = ccdLoader.LoadCCDToDisc(IN_FromPath, IN_DiscMountPolicy);
|
||||
}
|
||||
else if (ext == ".mds")
|
||||
{
|
||||
MDS_Format mdsLoader = new MDS_Format();
|
||||
OUT_Disc = mdsLoader.LoadMDSToDisc(IN_FromPath, IN_DiscMountPolicy);
|
||||
}
|
||||
|
||||
|
||||
DONE:
|
||||
|
|
Loading…
Reference in New Issue