diff --git a/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj b/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj index 221aa72841..ebccdb8ab6 100644 --- a/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj +++ b/BizHawk.Emulation.DiscSystem/BizHawk.Emulation.DiscSystem.csproj @@ -93,12 +93,14 @@ + + diff --git a/BizHawk.Emulation.DiscSystem/Disc.cs b/BizHawk.Emulation.DiscSystem/Disc.cs index e048aae907..5fc2acae9e 100644 --- a/BizHawk.Emulation.DiscSystem/Disc.cs +++ b/BizHawk.Emulation.DiscSystem/Disc.cs @@ -21,6 +21,17 @@ namespace BizHawk.Emulation.DiscSystem { public partial class Disc : IDisposable { + /// + /// Automagically loads a disc, without any fine-tuned control at all + /// + public static Disc LoadAutomagic(string path) + { + var job = new DiscMountJob { IN_FromPath = path }; + //job.IN_DiscInterface = DiscInterface.MednaDisc; //TEST + job.Run(); + return job.OUT_Disc; + } + /// /// The DiscStructure corresponding to the TOCRaw /// @@ -33,8 +44,9 @@ namespace BizHawk.Emulation.DiscSystem /// /// The DiscTOCRaw corresponding to the RawTOCEntries. - /// TODO - rename to TOC /// TODO - there's one of these for every session, so... having one here doesnt make sense + /// so... + /// TODO - remove me /// public DiscTOC TOC; @@ -63,7 +75,6 @@ namespace BizHawk.Emulation.DiscSystem /// //public DiscMountPolicy DiscMountPolicy; - //---------------------------------------------------------------------------- /// @@ -73,6 +84,7 @@ namespace BizHawk.Emulation.DiscSystem /// /// The sectors on the disc + /// TODO - replace with delegate (much faster disc loading, support of reading of arbitrary lead-out and lead-in sectors) /// internal List Sectors = new List(); @@ -81,441 +93,11 @@ namespace BizHawk.Emulation.DiscSystem /// internal SectorSynthParams SynthParams = new SectorSynthParams(); + /// + /// Forbid public construction + /// internal Disc() - { - } + {} - - - /// - /// Automagically loads a disc, without any fine-tuned control at all - /// - public static Disc LoadAutomagic(string path) - { - var job = new DiscMountJob { IN_FromPath = path }; - //job.IN_DiscInterface = DiscInterface.MednaDisc; //TEST - job.Run(); - return job.OUT_Disc; - } - - 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]; - } - } - - /// - /// applies an SBI file to the disc - /// - public void ApplySBI(SBI.SubQPatchData sbi, bool asMednafen) - { - //TODO - could implement as a blob, to avoid allocating so many byte buffers - - //save this, it's small, and we'll want it for disc processing a/b checks - Memos["sbi"] = sbi; - - DiscSectorReader dsr = new DiscSectorReader(this); - - int n = sbi.ABAs.Count; - int b=0; - for (int i = 0; i < n; i++) - { - int lba = sbi.ABAs[i] - 150; - - //create a synthesizer which can return the patched data - var ss_patchq = new SS_PatchQ() { Original = this.Sectors[lba+150] }; - byte[] subQbuf = ss_patchq.Buffer_SubQ; - - //read the old subcode - dsr.ReadLBA_SubQ(lba, subQbuf, 0); - - //insert patch - Sectors[lba + 150] = ss_patchq; - - //apply SBI patch - for (int j = 0; j < 12; j++) - { - short patch = sbi.subq[b++]; - if (patch == -1) continue; - else subQbuf[j] = (byte)patch; - } - - //Apply mednafen hacks - //The reasoning here is that we know we expect these sectors to have a wrong checksum. therefore, generate a checksum, and make it wrong - //However, this seems senseless to me. The whole point of the SBI data is that it stores the patches needed to generate an acceptable subQ, right? - if (asMednafen) - { - SynthUtils.SubQ_SynthChecksum(subQbuf, 0); - subQbuf[10] ^= 0xFF; - subQbuf[11] ^= 0xFF; - } - } - } - - static byte IntToBCD(int n) - { - int ones; - int tens = Math.DivRem(n,10,out ones); - return (byte)((tens<<4)|ones); - } } - - /// - /// encapsulates a 2 digit BCD number as used various places in the CD specs - /// - public struct BCD2 - { - /// - /// The raw BCD value. you can't do math on this number! but you may be asked to supply it to a game program. - /// The largest number it can logically contain is 99 - /// - public byte BCDValue; - - /// - /// The derived decimal value. you can do math on this! the largest number it can logically contain is 99. - /// - public int DecimalValue - { - get { return (BCDValue & 0xF) + ((BCDValue >> 4) & 0xF) * 10; } - set { BCDValue = IntToBCD(value); } - } - - /// - /// makes a BCD2 from a decimal number. don't supply a number > 99 or you might not like the results - /// - public static BCD2 FromDecimal(int d) - { - return new BCD2 {DecimalValue = d}; - } - - public static BCD2 FromBCD(byte b) - { - return new BCD2 { BCDValue = b }; - } - - public static int BCDToInt(byte n) - { - var bcd = new BCD2(); - bcd.BCDValue = n; - return bcd.DecimalValue; - } - - public static byte IntToBCD(int n) - { - int ones; - int tens = Math.DivRem(n, 10, out ones); - return (byte)((tens << 4) | ones); - } - - public override string ToString() - { - return BCDValue.ToString("X2"); - } - } - - /// - /// todo - rename to MSF? It can specify durations, so maybe it should be not suggestive of timestamp - /// TODO - can we maybe use BCD2 in here - /// - public struct Timestamp - { - /// - /// Checks if the string is a legit MSF. It's strict. - /// - public static bool IsMatch(string str) - { - return new Timestamp(str).Valid; - } - - /// - /// creates a timestamp from a string in the form mm:ss:ff - /// - public Timestamp(string str) - { - if (str.Length != 8) goto BOGUS; - if (str[0] < '0' || str[0] > '9') goto BOGUS; - if (str[1] < '0' || str[1] > '9') goto BOGUS; - if (str[2] != ':') goto BOGUS; - if (str[3] < '0' || str[3] > '9') goto BOGUS; - if (str[4] < '0' || str[4] > '9') goto BOGUS; - if (str[5] != ':') goto BOGUS; - if (str[6] < '0' || str[6] > '9') goto BOGUS; - if (str[7] < '0' || str[7] > '9') goto BOGUS; - MIN = (byte)((str[0] - '0') * 10 + (str[1] - '0')); - SEC = (byte)((str[3] - '0') * 10 + (str[4] - '0')); - FRAC = (byte)((str[6] - '0') * 10 + (str[7] - '0')); - Valid = true; - Negative = false; - return; - BOGUS: - MIN = SEC = FRAC = 0; - Valid = false; - Negative = false; - return; - } - - /// - /// The string representation of the MSF - /// - public string Value - { - get - { - if (!Valid) return "--:--:--"; - return string.Format("{0}{1:D2}:{2:D2}:{3:D2}", Negative?'-':'+',MIN, SEC, FRAC); - } - } - - public readonly byte MIN, SEC, FRAC; - public readonly bool Valid, Negative; - - /// - /// The fully multiplied out flat-address Sector number - /// - public int Sector { get { return MIN * 60 * 75 + SEC * 75 + FRAC; } } - - /// - /// creates timestamp from the supplied MSF - /// - public Timestamp(int m, int s, int f) - { - MIN = (byte)m; - SEC = (byte)s; - FRAC = (byte)f; - Valid = true; - Negative = false; - } - - /// - /// creates timestamp from supplied SectorNumber - /// - public Timestamp(int SectorNumber) - { - if (SectorNumber < 0) - { - SectorNumber = -SectorNumber; - Negative = true; - } - else Negative = false; - MIN = (byte)(SectorNumber / (60 * 75)); - SEC = (byte)((SectorNumber / 75) % 60); - FRAC = (byte)(SectorNumber % 75); - Valid = true; - } - - public override string ToString() - { - return Value; - } - } - - - static class SynthUtils - { - /// - /// Calculates the checksum of the provided Q subchannel buffer and emplaces it - /// - /// 12 byte Q subchannel buffer: input and output buffer for operation - /// location within buffer of Q subchannel - public static ushort SubQ_SynthChecksum(byte[] buf12, int offset) - { - ushort crc16 = CRC16_CCITT.Calculate(buf12, offset, 10); - - //CRC is stored inverted and big endian - buf12[offset + 10] = (byte)(~(crc16 >> 8)); - buf12[offset + 11] = (byte)(~(crc16)); - - return crc16; - } - - /// - /// Caclulates the checksum of the provided Q subchannel buffer - /// - public static ushort SubQ_CalcChecksum(byte[] buf12, int offset) - { - return CRC16_CCITT.Calculate(buf12, offset, 10); - } - - /// - /// Serializes the provided SubchannelQ structure into a buffer - /// Returns the crc, calculated or otherwise. - /// - public static ushort SubQ_Serialize(byte[] buf12, int offset, ref SubchannelQ sq) - { - buf12[offset + 0] = sq.q_status; - buf12[offset + 1] = sq.q_tno.BCDValue; - buf12[offset + 2] = sq.q_index.BCDValue; - buf12[offset + 3] = sq.min.BCDValue; - buf12[offset + 4] = sq.sec.BCDValue; - buf12[offset + 5] = sq.frame.BCDValue; - buf12[offset + 6] = sq.zero; - buf12[offset + 7] = sq.ap_min.BCDValue; - buf12[offset + 8] = sq.ap_sec.BCDValue; - buf12[offset + 9] = sq.ap_frame.BCDValue; - - return SubQ_SynthChecksum(buf12, offset); - } - - /// - /// Synthesizes the typical subP data into the provided buffer depending on the indicated pause flag - /// - public static void SubP(byte[] buffer12, int offset, bool pause) - { - byte val = (byte)(pause ? 0xFF : 0x00); - for (int i = 0; i < 12; i++) - buffer12[offset + i] = val; - } - - /// - /// Synthesizes a data sector header - /// - public static void SectorHeader(byte[] buffer16, int offset, int LBA, byte mode) - { - buffer16[offset + 0] = 0x00; - for (int i = 1; i < 11; i++) buffer16[offset + i] = 0xFF; - buffer16[offset + 11] = 0x00; - Timestamp ts = new Timestamp(LBA + 150); - buffer16[offset + 12] = BCD2.IntToBCD(ts.MIN); - buffer16[offset + 13] = BCD2.IntToBCD(ts.SEC); - buffer16[offset + 14] = BCD2.IntToBCD(ts.FRAC); - buffer16[offset + 15] = mode; - } - - /// - /// Synthesizes the EDC checksum for a Mode 2 Form 1 data sector (and puts it in place) - /// - public static void EDC_Mode2_Form1(byte[] buf2352, int offset) - { - uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2048 + 8); - ECM.PokeUint(buf2352, offset + 2072, edc); - } - - /// - /// Synthesizes the EDC checksum for a Mode 2 Form 2 data sector (and puts it in place) - /// - public static void EDC_Mode2_Form2(byte[] buf2352, int offset) - { - uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2324 + 8); - ECM.PokeUint(buf2352, offset + 2348, edc); - } - - - /// - /// Synthesizes the complete ECM data (EDC + ECC) for a Mode 1 data sector (and puts it in place) - /// Make sure everything else in the sector userdata is done before calling this - /// - public static void ECM_Mode1(byte[] buf2352, int offset, int LBA) - { - //EDC - uint edc = ECM.EDC_Calc(buf2352, offset, 2064); - ECM.PokeUint(buf2352, offset + 2064, edc); - - //reserved, zero - for (int i = 0; i < 8; i++) buf2352[offset + 2068 + i] = 0; - - //ECC - ECM.ECC_Populate(buf2352, offset, buf2352, offset, false); - } - - /// - /// Converts the useful (but unrealistic) deinterleaved subchannel data into the useless (but realistic) interleaved format. - /// in_buf and out_buf should not overlap - /// - public static void InterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index) - { - for (int d = 0; d < 12; d++) - { - for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++) - { - int rawb = 0; - - for (int ch = 0; ch < 8; ch++) - { - rawb |= ((in_buf[ch * 12 + d + in_buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch); - } - out_buf[(d << 3) + bitpoodle + out_buf_index] = (byte)rawb; - } - } - } - - /// - /// Converts the useless (but realistic) interleaved subchannel data into a useful (but unrealistic) deinterleaved format. - /// in_buf and out_buf should not overlap - /// - public static void DeinterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index) - { - for (int i = 0; i < 96; i++) - out_buf[i] = 0; - - for (int ch = 0; ch < 8; ch++) - { - for (int i = 0; i < 96; i++) - { - out_buf[(ch * 12) + (i >> 3) + out_buf_index] |= (byte)(((in_buf[i + in_buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7))); - } - } - } - - /// - /// Converts the useful (but unrealistic) deinterleaved data into the useless (but realistic) interleaved subchannel format. - /// - public unsafe static void InterleaveSubcodeInplace(byte[] buf, int buf_index) - { - byte* out_buf = stackalloc byte[96]; - - for (int i = 0; i < 96; i++) - out_buf[i] = 0; - - for (int d = 0; d < 12; d++) - { - for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++) - { - int rawb = 0; - - for (int ch = 0; ch < 8; ch++) - { - rawb |= ((buf[ch * 12 + d + buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch); - } - out_buf[(d << 3) + bitpoodle] = (byte)rawb; - } - } - - for (int i = 0; i < 96; i++) - buf[i + buf_index] = out_buf[i]; - } - - /// - /// Converts the useless (but realistic) interleaved subchannel data into a useful (but unrealistic) deinterleaved format. - /// - public unsafe static void DeinterleaveSubcodeInplace(byte[] buf, int buf_index) - { - byte* out_buf = stackalloc byte[96]; - - for (int i = 0; i < 96; i++) - out_buf[i] = 0; - - for (int ch = 0; ch < 8; ch++) - { - for (int i = 0; i < 96; i++) - { - out_buf[(ch * 12) + (i >> 3)] |= (byte)(((buf[i + buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7))); - } - } - - for (int i = 0; i < 96; i++) - buf[i + buf_index] = out_buf[i]; - } - } - } \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/DiscMountJob.cs b/BizHawk.Emulation.DiscSystem/DiscMountJob.cs index 20f6eb8a94..957a5c3d22 100644 --- a/BizHawk.Emulation.DiscSystem/DiscMountJob.cs +++ b/BizHawk.Emulation.DiscSystem/DiscMountJob.cs @@ -6,8 +6,11 @@ using System.Collections.Generic; namespace BizHawk.Emulation.DiscSystem { - - + /// + /// A Job interface for mounting discs. + /// This is publicly exposed because it's the main point of control for fine-tuning disc loading options. + /// This would typically be used to load discs. + /// public partial class DiscMountJob : DiscJob { /// @@ -142,10 +145,10 @@ namespace BizHawk.Emulation.DiscSystem var sbiPath = Path.ChangeExtension(IN_FromPath, ".sbi"); if (File.Exists(sbiPath) && SBI.SBIFormat.QuickCheckISSBI(sbiPath)) { - var sbiJob = new SBI.LoadSBIJob(); - sbiJob.IN_Path = sbiPath; - sbiJob.Run(); - OUT_Disc.ApplySBI(sbiJob.OUT_Data, IN_DiscMountPolicy.SBI_As_Mednafen); + var loadSbiJob = new SBI.LoadSBIJob() { IN_Path = sbiPath }; + loadSbiJob.Run(); + var applySbiJob = new ApplySBIJob(); + applySbiJob.Run(OUT_Disc, loadSbiJob.OUT_Data, IN_DiscMountPolicy.SBI_As_Mednafen); } } else if (ext == ".ccd") diff --git a/BizHawk.Emulation.DiscSystem/DiscTypes.cs b/BizHawk.Emulation.DiscSystem/DiscTypes.cs index 7ba69358e1..1b21343511 100644 --- a/BizHawk.Emulation.DiscSystem/DiscTypes.cs +++ b/BizHawk.Emulation.DiscSystem/DiscTypes.cs @@ -27,4 +27,153 @@ namespace BizHawk.Emulation.DiscSystem Type10_CDI = 0x10, Type20_CDXA = 0x20 } + + /// + /// encapsulates a 2 digit BCD number as used various places in the CD specs + /// + public struct BCD2 + { + /// + /// The raw BCD value. you can't do math on this number! but you may be asked to supply it to a game program. + /// The largest number it can logically contain is 99 + /// + public byte BCDValue; + + /// + /// The derived decimal value. you can do math on this! the largest number it can logically contain is 99. + /// + public int DecimalValue + { + get { return (BCDValue & 0xF) + ((BCDValue >> 4) & 0xF) * 10; } + set { BCDValue = IntToBCD(value); } + } + + /// + /// makes a BCD2 from a decimal number. don't supply a number > 99 or you might not like the results + /// + public static BCD2 FromDecimal(int d) + { + return new BCD2 { DecimalValue = d }; + } + + public static BCD2 FromBCD(byte b) + { + return new BCD2 { BCDValue = b }; + } + + public static int BCDToInt(byte n) + { + var bcd = new BCD2(); + bcd.BCDValue = n; + return bcd.DecimalValue; + } + + public static byte IntToBCD(int n) + { + int ones; + int tens = Math.DivRem(n, 10, out ones); + return (byte)((tens << 4) | ones); + } + + public override string ToString() + { + return BCDValue.ToString("X2"); + } + } + + /// + /// todo - rename to MSF? It can specify durations, so maybe it should be not suggestive of timestamp + /// TODO - can we maybe use BCD2 in here + /// + public struct Timestamp + { + /// + /// Checks if the string is a legit MSF. It's strict. + /// + public static bool IsMatch(string str) + { + return new Timestamp(str).Valid; + } + + /// + /// creates a timestamp from a string in the form mm:ss:ff + /// + public Timestamp(string str) + { + if (str.Length != 8) goto BOGUS; + if (str[0] < '0' || str[0] > '9') goto BOGUS; + if (str[1] < '0' || str[1] > '9') goto BOGUS; + if (str[2] != ':') goto BOGUS; + if (str[3] < '0' || str[3] > '9') goto BOGUS; + if (str[4] < '0' || str[4] > '9') goto BOGUS; + if (str[5] != ':') goto BOGUS; + if (str[6] < '0' || str[6] > '9') goto BOGUS; + if (str[7] < '0' || str[7] > '9') goto BOGUS; + MIN = (byte)((str[0] - '0') * 10 + (str[1] - '0')); + SEC = (byte)((str[3] - '0') * 10 + (str[4] - '0')); + FRAC = (byte)((str[6] - '0') * 10 + (str[7] - '0')); + Valid = true; + Negative = false; + return; + BOGUS: + MIN = SEC = FRAC = 0; + Valid = false; + Negative = false; + return; + } + + /// + /// The string representation of the MSF + /// + public string Value + { + get + { + if (!Valid) return "--:--:--"; + return string.Format("{0}{1:D2}:{2:D2}:{3:D2}", Negative ? '-' : '+', MIN, SEC, FRAC); + } + } + + public readonly byte MIN, SEC, FRAC; + public readonly bool Valid, Negative; + + /// + /// The fully multiplied out flat-address Sector number + /// + public int Sector { get { return MIN * 60 * 75 + SEC * 75 + FRAC; } } + + /// + /// creates timestamp from the supplied MSF + /// + public Timestamp(int m, int s, int f) + { + MIN = (byte)m; + SEC = (byte)s; + FRAC = (byte)f; + Valid = true; + Negative = false; + } + + /// + /// creates timestamp from supplied SectorNumber + /// + public Timestamp(int SectorNumber) + { + if (SectorNumber < 0) + { + SectorNumber = -SectorNumber; + Negative = true; + } + else Negative = false; + MIN = (byte)(SectorNumber / (60 * 75)); + SEC = (byte)((SectorNumber / 75) % 60); + FRAC = (byte)(SectorNumber % 75); + Valid = true; + } + + public override string ToString() + { + return Value; + } + } } \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/DiscUtils.cs b/BizHawk.Emulation.DiscSystem/DiscUtils.cs index bbb650578b..857d0e22f6 100644 --- a/BizHawk.Emulation.DiscSystem/DiscUtils.cs +++ b/BizHawk.Emulation.DiscSystem/DiscUtils.cs @@ -1,7 +1,16 @@ +using System; + namespace BizHawk.Emulation.DiscSystem { public static class DiscUtils { + static byte IntToBCD(int n) + { + int ones; + int tens = Math.DivRem(n, 10, out ones); + return (byte)((tens << 4) | ones); + } + /// /// converts the given int to a BCD value /// diff --git a/BizHawk.Emulation.DiscSystem/Internal/Jobs/ApplySBIJob.cs b/BizHawk.Emulation.DiscSystem/Internal/Jobs/ApplySBIJob.cs new file mode 100644 index 0000000000..664c2cf5a0 --- /dev/null +++ b/BizHawk.Emulation.DiscSystem/Internal/Jobs/ApplySBIJob.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using System.Text; +using System.IO; +using System.Collections.Generic; + +//TODO - generate correct Q subchannel CRC + +namespace BizHawk.Emulation.DiscSystem +{ + class ApplySBIJob + { + /// + /// applies an SBI file to the disc + /// + public void Run(Disc disc, SBI.SubQPatchData sbi, bool asMednafen) + { + //TODO - could implement as a blob, to avoid allocating so many byte buffers + + //save this, it's small, and we'll want it for disc processing a/b checks + disc.Memos["sbi"] = sbi; + + DiscSectorReader dsr = new DiscSectorReader(disc); + + int n = sbi.ABAs.Count; + int b = 0; + for (int i = 0; i < n; i++) + { + int lba = sbi.ABAs[i] - 150; + + //create a synthesizer which can return the patched data + var ss_patchq = new SS_PatchQ() { Original = disc.Sectors[lba + 150] }; + byte[] subQbuf = ss_patchq.Buffer_SubQ; + + //read the old subcode + dsr.ReadLBA_SubQ(lba, subQbuf, 0); + + //insert patch + disc.Sectors[lba + 150] = ss_patchq; + + //apply SBI patch + for (int j = 0; j < 12; j++) + { + short patch = sbi.subq[b++]; + if (patch == -1) continue; + else subQbuf[j] = (byte)patch; + } + + //Apply mednafen hacks + //The reasoning here is that we know we expect these sectors to have a wrong checksum. therefore, generate a checksum, and make it wrong + //However, this seems senseless to me. The whole point of the SBI data is that it stores the patches needed to generate an acceptable subQ, right? + if (asMednafen) + { + SynthUtils.SubQ_SynthChecksum(subQbuf, 0); + subQbuf[10] ^= 0xFF; + subQbuf[11] ^= 0xFF; + } + } + } + } +} diff --git a/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs b/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs index ef21c84e71..ae0c6e5e41 100644 --- a/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs +++ b/BizHawk.Emulation.DiscSystem/Internal/SectorSynth.cs @@ -126,4 +126,24 @@ namespace BizHawk.Emulation.DiscSystem 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]; + } + } + + } \ No newline at end of file diff --git a/BizHawk.Emulation.DiscSystem/Internal/SynthUtils.cs b/BizHawk.Emulation.DiscSystem/Internal/SynthUtils.cs new file mode 100644 index 0000000000..1f486db75a --- /dev/null +++ b/BizHawk.Emulation.DiscSystem/Internal/SynthUtils.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace BizHawk.Emulation.DiscSystem +{ + static class SynthUtils + { + /// + /// Calculates the checksum of the provided Q subchannel buffer and emplaces it + /// + /// 12 byte Q subchannel buffer: input and output buffer for operation + /// location within buffer of Q subchannel + public static ushort SubQ_SynthChecksum(byte[] buf12, int offset) + { + ushort crc16 = CRC16_CCITT.Calculate(buf12, offset, 10); + + //CRC is stored inverted and big endian + buf12[offset + 10] = (byte)(~(crc16 >> 8)); + buf12[offset + 11] = (byte)(~(crc16)); + + return crc16; + } + + /// + /// Caclulates the checksum of the provided Q subchannel buffer + /// + public static ushort SubQ_CalcChecksum(byte[] buf12, int offset) + { + return CRC16_CCITT.Calculate(buf12, offset, 10); + } + + /// + /// Serializes the provided SubchannelQ structure into a buffer + /// Returns the crc, calculated or otherwise. + /// + public static ushort SubQ_Serialize(byte[] buf12, int offset, ref SubchannelQ sq) + { + buf12[offset + 0] = sq.q_status; + buf12[offset + 1] = sq.q_tno.BCDValue; + buf12[offset + 2] = sq.q_index.BCDValue; + buf12[offset + 3] = sq.min.BCDValue; + buf12[offset + 4] = sq.sec.BCDValue; + buf12[offset + 5] = sq.frame.BCDValue; + buf12[offset + 6] = sq.zero; + buf12[offset + 7] = sq.ap_min.BCDValue; + buf12[offset + 8] = sq.ap_sec.BCDValue; + buf12[offset + 9] = sq.ap_frame.BCDValue; + + return SubQ_SynthChecksum(buf12, offset); + } + + /// + /// Synthesizes the typical subP data into the provided buffer depending on the indicated pause flag + /// + public static void SubP(byte[] buffer12, int offset, bool pause) + { + byte val = (byte)(pause ? 0xFF : 0x00); + for (int i = 0; i < 12; i++) + buffer12[offset + i] = val; + } + + /// + /// Synthesizes a data sector header + /// + public static void SectorHeader(byte[] buffer16, int offset, int LBA, byte mode) + { + buffer16[offset + 0] = 0x00; + for (int i = 1; i < 11; i++) buffer16[offset + i] = 0xFF; + buffer16[offset + 11] = 0x00; + Timestamp ts = new Timestamp(LBA + 150); + buffer16[offset + 12] = BCD2.IntToBCD(ts.MIN); + buffer16[offset + 13] = BCD2.IntToBCD(ts.SEC); + buffer16[offset + 14] = BCD2.IntToBCD(ts.FRAC); + buffer16[offset + 15] = mode; + } + + /// + /// Synthesizes the EDC checksum for a Mode 2 Form 1 data sector (and puts it in place) + /// + public static void EDC_Mode2_Form1(byte[] buf2352, int offset) + { + uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2048 + 8); + ECM.PokeUint(buf2352, offset + 2072, edc); + } + + /// + /// Synthesizes the EDC checksum for a Mode 2 Form 2 data sector (and puts it in place) + /// + public static void EDC_Mode2_Form2(byte[] buf2352, int offset) + { + uint edc = ECM.EDC_Calc(buf2352, offset + 16, 2324 + 8); + ECM.PokeUint(buf2352, offset + 2348, edc); + } + + + /// + /// Synthesizes the complete ECM data (EDC + ECC) for a Mode 1 data sector (and puts it in place) + /// Make sure everything else in the sector userdata is done before calling this + /// + public static void ECM_Mode1(byte[] buf2352, int offset, int LBA) + { + //EDC + uint edc = ECM.EDC_Calc(buf2352, offset, 2064); + ECM.PokeUint(buf2352, offset + 2064, edc); + + //reserved, zero + for (int i = 0; i < 8; i++) buf2352[offset + 2068 + i] = 0; + + //ECC + ECM.ECC_Populate(buf2352, offset, buf2352, offset, false); + } + + /// + /// Converts the useful (but unrealistic) deinterleaved subchannel data into the useless (but realistic) interleaved format. + /// in_buf and out_buf should not overlap + /// + public static void InterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index) + { + for (int d = 0; d < 12; d++) + { + for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++) + { + int rawb = 0; + + for (int ch = 0; ch < 8; ch++) + { + rawb |= ((in_buf[ch * 12 + d + in_buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch); + } + out_buf[(d << 3) + bitpoodle + out_buf_index] = (byte)rawb; + } + } + } + + /// + /// Converts the useless (but realistic) interleaved subchannel data into a useful (but unrealistic) deinterleaved format. + /// in_buf and out_buf should not overlap + /// + public static void DeinterleaveSubcode(byte[] in_buf, int in_buf_index, byte[] out_buf, int out_buf_index) + { + for (int i = 0; i < 96; i++) + out_buf[i] = 0; + + for (int ch = 0; ch < 8; ch++) + { + for (int i = 0; i < 96; i++) + { + out_buf[(ch * 12) + (i >> 3) + out_buf_index] |= (byte)(((in_buf[i + in_buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7))); + } + } + } + + /// + /// Converts the useful (but unrealistic) deinterleaved data into the useless (but realistic) interleaved subchannel format. + /// + public unsafe static void InterleaveSubcodeInplace(byte[] buf, int buf_index) + { + byte* out_buf = stackalloc byte[96]; + + for (int i = 0; i < 96; i++) + out_buf[i] = 0; + + for (int d = 0; d < 12; d++) + { + for (int bitpoodle = 0; bitpoodle < 8; bitpoodle++) + { + int rawb = 0; + + for (int ch = 0; ch < 8; ch++) + { + rawb |= ((buf[ch * 12 + d + buf_index] >> (7 - bitpoodle)) & 1) << (7 - ch); + } + out_buf[(d << 3) + bitpoodle] = (byte)rawb; + } + } + + for (int i = 0; i < 96; i++) + buf[i + buf_index] = out_buf[i]; + } + + /// + /// Converts the useless (but realistic) interleaved subchannel data into a useful (but unrealistic) deinterleaved format. + /// + public unsafe static void DeinterleaveSubcodeInplace(byte[] buf, int buf_index) + { + byte* out_buf = stackalloc byte[96]; + + for (int i = 0; i < 96; i++) + out_buf[i] = 0; + + for (int ch = 0; ch < 8; ch++) + { + for (int i = 0; i < 96; i++) + { + out_buf[(ch * 12) + (i >> 3)] |= (byte)(((buf[i + buf_index] >> (7 - ch)) & 0x1) << (7 - (i & 0x7))); + } + } + + for (int i = 0; i < 96; i++) + buf[i + buf_index] = out_buf[i]; + } + } +} \ No newline at end of file