diff --git a/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs b/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs index d3fc01cd82..63e3120eb2 100644 --- a/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs +++ b/BizHawk.Emulation.DiscSystem/CUE/CUE_Load.cs @@ -4,6 +4,18 @@ //TODO //check for flags changing after a PREGAP is processed. the PREGAP can't correctly run if the flags aren't set +//IN GENERAL: validate more pedantically (but that code gets in the way majorly) +// - perhaps isolate validation checks into a different pass distinct from a Burn pass + +//NEW IDEA: +//a cue file is a compressed representation of a more verbose format which is easier to understand +//most fundamentally, it is organized with TRACK and INDEX commands alternating. +//these should be flattened into individual records with CURRTRACK and CURRINDEX fields. +//more generally, it's organized with 'register' settings and INDEX commands alternating. +//whenever an INDEX command is received from the cue file, individual flattened records are written with the current 'register' settings +//and an incrementing timestamp until the INDEX command appears (or the EOF happens) +//PREGAP commands are special : at the moment it is received, emit flat records with a different pregap structure +//POSTGAP commands are special : TBD using System; using System.Linq; @@ -31,868 +43,310 @@ namespace BizHawk.Emulation.DiscSystem /// The resulting disc /// public Disc OUT_Disc; - } - private enum SectorWriteType - { - Normal, Pregap, Postgap - } - - public class CDTextData - { - public string Songwriter; - public string Performer; - public string Title; - } - - /// - /// Represents a significant event in the structure of the disc which you might encounter while reading it sequentially. - /// - public class DiscPoint - { - /// - /// The Absolute LBA of this event - /// - public int AbsoluteLBA; - - /// - /// The relative LBA of this event -- in other words, relative to Index 1. - /// This would be used for starting the pregap relative addressing. - /// - public int RelativeLBA; - - /// - /// Track number at this point - /// - public int TrackNumber; - - /// - /// Index number at this point. - /// If it's 0, we'll be in a pregap - /// - public int IndexNumber; - - /// - /// Q status at this point - /// - public BCD2 Q_Status; - - /// - /// The CD Text information at this point - /// - public CDTextData CDText = new CDTextData(); - - /// - /// The ISRC code (if present) at this point. null if not present. - /// - public string ISRC; - } - - - class CueIndexInfo - { - public int Number; - public Timestamp FileTimestamp; - - public override string ToString() + private enum BurnType { - return string.Format("I#{0:D2} {1}", Number, FileTimestamp); + Normal, Pregap, Postgap } - } - class CueTrackInfo - { - public int BlobIndex; - public int Number; - public CDTextData CDTextData = new CDTextData(); - public Timestamp Pregap, Postgap; - 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.FileTimestamp,indexlist); - } - } - } - - enum CuePointType - { - ZeroPregap, NormalPregap, Normal, Postgap, - } - - class CuePoint - { - public DiscPoint DiscPoint = new DiscPoint(); - public CuePointType Type; - public int BlobIndex; - public int BlobTimestampLBA; - } - - void AnalyzeTracks(LoadCueJob job) - { - var a = job.IN_AnalyzeJob; - var cue = job.IN_AnalyzeJob.IN_CueFile; - - List tracks = new List(); - - //current file tracking - int blob_index = -1; + //some sloshy output tracking: + int sloshy_firstRecordedTrackNumber = -1, sloshy_lastRecordedTrackNumber = -1; + 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 track/index state - bool trackHasFlags = false; - CueTrackInfo track_curr = null; + //current blob file state + int file_cfi_index = -1; + IBlob file_blob = null; + CueFile.Command.FILE file_currentCommand = null; + long file_ofs = 0, file_len = 0; + int file_msf = -1; - //first, collate information into high level description of each track - for (int i = 0; i < cue.Commands.Count; i++) + //current track, flags, and index state + CueFile.Command.TRACK track_pendingCommand = null; + CueFile.Command.TRACK track_currentCommand = null; + CueFile.TrackFlags track_pendingFlags = CueFile.TrackFlags.None; + CueFile.TrackFlags track_currentFlags = CueFile.TrackFlags.None; + + //burn state. + //TODO - separate burner into another class? + 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) { - var cmd = cue.Commands[i]; - - //these commands get dealt with globally. nothing to be done here yet - 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; - - //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) + if (sloshy_firstRecordedTrackNumber == -1) + sloshy_firstRecordedTrackNumber = track.Number; + sloshy_lastRecordedTrackNumber = track.Number; + if(!sloshy_session1Format_determined) { - if (track_curr == null) - job.Warn("FLAG command received before a TRACK command; ignoring"); - else if (trackHasFlags) - job.Warn("Multiple FLAGS commands in track {0}; subsequent commands are ignored", track_curr.Number); - else + switch (track.Type) { - track_curr.Flags = (cmd as CueFile.Command.FLAGS).Flags; - trackHasFlags = true; + 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; } } + } - if (cmd is CueFile.Command.TRACK) + void BeginBurnPregap() + { + //TODO? + } + + void BurnPregap(Timestamp length) + { + burntype_current = BurnType.Pregap; + burn_pregap_timestamp = length; + int length_lba = length.Sector; + + //TODO: read [IEC10149] 20, 20.1, & 20.2 to assign pre-gap and post-gap types correctly depending on track number and previous track + //ALSO, if the last track is data, we need to make a post-gap + //we can grab the previously generated sector in order to figure out how to encode new pregap sectors + for(int i=0;i file_len) + { + Warn("Zero-padding mis-sized cue blob file: " + Path.GetFileName(file_currentCommand.Path)); + blob = Disc.Blob_ZeroPadBuffer.MakeBufferFrom(file_blob,file_ofs,required); + OUT_Disc.DisposableResources.Add(blob); + blobOffset = 0; + } + file_ofs += required; + } + + void BurnSector_Normal() + { + SS_Base ss = null; + switch (track_currentCommand.Type) + { + case CueFile.TrackType.Mode2_2352: + ss = new SS_2352(); + EatBlobFileSector(2352, out ss.Blob, out ss.BlobOffset); + break; + case CueFile.TrackType.Audio: + ss = new SS_2352(); + EatBlobFileSector(2352, out ss.Blob, out ss.BlobOffset); + break; + } + + var se = new SectorEntry(null); + se.SectorSynth = ss; + OUT_Disc.Sectors.Add(se); + } + + void BurnSector_Pregap() + { + var se = new SectorEntry(null); + se.SectorSynth = new SS_Mode1_2048(); //TODO - actually burn the right thing + OUT_Disc.Sectors.Add(se); + + burn_pregap_timestamp = new Timestamp(burn_pregap_timestamp.Sector - 1); + } + + void BurnSector() + { + switch (burntype_current) + { + case BurnType.Normal: + BurnSector_Normal(); + break; + case BurnType.Pregap: + BurnSector_Pregap(); + break; + } + } + + public void Run() + { + //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); + } + + //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; + + //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; + + //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; - //setup new track - track_curr = new CueTrackInfo(); - track_curr.BlobIndex = blob_index; - track_curr.Number = track.Number; - track_curr.TrackType = track.Type; - tracks.Add(track_curr); + //register the track for further processing when an GENERATION command appears + track_pendingCommand = track; + track_pendingFlags = CueFile.TrackFlags.None; + + UpdateSloshyTrackData(track); + } - //setup default flags for the track - if (track.Type != CueFile.TrackType.Audio) - track_curr.Flags = CueFile.TrackFlags.DATA; - trackHasFlags = false; + 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.FILE) - { - blob_index++; - track_curr = null; - } - - if (cmd is CueFile.Command.INDEX) - { - var cfindex = cmd as CueFile.Command.INDEX; - if(track_curr == null) - job.Error("INDEX command received before TRACK command; ignoring"); - else if (track_curr.Postgap.Valid) - job.Warn("INDEX command received after POSTGAP; ignoring"); - else if (cfindex.Number != track_curr.Indexes.Count && (cfindex.Number != 1 && track_curr.Indexes.Count==0)) - job.Warn("non-sequential INDEX command received; ignoring"); - else { - var index = new CueIndexInfo { Number = cfindex.Number, FileTimestamp = cfindex.Timestamp }; - track_curr.Indexes.Add(index); - } - } - - if (cmd is CueFile.Command.POSTGAP) - { - if(track_curr == null) - job.Warn("POSTGAP command received before TRACK command; ignoring"); - else if(track_curr.Postgap.Valid) - job.Warn("Multiple POSTGAP commands specified for a track; ignoring"); - else - track_curr.Postgap = (cmd as CueFile.Command.POSTGAP).Length; - } - - if (cmd is CueFile.Command.PREGAP) - { - if (track_curr == null) - job.Warn("PREGAP command received before TRACK command; ignoring"); - else if (track_curr.Pregap.Valid) - job.Warn("Multiple PREGAP commands specified for a track; ignoring"); - else - track_curr.Pregap = (cmd as CueFile.Command.PREGAP).Length; - } - } //commands loop - - //check for tracks with no indexes, which will wreck our processing later and is an error anyway - { - RETRY: - foreach (var t in tracks) - if(t.Indexes.Count == 0) - { - job.Error("TRACK {0} is missing an INDEX",t.Number); - tracks.Remove(t); - goto RETRY; - } - } - - //now, create a todo list - List todo = new List(); - int lba = 0; - foreach (var t in tracks) - { - //find total length of pregap, so we can figure out how negative the relative timestamp goes - int lbaPregap0to1 = 0; - if (t.Indexes.Count > 1) - lbaPregap0to1 = t.Indexes[1].FileTimestamp.Sector - t.Indexes[0].FileTimestamp.Sector; - - //if(t.Pregap.Valid) - //{ - // var cpPregap = new CuePoint(); - // cpPregap.DiscPoint.AbsoluteLBA = lba; - // cpPregap.DiscPoint - - } - } - - /// - /// runs a LoadCueJob - /// - public void LoadCueFile(LoadCueJob job) - { - AnalyzeTracks(job); - - //params - var a = job.IN_AnalyzeJob; - var cue = job.IN_AnalyzeJob.IN_CueFile; - var disc = new Disc(); - - //utils - var zero_sector = new Sector_Zero(); - var zero_subSector = new ZeroSubcodeSector(); - - //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 se_leadin = new SectorEntry(zero_sector); - se_leadin.SubcodeSector = zero_subSector; - disc.Sectors.Add(se_leadin); - } - - //current cdtext and ISRC state - string cdtext_songwriter = null, cdtext_performer = null, cdtext_title = null; - string isrc = null; - - //current track state - CueFile.TrackFlags track_pendingFlags = CueFile.TrackFlags.None; - CueFile.TrackFlags track_flags = CueFile.TrackFlags.None; - bool track_hasPendingFlags = false; - int track_num = 0; - //int track_index0MSF = -1; //NOT NEED ANYMORE - int track_index1MSF = -1; - bool track_readyForPregapCommand = false; - CueFile.Command.TRACK track_pendingCommand = null; - CueFile.Command.TRACK track_currentCommand = null; - Timestamp postgap_pending = new Timestamp(); - Timestamp pregap_pending = new Timestamp(); - Timestamp pregap_current = new Timestamp(); - - //current index state - int index_num = -1; - int index_msf = -1; //file-relative, remember - - //current file state - CueFile.Command.FILE file_command = null; - int file_cfi_index = -1; - long file_ofs = 0, file_len = 0; - int file_ownmsf = -1; - IBlob file_blob = null; - - //current output state - SubchannelQ priorSubchannelQ = new SubchannelQ(); - int LBA = 0; - List resources = disc.DisposableResources; - - //lets start with session type CDDA. we'll change it if we ever find a track of a different format (this is based on mednafen's behaviour) - var TOCMiscInfo = new Synthesize_A0A1A2_Job { IN_FirstRecordedTrackNumber = -1, IN_LastRecordedTrackNumber = -1, IN_LeadoutTimestamp = new Timestamp(0) }; - TOCMiscInfo.IN_Session1Format = DiscTOCRaw.SessionFormat.Type00_CDROM_CDDA; - - //a little subroutine to wrap a sector if the blob is out of space - Func> eatBlobAndWrap = (required) => - { - IBlob blob = file_blob; - var ret = new Tuple(file_blob, file_ofs); - if (file_ofs + required > file_len) - { - job.Warn("Zero-padding mis-sized file: " + Path.GetFileName(file_command.Path)); - blob = Disc.Blob_ZeroPadBuffer.MakeBufferFrom(file_blob,file_ofs,required); - resources.Add(blob); - ret = new Tuple(blob,0); - } - file_ofs += required; - return ret; - }; - - Action emitRawTocEntry = () => - { - //NOT TOO SURE ABOUT ALL THIS YET. NEED TO VALIDATE MORE DEEPLY. - - SubchannelQ sq = new SubchannelQ(); - - //absent some kind of policy for how to set it, this is a safe assumption: - byte ADR = 1; - - sq.SetStatus(ADR, (EControlQ)(int)track_flags); - sq.q_tno.BCDValue = 0; //kind of a little weird here.. the track number becomes the 'point' and put in the index instead. 0 is the track number here. - sq.q_index = BCD2.FromDecimal(track_num); - - //not too sure about these yet - sq.min = BCD2.FromDecimal(0); - sq.sec = BCD2.FromDecimal(0); - sq.frame = BCD2.FromDecimal(0); - - sq.AP_Timestamp = new Timestamp(LBA + 150); //its supposed to be an absolute timestamp - - disc.RawTOCEntries.Add(new RawTOCEntry { QData = sq }); - }; - - //a little subroutine to write a sector - //TODO - I intend to rethink how the sector interfaces work, but not yet. It works well enough now. - Action writeSector = (SectorWriteType type) => - { - ISector siface = null; - SS_Base ss = null; - - if (type == SectorWriteType.Normal) - { - switch (track_currentCommand.Type) - { - default: - case CueFile.TrackType.Unknown: - throw new InvalidOperationException("Internal error processing unknown track type which should have been caught earlier"); - - case CueFile.TrackType.CDI_2352: - case CueFile.TrackType.Mode1_2352: - break; - - case CueFile.TrackType.Mode2_2352: - { - var t = eatBlobAndWrap(2352); - ss = new SS_2352() { Blob = t.Item1, BlobOffset = t.Item2 }; - break; - } - - case CueFile.TrackType.Audio: - { - var t = eatBlobAndWrap(2352); - ss = new SS_2352() { Blob = t.Item1, BlobOffset = t.Item2 }; - break; - } - - case CueFile.TrackType.Mode1_2048: - { - //2048 bytes are present. ECM needs to be generated to create a full raw sector - //var raw = eatBlobAndWrap(2048); - //siface = new Sector_Mode1_2048(LBA + 150) //pass the ABA I guess - //{ - // Blob = new ECMCacheBlob(raw.Blob), //archaic - // Offset = raw.Offset - //}; - //TODO - break; - } - - case CueFile.TrackType.CDG: //2448 - case CueFile.TrackType.Mode2_2336: - case CueFile.TrackType.CDI_2336: - throw new InvalidOperationException("Track types not supported yet"); - } - } - else if (type == SectorWriteType.Postgap) - { - //TODO - //siface = zero_sector; - throw new InvalidOperationException("cue postgap broken right now"); - } - else if (type == SectorWriteType.Pregap) - { - ss = new SS_Pregap(); - } - - //make the subcode - //TODO - according to policies, or better yet, defer this til it's needed (user delivers a policies object to disc reader apis) - //at any rate, we'd paste this logic into there so let's go ahead and write it here - var subcode = new BufferedSubcodeSector(); - SubchannelQ sq = new SubchannelQ(); - - //absent some kind of policy for how to set it, this is a safe assumption: - byte ADR = 1; - - sq.SetStatus(ADR, (EControlQ)(int)track_flags); - - sq.q_tno = BCD2.FromDecimal(track_num); - sq.q_index = BCD2.FromDecimal(index_num); - - int ABA = LBA + 150; - sq.ap_min = BCD2.FromDecimal(new Timestamp(ABA).MIN); - sq.ap_sec = BCD2.FromDecimal(new Timestamp(ABA).SEC); - sq.ap_frame = BCD2.FromDecimal(new Timestamp(ABA).FRAC); - - int track_relative_msf = file_ownmsf - track_index1MSF; - - //adjust the track_relative_msf here, since it's inserted before index 0 - if (type == SectorWriteType.Pregap) - { - track_relative_msf -= pregap_current.Sector; - } - - if (index_num == 0 || type == SectorWriteType.Pregap) - { - //PREGAP: - //things are negative here. - if (track_relative_msf > 0) throw new InvalidOperationException("Perplexing internal error with non-negative pregap MSF"); - track_relative_msf = -track_relative_msf; - - //now for something special. - //yellow-book says: - //pre-gap for "first part of a digital data track not containing user data and encoded as a pause" - //first interval: at least 75 sectors coded as preceding track - //second interval: at least 150 sectors coded as user data track. - //so... we ASSUME the 150 sector pregap is more important. so if thats all there is, theres no 75 sector pregap like the old track - //if theres a longer pregap, then we generate weird old track pregap to contain the rest. - if (track_relative_msf > 150) - { - //only if we're burning a data track now - if((track_flags & CueFile.TrackFlags.DATA)!=0) - sq.q_status = priorSubchannelQ.q_status; - } - } - sq.min = BCD2.FromDecimal(new Timestamp(track_relative_msf).MIN); - sq.sec = BCD2.FromDecimal(new Timestamp(track_relative_msf).SEC); - sq.frame = BCD2.FromDecimal(new Timestamp(track_relative_msf).FRAC); - - //finally we're done: synthesize subchannel - subcode.Synthesize_SubchannelQ(ref sq, true); - - priorSubchannelQ = sq; - - //now we have the ISector and subcode; make the SectorEntry - var se = new SectorEntry(null); - se.SectorSynth = ss; - ss.sq = sq; - disc.Sectors.Add(se); - LBA++; - file_ownmsf++; - }; - - //generates sectors until the file is exhausted - Action finishFile = () => - { - while (file_ofs < file_len) - writeSector(SectorWriteType.Normal); - }; - - Action writePostgap = () => - { - if (postgap_pending.Valid) - for (int i = 0; i < postgap_pending.Sector; i++) - { - writeSector(SectorWriteType.Postgap); - } - }; - - Action writePregap = () => - { - if (pregap_current.Valid) - { - if (pregap_current.Sector > 150) - { - int zzz = 9; - } - for (int i = 0; i < pregap_current.Sector; i++) - { - writeSector(SectorWriteType.Pregap); - } - } - }; - - //prepare disc structure - disc.Structure = new DiscStructure(); - disc.Structure.Sessions.Add(new DiscStructure.Session()); - - //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; - - //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; - - //flags are also a kind of a register. but the flags value is reset by the track command - if (cmd is CueFile.Command.FLAGS) - { - if (track_hasPendingFlags) - job.Warn("Multiple FLAGS commands in track {0}; subsequent commands are ignored", track_num); - else - { - track_pendingFlags = (cmd as CueFile.Command.FLAGS).Flags; - track_hasPendingFlags = true; - } - } - - if (cmd is CueFile.Command.TRACK) - { - var track = cmd as CueFile.Command.TRACK; - - //HOW TO HANDLE TRACKS: - //Tracks don't have timestamps. Therefore, they're always pending until something that does have a timestamp - track_pendingCommand = track; - track_pendingFlags = CueFile.TrackFlags.None; - track_readyForPregapCommand = true; - } - - if (cmd is CueFile.Command.FILE) - { - var file = cmd as CueFile.Command.FILE; - - //HOW TO HANDLE FILES: - //1. flush the current file with all current register settings - //2. mount the next file - //3. clear some register settings - - //1. do the flush, if needed - if (file_cfi_index != -1) - finishFile(); - - //2a. clean up by nulling before starting next file... - file_command = null; - file_ofs = 0; - file_len = 0; - file_blob = null; - - //2b. mount the next file - file_command = file; - file_ownmsf = 0; - var cfi = a.OUT_FileInfos[++file_cfi_index]; - - //TODO - a lot of redundant code here, maybe Blob should know his length in the IBlob interface since every freaking thing depends on it - if (cfi.Type == AnalyzeCueJob.CueFileType.BIN || cfi.Type == AnalyzeCueJob.CueFileType.Unknown) - { - //raw files: - var blob = new Disc.Blob_RawFile { PhysicalPath = cfi.FullPath }; - resources.Add(file_blob = blob); - file_len = blob.Length; - } - else if (cfi.Type == AnalyzeCueJob.CueFileType.ECM) - { - var blob = new Disc.Blob_ECM(); - resources.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(); - resources.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(); - resources.Add(file_blob = blob); - blob.Load(new MemoryStream(buf)); - } - - //3. reset track and index registers - track_num = 0; - //TODO - do I need to reset the track extra stuff here? nothing can get generated - //track_pendingCommand = null; - //track_currentCommand = null; - //track_flags = CueFile.TrackFlags.None; - //track_hasFlags = false; - //track_index0MSF = -1; - //track_index1MSF = -1; - //and how about this? it should get reset when the track begins - //index_num = -1; - //index_msf = -1; - - } //FILE command - - if (cmd is CueFile.Command.INDEX) - { - var index = cmd as CueFile.Command.INDEX; - var timestamp = index.Timestamp; - var i_num = index.Number; - - //cant get a pregap command anymore - track_readyForPregapCommand = false; - - //ok, now for some truly arcane stuff. - - //first, a warning if this isn't sequential - if (i_num != 0 && i_num != 1 && i_num != index_num + 1) - { - job.Error("Invalid index number {0}: must be one greater than previous. Fixing that for you.", i_num); - i_num = index_num + 1; - } - - //now, an error if this is the first index and isnt 0 or 1 - if (index_num == -1 && i_num != 0 && i_num != 1) - { - job.Error("Invalid index {0}: 1st IDX of track must be 0 or 1. Pretend it's 1.", i_num); - //how to recover? assume it's 1 - i_num = 1; - } - - //now, an error if this is not at 00:00:00 for the first index in a file - if (file_ofs == 0 && timestamp.Sector != 0 && index_num == -1) - { - job.Error("Invalid IDX {0}: 1st IDX in file must be at 00:00:00 but it's at {1}", i_num, index.Timestamp); - timestamp = new Timestamp(0); - } - - if (i_num == 0) - { - //if this is index 0, we're gonna have a pregap. - //lets just record that we got this index, and go to the next command - index_num = 0; - //DONT NEED THIS ANYMORE? - //track_index0MSF = timestamp.Sector; - } - else if (i_num == 1) - { - //if we got an index 1: - track_index1MSF = timestamp.Sector; - - //DONT NEED THIS ANYMORE? - ////if we didnt get an index 0 LBA, give up and assume it's the same as this - //if (track_index0MSF == -1) - // track_index0MSF = timestamp.Sector; - - //we can now execute a pending track change command - if (track_pendingCommand == null) - throw new InvalidOperationException("Unrecoverable error processing CUE: index without track context"); - - //before we begin a track, write any postgap that we may have queued... - writePostgap(); - - //...and also generate sectors up til the current index (as the last index) - while (file_ownmsf < timestamp.Sector) - writeSector(SectorWriteType.Normal); - - //begin the track: - track_currentCommand = track_pendingCommand; - track_pendingCommand = null; - track_hasPendingFlags = false; - track_flags = track_pendingFlags; - track_num = track_currentCommand.Number; - - pregap_current = pregap_pending; - pregap_pending = new Timestamp(); - - //default flags: - if (track_currentCommand.Type == CueFile.TrackType.Audio) { } - else track_flags |= CueFile.TrackFlags.DATA; - postgap_pending = new Timestamp(); - - //account for it in structure - var st = new DiscStructure.Track { Number = track_num }; - //TODO - move out of here into analysis stage - switch (track_currentCommand.Type) - { - default: - case CueFile.TrackType.CDG: - case CueFile.TrackType.Unknown: throw new InvalidOperationException("UNEXPECTED PANDA MAYOR"); - - case CueFile.TrackType.Audio: - st.TrackType = DiscStructure.ETrackType.Audio; - st.ModeHeuristic = 0; - break; - case CueFile.TrackType.Mode1_2048: - case CueFile.TrackType.Mode1_2352: - st.TrackType = DiscStructure.ETrackType.Data; - st.ModeHeuristic = 1; - break; - case CueFile.TrackType.Mode2_2352: - case CueFile.TrackType.Mode2_2336: - case CueFile.TrackType.CDI_2336: - case CueFile.TrackType.CDI_2352: - st.TrackType = DiscStructure.ETrackType.Data; - st.ModeHeuristic = 1; - break; - } - disc.Structure.Sessions[0].Tracks.Add(st); - - //maintain some memos - if (TOCMiscInfo.IN_FirstRecordedTrackNumber == -1) - TOCMiscInfo.IN_FirstRecordedTrackNumber = track_num; - TOCMiscInfo.IN_LastRecordedTrackNumber = track_num; - //update the disc type... do we need to do any double checks here to check for regressions? doubt it. - if (TOCMiscInfo.IN_Session1Format == DiscTOCRaw.SessionFormat.Type00_CDROM_CDDA) - { - switch (track_currentCommand.Type) - { - case CueFile.TrackType.Mode2_2336: - case CueFile.TrackType.Mode2_2352: - TOCMiscInfo.IN_Session1Format = DiscTOCRaw.SessionFormat.Type20_CDXA; - break; - case CueFile.TrackType.CDI_2336: - case CueFile.TrackType.CDI_2352: - TOCMiscInfo.IN_Session1Format = DiscTOCRaw.SessionFormat.Type10_CDI; - break; - default: - //no changes here - break; - } - } - - { - var _currTrack = disc.Structure.Sessions[0].Tracks[disc.Structure.Sessions[0].Tracks.Count - 1]; - if (_currTrack.Indexes.Count == 0) - _currTrack.Indexes.Add(new DiscStructure.Index { Number = 0, LBA = LBA }); - } - - //write a special pregap if a command was pending - writePregap(); - } - - //emit it to TOC if needed - if (i_num == 1) - emitRawTocEntry(); - - //update sense of current index - index_num = i_num; - index_msf = timestamp.Sector; - - { - var _currTrack = disc.Structure.Sessions[0].Tracks[disc.Structure.Sessions[0].Tracks.Count - 1]; - _currTrack.Indexes.Add(new DiscStructure.Index { Number = index_num, LBA = LBA }); - } - } - - if (cmd is CueFile.Command.POSTGAP) - { - var postgap = cmd as CueFile.Command.POSTGAP; - - if(index_num == -1) - { - job.Warn("Ignoring POSTGAP before any indexes"); - goto NO_POSTGAP; - } - - if(postgap_pending.Valid) - { - job.Warn("Ignoring multiple POSTGAPs for track"); - goto NO_POSTGAP; - } - - postgap_pending = postgap.Length; - - NO_POSTGAP: ; - } - - if (cmd is CueFile.Command.PREGAP) - { - //see particularly http://digitalx.org/cue-sheet/examples/index.html#example05 for the most annoying example - var pregap = cmd as CueFile.Command.PREGAP; - - if (!track_readyForPregapCommand) - { - job.Warn("Not ready for PREGAP command; ignoring"); - goto NO_PREGAP; - } - - pregap_pending = pregap.Length; - track_readyForPregapCommand = false; - - NO_PREGAP: - ; - } - - } - - //do a final flush - finishFile(); - writePostgap(); - - //do some stupid crap for testing. maybe it isnt so stupid, but its what we have for now - disc.Structure.LengthInSectors = disc.Sectors.Count; - - //add RawTOCEntries A0 A1 A2 to round out the TOC - TOCMiscInfo.IN_LeadoutTimestamp = new Timestamp(LBA + 150); - TOCMiscInfo.Run(disc.RawTOCEntries); - - //generate the TOCRaw from the RawTocEntries - var tocSynth = new DiscTOCRaw.SynthesizeFromRawTOCEntriesJob() { Entries = disc.RawTOCEntries }; - tocSynth.Run(); - 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(); - - //blech, old crap, maybe - disc.Structure.Synthesize_TOCPointsFromSessions(); - - job.OUT_Disc = disc; - job.FinishLog(); - - } //LoadCueFile - } -} \ No newline at end of file + 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); + + //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(); + + //blech, old crap, maybe + OUT_Disc.Structure.Synthesize_TOCPointsFromSessions(); + + FinishLog(); + + } //Run() + } //class LoadCueJob + } //partial class CUE_Format2 +} //namespace BizHawk.Emulation.DiscSystem \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/CUE/CUE_Synths.cs b/BizHawk.Emulation.DiscSystem/CUE/CUE_Synths.cs index 0dcb4dd240..6334641c63 100644 --- a/BizHawk.Emulation.DiscSystem/CUE/CUE_Synths.cs +++ b/BizHawk.Emulation.DiscSystem/CUE/CUE_Synths.cs @@ -8,6 +8,8 @@ namespace BizHawk.Emulation.DiscSystem { abstract class SS_Base : ISectorSynthJob2448 { + public IBlob Blob; + public long BlobOffset; public SubchannelQ sq; public abstract void Synth(SectorSynthJob job); @@ -57,8 +59,6 @@ namespace BizHawk.Emulation.DiscSystem /// class SS_2352 : SS_Base { - public IBlob Blob; - public long BlobOffset; public override void Synth(SectorSynthJob job) { //read the sector user data diff --git a/BizHawk.Emulation.DiscSystem/Jobs/DiscMountJob.cs b/BizHawk.Emulation.DiscSystem/Jobs/DiscMountJob.cs index d68e8dc1d1..d0413766e6 100644 --- a/BizHawk.Emulation.DiscSystem/Jobs/DiscMountJob.cs +++ b/BizHawk.Emulation.DiscSystem/Jobs/DiscMountJob.cs @@ -102,7 +102,7 @@ namespace BizHawk.Emulation.DiscSystem //actually load it all up var loadJob = new CUE_Format2.LoadCueJob(); loadJob.IN_AnalyzeJob = analyzeJob; - cue2.LoadCueFile(loadJob); + loadJob.Run(); if (loadJob.OUT_Log != "") Console.WriteLine(loadJob.OUT_Log); ConcatenateJobLog(analyzeJob); diff --git a/BizHawk.Emulation.DiscSystem/TOC/DiscStructure.cs b/BizHawk.Emulation.DiscSystem/TOC/DiscStructure.cs index 412613a38b..9df39a3f43 100644 --- a/BizHawk.Emulation.DiscSystem/TOC/DiscStructure.cs +++ b/BizHawk.Emulation.DiscSystem/TOC/DiscStructure.cs @@ -54,6 +54,7 @@ namespace BizHawk.Emulation.DiscSystem /// /// Synthesizes the DiscStructure from RawTOCEntriesJob /// TODO - move to attic, not being used + /// WOULD BE NICE TO USE FOR CUE /// public class SynthesizeFromRawTOCEntriesJob {