change analyze stage to be a compile stage, which digests the commands as well as analyzing the files and gives an area for extra validation separate from the loading stage

This commit is contained in:
zeromus 2015-06-28 17:27:21 -05:00
parent 8b0593dcb2
commit 3c26d48a59
6 changed files with 641 additions and 466 deletions

View File

@ -1,313 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
{
public class AnalyzeCueJob : LoggedJob
{
/// <summary>
/// input: the CueFile to analyze
/// </summary>
public CueFile IN_CueFile;
/// <summary>
/// An integer between 0 and 10 indicating how costly it will be to load this disc completely.
/// Activites like decoding non-seekable media will increase the load time.
/// 0 - Requires no noticeable time
/// 1 - Requires minimal processing (indexing ECM)
/// 10 - Requires ages, decoding audio data, etc.
/// </summary>
public int OUT_LoadTime;
/// <summary>
/// Analyzed-information about a file member of a cue
/// </summary>
public class CueFileInfo
{
public string FullPath;
public CueFileType Type;
public override string ToString()
{
return string.Format("{0}: {1}", Type, Path.GetFileName(FullPath));
}
}
/// <summary>
/// What type of file we're looking at.. each one would require a different ingestion handler
/// </summary>
public enum CueFileType
{
Unknown,
/// <summary>
/// a raw BIN that can be mounted directly
/// </summary>
BIN,
/// <summary>
/// a raw WAV that can be mounted directly
/// </summary>
WAVE,
/// <summary>
/// an ECM file that can be mounted directly (once the index is generated)
/// </summary>
ECM,
/// <summary>
/// An encoded audio file which can be seeked on the fly, therefore roughly mounted on the fly
/// THIS ISN'T SUPPORTED YET
/// </summary>
SeekAudio,
/// <summary>
/// An encoded audio file which can't be seeked on the fly. It must be decoded to a temp buffer, or pre-discohawked
/// </summary>
DecodeAudio,
}
/// <summary>
/// For each file referenced by the cue file, info about it
/// </summary>
public List<CueFileInfo> OUT_FileInfos;
}
public class CueFileResolver
{
public bool caseSensitive = false;
public bool IsHardcodedResolve { get; private set; }
string baseDir;
/// <summary>
/// Retrieving the FullName from a FileInfo can be slow (and probably other operations), so this will cache all the needed values
/// TODO - could we treat it like an actual cache and only fill the FullName if it's null?
/// </summary>
struct MyFileInfo
{
public string FullName;
public FileInfo FileInfo;
}
DirectoryInfo diBasedir;
MyFileInfo[] fisBaseDir;
/// <summary>
/// sets the base directory and caches the list of files in the directory
/// </summary>
public void SetBaseDirectory(string baseDir)
{
this.baseDir = baseDir;
diBasedir = new DirectoryInfo(baseDir);
//list all files, so we dont scan repeatedly.
fisBaseDir = MyFileInfosFromFileInfos(diBasedir.GetFiles());
}
/// <summary>
/// TODO - doesnt seem like we're using this...
/// </summary>
public void SetHardcodeResolve(IDictionary<string, string> hardcodes)
{
IsHardcodedResolve = true;
fisBaseDir = new MyFileInfo[hardcodes.Count];
int i = 0;
foreach (var kvp in hardcodes)
{
fisBaseDir[i++] = new MyFileInfo { FullName = kvp.Key, FileInfo = new FileInfo(kvp.Value) };
}
}
MyFileInfo[] MyFileInfosFromFileInfos(FileInfo[] fis)
{
var myfis = new MyFileInfo[fis.Length];
for (int i = 0; i < fis.Length; i++)
{
myfis[i].FileInfo = fis[i];
myfis[i].FullName = fis[i].FullName;
}
return myfis;
}
/// <summary>
/// Performs cue-intelligent logic to acquire a file requested by the cue.
/// Returns the resulting full path(s).
/// If there are multiple options, it returns them all
/// </summary>
public List<string> Resolve(string path)
{
string targetFile = Path.GetFileName(path);
string targetFragment = Path.GetFileNameWithoutExtension(path);
DirectoryInfo di = null;
MyFileInfo[] fileInfos;
if (!string.IsNullOrEmpty(Path.GetDirectoryName(path)))
{
di = new FileInfo(path).Directory;
//fileInfos = di.GetFiles(Path.GetFileNameWithoutExtension(path)); //does this work?
fileInfos = MyFileInfosFromFileInfos(di.GetFiles()); //we (probably) have to enumerate all the files to do a search anyway, so might as well do this
//TODO - dont do the search until a resolve fails
}
else
{
di = diBasedir;
fileInfos = fisBaseDir;
}
var results = new List<FileInfo>();
foreach (var fi in fileInfos)
{
var ext = Path.GetExtension(fi.FullName).ToLowerInvariant();
//some choices are always bad: (we're looking for things like .bin and .wav)
//it's a little unclear whether we should go for a whitelist or a blacklist here.
//there's similar numbers of cases either way.
//perhaps we could code both (and prefer choices from the whitelist)
if (ext == ".cue" || ext == ".sbi" || ext == ".ccd" || ext == ".sub")
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.FileInfo);
}
var ret = new List<string>();
foreach (var fi in results)
ret.Add(fi.FullName);
return ret;
}
}
/// <summary>
/// Analyzes a cue file and its dependencies.
/// This should run as fast as possible.. no deep inspection of files
/// </summary>
public void AnalyzeCueFile(AnalyzeCueJob job)
{
//TODO - handle CD text file
job.OUT_FileInfos = new List<AnalyzeCueJob.CueFileInfo>();
var cue = job.IN_CueFile;
//first, collect information about all the input files
foreach (var cmd in cue.Commands)
{
var f = cmd as CueFile.Command.FILE;
if (f == null) continue;
//TODO TODO TODO TODO
//TODO TODO TODO TODO
//TODO TODO TODO TODO
//smart audio file resolving only for AUDIO types. not BINARY or MOTOROLA or AIFF or ECM or what have you
var options = Resolver.Resolve(f.Path);
string choice = null;
if (options.Count == 0)
{
job.Error("Couldn't resolve referenced cue file: " + f.Path);
continue;
}
else
{
choice = options[0];
if (options.Count > 1)
job.Warn("Multiple options resolving referenced cue file; choosing: " + Path.GetFileName(choice));
}
var cfi = new AnalyzeCueJob.CueFileInfo();
job.OUT_FileInfos.Add(cfi);
cfi.FullPath = choice;
//determine the CueFileInfo's type, based on extension and extra checking
//TODO - once we reorganize the file ID stuff, do legit checks here (this is completely redundant with the fileID system
//TODO - decode vs stream vs unpossible policies in input policies object (including ffmpeg availability-checking callback (results can be cached))
string blobPathExt = Path.GetExtension(choice).ToUpperInvariant();
if (blobPathExt == ".BIN" || blobPathExt == ".IMG") cfi.Type = AnalyzeCueJob.CueFileType.BIN;
else if (blobPathExt == ".ISO") cfi.Type = AnalyzeCueJob.CueFileType.BIN;
else if (blobPathExt == ".WAV")
{
//quickly, check the format. turn it to DecodeAudio if it can't be supported
//TODO - fix exception-throwing inside
//TODO - verify stream-disposing semantics
var fs = File.OpenRead(choice);
using (var blob = new Disc.Blob_WaveFile())
{
try
{
blob.Load(fs);
cfi.Type = AnalyzeCueJob.CueFileType.WAVE;
}
catch
{
cfi.Type = AnalyzeCueJob.CueFileType.DecodeAudio;
}
}
}
else if (blobPathExt == ".APE") cfi.Type = AnalyzeCueJob.CueFileType.DecodeAudio;
else if (blobPathExt == ".MP3") cfi.Type = AnalyzeCueJob.CueFileType.DecodeAudio;
else if (blobPathExt == ".MPC") cfi.Type = AnalyzeCueJob.CueFileType.DecodeAudio;
else if (blobPathExt == ".FLAC") cfi.Type = AnalyzeCueJob.CueFileType.DecodeAudio;
else if (blobPathExt == ".ECM")
{
cfi.Type = AnalyzeCueJob.CueFileType.ECM;
if (!Disc.Blob_ECM.IsECM(choice))
{
job.Error("an ECM file was specified or detected, but it isn't a valid ECM file: " + Path.GetFileName(choice));
cfi.Type = AnalyzeCueJob.CueFileType.Unknown;
}
}
else
{
job.Error("Unknown cue file type. Since it's likely an unsupported compression, this is an error: ", Path.GetFileName(choice));
cfi.Type = AnalyzeCueJob.CueFileType.Unknown;
}
}
//TODO - check for mismatches between track types and file types, or is that best done when interpreting the commands?
//some quick checks:
if (job.OUT_FileInfos.Count == 0)
job.Error("Cue file doesn't specify any input files!");
//we can't readily analyze the length of files here, because we'd have to be interpreting the commands to know the track types. Not really worth the trouble
//we could check the format of the wav file here, though
//score the cost of loading the file
bool needsCodec = false;
job.OUT_LoadTime = 0;
foreach (var cfi in job.OUT_FileInfos)
{
if (cfi.Type == AnalyzeCueJob.CueFileType.DecodeAudio)
{
needsCodec = true;
job.OUT_LoadTime = Math.Max(job.OUT_LoadTime, 10);
}
if (cfi.Type == AnalyzeCueJob.CueFileType.SeekAudio)
needsCodec = true;
if (cfi.Type == AnalyzeCueJob.CueFileType.ECM)
job.OUT_LoadTime = Math.Max(job.OUT_LoadTime, 1);
}
//check whether processing was available
if (needsCodec)
{
FFMpeg ffmpeg = new FFMpeg();
if (!ffmpeg.QueryServiceAvailable())
job.Warn("Decoding service will be required for further processing, but is not available");
}
job.FinishLog();
}
} //partial class
} //namespace

