diff --git a/BizHawk.Emulation.DiscSystem/CUE/CUE_Analyze.cs b/BizHawk.Emulation.DiscSystem/CUE/CUE_Analyze.cs deleted file mode 100644 index 31fb4c5263..0000000000 --- a/BizHawk.Emulation.DiscSystem/CUE/CUE_Analyze.cs +++ /dev/null @@ -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 - { - /// - /// input: the CueFile to analyze - /// - public CueFile IN_CueFile; - - /// - /// 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. - /// - public int OUT_LoadTime; - - /// - /// Analyzed-information about a file member of a cue - /// - public class CueFileInfo - { - public string FullPath; - public CueFileType Type; - public override string ToString() - { - return string.Format("{0}: {1}", Type, Path.GetFileName(FullPath)); - } - } - - /// - /// What type of file we're looking at.. each one would require a different ingestion handler - /// - public enum CueFileType - { - Unknown, - - /// - /// a raw BIN that can be mounted directly - /// - BIN, - - /// - /// a raw WAV that can be mounted directly - /// - WAVE, - - /// - /// an ECM file that can be mounted directly (once the index is generated) - /// - ECM, - - /// - /// An encoded audio file which can be seeked on the fly, therefore roughly mounted on the fly - /// THIS ISN'T SUPPORTED YET - /// - SeekAudio, - - /// - /// An encoded audio file which can't be seeked on the fly. It must be decoded to a temp buffer, or pre-discohawked - /// - DecodeAudio, - } - - /// - /// For each file referenced by the cue file, info about it - /// - public List OUT_FileInfos; - } - - public class CueFileResolver - { - public bool caseSensitive = false; - public bool IsHardcodedResolve { get; private set; } - string baseDir; - - /// - /// 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? - /// - struct MyFileInfo - { - public string FullName; - public FileInfo FileInfo; - } - - DirectoryInfo diBasedir; - MyFileInfo[] fisBaseDir; - - /// - /// sets the base directory and caches the list of files in the directory - /// - public void SetBaseDirectory(string baseDir) - { - this.baseDir = baseDir; - diBasedir = new DirectoryInfo(baseDir); - //list all files, so we dont scan repeatedly. - fisBaseDir = MyFileInfosFromFileInfos(diBasedir.GetFiles()); - } - - /// - /// TODO - doesnt seem like we're using this... - /// - public void SetHardcodeResolve(IDictionary 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; - } - - /// - /// 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 - /// - public List 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(); - 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(); - foreach (var fi in results) - ret.Add(fi.FullName); - return ret; - } - } - - /// - /// Analyzes a cue file and its dependencies. - /// This should run as fast as possible.. no deep inspection of files - /// - public void AnalyzeCueFile(AnalyzeCueJob job) - { - //TODO - handle CD text file - - job.OUT_FileInfos = new List(); - - 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 \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/CUE/CUE_Compile.cs b/BizHawk.Emulation.DiscSystem/CUE/CUE_Compile.cs new file mode 100644 index 0000000000..353d4f53a7 --- /dev/null +++ b/BizHawk.Emulation.DiscSystem/CUE/CUE_Compile.cs @@ -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); + } + } + + /// + /// What type of file we're looking at.. each one would require a different ingestion handler + /// + public enum CompiledCueFileType + { + Unknown, + + /// + /// a raw BIN that can be mounted directly + /// + BIN, + + /// + /// a raw WAV that can be mounted directly + /// + WAVE, + + /// + /// an ECM file that can be mounted directly (once the index is generated) + /// + ECM, + + /// + /// An encoded audio file which can be seeked on the fly, therefore roughly mounted on the fly + /// THIS ISN'T SUPPORTED YET + /// + SeekAudio, + + /// + /// An encoded audio file which can't be seeked on the fly. It must be decoded to a temp buffer, or pre-discohawked + /// + 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 Indexes = new List(); + + 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 + { + /// + /// input: the CueFile to analyze + /// + public CueFile IN_CueFile; + + /// + /// The context used for this compiling job + /// + public CUE_Format2 IN_CueFormat; + + /// + /// output: high level disc info + /// + public CompiledDiscInfo OUT_CompiledDiscInfo; + + /// + /// output: CD-Text set at the global level (before any track commands) + /// + public CompiledCDText OUT_GlobalCDText; + + /// + /// output: The compiled file info + /// + public List OUT_CompiledCueFiles; + + /// + /// output: The compiled track info + /// + public List OUT_CompiledCueTracks; + + /// + /// 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. + /// + 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(); + OUT_CompiledCueTracks = new List(); + + //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 \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/CUE/CUE_Format.cs b/BizHawk.Emulation.DiscSystem/CUE/CUE_Format.cs index 764417a342..28baa4ed5a 100644 --- a/BizHawk.Emulation.DiscSystem/CUE/CUE_Format.cs +++ b/BizHawk.Emulation.DiscSystem/CUE/CUE_Format.cs @@ -14,7 +14,5 @@ namespace BizHawk.Emulation.DiscSystem /// The CueFileResolver to be used by this instance /// public CueFileResolver Resolver; - - } } \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs b/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs index 63e3120eb2..0865566a4c 100644 --- a/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs +++ b/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs @@ -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 /// - public class LoadCueJob : LoggedJob + internal class LoadCueJob : LoggedJob { /// - /// The results of the analysis job, a prerequisite for this + /// The results of the compile job, a prerequisite for this /// - public AnalyzeCueJob IN_AnalyzeJob; + public CompileCueJob IN_CompileJob; /// /// 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 diff --git a/BizHawk.Emulation.DiscSystem/CUE/CUE_Parse.cs b/BizHawk.Emulation.DiscSystem/CUE/CUE_Parse.cs index f49099e772..33ced561c9 100644 --- a/BizHawk.Emulation.DiscSystem/CUE/CUE_Parse.cs +++ b/BizHawk.Emulation.DiscSystem/CUE/CUE_Parse.cs @@ -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 } diff --git a/BizHawk.Emulation.DiscSystem/CUE/CueFileResolver.cs b/BizHawk.Emulation.DiscSystem/CUE/CueFileResolver.cs new file mode 100644 index 0000000000..48ecdf7a6e --- /dev/null +++ b/BizHawk.Emulation.DiscSystem/CUE/CueFileResolver.cs @@ -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 + { + /// + /// The CUE module user's hook for controlling how cue member file paths get resolved + /// + public class CueFileResolver + { + public bool caseSensitive = false; + public bool IsHardcodedResolve { get; private set; } + string baseDir; + + /// + /// 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? + /// + struct MyFileInfo + { + public string FullName; + public FileInfo FileInfo; + } + + DirectoryInfo diBasedir; + MyFileInfo[] fisBaseDir; + + /// + /// sets the base directory and caches the list of files in the directory + /// + public void SetBaseDirectory(string baseDir) + { + this.baseDir = baseDir; + diBasedir = new DirectoryInfo(baseDir); + //list all files, so we dont scan repeatedly. + fisBaseDir = MyFileInfosFromFileInfos(diBasedir.GetFiles()); + } + + /// + /// TODO - doesnt seem like we're using this... + /// + public void SetHardcodeResolve(IDictionary 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; + } + + /// + /// 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 + /// + public List 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(); + 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(); + foreach (var fi in results) + ret.Add(fi.FullName); + return ret; + } + } + } +} \ No newline at end of file