public void Synth(SectorSynthJob job) { //be lazy, just generate the whole sector unconditionally //this is mostly based on mednafen's approach, which was probably finely tailored for PSX //heres the comments on the subject: // I'm not trusting that the "control" field for the TOC leadout entry will always be set properly, so | the control fields for the last track entry // and the leadout entry together before extracting the D2 bit. Audio track->data leadout is fairly benign though maybe noisy(especially if we ever implement // data scrambling properly), but data track->audio leadout could break things in an insidious manner for the more accurate drive emulation code). var ses = job.Disc.Structure.Sessions[SessionNumber]; int lba_relative = job.LBA - ses.LeadoutTrack.LBA; //data is zero int ts = lba_relative; int ats = job.LBA; const int ADR = 0x1; // Q channel data encodes position EControlQ control = ses.LeadoutTrack.Control; //ehhh? CDI? //if(toc.tracks[toc.last_track].valid) // control |= toc.tracks[toc.last_track].control & 0x4; //else if(toc.disc_type == DISC_TYPE_CD_I) // control |= 0x4; control |= (EControlQ)(((int)ses.LastInformationTrack.Control) & 4); SubchannelQ sq = new SubchannelQ(); sq.SetStatus(ADR, control); sq.q_tno.BCDValue = 0xAA; sq.q_index.BCDValue = 0x01; sq.Timestamp = ts; sq.AP_Timestamp = ats; sq.zero = 0; //finally, rely on a gap sector to do the heavy lifting to synthesize this CUE.CueTrackType TrackType = CUE.CueTrackType.Audio; if (ses.LeadoutTrack.IsData) { if (job.Disc.TOC.Session1Format == SessionFormat.Type20_CDXA || job.Disc.TOC.Session1Format == SessionFormat.Type10_CDI) { TrackType = CUE.CueTrackType.Mode2_2352; } else { TrackType = CUE.CueTrackType.Mode1_2352; } } CUE.SS_Gap ss_gap = new CUE.SS_Gap() { Policy = Policy, sq = sq, TrackType = TrackType, Pause = true //? }; ss_gap.Synth(job); }
/// <exception cref="MDSParseException">no file found at <paramref name="mdsPath"/> or BLOB error</exception> public Disc LoadMDSToDisc(string mdsPath, DiscMountPolicy IN_DiscMountPolicy) { var loadResults = LoadMDSPath(mdsPath); if (!loadResults.Valid) { throw loadResults.FailureException; } Disc disc = new Disc(); // load all blobs Dictionary <int, IBlob> BlobIndex = MountBlobs(loadResults.ParsedMDSFile, disc); var mdsf = loadResults.ParsedMDSFile; //generate DiscTOCRaw items from the ones specified in the MDS file disc.RawTOCEntries = new List <RawTOCEntry>(); foreach (var entry in mdsf.TOCEntries) { disc.RawTOCEntries.Add(EmitRawTOCEntry(entry)); } //analyze the RAWTocEntries to figure out what type of track track 1 is var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job(disc.RawTOCEntries); tocSynth.Run(); // now build the sectors int currBlobIndex = 0; foreach (var session in mdsf.ParsedSession) { for (int i = session.StartTrack; i <= session.EndTrack; i++) { int relMSF = -1; var track = mdsf.TOCEntries.FirstOrDefault(t => t.Point == i); if (track == null) { break; } // ignore the info entries if (track.Point == 0xA0 || track.Point == 0xA1 || track.Point == 0xA2) { continue; } // get the blob(s) for this track // it's probably a safe assumption that there will be only one blob per track, but I'm still not 100% sure on this var tr = mdsf.TOCEntries.FirstOrDefault(a => a.Point == i) ?? throw new MDSParseException("BLOB Error!"); #if true if (tr.ImageFileNamePaths.Count == 0) { throw new MDSParseException("BLOB Error!"); } #else // this is the worst use of lists and LINQ I've seen in this god-forsaken codebase, I hope for all our sakes that it's not a workaround for some race condition --yoshi List <string> blobstrings = new List <string>(); foreach (var t in tr.ImageFileNamePaths) { if (!blobstrings.Contains(t)) { blobstrings.Add(t); } } var tBlobs = (from a in tr.ImageFileNamePaths select a).ToList(); if (tBlobs.Count < 1) { throw new MDSParseException("BLOB Error!"); } // is the currBlob valid for this track, or do we need to increment? string bString = tBlobs.First(); #endif IBlob mdfBlob = null; // check for track pregap and create if necessary // this is specified in the track extras block if (track.ExtraBlock.Pregap > 0) { CUE.CueTrackType pregapTrackType = CUE.CueTrackType.Audio; if (tocSynth.Result.TOCItems[1].IsData) { if (tocSynth.Result.Session1Format == SessionFormat.Type20_CDXA) { pregapTrackType = CUE.CueTrackType.Mode2_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type10_CDI) { pregapTrackType = CUE.CueTrackType.CDI_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type00_CDROM_CDDA) { pregapTrackType = CUE.CueTrackType.Mode1_2352; } } for (int pre = 0; pre < track.ExtraBlock.Pregap; pre++) { relMSF++; var ss_gap = new CUE.SS_Gap() { Policy = IN_DiscMountPolicy, TrackType = pregapTrackType }; disc._Sectors.Add(ss_gap); int qRelMSF = pre - Convert.ToInt32(track.ExtraBlock.Pregap); //tweak relMSF due to ambiguity/contradiction in yellowbook docs if (!IN_DiscMountPolicy.CUE_PregapContradictionModeA) { qRelMSF++; } //setup subQ byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption: ss_gap.sq.SetStatus(ADR, tocSynth.Result.TOCItems[1].Control); ss_gap.sq.q_tno = BCD2.FromDecimal(1); ss_gap.sq.q_index = BCD2.FromDecimal(0); ss_gap.sq.AP_Timestamp = pre; ss_gap.sq.Timestamp = qRelMSF; //setup subP ss_gap.Pause = true; } // pregap processing completed } // create track sectors long currBlobOffset = track.TrackOffset; for (long sector = session.StartSector; sector <= session.EndSector; sector++) { CUE.SS_Base sBase = null; // get the current blob from the BlobIndex Blob_RawFile currBlob = (Blob_RawFile)BlobIndex[currBlobIndex]; long currBlobLength = currBlob.Length; long currBlobPosition = sector; if (currBlobPosition == currBlobLength) { currBlobIndex++; } mdfBlob = disc.DisposableResources[currBlobIndex] as Blob_RawFile; //int userSector = 2048; switch (track.SectorSize) { case 2448: sBase = new CUE.SS_2352() { Policy = IN_DiscMountPolicy }; //userSector = 2352; break; case 2048: default: sBase = new CUE.SS_Mode1_2048() { Policy = IN_DiscMountPolicy }; //userSector = 2048; break; //throw new Exception($"Not supported: Sector Size {track.SectorSize}"); } // configure blob sBase.Blob = mdfBlob; sBase.BlobOffset = currBlobOffset; currBlobOffset += track.SectorSize; // userSector; // add subchannel data relMSF++; BCD2 tno, ino; //this should actually be zero. im not sure if this is stored as BCD2 or not tno = BCD2.FromDecimal(track.TrackNo); //these are special values.. I think, taken from this: //http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html //the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD. //Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing. ino = BCD2.FromDecimal(track.Point); if (track.Point == 0xA0) { ino.BCDValue = 0xA0; } else if (track.Point == 0xA1) { ino.BCDValue = 0xA1; } else if (track.Point == 0xA2) { ino.BCDValue = 0xA2; } // get ADR & Control from ADR_Control byte byte adrc = Convert.ToByte(track.ADR_Control); var Control = adrc & 0x0F; var ADR = adrc >> 4; var q = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(ADR, (EControlQ)(Control & 0xF)), q_tno = BCD2.FromDecimal(track.Point), q_index = ino, AP_Timestamp = disc._Sectors.Count, Timestamp = relMSF - Convert.ToInt32(track.ExtraBlock.Pregap) }; sBase.sq = q; disc._Sectors.Add(sBase); } } } return(disc); }
/// <exception cref="CCDParseException">file <paramref name="ccdPath"/> not found, nonexistent IMG file, nonexistent SUB file, IMG or SUB file not multiple of <c>2352 B</c>, or IMG and SUB files differ in length</exception> public Disc LoadCCDToDisc(string ccdPath, DiscMountPolicy IN_DiscMountPolicy) { var loadResults = LoadCCDPath(ccdPath); if (!loadResults.Valid) { throw loadResults.FailureException; } Disc disc = new Disc(); IBlob imgBlob = null, subBlob = null; long imgLen = -1, subLen; //mount the IMG file //first check for a .ecm in place of the img var imgPath = loadResults.ImgPath; if (!File.Exists(imgPath)) { var ecmPath = Path.ChangeExtension(imgPath, ".img.ecm"); if (File.Exists(ecmPath)) { if (Disc.Blob_ECM.IsECM(ecmPath)) { var ecm = new Disc.Blob_ECM(); ecm.Load(ecmPath); imgBlob = ecm; imgLen = ecm.Length; } } } if (imgBlob == null) { if (!File.Exists(loadResults.ImgPath)) { throw new CCDParseException("Malformed CCD format: nonexistent IMG file!"); } var imgFile = new Disc.Blob_RawFile() { PhysicalPath = loadResults.ImgPath }; imgLen = imgFile.Length; imgBlob = imgFile; } disc.DisposableResources.Add(imgBlob); //mount the SUB file if (!File.Exists(loadResults.SubPath)) { throw new CCDParseException("Malformed CCD format: nonexistent SUB file!"); } var subFile = new Disc.Blob_RawFile() { PhysicalPath = loadResults.SubPath }; subBlob = subFile; disc.DisposableResources.Add(subBlob); subLen = subFile.Length; //quick integrity check of file sizes if (imgLen % 2352 != 0) { throw new CCDParseException("Malformed CCD format: IMG file length not multiple of 2352"); } int NumImgSectors = (int)(imgLen / 2352); if (subLen != NumImgSectors * 96) { throw new CCDParseException("Malformed CCD format: SUB file length not matching IMG"); } var ccdf = loadResults.ParsedCCDFile; //the only instance of a sector synthesizer we'll need SS_CCD synth = new SS_CCD(); //generate DiscTOCRaw items from the ones specified in the CCD file //TODO - range validate these (too many truncations to byte) disc.RawTOCEntries = new List <RawTOCEntry>(); foreach (var entry in ccdf.TOCEntries) { BCD2 tno, ino; //this should actually be zero. im not sure if this is stored as BCD2 or not tno = BCD2.FromDecimal(entry.TrackNo); //these are special values.. I think, taken from this: //http://www.staff.uni-mainz.de/tacke/scsi/SCSI2-14.html //the CCD will contain Points as decimal values except for these specially converted decimal values which should stay as BCD. //Why couldn't they all be BCD? I don't know. I guess because BCD is inconvenient, but only A0 and friends have special meaning. It's confusing. ino = BCD2.FromDecimal(entry.Point); if (entry.Point == 0xA0) { ino.BCDValue = 0xA0; } else if (entry.Point == 0xA1) { ino.BCDValue = 0xA1; } else if (entry.Point == 0xA2) { ino.BCDValue = 0xA2; } var q = new SubchannelQ { q_status = SubchannelQ.ComputeStatus(entry.ADR, (EControlQ)(entry.Control & 0xF)), q_tno = tno, q_index = ino, min = BCD2.FromDecimal(entry.AMin), sec = BCD2.FromDecimal(entry.ASec), frame = BCD2.FromDecimal(entry.AFrame), zero = (byte)entry.Zero, ap_min = BCD2.FromDecimal(entry.PMin), ap_sec = BCD2.FromDecimal(entry.PSec), ap_frame = BCD2.FromDecimal(entry.PFrame), q_crc = 0, //meaningless }; disc.RawTOCEntries.Add(new RawTOCEntry { QData = q }); } //analyze the RAWTocEntries to figure out what type of track track 1 is var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job() { Entries = disc.RawTOCEntries }; tocSynth.Run(); //Add sectors for the mandatory track 1 pregap, which isn't stored in the CCD file //We reuse some CUE code for this. //If we load other formats later we might should abstract this even further (to a synthesizer job) //It can't really be abstracted from cue files though due to the necessity of merging this with other track 1 pregaps CUE.CueTrackType pregapTrackType = CUE.CueTrackType.Audio; if (tocSynth.Result.TOCItems[1].IsData) { if (tocSynth.Result.Session1Format == SessionFormat.Type20_CDXA) { pregapTrackType = CUE.CueTrackType.Mode2_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type10_CDI) { pregapTrackType = CUE.CueTrackType.CDI_2352; } else if (tocSynth.Result.Session1Format == SessionFormat.Type00_CDROM_CDDA) { pregapTrackType = CUE.CueTrackType.Mode1_2352; } } for (int i = 0; i < 150; i++) { var ss_gap = new CUE.SS_Gap() { Policy = IN_DiscMountPolicy, TrackType = pregapTrackType }; disc._Sectors.Add(ss_gap); int qRelMSF = i - 150; //tweak relMSF due to ambiguity/contradiction in yellowbook docs if (!IN_DiscMountPolicy.CUE_PregapContradictionModeA) { qRelMSF++; } //setup subQ byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption: ss_gap.sq.SetStatus(ADR, tocSynth.Result.TOCItems[1].Control); ss_gap.sq.q_tno = BCD2.FromDecimal(1); ss_gap.sq.q_index = BCD2.FromDecimal(0); ss_gap.sq.AP_Timestamp = i; ss_gap.sq.Timestamp = qRelMSF; //setup subP ss_gap.Pause = true; } //build the sectors: //set up as many sectors as we have img/sub for, even if the TOC doesnt reference them //(the TOC is unreliable, and the Track records are redundant) for (int i = 0; i < NumImgSectors; i++) { disc._Sectors.Add(synth); } return(disc); }
public void Run() { //params var compiled = IN_CompileJob; var context = compiled.IN_CueContext; OUT_Disc = new Disc(); //generation state int curr_index; int curr_blobIndex = -1; int curr_blobMSF = -1; BlobInfo curr_blobInfo = null; long curr_blobOffset = -1; //mount all input files MountBlobs(); //unhappily, we cannot determine the length of all the tracks without knowing the length of the files //now that the files are mounted, we can figure the track lengths AnalyzeTracks(); //loop from track 1 to 99 //(track 0 isnt handled yet, that's way distant work) for (int t = 1; t < TrackInfos.Count; t++) { TrackInfo ti = TrackInfos[t]; CompiledCueTrack cct = ti.CompiledCueTrack; //--------------------------------- //setup track pregap processing //per "Example 05" on digitalx.org, pregap can come from index specification and pregap command int specifiedPregapLength = cct.PregapLength.Sector; int impliedPregapLength = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector; int totalPregapLength = specifiedPregapLength + impliedPregapLength; //from now on we'll track relative timestamp and increment it continually int relMSF = -totalPregapLength; //read more at policies declaration //if (!context.DiscMountPolicy.CUE_PauseContradictionModeA) // relMSF += 1; //--------------------------------- //--------------------------------- //generate sectors for this track. //advance to the next file if needed if (curr_blobIndex != cct.BlobIndex) { curr_blobIndex = cct.BlobIndex; curr_blobOffset = 0; curr_blobMSF = 0; curr_blobInfo = BlobInfos[curr_blobIndex]; } //work until the next track is reached, or the end of the current file is reached, depending on the track type curr_index = 0; for (; ;) { bool trackDone = false; bool generateGap = false; if (specifiedPregapLength > 0) { //if burning through a specified pregap, count it down generateGap = true; specifiedPregapLength--; } else { //if burning through the file, select the appropriate index by inspecting the next index and seeing if we've reached it for (; ;) { if (curr_index == cct.Indexes.Count - 1) { break; } if (curr_blobMSF >= cct.Indexes[curr_index + 1].FileMSF.Sector) { curr_index++; if (curr_index == 1) { //WE ARE NOW AT INDEX 1: generate the RawTOCEntry for this track EmitRawTOCEntry(cct); } } else { break; } } } //select the track type for the subQ //it's obviously the same as the main track type usually, but during a pregap it can be different TrackInfo qTrack = ti; int qRelMSF = relMSF; if (curr_index == 0) { //tweak relMSF due to ambiguity/contradiction in yellowbook docs if (!context.DiscMountPolicy.CUE_PregapContradictionModeA) { qRelMSF++; } //[IEC10149] says there's two "intervals" of a pregap. //mednafen's pseudocode interpretation of this: //if this is a data track and the previous track was not data, the last 150 sectors of the pregap match this track and the earlier sectors (at least 75) math the previous track //I agree, so let's do it that way if (t != 1 && cct.TrackType != CueTrackType.Audio && TrackInfos[t - 1].CompiledCueTrack.TrackType == CueTrackType.Audio) { if (relMSF < -150) { qTrack = TrackInfos[t - 1]; } } } //generate the right kind of sector synth for this track SS_Base ss = null; if (generateGap) { var ss_gap = new SS_Gap(); ss_gap.TrackType = qTrack.CompiledCueTrack.TrackType; ss = ss_gap; } else { int sectorSize = int.MaxValue; switch (qTrack.CompiledCueTrack.TrackType) { case CueTrackType.Audio: case CueTrackType.CDI_2352: case CueTrackType.Mode1_2352: case CueTrackType.Mode2_2352: ss = new SS_2352(); sectorSize = 2352; break; case CueTrackType.Mode1_2048: ss = new SS_Mode1_2048(); sectorSize = 2048; break; default: case CueTrackType.Mode2_2336: throw new InvalidOperationException($"Not supported: {cct.TrackType}"); } ss.Blob = curr_blobInfo.Blob; ss.BlobOffset = curr_blobOffset; curr_blobOffset += sectorSize; curr_blobMSF++; } ss.Policy = context.DiscMountPolicy; //setup subQ byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption: ss.sq.SetStatus(ADR, (EControlQ)(int)qTrack.CompiledCueTrack.Flags); ss.sq.q_tno = BCD2.FromDecimal(cct.Number); ss.sq.q_index = BCD2.FromDecimal(curr_index); ss.sq.AP_Timestamp = OUT_Disc._Sectors.Count; ss.sq.Timestamp = qRelMSF; //setup subP if (curr_index == 0) { ss.Pause = true; } OUT_Disc._Sectors.Add(ss); relMSF++; if (cct.IsFinalInFile) { //sometimes, break when the file is exhausted if (curr_blobOffset >= curr_blobInfo.Length) { trackDone = true; } } else { //other times, break when the track is done //(this check is safe because it's not the final track overall if it's not the final track in a file) if (curr_blobMSF >= TrackInfos[t + 1].CompiledCueTrack.Indexes[0].FileMSF.Sector) { trackDone = true; } } if (trackDone) { break; } } //--------------------------------- //gen postgap sectors int specifiedPostgapLength = cct.PostgapLength.Sector; for (int s = 0; s < specifiedPostgapLength; s++) { var ss = new SS_Gap(); ss.TrackType = cct.TrackType; //TODO - old track type in some < -150 cases? //-subq- byte ADR = 1; ss.sq.SetStatus(ADR, (EControlQ)(int)cct.Flags); ss.sq.q_tno = BCD2.FromDecimal(cct.Number); ss.sq.q_index = BCD2.FromDecimal(curr_index); ss.sq.AP_Timestamp = OUT_Disc._Sectors.Count; ss.sq.Timestamp = relMSF; //-subP- //always paused--is this good enough? ss.Pause = true; OUT_Disc._Sectors.Add(ss); relMSF++; } } //end track loop //add RawTOCEntries A0 A1 A2 to round out the TOC var TOCMiscInfo = new Synthesize_A0A1A2_Job { IN_FirstRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.FirstRecordedTrackNumber, IN_LastRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.LastRecordedTrackNumber, IN_Session1Format = IN_CompileJob.OUT_CompiledDiscInfo.SessionFormat, IN_LeadoutTimestamp = OUT_Disc._Sectors.Count }; TOCMiscInfo.Run(OUT_Disc.RawTOCEntries); //TODO - generate leadout, or delegates at least //blech, old crap, maybe //OUT_Disc.Structure.Synthesize_TOCPointsFromSessions(); //FinishLog(); } //Run()
public void Run() { //params var compiled = IN_CompileJob; var context = compiled.IN_CueContext; OUT_Disc = new Disc(); //generation state int curr_index; int curr_blobIndex = -1; int curr_blobMSF = -1; BlobInfo curr_blobInfo = null; long curr_blobOffset = -1; //mount all input files MountBlobs(); //unhappily, we cannot determine the length of all the tracks without knowing the length of the files //now that the files are mounted, we can figure the track lengths AnalyzeTracks(); //loop from track 1 to 99 //(track 0 isnt handled yet, that's way distant work) for (int t = 1; t < TrackInfos.Count; t++) { TrackInfo ti = TrackInfos[t]; CompiledCueTrack cct = ti.CompiledCueTrack; //--------------------------------- //setup track pregap processing //per "Example 05" on digitalx.org, pregap can come from index specification and pregap command int specifiedPregapLength = cct.PregapLength.Sector; int impliedPregapLength = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector; int totalPregapLength = specifiedPregapLength + impliedPregapLength; //from now on we'll track relative timestamp and increment it continually int relMSF = -totalPregapLength; //read more at policies declaration //if (!context.DiscMountPolicy.CUE_PauseContradictionModeA) // relMSF += 1; //--------------------------------- //--------------------------------- //generate sectors for this track. //advance to the next file if needed if (curr_blobIndex != cct.BlobIndex) { curr_blobIndex = cct.BlobIndex; curr_blobOffset = 0; curr_blobMSF = 0; curr_blobInfo = BlobInfos[curr_blobIndex]; } //work until the next track is reached, or the end of the current file is reached, depending on the track type curr_index = 0; for (; ; ) { bool trackDone = false; bool generateGap = false; if (specifiedPregapLength > 0) { //if burning through a specified pregap, count it down generateGap = true; specifiedPregapLength--; } else { //if burning through the file, select the appropriate index by inspecting the next index and seeing if we've reached it for (; ; ) { if (curr_index == cct.Indexes.Count - 1) break; if (curr_blobMSF >= cct.Indexes[curr_index + 1].FileMSF.Sector) { curr_index++; if (curr_index == 1) { //WE ARE NOW AT INDEX 1: generate the RawTOCEntry for this track EmitRawTOCEntry(cct); } } else break; } } //select the track type for the subQ //it's obviously the same as the main track type usually, but during a pregap it can be different TrackInfo qTrack = ti; int qRelMSF = relMSF; if (curr_index == 0) { //tweak relMSF due to ambiguity/contradiction in yellowbook docs if (!context.DiscMountPolicy.CUE_PregapContradictionModeA) qRelMSF++; //[IEC10149] says there's two "intervals" of a pregap. //mednafen's pseudocode interpretation of this: //if this is a data track and the previous track was not data, the last 150 sectors of the pregap match this track and the earlier sectors (at least 75) math the previous track //I agree, so let's do it that way if (t != 1 && cct.TrackType != CueTrackType.Audio && TrackInfos[t - 1].CompiledCueTrack.TrackType == CueTrackType.Audio) { if (relMSF < -150) { qTrack = TrackInfos[t - 1]; } } } //generate the right kind of sector synth for this track SS_Base ss = null; if (generateGap) { var ss_gap = new SS_Gap(); ss_gap.TrackType = qTrack.CompiledCueTrack.TrackType; ss = ss_gap; } else { int sectorSize = int.MaxValue; switch (qTrack.CompiledCueTrack.TrackType) { case CueTrackType.Audio: case CueTrackType.CDI_2352: case CueTrackType.Mode1_2352: case CueTrackType.Mode2_2352: ss = new SS_2352(); sectorSize = 2352; break; case CueTrackType.Mode1_2048: ss = new SS_Mode1_2048(); sectorSize = 2048; break; default: case CueTrackType.Mode2_2336: throw new InvalidOperationException("Not supported: " + cct.TrackType); } ss.Blob = curr_blobInfo.Blob; ss.BlobOffset = curr_blobOffset; curr_blobOffset += sectorSize; curr_blobMSF++; } ss.Policy = context.DiscMountPolicy; //setup subQ byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption: ss.sq.SetStatus(ADR, (EControlQ)(int)qTrack.CompiledCueTrack.Flags); ss.sq.q_tno = BCD2.FromDecimal(cct.Number); ss.sq.q_index = BCD2.FromDecimal(curr_index); ss.sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count); ss.sq.Timestamp = new Timestamp(qRelMSF); //setup subP if (curr_index == 0) ss.Pause = true; OUT_Disc.Sectors.Add(ss); relMSF++; if (cct.IsFinalInFile) { //sometimes, break when the file is exhausted if (curr_blobOffset >= curr_blobInfo.Length) trackDone = true; } else { //other times, break when the track is done //(this check is safe because it's not the final track overall if it's not the final track in a file) if (curr_blobMSF >= TrackInfos[t + 1].CompiledCueTrack.Indexes[0].FileMSF.Sector) trackDone = true; } if (trackDone) break; } //--------------------------------- //gen postgap sectors int specifiedPostgapLength = cct.PostgapLength.Sector; for (int s = 0; s < specifiedPostgapLength; s++) { var ss = new SS_Gap(); ss.TrackType = cct.TrackType; //TODO - old track type in some < -150 cases? //-subq- byte ADR = 1; ss.sq.SetStatus(ADR, (EControlQ)(int)cct.Flags); ss.sq.q_tno = BCD2.FromDecimal(cct.Number); ss.sq.q_index = BCD2.FromDecimal(curr_index); ss.sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count); ss.sq.Timestamp = new Timestamp(relMSF); //-subP- //always paused--is this good enough? ss.Pause = true; OUT_Disc.Sectors.Add(ss); relMSF++; } } //end track loop //add RawTOCEntries A0 A1 A2 to round out the TOC var TOCMiscInfo = new Synthesize_A0A1A2_Job { IN_FirstRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.FirstRecordedTrackNumber, IN_LastRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.LastRecordedTrackNumber, IN_Session1Format = IN_CompileJob.OUT_CompiledDiscInfo.SessionFormat, IN_LeadoutTimestamp = new Timestamp(OUT_Disc.Sectors.Count) //do we need a +150? }; TOCMiscInfo.Run(OUT_Disc.RawTOCEntries); //TODO - generate leadout, or delegates at least //blech, old crap, maybe //OUT_Disc.Structure.Synthesize_TOCPointsFromSessions(); //FinishLog(); }
public void Synth(SectorSynthJob job) { //be lazy, just generate the whole sector unconditionally //this is mostly based on mednafen's approach, which was probably finely tailored for PSX //heres the comments on the subject: // I'm not trusting that the "control" field for the TOC leadout entry will always be set properly, so | the control fields for the last track entry // and the leadout entry together before extracting the D2 bit. Audio track->data leadout is fairly benign though maybe noisy(especially if we ever implement // data scrambling properly), but data track->audio leadout could break things in an insidious manner for the more accurate drive emulation code). var ses = job.Disc.Structure.Sessions[SessionNumber]; int lba_relative = job.LBA - ses.LeadoutTrack.LBA; //data is zero int ts = lba_relative; int ats = job.LBA; const int ADR = 0x1; // Q channel data encodes position EControlQ control = ses.LeadoutTrack.Control; //ehhh? CDI? //if(toc.tracks[toc.last_track].valid) // control |= toc.tracks[toc.last_track].control & 0x4; //else if(toc.disc_type == DISC_TYPE_CD_I) // control |= 0x4; control |= (EControlQ)(((int)ses.LastInformationTrack.Control) & 4); SubchannelQ sq = new SubchannelQ(); sq.SetStatus(ADR, control); sq.q_tno.BCDValue = 0xAA; sq.q_index.BCDValue = 0x01; sq.Timestamp = ts; sq.AP_Timestamp = ats; sq.zero = 0; //finally, rely on a gap sector to do the heavy lifting to synthesize this CUE.CueTrackType TrackType = CUE.CueTrackType.Audio; if (ses.LeadoutTrack.IsData) { if (job.Disc.TOC.Session1Format == SessionFormat.Type20_CDXA || job.Disc.TOC.Session1Format == SessionFormat.Type10_CDI) TrackType = CUE.CueTrackType.Mode2_2352; else TrackType = CUE.CueTrackType.Mode1_2352; } CUE.SS_Gap ss_gap = new CUE.SS_Gap() { Policy = Policy, sq = sq, TrackType = TrackType, Pause = true //? }; ss_gap.Synth(job); }