View File

@ -0,0 +1,398 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
//this would be a good place for structural validation
//after this step, we won't want to have to do stuff like that (it will gunk up already sticky code)
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
{
internal class CompiledCDText
{
public string Songwriter;
public string Performer;
public string Title;
public string ISRC;
}
internal class CompiledCueIndex
{
public int Number;
public Timestamp FileMSF;
public override string ToString()
{
return string.Format("I#{0:D2} {1}", Number, FileMSF);
}
}
/// <summary>
/// What type of file we're looking at.. each one would require a different ingestion handler
/// </summary>
public enum CompiledCueFileType
{
Unknown,
/// <summary>
/// a raw BIN that can be mounted directly
/// </summary>
BIN,
/// <summary>
/// a raw WAV that can be mounted directly
/// </summary>
WAVE,
/// <summary>
/// an ECM file that can be mounted directly (once the index is generated)
/// </summary>
ECM,
/// <summary>
/// An encoded audio file which can be seeked on the fly, therefore roughly mounted on the fly
/// THIS ISN'T SUPPORTED YET
/// </summary>
SeekAudio,
/// <summary>
/// An encoded audio file which can't be seeked on the fly. It must be decoded to a temp buffer, or pre-discohawked
/// </summary>
DecodeAudio,
}
internal class CompiledCueFile
{
public string FullPath;
public CompiledCueFileType Type;
public override string ToString()
{
return string.Format("{0}: {1}", Type, Path.GetFileName(FullPath));
}
}
internal class CompiledDiscInfo
{
public int FirstRecordedTrackNumber, LastRecordedTrackNumber;
public DiscTOCRaw.SessionFormat SessionFormat;
}
internal class CompiledCueTrack
{
public int BlobIndex;
public int Number;
public CompiledCDText CDTextData = new CompiledCDText();
public Timestamp PregapLength, PostgapLength;
public CueFile.TrackFlags Flags = CueFile.TrackFlags.None;
public CueFile.TrackType TrackType = CueFile.TrackType.Unknown;
public List<CompiledCueIndex> Indexes = new List<CompiledCueIndex>();
public override string ToString()
{
var idx = Indexes.Find((i) => i.Number == 1);
if (idx == null)
return string.Format("T#{0:D2} NO INDEX 1", Number);
else
{
var indexlist = string.Join("|", Indexes);
return string.Format("T#{0:D2} {1}:{2} ({3})", Number, BlobIndex, idx.FileMSF, indexlist);
}
}
}
internal class CompileCueJob : LoggedJob
{
/// <summary>
/// input: the CueFile to analyze
/// </summary>
public CueFile IN_CueFile;
/// <summary>
/// The context used for this compiling job
/// </summary>
public CUE_Format2 IN_CueFormat;
/// <summary>
/// output: high level disc info
/// </summary>
public CompiledDiscInfo OUT_CompiledDiscInfo;
/// <summary>
/// output: CD-Text set at the global level (before any track commands)
/// </summary>
public CompiledCDText OUT_GlobalCDText;
/// <summary>
/// output: The compiled file info
/// </summary>
public List<CompiledCueFile> OUT_CompiledCueFiles;
/// <summary>
/// output: The compiled track info
/// </summary>
public List<CompiledCueTrack> OUT_CompiledCueTracks;
/// <summary>
/// output: An integer between 0 and 10 indicating how costly it will be to load this disc completely.
/// Activites like decoding non-seekable media will increase the load time.
/// 0 - Requires no noticeable time
/// 1 - Requires minimal processing (indexing ECM)
/// 10 - Requires ages, decoding audio data, etc.
/// </summary>
public int OUT_LoadTime;
//-----------------------------------------------------------------
CompiledCDText curr_cdtext;
int curr_blobIndex = -1;
CompiledCueTrack curr_track = null;
bool discinfo_session1Format_determined = false;
void UpdateDiscInfo(CueFile.Command.TRACK trackCommand)
{
if (OUT_CompiledDiscInfo.FirstRecordedTrackNumber == 0)
OUT_CompiledDiscInfo.FirstRecordedTrackNumber = trackCommand.Number;
OUT_CompiledDiscInfo.LastRecordedTrackNumber = trackCommand.Number;
if (!discinfo_session1Format_determined)
{
switch (trackCommand.Type)
{
case CueFile.TrackType.Mode2_2336:
case CueFile.TrackType.Mode2_2352:
OUT_CompiledDiscInfo.SessionFormat = DiscTOCRaw.SessionFormat.Type20_CDXA;
discinfo_session1Format_determined = true;
break;
case CueFile.TrackType.CDI_2336:
case CueFile.TrackType.CDI_2352:
OUT_CompiledDiscInfo.SessionFormat = DiscTOCRaw.SessionFormat.Type10_CDI;
discinfo_session1Format_determined = true;
break;
default:
break;
}
}
}
void AddFile(CueFile.Command.FILE f)
{
curr_blobIndex++;
var Resolver = IN_CueFormat.Resolver;
//TODO - smart audio file resolving only for AUDIO types. not BINARY or MOTOROLA or AIFF or ECM or what have you
var options = Resolver.Resolve(f.Path);
string choice = null;
if (options.Count == 0)
{
Error("Couldn't resolve referenced cue file: " + f.Path);
return;
}
else
{
choice = options[0];
if (options.Count > 1)
Warn("Multiple options resolving referenced cue file; choosing: " + Path.GetFileName(choice));
}
var cfi = new CompiledCueFile();
OUT_CompiledCueFiles.Add(cfi);
cfi.FullPath = choice;
//determine the CueFileInfo's type, based on extension and extra checking
//TODO - once we reorganize the file ID stuff, do legit checks here (this is completely redundant with the fileID system
//TODO - decode vs stream vs unpossible policies in input policies object (including ffmpeg availability-checking callback (results can be cached))
string blobPathExt = Path.GetExtension(choice).ToUpperInvariant();
if (blobPathExt == ".BIN" || blobPathExt == ".IMG") cfi.Type = CompiledCueFileType.BIN;
else if (blobPathExt == ".ISO") cfi.Type = CompiledCueFileType.BIN;
else if (blobPathExt == ".WAV")
{
//quickly, check the format. turn it to DecodeAudio if it can't be supported
//TODO - fix exception-throwing inside
//TODO - verify stream-disposing semantics
var fs = File.OpenRead(choice);
using (var blob = new Disc.Blob_WaveFile())
{
try
{
blob.Load(fs);
cfi.Type = CompiledCueFileType.WAVE;
}
catch
{
cfi.Type = CompiledCueFileType.DecodeAudio;
}
}
}
else if (blobPathExt == ".APE") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".MP3") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".MPC") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".FLAC") cfi.Type = CompiledCueFileType.DecodeAudio;
else if (blobPathExt == ".ECM")
{
cfi.Type = CompiledCueFileType.ECM;
if (!Disc.Blob_ECM.IsECM(choice))
{
Error("an ECM file was specified or detected, but it isn't a valid ECM file: " + Path.GetFileName(choice));
cfi.Type = CompiledCueFileType.Unknown;
}
}
else
{
Error("Unknown cue file type. Since it's likely an unsupported compression, this is an error: ", Path.GetFileName(choice));
cfi.Type = CompiledCueFileType.Unknown;
}
//TODO - check for mismatches between track types and file types, or is that best done when interpreting the commands?
}
void FinalAnalysis()
{
//some quick checks:
if (OUT_CompiledCueFiles.Count == 0)
Error("Cue file doesn't specify any input files!");
//we can't readily analyze the length of files here, because we'd have to be interpreting the commands to know the track types. Not really worth the trouble
//we could check the format of the wav file here, though
//score the cost of loading the file
bool needsCodec = false;
OUT_LoadTime = 0;
foreach (var cfi in OUT_CompiledCueFiles)
{
if (cfi.Type == CompiledCueFileType.DecodeAudio)
{
needsCodec = true;
OUT_LoadTime = Math.Max(OUT_LoadTime, 10);
}
if (cfi.Type == CompiledCueFileType.SeekAudio)
needsCodec = true;
if (cfi.Type == CompiledCueFileType.ECM)
OUT_LoadTime = Math.Max(OUT_LoadTime, 1);
}
//check whether processing was available
if (needsCodec)
{
FFMpeg ffmpeg = new FFMpeg();
if (!ffmpeg.QueryServiceAvailable())
Warn("Decoding service will be required for further processing, but is not available");
}
}
void CloseTrack()
{
if (curr_track == null)
return;
//normalize: if an index 0 is missing, add it here
if (curr_track.Indexes[0].Number != 0)
{
var index0 = new CompiledCueIndex();
var index1 = curr_track.Indexes[1];
index0.Number = 0;
index0.FileMSF = index1.FileMSF;
curr_track.Indexes.Insert(0, index0);
}
OUT_CompiledCueTracks.Add(curr_track);
curr_track = null;
}
void OpenTrack(CueFile.Command.TRACK trackCommand)
{
curr_track = new CompiledCueTrack();
//spill cdtext data into this track
curr_cdtext = curr_track.CDTextData;
curr_track.BlobIndex = curr_blobIndex;
curr_track.Number = trackCommand.Number;
curr_track.TrackType = trackCommand.Type;
//default flags
if (curr_track.TrackType != CueFile.TrackType.Audio)
curr_track.Flags = CueFile.TrackFlags.DATA;
UpdateDiscInfo(trackCommand);
}
void AddIndex(CueFile.Command.INDEX indexCommand)
{
var newindex = new CompiledCueIndex();
newindex.FileMSF = indexCommand.Timestamp;
newindex.Number = indexCommand.Number;
curr_track.Indexes.Add(newindex);
}
public void Run()
{
//params
var cue = IN_CueFile;
OUT_GlobalCDText = new CompiledCDText();
OUT_CompiledDiscInfo = new CompiledDiscInfo();
OUT_CompiledCueFiles = new List<CompiledCueFile>();
OUT_CompiledCueTracks = new List<CompiledCueTrack>();
//global cd text will acquire the cdtext commands set before track commands
curr_cdtext = OUT_GlobalCDText;
for (int i = 0; i < cue.Commands.Count; i++)
{
var cmd = cue.Commands[i];
//these commands get dealt with globally. nothing to be done here
//(but in the future we need to accumulate them into the compile pass output)
if (cmd is CueFile.Command.CATALOG || cmd is CueFile.Command.CDTEXTFILE) continue;
//nothing to be done for comments
if (cmd is CueFile.Command.REM) continue;
if (cmd is CueFile.Command.COMMENT) continue;
//CD-text and related
if (cmd is CueFile.Command.PERFORMER) curr_cdtext.Performer = (cmd as CueFile.Command.PERFORMER).Value;
if (cmd is CueFile.Command.SONGWRITER) curr_cdtext.Songwriter = (cmd as CueFile.Command.SONGWRITER).Value;
if (cmd is CueFile.Command.TITLE) curr_cdtext.Title = (cmd as CueFile.Command.TITLE).Value;
if (cmd is CueFile.Command.ISRC) curr_cdtext.ISRC = (cmd as CueFile.Command.ISRC).Value;
//flags can only be set when a track command is running
if (cmd is CueFile.Command.FLAGS)
{
curr_track.Flags = (cmd as CueFile.Command.FLAGS).Flags;
}
if (cmd is CueFile.Command.TRACK)
{
CloseTrack();
OpenTrack(cmd as CueFile.Command.TRACK);
}
if (cmd is CueFile.Command.FILE)
{
AddFile(cmd as CueFile.Command.FILE);
}
if (cmd is CueFile.Command.INDEX)
{
AddIndex(cmd as CueFile.Command.INDEX);
}
}
CloseTrack();
} //Run()
} //class CompileCueJob
} //partial class CUE_Format2
} //namespace BizHawk.Emulation.DiscSystem

