using System; using System.Collections.Generic; using BizHawk.Common.BufferExtensions; namespace BizHawk.Emulation.DiscSystem { public class DiscSectorReaderPolicy { /// /// Different methods that can be used to get 2048 byte sectors /// public enum EUserData2048Mode { /// /// The contents of the sector should be inspected (mode and form) and 2048 bytes returned accordingly /// InspectSector, /// /// Read it as mode 1 /// AssumeMode1, /// /// Read it as mode 2 (form 1) /// AssumeMode2_Form1, } /// /// The method used to get 2048 byte sectors /// public EUserData2048Mode UserData2048Mode = EUserData2048Mode.InspectSector; /// /// Throw exceptions if 2048 byte data can't be read /// public bool ThrowExceptions2048 = true; /// /// Indicates whether subcode should be delivered deinterleaved. It isn't stored that way on actual discs. But it is in .sub files. /// This defaults to true because it's most likely higher-performing, and it's rarely ever wanted interleaved. /// public bool DeinterleavedSubcode = true; /// /// Indicates whether the output buffer should be cleared before returning any data. /// This will unfortunately involve clearing sections you didn't ask for, and clearing sections about to be filled with data from the disc. /// It is a waste of performance, but it will ensure reliability. /// public bool DeterministicClearBuffer = true; } /// /// Main entry point for reading sectors from a disc. /// This is not a multi-thread capable interface. /// public class DiscSectorReader { public DiscSectorReaderPolicy Policy = new DiscSectorReaderPolicy(); Disc disc; public DiscSectorReader(Disc disc) { this.disc = disc; } void PrepareJob(int lba) { job.LBA = lba; job.Params = disc.SynthParams; job.Disc = disc; } void PrepareBuffer(byte[] buffer, int offset, int size) { if (Policy.DeterministicClearBuffer) Array.Clear(buffer, offset, size); } /// /// Reads a full 2352 bytes of user data from a sector /// public int ReadLBA_2352(int lba, byte[] buffer, int offset) { var sector = disc.SynthProvider.Get(lba); if (sector == null) return 0; PrepareBuffer(buffer, offset, 2352); PrepareJob(lba); job.DestBuffer2448 = buf2442; job.DestOffset = 0; job.Parts = ESectorSynthPart.User2352; job.Disc = disc; //this can't include subcode, so it's senseless to handle it here //if (Policy.DeinterleavedSubcode) job.Parts |= ESectorSynthPart.SubcodeDeinterleave; sector.Synth(job); Buffer.BlockCopy(buf2442, 0, buffer, offset, 2352); return 2352; } /// /// Reads the absolutely complete 2448 byte sector including all the user data and subcode /// public int ReadLBA_2448(int lba, byte[] buffer, int offset) { var sector = disc.SynthProvider.Get(lba); if (sector == null) return 0; PrepareBuffer(buffer, offset, 2352); PrepareJob(lba); job.DestBuffer2448 = buffer; //go straight to the caller's buffer job.DestOffset = offset; //go straight to the caller's buffer job.Parts = ESectorSynthPart.Complete2448; if (Policy.DeinterleavedSubcode) job.Parts |= ESectorSynthPart.SubcodeDeinterleave; sector.Synth(job); //we went straight to the caller's buffer, so no need to copy return 2448; } int ReadLBA_2048_Mode1(int lba, byte[] buffer, int offset) { //we can read the 2048 bytes directly var sector = disc.SynthProvider.Get(lba); if (sector == null) return 0; PrepareBuffer(buffer, offset, 2048); PrepareJob(lba); job.DestBuffer2448 = buf2442; job.DestOffset = 0; job.Parts = ESectorSynthPart.User2048; sector.Synth(job); Buffer.BlockCopy(buf2442, 16, buffer, offset, 2048); return 2048; } int ReadLBA_2048_Mode2_Form1(int lba, byte[] buffer, int offset) { //we can read the 2048 bytes directly but we have to get them from the mode 2 data var sector = disc.SynthProvider.Get(lba); if (sector == null) return 0; PrepareBuffer(buffer, offset, 2048); PrepareJob(lba); job.DestBuffer2448 = buf2442; job.DestOffset = 0; job.Parts = ESectorSynthPart.User2336; sector.Synth(job); Buffer.BlockCopy(buf2442, 24, buffer, offset, 2048); return 2048; } /// /// Reads 12 bytes of subQ data from a sector. /// This is necessarily deinterleaved. /// public int ReadLBA_SubQ(int lba, byte[] buffer, int offset) { var sector = disc.SynthProvider.Get(lba); if (sector == null) return 0; PrepareBuffer(buffer, offset, 12); PrepareJob(lba); job.DestBuffer2448 = buf2442; job.DestOffset = 0; job.Parts = ESectorSynthPart.SubchannelQ | ESectorSynthPart.SubcodeDeinterleave; sector.Synth(job); Buffer.BlockCopy(buf2442, 2352 + 12, buffer, offset, 12); return 12; } /// /// reads 2048 bytes of user data from a sector. /// This is only valid for Mode 1 and XA Mode 2 (Form 1) sectors. /// Attempting it on any other sectors is ill-defined. /// If any console is trying to do that, we'll have to add a policy for it, or handle it in the console. /// (We can add a method to this API that checks the type of a sector to make that easier) /// public int ReadLBA_2048(int lba, byte[] buffer, int offset) { if (Policy.UserData2048Mode == DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode1) return ReadLBA_2048_Mode1(lba, buffer, offset); else if (Policy.UserData2048Mode == DiscSectorReaderPolicy.EUserData2048Mode.AssumeMode2_Form1) return ReadLBA_2048_Mode2_Form1(lba, buffer, offset); else { //we need to determine the type of the sector. //in no case do we need the ECC so build special flags here var sector = disc.SynthProvider.Get(lba); if (sector == null) return 0; PrepareBuffer(buffer, offset, 2048); PrepareJob(lba); job.DestBuffer2448 = buf2442; job.DestOffset = 0; job.Parts = ESectorSynthPart.Header16 | ESectorSynthPart.User2048 | ESectorSynthPart.EDC12; sector.Synth(job); //now the inspection, based on the mode byte mode = buf2442[15]; if (mode == 1) { Buffer.BlockCopy(buf2442, 16, buffer, offset, 2048); return 2048; } else if (mode == 2) { //greenbook pg II-22 //we're going to do a sanity check here.. we're not sure what happens if we try to read 2048 bytes from a form-2 2324 byte sector //we could handle it by policy but for now the policy is exception byte submodeByte = buf2442[18]; int form = ((submodeByte >> 5) & 1) + 1; if (form == 2) { if (Policy.ThrowExceptions2048) throw new InvalidOperationException("Unsupported scenario: reading 2048 bytes from a Mode2 Form 2 sector"); else return 0; } //otherwise it's OK Buffer.BlockCopy(buf2442, 24, buffer, offset, 2048); return 2048; } else { if (Policy.ThrowExceptions2048) throw new InvalidOperationException("Unsupported scenario: reading 2048 bytes from an unhandled sector type"); else return 0; } } } /// /// Reads 12 bytes of subQ data from a sector and stores it unpacked into the provided struct /// TODO - make use of deserialize code elsewhere /// public void ReadLBA_SubQ(int lba, out SubchannelQ sq) { ReadLBA_SubQ(lba, buf12, 0); sq.q_status = buf12[0]; sq.q_tno.BCDValue = buf12[1]; sq.q_index.BCDValue = buf12[2]; sq.min.BCDValue = buf12[3]; sq.sec.BCDValue = buf12[4]; sq.frame.BCDValue = buf12[5]; sq.zero = buf12[6]; sq.ap_min.BCDValue = buf12[7]; sq.ap_sec.BCDValue = buf12[8]; sq.ap_frame.BCDValue = buf12[9]; //CRC is stored inverted and big endian.. so... do the opposite byte hibyte = (byte)(~buf12[10]); byte lobyte = (byte)(~buf12[11]); sq.q_crc = (ushort)((hibyte << 8) | lobyte); } /// /// Reads the mode field from a sector /// If this is an audio sector, the results will be nonsense. /// public int ReadLBA_Mode(int lba) { var sector = disc.SynthProvider.Get(lba); if (sector == null) return 0; PrepareJob(lba); job.DestBuffer2448 = buf2442; job.DestOffset = 0; job.Parts = ESectorSynthPart.Header16; job.Disc = disc; sector.Synth(job); return buf2442[15]; } //lets not try to these as a sector cache. it gets too complicated. its just a temporary variable. byte[] buf2442 = new byte[2448]; byte[] buf12 = new byte[12]; SectorSynthJob job = new SectorSynthJob(); } }