diff --git a/BizHawk.Emulation.DiscSystem/DiscMountJob.cs b/BizHawk.Emulation.DiscSystem/DiscMountJob.cs index 30442d2526..556f5857ee 100644 --- a/BizHawk.Emulation.DiscSystem/DiscMountJob.cs +++ b/BizHawk.Emulation.DiscSystem/DiscMountJob.cs @@ -78,10 +78,19 @@ namespace BizHawk.Emulation.DiscSystem var tocSynth = new Synthesize_DiscTOC_From_RawTOCEntries_Job() { Entries = OUT_Disc.RawTOCEntries }; tocSynth.Run(); OUT_Disc.TOC = tocSynth.Result; - //2. Structure frmo TOCRaw + //2. Structure from TOCRaw var structureSynth = new Synthesize_DiscStructure_From_DiscTOC_Job() { IN_Disc = OUT_Disc, TOCRaw = OUT_Disc.TOC }; structureSynth.Run(); OUT_Disc.Structure = structureSynth.Result; + + //insert a synth provider to take care of the leadout track + var ss_leadout = new SS_Leadout() + { + SessionNumber = 1, + Policy = IN_DiscMountPolicy + }; + Func condition = (int lba) => lba >= OUT_Disc.Session1.LeadoutLBA; + new ConditionalSectorSynthProvider().Install(OUT_Disc, condition, ss_leadout); } FinishLog(); diff --git a/BizHawk.Emulation.DiscSystem/DiscStructure.cs b/BizHawk.Emulation.DiscSystem/DiscStructure.cs index 77c745f737..588de48072 100644 --- a/BizHawk.Emulation.DiscSystem/DiscStructure.cs +++ b/BizHawk.Emulation.DiscSystem/DiscStructure.cs @@ -12,9 +12,8 @@ namespace BizHawk.Emulation.DiscSystem public class DiscStructure { /// - /// This is a 0-indexed list of sessions (session 1 is at [0]) + /// This is a 1-indexed list of sessions (session 1 is at [1]) /// Support for multiple sessions is thoroughly not working yet - /// TODO - make re-index me with a null session 0 /// public List Sessions = new List(); @@ -50,11 +49,13 @@ namespace BizHawk.Emulation.DiscSystem /// /// A reference to the first information track (Track 1) + /// The raw TOC may have specified something different; it's not clear how this discrepancy is handled. /// public Track FirstInformationTrack { get { return Tracks[1]; } } /// - /// A reference to the first information track (Track 1) + /// A reference to the last information track on the disc. + /// The raw TOC may have specified something different; it's not clear how this discrepancy is handled. /// public Track LastInformationTrack { get { return Tracks[InformationTrackCount]; } } @@ -147,8 +148,8 @@ namespace BizHawk.Emulation.DiscSystem public bool IsAudio { get { return Mode == 0; } } /// - /// The 'control' properties of the track indicated by the subchannel Q. - /// This is as indicated by the disc TOC. + /// The 'control' properties of the track expected to be found in the track's subQ. + /// However, this is what's indicated by the disc TOC. /// Actual sector contents may vary. /// public EControlQ Control; diff --git a/BizHawk.Emulation.DiscSystem/DiscSubQ.cs b/BizHawk.Emulation.DiscSystem/DiscSubQ.cs index a6081656ca..cc5b44dd19 100644 --- a/BizHawk.Emulation.DiscSystem/DiscSubQ.cs +++ b/BizHawk.Emulation.DiscSystem/DiscSubQ.cs @@ -99,6 +99,7 @@ namespace BizHawk.Emulation.DiscSystem /// /// Retrieves the second set of timestamps (ap_min, ap_sec, ap_frac) as a convenient Timestamp. + /// TODO - rename everything AP here, it's nonsense. (the P is) /// public Timestamp AP_Timestamp { get { return new Timestamp(ap_min.DecimalValue, ap_sec.DecimalValue, ap_frame.DecimalValue); } diff --git a/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs b/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs index 741254a16a..95e00c8c15 100644 --- a/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs +++ b/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs @@ -137,6 +137,29 @@ namespace BizHawk.Emulation.DiscSystem } } + /// + /// Returns 'Patch' synth if the provided condition is met + /// + class ConditionalSectorSynthProvider : ISectorSynthProvider + { + Func Condition; + ISectorSynthJob2448 Patch; + ISectorSynthProvider Parent; + public void Install(Disc disc, Func condition, ISectorSynthJob2448 patch) + { + Parent = disc.SynthProvider; + disc.SynthProvider = this; + Condition = condition; + Patch = patch; + } + public ISectorSynthJob2448 Get(int lba) + { + if (Condition(lba)) + return Patch; + else return Parent.Get(lba); + } + } + /// /// When creating a disc, this is set with a callback that can deliver an ISectorSynthJob2448 for the given LBA /// @@ -177,5 +200,66 @@ namespace BizHawk.Emulation.DiscSystem } } + class SS_Leadout : ISectorSynthJob2448 + { + public int SessionNumber; + public DiscMountPolicy Policy; + + 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 + + Timestamp ts = new Timestamp(lba_relative); + Timestamp ats = new Timestamp(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_index.DecimalValue = 0xAA; + sq.q_index.DecimalValue = 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); + } + } } \ No newline at end of file