View File

@ -14,7 +14,5 @@ namespace BizHawk.Emulation.DiscSystem
/// The CueFileResolver to be used by this instance
/// </summary>
public CueFileResolver Resolver;
}
}

View File

@ -32,12 +32,12 @@ namespace BizHawk.Emulation.DiscSystem
/// For this job, virtually all nonsense input is treated as errors, but the process will try to recover as best it can.
/// The user should still reject any jobs which generated errors
/// </summary>
public class LoadCueJob : LoggedJob
internal class LoadCueJob : LoggedJob
{
/// <summary>
/// The results of the analysis job, a prerequisite for this
/// The results of the compile job, a prerequisite for this
/// </summary>
public AnalyzeCueJob IN_AnalyzeJob;
public CompileCueJob IN_CompileJob;
/// <summary>
/// The resulting disc
@ -54,10 +54,6 @@ namespace BizHawk.Emulation.DiscSystem
DiscTOCRaw.SessionFormat sloshy_session1Format = DiscTOCRaw.SessionFormat.Type00_CDROM_CDDA;
bool sloshy_session1Format_determined = false;
//current cdtext and ISRC state
string cdtext_songwriter = null, cdtext_performer = null, cdtext_title = null;
string isrc = null;
//current blob file state
int file_cfi_index = -1;
IBlob file_blob = null;
@ -76,33 +72,6 @@ namespace BizHawk.Emulation.DiscSystem
BurnType burntype_current;
Timestamp burn_pregap_timestamp;
//TODO - could this be determiend in an entirely different job from the main TOC entries?
void UpdateSloshyTrackData(CueFile.Command.TRACK track)
{
if (sloshy_firstRecordedTrackNumber == -1)
sloshy_firstRecordedTrackNumber = track.Number;
sloshy_lastRecordedTrackNumber = track.Number;
if(!sloshy_session1Format_determined)
{
switch (track.Type)
{
case CueFile.TrackType.Mode2_2336:
case CueFile.TrackType.Mode2_2352:
sloshy_session1Format = DiscTOCRaw.SessionFormat.Type20_CDXA;
sloshy_session1Format_determined = true;
break;
case CueFile.TrackType.CDI_2336:
case CueFile.TrackType.CDI_2352:
sloshy_session1Format = DiscTOCRaw.SessionFormat.Type10_CDI;
sloshy_session1Format_determined = true;
break;
default:
break;
}
}
}
void BeginBurnPregap()
{
@ -124,50 +93,50 @@ namespace BizHawk.Emulation.DiscSystem
void ProcessFile(CueFile.Command.FILE file)
{
//if we're currently in a file, finish it
if (file_currentCommand != null)
BurnToEOF();
////if we're currently in a file, finish it
//if (file_currentCommand != null)
// BurnToEOF();
//open the new blob
file_currentCommand = file;
file_msf = 0;
var cfi = IN_AnalyzeJob.OUT_FileInfos[++file_cfi_index];
////open the new blob
//file_currentCommand = file;
//file_msf = 0;
//var cfi = IN_CompileJob.OUT_FileInfos[++file_cfi_index];
//mount the file
if (cfi.Type == AnalyzeCueJob.CueFileType.BIN || cfi.Type == AnalyzeCueJob.CueFileType.Unknown)
{
//raw files:
var blob = new Disc.Blob_RawFile { PhysicalPath = cfi.FullPath };
OUT_Disc.DisposableResources.Add(file_blob = blob);
file_len = blob.Length;
}
else if (cfi.Type == AnalyzeCueJob.CueFileType.ECM)
{
var blob = new Disc.Blob_ECM();
OUT_Disc.DisposableResources.Add(file_blob = blob);
blob.Load(cfi.FullPath);
file_len = blob.Length;
}
else if (cfi.Type == AnalyzeCueJob.CueFileType.WAVE)
{
var blob = new Disc.Blob_WaveFile();
OUT_Disc.DisposableResources.Add(file_blob = blob);
blob.Load(cfi.FullPath);
file_len = blob.Length;
}
else if (cfi.Type == AnalyzeCueJob.CueFileType.DecodeAudio)
{
FFMpeg ffmpeg = new FFMpeg();
if (!ffmpeg.QueryServiceAvailable())
{
throw new DiscReferenceException(cfi.FullPath, "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(cfi.FullPath);
var blob = new Disc.Blob_WaveFile();
OUT_Disc.DisposableResources.Add(file_blob = blob);
blob.Load(new MemoryStream(buf));
}
////mount the file
//if (cfi.Type == AnalyzeCueJob.CueFileType.BIN || cfi.Type == AnalyzeCueJob.CueFileType.Unknown)
//{
// //raw files:
// var blob = new Disc.Blob_RawFile { PhysicalPath = cfi.FullPath };
// OUT_Disc.DisposableResources.Add(file_blob = blob);
// file_len = blob.Length;
//}
//else if (cfi.Type == AnalyzeCueJob.CueFileType.ECM)
//{
// var blob = new Disc.Blob_ECM();
// OUT_Disc.DisposableResources.Add(file_blob = blob);
// blob.Load(cfi.FullPath);
// file_len = blob.Length;
//}
//else if (cfi.Type == AnalyzeCueJob.CueFileType.WAVE)
//{
// var blob = new Disc.Blob_WaveFile();
// OUT_Disc.DisposableResources.Add(file_blob = blob);
// blob.Load(cfi.FullPath);
// file_len = blob.Length;
//}
//else if (cfi.Type == AnalyzeCueJob.CueFileType.DecodeAudio)
//{
// FFMpeg ffmpeg = new FFMpeg();
// if (!ffmpeg.QueryServiceAvailable())
// {
// throw new DiscReferenceException(cfi.FullPath, "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(cfi.FullPath);
// var blob = new Disc.Blob_WaveFile();
// OUT_Disc.DisposableResources.Add(file_blob = blob);
// blob.Load(new MemoryStream(buf));
//}
}
void BurnToEOF()
@ -254,97 +223,96 @@ namespace BizHawk.Emulation.DiscSystem
public void Run()
{
//params
var cue = IN_AnalyzeJob.IN_CueFile;
OUT_Disc = new Disc();
////params
//var cue = IN_AnalyzeJob.IN_CueFile;
//OUT_Disc = new Disc();
//add sectors for the "mandatory track 1 pregap", which isn't stored in the CCD file
//THIS IS JUNK. MORE CORRECTLY SYNTHESIZE IT
for (int i = 0; i < 150; i++)
{
var zero_sector = new Sector_Zero();
var zero_subSector = new ZeroSubcodeSector();
var se_leadin = new SectorEntry(zero_sector);
se_leadin.SubcodeSector = zero_subSector;
OUT_Disc.Sectors.Add(se_leadin);
}
////add sectors for the "mandatory track 1 pregap", which isn't stored in the CCD file
////THIS IS JUNK. MORE CORRECTLY SYNTHESIZE IT
//for (int i = 0; i < 150; i++)
//{
// var zero_sector = new Sector_Zero();
// var zero_subSector = new ZeroSubcodeSector();
// var se_leadin = new SectorEntry(zero_sector);
// se_leadin.SubcodeSector = zero_subSector;
// OUT_Disc.Sectors.Add(se_leadin);
//}
//now for the magic. Process commands in order
for (int i = 0; i < cue.Commands.Count; i++)
{
var cmd = cue.Commands[i];
////now for the magic. Process commands in order
//for (int i = 0; i < cue.Commands.Count; i++)
//{
// var cmd = cue.Commands[i];
//these commands get dealt with globally. nothing to be done here
if (cmd is CueFile.Command.CATALOG || cmd is CueFile.Command.CDTEXTFILE) continue;
// //these commands get dealt with globally. nothing to be done here
// if (cmd is CueFile.Command.CATALOG || cmd is CueFile.Command.CDTEXTFILE) continue;
//nothing to be done for comments
if (cmd is CueFile.Command.REM) continue;
if (cmd is CueFile.Command.COMMENT) continue;
// //nothing to be done for comments
// if (cmd is CueFile.Command.REM) continue;
// if (cmd is CueFile.Command.COMMENT) continue;
//handle cdtext and ISRC state updates, theyre kind of like little registers
if (cmd is CueFile.Command.PERFORMER)
cdtext_performer = (cmd as CueFile.Command.PERFORMER).Value;
if (cmd is CueFile.Command.SONGWRITER)
cdtext_songwriter = (cmd as CueFile.Command.SONGWRITER).Value;
if (cmd is CueFile.Command.TITLE)
cdtext_title = (cmd as CueFile.Command.TITLE).Value;
if (cmd is CueFile.Command.ISRC)
isrc = (cmd as CueFile.Command.ISRC).Value;
// //handle cdtext and ISRC state updates, theyre kind of like little registers
// if (cmd is CueFile.Command.PERFORMER)
// cdtext_performer = (cmd as CueFile.Command.PERFORMER).Value;
// if (cmd is CueFile.Command.SONGWRITER)
// cdtext_songwriter = (cmd as CueFile.Command.SONGWRITER).Value;
// if (cmd is CueFile.Command.TITLE)
// cdtext_title = (cmd as CueFile.Command.TITLE).Value;
// if (cmd is CueFile.Command.ISRC)
// isrc = (cmd as CueFile.Command.ISRC).Value;
//flags are also a kind of a register. but the flags value is reset by the track command
if (cmd is CueFile.Command.FLAGS)
{
track_pendingFlags = (cmd as CueFile.Command.FLAGS).Flags;
}
// //flags are also a kind of a register. but the flags value is reset by the track command
// if (cmd is CueFile.Command.FLAGS)
// {
// track_pendingFlags = (cmd as CueFile.Command.FLAGS).Flags;
// }
if (cmd is CueFile.Command.TRACK)
{
var track = cmd as CueFile.Command.TRACK;
// if (cmd is CueFile.Command.TRACK)
// {
// var track = cmd as CueFile.Command.TRACK;
//register the track for further processing when an GENERATION command appears
track_pendingCommand = track;
track_pendingFlags = CueFile.TrackFlags.None;
// //register the track for further processing when an GENERATION command appears
// track_pendingCommand = track;
// track_pendingFlags = CueFile.TrackFlags.None;
UpdateSloshyTrackData(track);
}
// }
if (cmd is CueFile.Command.FILE)
{
ProcessFile(cmd as CueFile.Command.FILE);
}
// if (cmd is CueFile.Command.FILE)
// {
// ProcessFile(cmd as CueFile.Command.FILE);
// }
if (cmd is CueFile.Command.INDEX)
{
ProcessIndex(cmd as CueFile.Command.INDEX);
}
}
// if (cmd is CueFile.Command.INDEX)
// {
// ProcessIndex(cmd as CueFile.Command.INDEX);
// }
//}
BurnToEOF();
//BurnToEOF();
//add RawTOCEntries A0 A1 A2 to round out the TOC
var TOCMiscInfo = new Synthesize_A0A1A2_Job {
IN_FirstRecordedTrackNumber = sloshy_firstRecordedTrackNumber,
IN_LastRecordedTrackNumber = sloshy_lastRecordedTrackNumber,
IN_Session1Format = sloshy_session1Format,
IN_LeadoutTimestamp = new Timestamp(OUT_Disc.Sectors.Count)
};
TOCMiscInfo.Run(OUT_Disc.RawTOCEntries);
////add RawTOCEntries A0 A1 A2 to round out the TOC
//var TOCMiscInfo = new Synthesize_A0A1A2_Job {
// IN_FirstRecordedTrackNumber = sloshy_firstRecordedTrackNumber,
// IN_LastRecordedTrackNumber = sloshy_lastRecordedTrackNumber,
// IN_Session1Format = sloshy_session1Format,
// IN_LeadoutTimestamp = new Timestamp(OUT_Disc.Sectors.Count)
//};
//TOCMiscInfo.Run(OUT_Disc.RawTOCEntries);
//generate the TOCRaw from the RawTocEntries
var tocSynth = new DiscTOCRaw.SynthesizeFromRawTOCEntriesJob() { Entries = OUT_Disc.RawTOCEntries };
tocSynth.Run();
OUT_Disc.TOCRaw = tocSynth.Result;
////generate the TOCRaw from the RawTocEntries
//var tocSynth = new DiscTOCRaw.SynthesizeFromRawTOCEntriesJob() { Entries = OUT_Disc.RawTOCEntries };
//tocSynth.Run();
//OUT_Disc.TOCRaw = tocSynth.Result;
//generate lead-out track with some canned number of sectors
//TODO - move this somewhere else and make it controllable depending on which console is loading up the disc
//TODO - we're not doing this yet
//var synthLeadoutJob = new Disc.SynthesizeLeadoutJob { Disc = disc, Length = 150 };
//synthLeadoutJob.Run();
////generate lead-out track with some canned number of sectors
////TODO - move this somewhere else and make it controllable depending on which console is loading up the disc
////TODO - we're not doing this yet
////var synthLeadoutJob = new Disc.SynthesizeLeadoutJob { Disc = disc, Length = 150 };
////synthLeadoutJob.Run();
//blech, old crap, maybe
OUT_Disc.Structure.Synthesize_TOCPointsFromSessions();
////blech, old crap, maybe
//OUT_Disc.Structure.Synthesize_TOCPointsFromSessions();
FinishLog();
//FinishLog();
} //Run()
} //class LoadCueJob

View File

@ -109,7 +109,7 @@ namespace BizHawk.Emulation.DiscSystem
Mode1_2048, //CDROM Mode1 Data (cooked)
Mode1_2352, //CDROM Mode1 Data (raw)
Mode2_2336, //CDROM-XA Mode2 Data (could contain form 1 or form 2)
Mode2_2352, //CDROM-XA Mode2 Data (raw--whats the reason to distinguish this from the other 2352?)
Mode2_2352, //CDROM-XA Mode2 Data (but there's no reason to distinguish this from Mode1_2352 other than to alert us that the entire session should be XA
CDI_2336, //CDI Mode2 Data
CDI_2352 //CDI Mode2 Data
}

View File

@ -0,0 +1,124 @@
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.Generic;
namespace BizHawk.Emulation.DiscSystem
{
partial class CUE_Format2
{
/// <summary>
/// The CUE module user's hook for controlling how cue member file paths get resolved
/// </summary>
public class CueFileResolver
{
public bool caseSensitive = false;
public bool IsHardcodedResolve { get; private set; }
string baseDir;
/// <summary>
/// Retrieving the FullName from a FileInfo can be slow (and probably other operations), so this will cache all the needed values
/// TODO - could we treat it like an actual cache and only fill the FullName if it's null?
/// </summary>
struct MyFileInfo
{
public string FullName;
public FileInfo FileInfo;
}
DirectoryInfo diBasedir;
MyFileInfo[] fisBaseDir;
/// <summary>
/// sets the base directory and caches the list of files in the directory
/// </summary>
public void SetBaseDirectory(string baseDir)
{
this.baseDir = baseDir;
diBasedir = new DirectoryInfo(baseDir);
//list all files, so we dont scan repeatedly.
fisBaseDir = MyFileInfosFromFileInfos(diBasedir.GetFiles());
}
/// <summary>
/// TODO - doesnt seem like we're using this...
/// </summary>
public void SetHardcodeResolve(IDictionary<string, string> hardcodes)
{
IsHardcodedResolve = true;
fisBaseDir = new MyFileInfo[hardcodes.Count];
int i = 0;
foreach (var kvp in hardcodes)
{
fisBaseDir[i++] = new MyFileInfo { FullName = kvp.Key, FileInfo = new FileInfo(kvp.Value) };
}
}
MyFileInfo[] MyFileInfosFromFileInfos(FileInfo[] fis)
{
var myfis = new MyFileInfo[fis.Length];
for (int i = 0; i < fis.Length; i++)
{
myfis[i].FileInfo = fis[i];
myfis[i].FullName = fis[i].FullName;
}
return myfis;
}
/// <summary>
/// Performs cue-intelligent logic to acquire a file requested by the cue.
/// Returns the resulting full path(s).
/// If there are multiple options, it returns them all
/// </summary>
public List<string> Resolve(string path)
{
string targetFile = Path.GetFileName(path);
string targetFragment = Path.GetFileNameWithoutExtension(path);
DirectoryInfo di = null;
MyFileInfo[] fileInfos;
if (!string.IsNullOrEmpty(Path.GetDirectoryName(path)))
{
di = new FileInfo(path).Directory;
//fileInfos = di.GetFiles(Path.GetFileNameWithoutExtension(path)); //does this work?
fileInfos = MyFileInfosFromFileInfos(di.GetFiles()); //we (probably) have to enumerate all the files to do a search anyway, so might as well do this
//TODO - dont do the search until a resolve fails
}
else
{
di = diBasedir;
fileInfos = fisBaseDir;
}
var results = new List<FileInfo>();
foreach (var fi in fileInfos)
{
var ext = Path.GetExtension(fi.FullName).ToLowerInvariant();
//some choices are always bad: (we're looking for things like .bin and .wav)
//it's a little unclear whether we should go for a whitelist or a blacklist here.
//there's similar numbers of cases either way.
//perhaps we could code both (and prefer choices from the whitelist)
if (ext == ".cue" || ext == ".sbi" || ext == ".ccd" || ext == ".sub")
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.FileInfo);
}
var ret = new List<string>();
foreach (var fi in results)
ret.Add(fi.FullName);
return ret;
}
}
}
}