using System; //a decent little subcode reference //http://www.jbum.com/cdg_revealed.html namespace BizHawk.Emulation.DiscSystem { public interface ISubcodeSector { /// /// reads 96 bytes of subcode data (deinterleaved) for this sector into the supplied buffer /// void ReadSubcodeDeinterleaved(byte[] buffer, int offset); /// /// Reads just one of the channels. p=0, q=1, etc. /// void ReadSubcodeChannel(int number, byte[] buffer, int offset); } public static class SubcodeUtils { public static void Interleave(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; } } } public static void Deinterleave(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))); } } } } /// /// Reads subcode from an internally-managed buffer /// class BufferedSubcodeSector : ISubcodeSector { public void Synthesize_SubchannelP(bool pause) { byte val = pause ? (byte)0xFF : (byte)0x00; for (int i = 0; i < 12; i++) SubcodeDeinterleaved[i] = val; } /// /// Fills this subcode buffer with subchannel Q data. calculates the required CRC, as well. /// Returns the crc, calculated or otherwise. /// public ushort Synthesize_SubchannelQ(ref SubchannelQ sq, bool calculateCRC) { int offset = 12; //Q subchannel begins after P, 12 bytes in SubcodeDeinterleaved[offset + 0] = sq.q_status; SubcodeDeinterleaved[offset + 1] = sq.q_tno; SubcodeDeinterleaved[offset + 2] = sq.q_index; SubcodeDeinterleaved[offset + 3] = sq.min.BCDValue; SubcodeDeinterleaved[offset + 4] = sq.sec.BCDValue; SubcodeDeinterleaved[offset + 5] = sq.frame.BCDValue; SubcodeDeinterleaved[offset + 6] = sq.zero; SubcodeDeinterleaved[offset + 7] = sq.ap_min.BCDValue; SubcodeDeinterleaved[offset + 8] = sq.ap_sec.BCDValue; SubcodeDeinterleaved[offset + 9] = sq.ap_frame.BCDValue; ushort crc16; if (calculateCRC) crc16 = CRC16_CCITT.Calculate(SubcodeDeinterleaved, offset, 10); else crc16 = sq.q_crc; //CRC is stored inverted and big endian SubcodeDeinterleaved[offset + 10] = (byte)(~(crc16 >> 8)); SubcodeDeinterleaved[offset + 11] = (byte)(~(crc16)); return crc16; } public void ReadSubcodeDeinterleaved(byte[] buffer, int offset) { Buffer.BlockCopy(SubcodeDeinterleaved, 0, buffer, offset, 96); } public void ReadSubcodeChannel(int number, byte[] buffer, int offset) { Buffer.BlockCopy(SubcodeDeinterleaved, number * 12, buffer, offset, 12); } public BufferedSubcodeSector() { SubcodeDeinterleaved = new byte[96]; } public static BufferedSubcodeSector CloneFromBytesDeinterleaved(byte[] buffer) { var ret = new BufferedSubcodeSector(); Buffer.BlockCopy(buffer, 0, ret.SubcodeDeinterleaved, 0, 96); return ret; } public byte[] SubcodeDeinterleaved; } public class ZeroSubcodeSector : ISubcodeSector { public void ReadSubcodeDeinterleaved(byte[] buffer, int offset) { for (int i = 0; i < 96; i++) buffer[i + offset] = 0; } public void ReadSubcodeChannel(int number, byte[] buffer, int offset) { for (int i = 0; i < 12; i++) buffer[i + offset] = 0; } } /// /// Reads subcode data from a blob, assuming it was already stored in deinterleaved format /// public class BlobSubcodeSectorPreDeinterleaved : ISubcodeSector { public void ReadSubcodeDeinterleaved(byte[] buffer, int offset) { Blob.Read(Offset, buffer, offset, 96); } public void ReadSubcodeChannel(int number, byte[] buffer, int offset) { Blob.Read(Offset + number * 12, buffer, offset, 12); } public IBlob Blob; public long Offset; } /// /// Control bit flags for the Q Subchannel /// [Flags] public enum EControlQ { None = 0, StereoNoPreEmph = 0, StereoPreEmph = 1, MonoNoPreemph = 8, MonoPreEmph = 9, DataUninterrupted = 4, DataIncremental = 5, CopyProhibitedMask = 0, CopyPermittedMask = 2, } /// /// Why did I make this a struct? I thought there might be a shitton of these and I was trying to cut down on object creation churn during disc-loading. /// But I ended up mostly just having a shitton of byte[] for each buffer (I could improve that later to possibly reference a blob on top of a memorystream) /// So, I should probably change that. /// public struct SubchannelQ { /// /// ADR and CONTROL /// TODO - make BCD2? /// public byte q_status; /// /// normal track: BCD indications of the current track number /// leadin track: should be 0 /// TODO - make BCD2? /// public byte q_tno; /// /// normal track: BCD indications of the current index /// leadin track: 'POINT' field used to ID the TOC entry # /// public byte q_index; /// /// These are the initial set of timestamps. Meaning varies: /// check yellowbook 22.3.3 and 22.3.4 /// normal track: relative timestamp /// leadin track: unknown /// public BCD2 min, sec, frame; /// /// This is supposed to be zero.. but CCD format stores it, so maybe it's useful for copy protection or something /// public byte zero; /// /// These are the second set of timestamps. Meaning varies: /// check yellowbook 22.3.3 and 22.3.4 /// normal track: absolute timestamp /// leadin track: timestamp of toc entry /// public BCD2 ap_min, ap_sec, ap_frame; /// /// The CRC. This is the actual CRC value as would be calculated from our library (it is inverted and written big endian to the disc) /// Don't assume this CRC is correct-- If this SubchannelQ was read from a dumped disc, the CRC might be wrong. /// CCD doesnt specify this for TOC entries, so it will be wrong. It may or may not be right for data track sectors from a CCD file. /// Or we may have computed this SubchannelQ data and generated the correct CRC at that time. /// public ushort q_crc; /// /// Retrieves the initial set of timestamps (min,sec,frac) as a convenient Timestamp /// public Timestamp Timestamp { get { return new Timestamp(min.DecimalValue, sec.DecimalValue, frame.DecimalValue); } } /// /// Retrieves the second set of timestamps (ap_min, ap_sec, ap_frac) as a convenient Timestamp /// public Timestamp AP_Timestamp { get { return new Timestamp(ap_min.DecimalValue, ap_sec.DecimalValue, ap_frame.DecimalValue); } } /// /// sets the status byte from the provided adr and control values /// public void SetStatus(byte adr, EControlQ control) { q_status = ComputeStatus(adr, control); } /// /// computes a status byte from the provided adr and control values /// public static byte ComputeStatus(int adr, EControlQ control) { return (byte)(adr | (((int)control) << 4)); } /// /// Retrives the ADR field of the q_status member (low 4 bits) /// public int ADR { get { return q_status & 0xF; } } /// /// Retrieves the CONTROL field of the q_status member (high 4 bits) /// public EControlQ CONTROL { get { return (EControlQ)((q_status >> 4) & 0xF); } } } //this has been checked against mednafen's and seems to match //there are a few dozen different ways to do CRC16-CCITT //this table is backwards or something. at any rate its tailored to the needs of the Q subchannel internal static class CRC16_CCITT { private static readonly ushort[] table = new ushort[256]; static CRC16_CCITT() { for (ushort i = 0; i < 256; ++i) { ushort value = 0; ushort temp = (ushort)(i << 8); for (byte j = 0; j < 8; ++j) { if (((value ^ temp) & 0x8000) != 0) value = (ushort)((value << 1) ^ 0x1021); else value <<= 1; temp <<= 1; } table[i] = value; } } public static ushort Calculate(byte[] data, int offset, int length) { ushort Result = 0; for(int i=0;i> 8) & 0xFF)); Result = (ushort)((Result << 8) ^ table[index]); } return Result; } } public class SubcodeDataDecoder { /// /// This seems to deinterleave Q from a subcode buffer? Not sure.. it isn't getting used anywhere right now, as you can see. /// public static void Unpack_Q(byte[] output, int out_ofs, byte[] input, int in_ofs) { for (int i = 0; i < 12; i++) output[out_ofs + i] = 0; for (int i = 0; i < 96; i++) { int bytenum = i >> 3; int bitnum = i & 7; bitnum = 7 - bitnum; int bitval = (byte)((input[in_ofs + i] >> 6) & 1); bitval <<= bitnum; output[out_ofs + bytenum] |= (byte)bitval; } } } }