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:
parent
8b0593dcb2
commit
3c26d48a59
|
@ -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
|
|
@ -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
|
|
@ -14,7 +14,5 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// The CueFileResolver to be used by this instance
|
||||
/// </summary>
|
||||
public CueFileResolver Resolver;
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue