cleaning up cue format

This commit is contained in:
zeromus 2015-07-07 23:14:14 -05:00
parent 691531421a
commit e84d212cea
8 changed files with 12 additions and 794 deletions

View File

@ -9,7 +9,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
partial class CUE_Context
{
internal class CompiledCDText
{
@ -131,7 +131,7 @@ namespace BizHawk.Emulation.DiscSystem
/// The context used for this compiling job
/// TODO - rename something like context
/// </summary>
public CUE_Format2 IN_CueFormat;
public CUE_Context IN_CueFormat;
/// <summary>
/// output: high level disc info

View File

@ -8,7 +8,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
public partial class CUE_Format2
public partial class CUE_Context
{
/// <summary>
/// The CueFileResolver to be used by this instance

View File

@ -25,7 +25,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
partial class CUE_Context
{
/// <summary>
/// Loads a cue file into a Disc.

View File

@ -12,7 +12,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
partial class CUE_Context
{
public class ParseCueJob : LoggedJob
{

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
partial class CUE_Context
{
abstract class SS_Base : ISectorSynthJob2448

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
partial class CUE_Context
{
/// <summary>
/// The CUE module user's hook for controlling how cue member file paths get resolved

View File

@ -1,782 +0,0 @@
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
//this rule is not supported correctly: `The first track number can be greater than one, but all track numbers after the first must be sequential.`
namespace BizHawk.Emulation.DiscSystem
{
public class CUE_Format
{
/// <summary>
/// Generates the CUE file for the provided DiscStructure
/// </summary>
public string GenerateCUE_OneBin(DiscStructure structure, CueBinPrefs prefs)
{
if (prefs.OneBlobPerTrack) throw new InvalidOperationException("OneBinPerTrack passed to GenerateCUE_OneBin");
//this generates a single-file cue!!!!!!! dont expect it to generate bin-per-track!
StringBuilder sb = new StringBuilder();
foreach (var session in structure.Sessions)
{
if (!prefs.SingleSession)
{
//dont want to screw around with sessions for now
sb.AppendFormat("SESSION {0:D2}\n", session.num);
if (prefs.AnnotateCue) sb.AppendFormat("REM ; session (length={0})\n", session.length_aba);
}
foreach (var track in session.Tracks)
{
ETrackType trackType = track.TrackType;
//mutate track type according to our principle of canonicalization
if (trackType == ETrackType.Mode1_2048 && prefs.DumpECM)
trackType = ETrackType.Mode1_2352;
sb.AppendFormat(" TRACK {0:D2} {1}\n", track.Number, Cue.TrackTypeStringForTrackType(trackType));
if (prefs.AnnotateCue) sb.AppendFormat(" REM ; track (length={0})\n", track.LengthInSectors);
foreach (var index in track.Indexes)
{
//cue+bin has an implicit 150 sector pregap which neither the cue nor the bin has any awareness of
//except for the baked-in sector addressing.
//but, if there is an extra-long pregap, we want to reflect it this way
int lba = index.aba - 150;
if (lba <= 0 && index.Number == 0 && track.Number == 1)
{
}
//dont emit index 0 when it is the same as index 1, it is illegal for some reason
else if (index.Number == 0 && index.aba == track.Indexes[1].aba)
{
//dont emit index 0 when it is the same as index 1, it confuses some cue parsers
}
else
{
sb.AppendFormat(" INDEX {0:D2} {1}\n", index.Number, new Timestamp(lba).Value);
}
}
}
}
return sb.ToString();
}
}
partial class Disc
{
/// <summary>
/// finds a file in the same directory with an extension alternate to the supplied one.
/// If two are found, an exception is thrown (later, we may have heuristics to try to acquire the desired content)
/// TODO - this whole concept could be turned into a gigantic FileResolver class and be way more powerful
/// </summary>
string FindAlternateExtensionFile(string path, bool caseSensitive, string baseDir)
{
string targetFile = Path.GetFileName(path);
string targetFragment = Path.GetFileNameWithoutExtension(path);
var di = new FileInfo(path).Directory;
//if the directory doesnt exist, it may be because it was a full path or something. try an alternate base directory
if (!di.Exists)
di = new DirectoryInfo(baseDir);
var results = new List<FileInfo>();
foreach (var fi in di.GetFiles())
{
//dont acquire cue files...
if (Path.GetExtension(fi.FullName).ToLower() == ".cue")
continue;
string fragment = Path.GetFileNameWithoutExtension(fi.FullName);
//match files with differing extensions
int cmp = string.Compare(fragment, targetFragment, !caseSensitive);
if(cmp != 0)
//match files with another extension added on (likely to be mygame.bin.ecm)
cmp = string.Compare(fragment, targetFile, !caseSensitive);
if (cmp == 0)
results.Add(fi);
}
if(results.Count == 0) throw new DiscReferenceException(path, "Cannot find the specified file");
if (results.Count > 1) throw new DiscReferenceException(path, "Cannot choose between multiple options");
return results[0].FullName;
}
//cue files can get their data from other sources using this
readonly Dictionary<string, string> CueFileResolver = new Dictionary<string, string>();
void FromCueInternal(Cue cue, string cueDir, CueBinPrefs prefs)
{
//TODO - add cue directory to CueBinPrefs???? could make things cleaner...
Structure = new DiscStructure();
var session = new DiscStructure.Session {num = 1};
Structure.Sessions.Add(session);
var pregap_sector = new Sector_Zero();
int curr_track = 1;
foreach (var cue_file in cue.Files)
{
//structural validation
if (cue_file.Tracks.Count < 1) throw new Cue.CueBrokenException("`You must specify at least one track per file.`");
string blobPath = Path.Combine(cueDir, cue_file.Path);
if (CueFileResolver.ContainsKey(cue_file.Path))
blobPath = CueFileResolver[cue_file.Path];
int blob_sectorsize = Cue.BINSectorSizeForTrackType(cue_file.Tracks[0].TrackType);
int blob_length_aba;
long blob_length_bytes;
IBlob cue_blob;
//try any way we can to acquire a file
if (!File.Exists(blobPath) && prefs.ExtensionAware)
{
blobPath = FindAlternateExtensionFile(blobPath, prefs.CaseSensitive, cueDir);
}
if (!File.Exists(blobPath))
throw new DiscReferenceException(blobPath, "");
//some simple rules to mutate the file type if we received something fishy
string blobPathExt = Path.GetExtension(blobPath).ToLower();
if (blobPathExt == ".ape") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".mp3") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".mpc") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".flac") cue_file.FileType = Cue.CueFileType.Wave;
if (blobPathExt == ".ecm") cue_file.FileType = Cue.CueFileType.ECM;
if (cue_file.FileType == Cue.CueFileType.Binary || cue_file.FileType == Cue.CueFileType.Unspecified)
{
//make a blob for the file
Blob_RawFile blob = new Blob_RawFile {PhysicalPath = blobPath};
Blobs.Add(blob);
blob_length_aba = (int)(blob.Length / blob_sectorsize);
blob_length_bytes = blob.Length;
cue_blob = blob;
}
else if (cue_file.FileType == Cue.CueFileType.ECM)
{
if (!Blob_ECM.IsECM(blobPath))
{
throw new DiscReferenceException(blobPath, "an ECM file was specified or detected, but it isn't a valid ECM file. You've got issues. Consult your iso vendor.");
}
Blob_ECM blob = new Blob_ECM();
Blobs.Add(blob);
blob.Load(blobPath);
cue_blob = blob;
blob_length_aba = (int)(blob.Length / blob_sectorsize);
blob_length_bytes = blob.Length;
}
else if (cue_file.FileType == Cue.CueFileType.Wave)
{
Blob_WaveFile blob = new Blob_WaveFile();
Blobs.Add(blob);
try
{
//check whether we can load the wav directly
bool loaded = false;
if (File.Exists(blobPath) && Path.GetExtension(blobPath).ToUpper() == ".WAV")
{
try
{
blob.Load(blobPath);
loaded = true;
}
catch
{
}
}
//if that didnt work or wasnt possible, try loading it through ffmpeg
if (!loaded)
{
FFMpeg ffmpeg = new FFMpeg();
if (!ffmpeg.QueryServiceAvailable())
{
throw new DiscReferenceException(blobPath, "No decoding service was available (make sure ffmpeg.exe is available. even though this may be a wav, ffmpeg is used to load oddly formatted wave files. If you object to this, please send us a note and we'll see what we can do. It shouldn't be too hard.)");
}
AudioDecoder dec = new AudioDecoder();
byte[] buf = dec.AcquireWaveData(blobPath);
blob.Load(new MemoryStream(buf));
}
}
catch (Exception ex)
{
throw new DiscReferenceException(blobPath, ex);
}
blob_length_aba = (int)(blob.Length / blob_sectorsize);
blob_length_bytes = blob.Length;
cue_blob = blob;
}
else throw new Exception("Internal error - Unhandled cue blob type");
//TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp
//start timekeeping for the blob. every time we hit an index, this will advance
int blob_timestamp = 0;
//because we can have different size sectors in a blob, we need to keep a file cursor within the blob
long blob_cursor = 0;
//the aba that this cue blob starts on
int blob_disc_aba_start = Sectors.Count;
//this is a bit dodgy.. lets fixup the indices so we have something for index 0
//TODO - I WISH WE DIDNT HAVE TO DO THIS. WE SHOULDNT PAY SO MUCH ATTENTION TO THE INTEGRITY OF THE INDEXES
Timestamp blob_ts = new Timestamp(0);
for (int t = 0; t < cue_file.Tracks.Count; t++)
{
var cue_track = cue_file.Tracks[t];
if (!cue_track.Indexes.ContainsKey(1))
throw new Cue.CueBrokenException("Track was missing an index 01");
for (int i = 0; i <= 99; i++)
{
if (cue_track.Indexes.ContainsKey(i))
{
blob_ts = cue_track.Indexes[i].Timestamp;
}
else if (i == 0)
{
var cti = new Cue.CueTrackIndex(0);
cue_track.Indexes[0] = cti;
cti.Timestamp = blob_ts;
}
}
}
//validate that the first index in the file is 00:00:00
//"The first index of a file must start at 00:00:00"
//zero 20-dec-2014 - NOTE - index 0 is OK. we've seen files that 'start' at non-zero but thats only with index 1 -- an index 0 was explicitly listed at time 0
if (cue_file.Tracks[0].Indexes[0].Timestamp.Sector != 0) throw new Cue.CueBrokenException("`The first index of a blob must start at 00:00:00.`");
//for each track within the file:
for (int t = 0; t < cue_file.Tracks.Count; t++)
{
var cue_track = cue_file.Tracks[t];
//record the disc ABA that this track started on
int track_disc_aba_start = Sectors.Count;
//record the pregap location. it will default to the start of the track unless we supplied a pregap command
int track_disc_pregap_aba = track_disc_aba_start;
int blob_track_start = blob_timestamp;
//once upon a time we had a check here to prevent a single blob from containing variant sector sizes. but we support that now.
//check integrity of track sequence and setup data structures
//TODO - check for skipped tracks in cue parser instead
if (cue_track.TrackNum != curr_track) throw new Cue.CueBrokenException("Found a cue with skipped tracks");
var toc_track = new DiscStructure.Track();
session.Tracks.Add(toc_track);
toc_track.Number = curr_track;
toc_track.TrackType = cue_track.TrackType;
toc_track.ADR = 1; //safe assumption. CUE can't store this.
//choose a Control value based on track type and other flags from cue
//TODO - this might need to be controlled by cue loading prefs
toc_track.Control = cue_track.Control;
if (toc_track.TrackType == ETrackType.Audio)
toc_track.Control |= EControlQ.None;
else toc_track.Control |= EControlQ.DATA;
if (curr_track == 1)
{
if (cue_track.PreGap.Sector != 0)
throw new InvalidOperationException("not supported (yet): cue files with track 1 pregaps");
//but now we add one anyway, because every existing cue+bin seems to implicitly specify this
cue_track.PreGap = new Timestamp(150);
}
//check whether a pregap is requested.
//this causes empty sectors to get generated without consuming data from the blob
if (cue_track.PreGap.Sector > 0)
{
for (int i = 0; i < cue_track.PreGap.Sector; i++)
{
Sectors.Add(new SectorEntry(pregap_sector));
}
}
//look ahead to the next track's index 1 so we can see how long this track's last index is
//or, for the last track, use the length of the file
int track_length_aba;
if (t == cue_file.Tracks.Count - 1)
track_length_aba = blob_length_aba - blob_timestamp;
else track_length_aba = cue_file.Tracks[t + 1].Indexes[1].Timestamp.Sector - blob_timestamp;
//toc_track.length_aba = track_length_aba; //xxx
//find out how many indexes we have
int num_indexes = 0;
for (num_indexes = 0; num_indexes <= 99; num_indexes++)
if (!cue_track.Indexes.ContainsKey(num_indexes)) break;
//for each index, calculate length of index and then emit it
for (int index = 0; index < num_indexes; index++)
{
bool is_last_index = index == num_indexes - 1;
//install index into hierarchy
var toc_index = new DiscStructure.Index {Number = index};
toc_track.Indexes.Add(toc_index);
if (index == 0)
{
//zero 18-dec-2014 - uhhhh cant make sense of this.
//toc_index.aba = track_disc_pregap_aba - (cue_track.Indexes[1].Timestamp.Sector - cue_track.Indexes[0].Timestamp.Sector);
toc_index.aba = track_disc_pregap_aba;
}
else toc_index.aba = Sectors.Count;
//calculate length of the index
//if it is the last index then we use our calculation from before, otherwise we check the next index
int index_length_aba;
if (is_last_index)
index_length_aba = track_length_aba - (blob_timestamp - blob_track_start);
else index_length_aba = cue_track.Indexes[index + 1].Timestamp.Sector - blob_timestamp;
//emit sectors
for (int aba = 0; aba < index_length_aba; aba++)
{
bool is_last_aba_in_index = (aba == index_length_aba - 1);
bool is_last_aba_in_track = is_last_aba_in_index && is_last_index;
switch (cue_track.TrackType)
{
case ETrackType.Audio: //all 2352 bytes are present
case ETrackType.Mode1_2352: //2352 bytes are present, containing 2048 bytes of user data as well as ECM
case ETrackType.Mode2_2352: //2352 bytes are present, containing the entirety of a mode2 sector (could be form0,1,2)
{
//these cases are all 2352 bytes
//in all these cases, either no ECM is present or ECM is provided.
//so we just emit a Sector_Raw
Sector_RawBlob sector_rawblob = new Sector_RawBlob
{
Blob = cue_blob,
Offset = blob_cursor
};
blob_cursor += 2352;
Sector_Mode1_or_Mode2_2352 sector_raw;
if(cue_track.TrackType == ETrackType.Mode1_2352)
sector_raw = new Sector_Mode1_2352();
else if (cue_track.TrackType == ETrackType.Audio)
sector_raw = new Sector_Mode1_2352(); //TODO should probably make a new sector adapter which errors if 2048B are requested
else if (cue_track.TrackType == ETrackType.Mode2_2352)
sector_raw = new Sector_Mode2_2352();
else throw new InvalidOperationException();
sector_raw.BaseSector = sector_rawblob;
Sectors.Add(new SectorEntry(sector_raw));
break;
}
case ETrackType.Mode1_2048:
//2048 bytes are present. ECM needs to be generated to create a full sector
{
//ECM needs to know the sector number so we have to record that here
int curr_disc_aba = Sectors.Count;
var sector_2048 = new Sector_Mode1_2048(curr_disc_aba + 150)
{
Blob = new ECMCacheBlob(cue_blob),
Offset = blob_cursor
};
blob_cursor += 2048;
Sectors.Add(new SectorEntry(sector_2048));
break;
}
} //switch(TrackType)
//we've emitted an ABA, so consume it from the blob
blob_timestamp++;
} //aba emit loop
} //index loop
//check whether a postgap is requested. if it is, we need to generate silent sectors
for (int i = 0; i < cue_track.PostGap.Sector; i++)
{
Sectors.Add(new SectorEntry(pregap_sector));
}
//we're done with the track now.
//record its length:
toc_track.LengthInSectors = Sectors.Count - toc_track.Indexes[1].aba;
curr_track++;
//if we ran off the end of the blob, pad it with zeroes, I guess
if (blob_cursor > blob_length_bytes)
{
//mutate the blob to an encapsulating Blob_ZeroPadAdapter
Blobs[Blobs.Count - 1] = new Blob_ZeroPadAdapter(Blobs[Blobs.Count - 1], blob_length_bytes, blob_cursor - blob_length_bytes);
}
} //track loop
} //file loop
//finally, analyze the length of the sessions and the entire disc by summing the lengths of the tracks
//this is a little more complex than it looks, because the length of a thing is not determined by summing it
//but rather by the difference in abas between start and end
//EDIT - or is the above nonsense? it should be the amount of data present, full stop.
Structure.LengthInSectors = 0;
foreach (var toc_session in Structure.Sessions)
{
var firstTrack = toc_session.Tracks[0];
//track 0, index 0 is actually -150. but cue sheets will never say that
//firstTrack.Indexes[0].aba -= 150;
var lastTrack = toc_session.Tracks[toc_session.Tracks.Count - 1];
session.length_aba = lastTrack.Indexes[1].aba + lastTrack.LengthInSectors - firstTrack.Indexes[0].aba;
Structure.LengthInSectors += toc_session.length_aba;
}
}
void FromCuePathInternal(string cuePath, CueBinPrefs prefs)
{
string cueDir = Path.GetDirectoryName(cuePath);
var cue = new Cue();
cue.LoadFromPath(cuePath);
FromCueInternal(cue, cueDir, prefs);
}
}
public class Cue
{
//TODO - export from isobuster and observe the SESSION directive, as well as the MSF directive.
public string DebugPrint()
{
StringBuilder sb = new StringBuilder();
foreach (CueFile cf in Files)
{
sb.AppendFormat("FILE \"{0}\"", cf.Path);
if (cf.FileType == CueFileType.Binary) sb.Append(" BINARY");
if (cf.FileType == CueFileType.Wave) sb.Append(" WAVE");
sb.AppendLine();
foreach (CueTrack ct in cf.Tracks)
{
sb.AppendFormat(" TRACK {0:D2} {1}\n", ct.TrackNum, ct.TrackType.ToString().Replace("_", "/").ToUpper());
foreach (CueTrackIndex cti in ct.Indexes.Values)
{
sb.AppendFormat(" INDEX {0:D2} {1}\n", cti.IndexNum, cti.Timestamp.Value);
}
}
}
return sb.ToString();
}
public enum CueFileType
{
Unspecified, Binary, Wave, ECM
}
public class CueFile
{
public string Path;
public List<CueTrack> Tracks = new List<CueTrack>();
public CueFileType FileType = CueFileType.Unspecified;
public string StrFileType;
}
public List<CueFile> Files = new List<CueFile>();
public static int BINSectorSizeForTrackType(ETrackType type)
{
switch (type)
{
case ETrackType.Mode1_2352:
case ETrackType.Mode2_2352:
case ETrackType.Audio:
return 2352;
case ETrackType.Mode1_2048:
return 2048;
default:
throw new ArgumentOutOfRangeException();
}
}
public static string TrackTypeStringForTrackType(ETrackType type)
{
switch (type)
{
case ETrackType.Mode1_2352: return "MODE1/2352";
case ETrackType.Mode2_2352: return "MODE2/2352";
case ETrackType.Audio: return "AUDIO";
case ETrackType.Mode1_2048: return "MODE1/2048";
default:
throw new ArgumentOutOfRangeException();
}
}
public static string RedumpTypeStringForTrackType(ETrackType type)
{
switch (type)
{
case ETrackType.Mode1_2352: return "Data/Mode 1";
case ETrackType.Mode1_2048: throw new InvalidOperationException("guh dunno what to put here");
case ETrackType.Mode2_2352: return "Data/Mode 2";
case ETrackType.Audio: return "Audio";
default:
throw new ArgumentOutOfRangeException();
}
}
public class CueTrack
{
public EControlQ Control;
public ETrackType TrackType;
public int TrackNum;
public Timestamp PreGap = new Timestamp();
public Timestamp PostGap = new Timestamp();
public Dictionary<int, CueTrackIndex> Indexes = new Dictionary<int, CueTrackIndex>();
}
public class CueTrackIndex
{
public CueTrackIndex(int num) { IndexNum = num; }
public int IndexNum;
/// <summary>
/// Is this an ABA or a LBA? please say.
/// </summary>
public Timestamp Timestamp;
}
[Serializable]
public class CueBrokenException : Exception
{
public CueBrokenException(string why)
: base(why)
{
}
}
public void LoadFromPath(string cuePath)
{
FileInfo fiCue = new FileInfo(cuePath);
if (!fiCue.Exists) throw new FileNotFoundException();
string cueString = File.ReadAllText(cuePath);
LoadFromString(cueString);
}
public void LoadFromString(string cueString)
{
TextReader tr = new StringReader(cueString);
bool track_has_pregap = false;
bool track_has_postgap = false;
int last_index_num = -1;
CueFile currFile = null;
CueTrack currTrack = null;
for (; ; )
{
string line = tr.ReadLine();
if (line == null) break;
line = line.Trim();
if (line == "") continue;
var clp = new CueLineParser(line);
string key = clp.ReadToken().ToUpper();
switch (key)
{
case "REM":
break;
case "FILE":
{
currTrack = null;
currFile = new CueFile();
Files.Add(currFile);
currFile.Path = clp.ReadPath().Trim('"');
if (!clp.EOF)
{
string temp = clp.ReadToken().ToUpper();
switch (temp)
{
case "BINARY":
currFile.FileType = CueFileType.Binary;
break;
case "WAVE":
case "MP3":
currFile.FileType = CueFileType.Wave;
break;
}
currFile.StrFileType = temp;
}
break;
}
case "TRACK":
{
if (currFile == null) throw new CueBrokenException("invalid cue structure");
if (clp.EOF) throw new CueBrokenException("invalid cue structure");
string strtracknum = clp.ReadToken();
int tracknum;
if (!int.TryParse(strtracknum, out tracknum))
throw new CueBrokenException("malformed track number");
if (clp.EOF) throw new CueBrokenException("invalid cue structure");
if (tracknum < 0 || tracknum > 99) throw new CueBrokenException("`All track numbers must be between 1 and 99 inclusive.`");
string strtracktype = clp.ReadToken().ToUpper();
currTrack = new CueTrack();
switch (strtracktype)
{
case "MODE1/2352": currTrack.TrackType = ETrackType.Mode1_2352; break;
case "MODE1/2048": currTrack.TrackType = ETrackType.Mode1_2048; break;
case "MODE2/2352": currTrack.TrackType = ETrackType.Mode2_2352; break;
case "AUDIO": currTrack.TrackType = ETrackType.Audio; break;
default:
throw new CueBrokenException("unhandled track type");
}
currTrack.TrackNum = tracknum;
currFile.Tracks.Add(currTrack);
track_has_pregap = false;
track_has_postgap = false;
last_index_num = -1;
break;
}
case "INDEX":
{
if (currTrack == null) throw new CueBrokenException("invalid cue structure");
if (clp.EOF) throw new CueBrokenException("invalid cue structure");
if (track_has_postgap) throw new CueBrokenException("`The POSTGAP command must appear after all INDEX commands for the current track.`");
string strindexnum = clp.ReadToken();
int indexnum;
if (!int.TryParse(strindexnum, out indexnum))
throw new CueBrokenException("malformed index number");
if (clp.EOF) throw new CueBrokenException("invalid cue structure (missing index timestamp)");
string str_timestamp = clp.ReadToken();
if (indexnum < 0 || indexnum > 99) throw new CueBrokenException("`All index numbers must be between 0 and 99 inclusive.`");
if (indexnum != 1 && indexnum != last_index_num + 1) throw new CueBrokenException("`The first index must be 0 or 1 with all other indexes being sequential to the first one.`");
last_index_num = indexnum;
CueTrackIndex cti = new CueTrackIndex(indexnum)
{
Timestamp = new Timestamp(str_timestamp), IndexNum = indexnum
};
currTrack.Indexes[indexnum] = cti;
break;
}
case "PREGAP":
if (track_has_pregap) throw new CueBrokenException("`Only one PREGAP command is allowed per track.`");
if (currTrack.Indexes.Count > 0) throw new CueBrokenException("`The PREGAP command must appear after a TRACK command, but before any INDEX commands.`");
currTrack.PreGap = new Timestamp(clp.ReadToken());
track_has_pregap = true;
break;
case "POSTGAP":
if (track_has_postgap) throw new CueBrokenException("`Only one POSTGAP command is allowed per track.`");
track_has_postgap = true;
currTrack.PostGap = new Timestamp(clp.ReadToken());
break;
case "CATALOG":
case "PERFORMER":
case "SONGWRITER":
case "TITLE":
case "ISRC":
case "FLAGS":
//TODO - keep these for later?
//known flags:
//FLAGS DCP
{
var flags = clp.ReadToken();
if (flags == "DCP")
{
currTrack.Control |= EControlQ.DCP;
} else throw new CueBrokenException("Unknown flags: " + flags);
}
break;
default:
throw new CueBrokenException("unsupported cue command: " + key);
}
} //end cue parsing loop
}
class CueLineParser
{
int index;
string str;
public bool EOF;
public CueLineParser(string line)
{
str = line;
}
public string ReadPath() { return ReadToken(true); }
public string ReadToken() { return ReadToken(false); }
public string ReadToken(bool isPath)
{
if (EOF) return null;
int startIndex = index;
bool inToken = false;
bool inQuote = false;
for (; ; )
{
bool done = false;
char c = str[index];
bool isWhiteSpace = (c == ' ' || c == '\t');
if (isWhiteSpace)
{
if (inQuote)
index++;
else
{
if (inToken)
done = true;
else
index++;
}
}
else
{
bool startedQuote = false;
if (!inToken)
{
startIndex = index;
if (isPath && c == '"')
startedQuote = inQuote = true;
inToken = true;
}
switch (str[index])
{
case '"':
index++;
if (inQuote && !startedQuote)
{
done = true;
}
break;
case '\\':
index++;
break;
default:
index++;
break;
}
}
if (index == str.Length)
{
EOF = true;
done = true;
}
if (done) break;
}
return str.Substring(startIndex, index - startIndex);
}
}
}
}

View File

@ -110,7 +110,7 @@ namespace BizHawk.Emulation.DiscSystem
string infile = IN_FromPath;
string cue_content = null;
var cfr = new CUE_Format2.CueFileResolver();
var cfr = new CUE_Context.CueFileResolver();
RERUN:
var ext = Path.GetExtension(infile).ToLowerInvariant();
@ -132,14 +132,14 @@ namespace BizHawk.Emulation.DiscSystem
//TODO - make sure code is designed so no matter what happens, a disc is disposed in case of errors.
//perhaps the CUE_Format2 (once renamed to something like Context) can handle that
var cuePath = IN_FromPath;
var cue2 = new CUE_Format2();
var cue2 = new CUE_Context();
cue2.DiscMountPolicy = IN_DiscMountPolicy;
cue2.Resolver = cfr;
if (!cfr.IsHardcodedResolve) cfr.SetBaseDirectory(Path.GetDirectoryName(infile));
//parse the cue file
var parseJob = new CUE_Format2.ParseCueJob();
var parseJob = new CUE_Context.ParseCueJob();
if (cue_content == null)
cue_content = File.ReadAllText(cuePath);
parseJob.IN_CueString = cue_content;
@ -150,7 +150,7 @@ namespace BizHawk.Emulation.DiscSystem
//compile the cue file:
//includes this work: resolve required bin files and find out what it's gonna take to load the cue
var compileJob = new CUE_Format2.CompileCueJob();
var compileJob = new CUE_Context.CompileCueJob();
compileJob.IN_CueFormat = cue2;
compileJob.IN_CueFile = parseJob.OUT_CueFile;
compileJob.Run();
@ -166,7 +166,7 @@ namespace BizHawk.Emulation.DiscSystem
}
//actually load it all up
var loadJob = new CUE_Format2.LoadCueJob();
var loadJob = new CUE_Context.LoadCueJob();
loadJob.IN_CompileJob = compileJob;
loadJob.Run();
//TODO - need better handling of log output