using System; using System.Collections.Generic; using System.IO; namespace BizHawk.Emulation.DiscSystem { /// /// Indicates which part of a sector are needing to be synthesized. /// Sector synthesis may create too much data, but this is a hint as to what's needed /// TODO - add a flag indicating whether clearing has happened /// TODO - add output to the job indicating whether interleaving has happened. let the sector reader be responsible /// [Flags] enum ESectorSynthPart { /// /// The data sector header is required. There's no header for audio tracks/sectors. /// Header16 = 1, /// /// The main 2048 user data bytes are required /// User2048 = 2, /// /// The 276 bytes of error correction are required /// ECC276 = 4, /// /// The 12 bytes preceding the ECC section are required (usually EDC and zero but also userdata sometimes) /// EDC12 = 8, /// /// The entire possible 276+12=288 bytes of ECM data is required (ECC276|EDC12) /// ECM288Complete = (ECC276 | EDC12), /// /// An alias for ECM288Complete /// ECMAny = ECM288Complete, /// /// A mode2 userdata section is required: the main 2048 user bytes AND the ECC and EDC areas /// User2336 = (User2048 | ECM288Complete), /// /// The complete sector userdata (2352 bytes) is required /// UserComplete = 15, /// /// An alias for UserComplete /// UserAny = UserComplete, /// /// An alias for UserComplete /// User2352 = UserComplete, /// /// SubP is required /// SubchannelP = 16, /// /// SubQ is required /// SubchannelQ = 32, /// /// Subchannels R-W (all except for P and Q) /// Subchannel_RSTUVW = (64|128|256|512|1024|2048), /// /// Complete subcode is required /// SubcodeComplete = (SubchannelP | SubchannelQ | Subchannel_RSTUVW), /// /// Any of the subcode might be required (just another way of writing SubcodeComplete) /// SubcodeAny = SubcodeComplete, /// /// The subcode should be deinterleaved /// SubcodeDeinterleave = 4096, /// /// The 100% complete sector is required including 2352 bytes of userdata and 96 bytes of subcode /// Complete2448 = SubcodeComplete | User2352, } /// /// Basic unit of sector synthesis /// interface ISectorSynthJob2448 { /// /// Synthesizes a sctor with the given job parameters /// void Synth(SectorSynthJob job); } /// /// Not a proper job? maybe with additional flags, it could be /// class SectorSynthJob { public int LBA; public ESectorSynthPart Parts; public byte[] DestBuffer2448; public int DestOffset; public SectorSynthParams Params; public Disc Disc; } /// /// an ISectorSynthProvider that just returns a value from an array of pre-made sectors /// class ArraySectorSynthProvider : ISectorSynthProvider { public List Sectors = new List(); public int FirstLBA; public ISectorSynthJob2448 Get(int lba) { int index = lba - FirstLBA; if (index < 0) return null; if (index >= Sectors.Count) return null; return Sectors[index]; } } /// /// an ISectorSynthProvider that just returns a fixed synthesizer /// class SimpleSectorSynthProvider : ISectorSynthProvider { public ISectorSynthJob2448 SS; public ISectorSynthJob2448 Get(int lba) { return SS; } } /// /// 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 /// interface ISectorSynthProvider { /// /// Retrieves an ISectorSynthJob2448 for the given LBA /// ISectorSynthJob2448 Get(int lba); } /// /// Generic parameters for sector synthesis. /// To cut down on resource utilization, these can be stored in a disc and are tightly coupled to /// the SectorSynths that have been setup for it /// struct SectorSynthParams { //public long[] BlobOffsets; public MednaDisc MednaDisc; } class SS_PatchQ : ISectorSynthJob2448 { public ISectorSynthJob2448 Original; public byte[] Buffer_SubQ = new byte[12]; public void Synth(SectorSynthJob job) { Original.Synth(job); if ((job.Parts & ESectorSynthPart.SubchannelQ) == 0) return; //apply patched subQ for (int i = 0; i < 12; i++) job.DestBuffer2448[2352 + 12 + i] = Buffer_SubQ[i]; } } 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 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); } } }