using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Xml.Serialization; using CUETools.Parity; using CUETools.CDImage; using CUETools.Codecs; namespace CUETools.AccurateRip { [Serializable] public class OffsetSafeCRCRecord { private uint[] val; public OffsetSafeCRCRecord() { this.val = new uint[1]; } public OffsetSafeCRCRecord(AccurateRipVerify ar) : this(new uint[64 + 64]) { int offset = 64 * 64; for (int i = 0; i < 64; i++) this.val[i] = ar.GetMiddleCRC32(offset + i * 64 + 64, 2 * offset - 64 - i * 64); for (int i = 0; i < 64; i++) this.val[i + 64] = ar.GetMiddleCRC32(offset + 64 - 1 - i, 2 * offset - 64 + 1 + i); } public OffsetSafeCRCRecord(uint[] val) { this.val = val; } [XmlIgnore] public uint[] Value { get { return val; } } public unsafe string Base64 { get { byte[] res = new byte[val.Length * 4]; fixed (byte* pres = &res[0]) fixed (uint* psrc = &val[0]) AudioSamples.MemCpy(pres, (byte*)psrc, res.Length); var b64 = new char[res.Length * 2 + 4]; int b64len = Convert.ToBase64CharArray(res, 0, res.Length, b64, 0); StringBuilder sb = new StringBuilder(b64len + b64len / 4 + 1); for (int i = 0; i < b64len; i += 64) { sb.Append(b64, i, Math.Min(64, b64len - i)); sb.AppendLine(); } return sb.ToString(); } set { if (value == null) throw new ArgumentNullException(); byte[] bytes = Convert.FromBase64String(value); if (bytes.Length % 4 != 0) throw new InvalidDataException(); val = new uint[bytes.Length / 4]; fixed (byte* pb = &bytes[0]) fixed (uint* pv = &val[0]) AudioSamples.MemCpy((byte*)pv, pb, bytes.Length); } } public override bool Equals(object obj) { return obj is OffsetSafeCRCRecord && this == (OffsetSafeCRCRecord)obj; } public override int GetHashCode() { return (int)val[0]; } public static bool operator == (OffsetSafeCRCRecord x, OffsetSafeCRCRecord y) { if (x as object == null || y as object == null) return x as object == null && y as object == null; if (x.Value.Length != y.Value.Length) return false; for (int i = 0; i < x.Value.Length; i++) if (x.Value[i] != y.Value[i]) return false; return true; } public static bool operator !=(OffsetSafeCRCRecord x, OffsetSafeCRCRecord y) { return !(x == y); } public bool DifferByOffset(OffsetSafeCRCRecord rec) { int offset; return FindOffset(rec, out offset); } public bool FindOffset (OffsetSafeCRCRecord rec, out int offset) { if (this.Value.Length != 128 || rec.Value.Length != 128) { offset = 0; return false; //throw new InvalidDataException("Unsupported OffsetSafeCRCRecord"); } for (int i = 0; i < 64; i++) { if (rec.Value[0] == Value[i]) { offset = i * 64; return true; } if (Value[0] == rec.Value[i]) { offset = -i * 64; return true; } for (int j = 0; j < 64; j++) { if (rec.Value[i] == Value[64 + j]) { offset = i * 64 + j + 1; return true; } if (Value[i] == rec.Value[64 + j]) { offset = -i * 64 - j - 1; return true; } } } offset = 0; return false; } } public class AccurateRipVerify : IAudioDest { public AccurateRipVerify(CDImageLayout toc, IWebProxy proxy) { this.proxy = proxy; _accDisks = new List(); _hasLogCRC = false; _CRCLOG = new uint[toc.AudioTracks + 1]; ExceptionStatus = WebExceptionStatus.Pending; Init(toc); } public uint Confidence(int iTrack) { if (ARStatus != null) return 0U; uint conf = 0; for (int di = 0; di < (int)AccDisks.Count; di++) if (iTrack + _toc.FirstAudio - 1 < AccDisks[di].tracks.Count && CRC(iTrack) == AccDisks[di].tracks[iTrack + _toc.FirstAudio - 1].CRC) conf += AccDisks[di].tracks[iTrack + _toc.FirstAudio - 1].count; return conf; } public uint WorstTotal() { uint worstTotal = 0xffff; for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { uint sumTotal = Total(iTrack); if (worstTotal > sumTotal && sumTotal != 0) worstTotal = sumTotal; } return worstTotal == 0xffff ? 0 : worstTotal; } public uint WorstConfidence() { uint worstConfidence = 0xffff; for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { uint sumConfidence = SumConfidence(iTrack); if (worstConfidence > sumConfidence && (Total(iTrack) != 0 || CRC(iTrack) != 0)) worstConfidence = sumConfidence; } return worstConfidence; } public uint SumConfidence(int iTrack) { if (ARStatus != null) return 0U; uint conf = 0; for (int iDisk = 0; iDisk < AccDisks.Count; iDisk++) for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) if (iTrack + _toc.FirstAudio - 1 < AccDisks[iDisk].tracks.Count && CRC(iTrack, oi) == AccDisks[iDisk].tracks[iTrack + _toc.FirstAudio - 1].CRC) conf += AccDisks[iDisk].tracks[iTrack + _toc.FirstAudio - 1].count; return conf; } public uint Confidence(int iTrack, int oi) { if (ARStatus != null) return 0U; uint conf = 0; for (int di = 0; di < (int)AccDisks.Count; di++) if (iTrack +_toc.FirstAudio - 1 < AccDisks[di].tracks.Count && CRC(iTrack, oi) == AccDisks[di].tracks[iTrack + _toc.FirstAudio - 1].CRC) conf += AccDisks[di].tracks[iTrack + _toc.FirstAudio - 1].count; return conf; } public uint Total(int iTrack) { if (ARStatus != null) return 0U; uint total = 0; for (int di = 0; di < (int)AccDisks.Count; di++) if (iTrack + _toc.FirstAudio - 1 < AccDisks[di].tracks.Count) total += AccDisks[di].tracks[iTrack + _toc.FirstAudio - 1].count; return total; } public uint DBCRC(int iTrack) { return ARStatus == null && iTrack +_toc.FirstAudio - 1 < AccDisks[0].tracks.Count ? AccDisks[0].tracks[iTrack + _toc.FirstAudio - 1].CRC : 0U; } public uint CRC(int iTrack) { return CRC(iTrack, 0); } public uint CRC(int iTrack, int oi) { int offs0 = iTrack == 0 ? 5 * 588 + oi - 1 : oi; int offs1 = iTrack == _toc.AudioTracks - 1 ? 20 * 588 - 5 * 588 + oi : (oi >= 0 ? 0 : 20 * 588 + oi); uint crcA = _CRCAR[iTrack + 1, offs1] - (offs0 > 0 ? _CRCAR[iTrack + 1, offs0] : 0); uint sumA = _CRCSM[iTrack + 1, offs1] - (offs0 > 0 ? _CRCSM[iTrack + 1, offs0] : 0); uint crc = crcA - sumA * (uint)oi; if (oi < 0 && iTrack > 0) { uint crcB = _CRCAR[iTrack, 0] - _CRCAR[iTrack, 20 * 588 + oi]; uint sumB = _CRCSM[iTrack, 0] - _CRCSM[iTrack, 20 * 588 + oi]; uint posB = _toc[iTrack + _toc.FirstAudio - 1].Length * 588 + (uint)oi; crc += crcB - sumB * posB; } if (oi > 0 && iTrack < _toc.AudioTracks - 1) { uint crcB = _CRCAR[iTrack + 2, oi]; uint sumB = _CRCSM[iTrack + 2, oi]; uint posB = _toc[iTrack + _toc.FirstAudio].Length * 588 + (uint)-oi; crc += crcB + sumB * posB; } return crc; } public uint CRC450(int iTrack, int oi) { uint crca = _CRCAR[iTrack + 1, 20 * 588 + 5 * 588 + oi]; uint crcb = _CRCAR[iTrack + 1, 20 * 588 + 6 * 588 + oi]; uint suma = _CRCSM[iTrack + 1, 20 * 588 + 5 * 588 + oi]; uint sumb = _CRCSM[iTrack + 1, 20 * 588 + 6 * 588 + oi]; uint offs = 450 * 588 + (uint)oi; return crcb - crca - offs * (sumb - suma); } public int PeakLevel() { int peak = 0; for (int track = 0; track <= _toc.AudioTracks; track++) if (peak < _Peak[track]) peak = _Peak[track]; return peak; } public int PeakLevel(int iTrack) { return _Peak[iTrack]; } internal uint GetMiddleCRC32(int prefixLen, int suffixLen) { return CTDBCRC(prefixLen * 2, suffixLen * 2); } public OffsetSafeCRCRecord OffsetSafeCRC { get { return new OffsetSafeCRCRecord(this); } } public uint CRC32(int iTrack) { return CRC32(iTrack, 0); } public uint GetCRC32(uint crcA, int lenA, uint crcB, int lenB) { int lenAXB = (int)_toc.AudioLength * 588 * 4; int lenXB = lenAXB - lenA; int lenX = lenXB - lenB; uint crcAXB = CRC32(0, 0) ^ _CRCMASK[0]; uint crcXB = Crc32.Combine(crcA, crcAXB, lenXB); uint crcX = Crc32.Substract(crcXB, crcB, lenB); return Crc32.Combine(0xffffffff, crcX, lenX) ^ 0xffffffff; } public uint CRC32(int iTrack, int oi) { if (_CacheCRC32[iTrack, _arOffsetRange + oi] == 0) { uint crc = 0; if (iTrack == 0) { for (iTrack = 0; iTrack <= _toc.AudioTracks; iTrack++) { int trackLength = (int)(iTrack > 0 ? _toc[iTrack + _toc.FirstAudio - 1].Length : _toc[_toc.FirstAudio].Pregap) * 588 * 4; if (oi < 0 && iTrack == 0) crc = Crc32.Combine(crc, 0, -oi * 4); if (trackLength == 0) continue; if (oi > 0 && (iTrack == 0 || (iTrack == 1 && _toc[_toc.FirstAudio].Pregap == 0))) { // Calculate track CRC skipping first oi samples by 'subtracting' their CRC crc = Crc32.Combine(_CRC32[iTrack, oi], _CRC32[iTrack, 0], trackLength - oi * 4); } else if (oi < 0 && iTrack == _toc.AudioTracks) { crc = Crc32.Combine(crc, _CRC32[iTrack, 20 * 588 + oi], trackLength + oi * 4); } else { crc = Crc32.Combine(crc, _CRC32[iTrack, 0], trackLength); } if (oi > 0 && iTrack == _toc.AudioTracks) crc = Crc32.Combine(crc, 0, oi * 4); } iTrack = 0; // Use 0xffffffff as an initial state crc ^= _CRCMASK[0]; } else { int trackLength = (int)(iTrack > 0 ? _toc[iTrack + _toc.FirstAudio - 1].Length : _toc[_toc.FirstAudio].Pregap) * 588 * 4; if (oi > 0) { // Calculate track CRC skipping first oi samples by 'subtracting' their CRC crc = Crc32.Combine(_CRC32[iTrack, oi], _CRC32[iTrack, 0], trackLength - oi * 4); // Add oi samples from next track CRC crc = Crc32.Combine(crc, 0, oi * 4); if (iTrack < _toc.AudioTracks) crc ^= _CRC32[iTrack + 1, oi]; } else if (oi < 0) { // Calculate CRC of previous track's last oi samples by 'subtracting' it's last CRCs crc = Crc32.Combine(_CRC32[iTrack - 1, 20 * 588 + oi], _CRC32[iTrack - 1, 0], -oi * 4); // Add this track's CRC without last oi samples crc = Crc32.Combine(crc, _CRC32[iTrack, 20 * 588 + oi], trackLength + oi * 4); } else // oi == 0 { crc = _CRC32[iTrack, 0]; } // Use 0xffffffff as an initial state crc ^= _CRCMASK[iTrack]; } _CacheCRC32[iTrack, _arOffsetRange + oi] = crc; } return _CacheCRC32[iTrack, _arOffsetRange + oi]; } public uint CRCWONULL(int iTrack) { return CRCWONULL(iTrack, 0); } public uint CRCWONULL(int iTrack, int oi) { if (_CacheCRCWN[iTrack, _arOffsetRange + oi] == 0) { uint crc = 0xffffffff; if (iTrack == 0) { for (iTrack = 0; iTrack <= _toc.AudioTracks; iTrack++) { int trackLength = (int)(iTrack > 0 ? _toc[iTrack + _toc.FirstAudio - 1].Length : _toc[_toc.FirstAudio].Pregap) * 588 * 4 - _CRCNL[iTrack, 0] * 2; crc = Crc32.Combine(crc, _CRCWN[iTrack, 0], trackLength); } iTrack = 0; } else { int trackLength = (int)(iTrack > 0 ? _toc[iTrack + _toc.FirstAudio - 1].Length : _toc[_toc.FirstAudio].Pregap) * 588 * 4; if (oi > 0) { int nonzeroPrevLength = trackLength - oi * 4 - (_CRCNL[iTrack, 0] - _CRCNL[iTrack, oi]) * 2; // Calculate track CRC skipping first oi samples by 'subtracting' their CRC crc = Crc32.Combine( _CRCWN[iTrack, oi], _CRCWN[iTrack, 0], nonzeroPrevLength); // Use 0xffffffff as an initial state crc = Crc32.Combine(0xffffffff, crc, nonzeroPrevLength); // Add oi samples from next track CRC if (iTrack < _toc.AudioTracks) crc = Crc32.Combine(crc, _CRCWN[iTrack + 1, oi], oi * 4 - _CRCNL[iTrack + 1, oi] * 2); } else if (oi < 0) { int nonzeroPrevLength = -oi * 4 - (_CRCNL[iTrack - 1, 0] - _CRCNL[iTrack - 1, 20 * 588 + oi]) * 2; // Calculate CRC of previous track's last oi samples by 'subtracting' it's last CRCs crc = Crc32.Combine( _CRCWN[iTrack - 1, 20 * 588 + oi], _CRCWN[iTrack - 1, 0], nonzeroPrevLength); // Use 0xffffffff as an initial state crc = Crc32.Combine(0xffffffff, crc, nonzeroPrevLength); // Add this track's CRC without last oi samples crc = Crc32.Combine(crc, _CRCWN[iTrack, 20 * 588 + oi], trackLength + oi * 4 - _CRCNL[iTrack, 20 * 588 + oi] * 2); } else // oi == 0 { // Use 0xffffffff as an initial state crc = Crc32.Combine(0xffffffff, _CRCWN[iTrack, 0], trackLength - _CRCNL[iTrack, 0] * 2); } } _CacheCRCWN[iTrack, _arOffsetRange + oi] = crc ^ 0xffffffff; } return _CacheCRCWN[iTrack, _arOffsetRange + oi]; } public uint CRCLOG(int iTrack) { return _CRCLOG[iTrack]; } public void CRCLOG(int iTrack, uint value) { _hasLogCRC = true; _CRCLOG[iTrack] = value; } internal ushort[,] syndrome; internal byte[] parity; internal ushort[] leadin; internal ushort[] leadout; private int stride = 1, laststride = 1, stridecount = 1, npar = 1; private bool calcSyn = false; private bool calcParity = false; internal void InitCDRepair(int stride, int laststride, int stridecount, int npar, bool calcSyn, bool calcParity) { if (npar != 8) throw new NotSupportedException("npar != 8"); this.stride = stride; this.laststride = laststride; this.stridecount = stridecount; this.npar = npar; this.calcSyn = calcSyn; this.calcParity = calcParity; Init(_toc); } public unsafe uint CTDBCRC(int prefixLen, int suffixLen) { if (prefixLen > leadin.Length || suffixLen > leadout.Length) throw new ArgumentOutOfRangeException(); // stride - 2 * actualOffset // laststride + 2 * actualOffset fixed (uint* crct = Crc32.table) { // calculate leadin CRC uint crc0 = 0; for (int off = 0; off < prefixLen; off++) { ushort dd = leadin[off]; crc0 = (crc0 >> 8) ^ crct[(byte)(crc0 ^ dd)]; crc0 = (crc0 >> 8) ^ crct[(byte)(crc0 ^ (dd >> 8))]; } // calculate leadout CRC uint crc2 = 0; for (int off = suffixLen - 1; off >= 0; off--) { ushort dd = leadout[off]; crc2 = (crc2 >> 8) ^ crct[(byte)(crc2 ^ dd)]; crc2 = (crc2 >> 8) ^ crct[(byte)(crc2 ^ (dd >> 8))]; } return GetCRC32(crc0, prefixLen * 2, crc2, suffixLen * 2); } } public uint CTDBCRC(int actualOffset) { return CTDBCRC(stride - 2 * actualOffset, laststride + 2 * actualOffset); } private unsafe static void CalcSyn8(ushort* exp, ushort* log, ushort* syn, uint lo, uint n, int npar) { syn[0] ^= (ushort)lo; uint idx = log[lo] + n; syn[1] ^= exp[(idx & 0xffff) + (idx >> 16)]; idx += n; syn[2] ^= exp[(idx & 0xffff) + (idx >> 16)]; idx += n; syn[3] ^= exp[(idx & 0xffff) + (idx >> 16)]; idx += n; syn[4] ^= exp[(idx & 0xffff) + (idx >> 16)]; idx += n; syn[5] ^= exp[(idx & 0xffff) + (idx >> 16)]; idx += n; syn[6] ^= exp[(idx & 0xffff) + (idx >> 16)]; idx += n; syn[7] ^= exp[(idx & 0xffff) + (idx >> 16)]; //for (int i = 8; i < npar; i += 8) //{ // idx += n; syn[i] ^= exp[(idx & 0xffff) + (idx >> 16)]; // idx += n; syn[i + 1] ^= exp[(idx & 0xffff) + (idx >> 16)]; // idx += n; syn[i + 2] ^= exp[(idx & 0xffff) + (idx >> 16)]; // idx += n; syn[i + 3] ^= exp[(idx & 0xffff) + (idx >> 16)]; // idx += n; syn[i + 4] ^= exp[(idx & 0xffff) + (idx >> 16)]; // idx += n; syn[i + 5] ^= exp[(idx & 0xffff) + (idx >> 16)]; // idx += n; syn[i + 6] ^= exp[(idx & 0xffff) + (idx >> 16)]; // idx += n; syn[i + 7] ^= exp[(idx & 0xffff) + (idx >> 16)]; //} } #if alternateSynCalc private unsafe static void CalcSyn8Alt(ushort* exp, ushort* log, ushort* syn, uint lo, uint n) { uint idx = log[lo] + 7 * n; ulong x = exp[(idx & 0xffff) + (idx >> 16)]; x <<= 16; idx -= n; x ^= exp[(idx & 0xffff) + (idx >> 16)]; x <<= 16; idx -= n; x ^= exp[(idx & 0xffff) + (idx >> 16)]; x <<= 16; idx -= n; x ^= exp[(idx & 0xffff) + (idx >> 16)]; ((ulong*)syn)[1] ^= x; idx -= n; x = exp[(idx & 0xffff) + (idx >> 16)]; x <<= 16; idx -= n; x ^= exp[(idx & 0xffff) + (idx >> 16)]; x <<= 16; idx -= n; x ^= exp[(idx & 0xffff) + (idx >> 16)]; ((ulong*)syn)[0] ^= (x << 16) + lo; } #endif private unsafe static void CalcPar8(ushort* exp, ushort* log, ushort* wr, uint lo) { uint ib = wr[0] ^ lo; if (ib != 0) { ushort* myexp = exp + log[ib]; wr[0] = (ushort)(wr[1] ^ myexp[19483]); wr[1] = (ushort)(wr[2] ^ myexp[41576]); wr[2] = (ushort)(wr[3] ^ myexp[9460]); wr[3] = (ushort)(wr[4] ^ myexp[52075]); wr[4] = (ushort)(wr[5] ^ myexp[9467]); wr[5] = (ushort)(wr[6] ^ myexp[41590]); wr[6] = (ushort)(wr[7] ^ myexp[19504]); wr[7] = myexp[28]; } else { wr[0] = wr[1]; wr[1] = wr[2]; wr[2] = wr[3]; wr[3] = wr[4]; wr[4] = wr[5]; wr[5] = wr[6]; wr[6] = wr[7]; wr[7] = 0; } } /// /// This function calculates three different CRCs and also /// collects some additional information for the purposes of /// offset detection. /// /// crcar is AccurateRip CRC /// crc32 is CRC32 /// crcwn is CRC32 without null samples (EAC) /// crcsm is sum of samples /// crcnl is a count of null samples /// /// /// /// public unsafe void CalculateCRCs(uint* t, ushort* exp, ushort* log, ushort* syn, ushort* wr, uint* pSampleBuff, int count, int offs) { int currentStride = ((int)_sampleCount * 2) / stride; bool doSyn = currentStride >= 1 && currentStride <= stridecount && calcSyn; bool doPar = currentStride >= 1 && currentStride <= stridecount && calcParity; uint n = (uint)(stridecount - currentStride); uint crcar = _CRCAR[_currentTrack, 0]; uint crcsm = _CRCSM[_currentTrack, 0]; uint crc32 = _CRC32[_currentTrack, 0]; uint crcwn = _CRCWN[_currentTrack, 0]; int crcnl = _CRCNL[_currentTrack, 0]; int peak = _Peak[_currentTrack]; for (int i = 0; i < count; i++) { if (offs >= 0) { _CRCAR[_currentTrack, offs + i] = crcar; _CRCSM[_currentTrack, offs + i] = crcsm; _CRC32[_currentTrack, offs + i] = crc32; _CRCWN[_currentTrack, offs + i] = crcwn; _CRCNL[_currentTrack, offs + i] = crcnl; } uint sample = *(pSampleBuff++); crcsm += sample; crcar += sample * (uint)(_samplesDoneTrack + i + 1); uint lo = sample & 0xffff; crc32 = (crc32 >> 8) ^ t[(byte)(crc32 ^ lo)]; crc32 = (crc32 >> 8) ^ t[(byte)(crc32 ^ (lo >> 8))]; if (lo != 0) { crcwn = (crcwn >> 8) ^ t[(byte)(crcwn ^ lo)]; crcwn = (crcwn >> 8) ^ t[(byte)(crcwn ^ (lo >> 8))]; } else crcnl++; int pk = ((int)(lo << 16)) >> 16; peak = Math.Max(peak, (pk << 1) ^ (pk >> 31)); if (doSyn && lo != 0) CalcSyn8(exp, log, syn + i * 16, lo, n, npar); if (doPar) CalcPar8(exp, log, wr + i * 16, lo); uint hi = sample >> 16; crc32 = (crc32 >> 8) ^ t[(byte)(crc32 ^ hi)]; crc32 = (crc32 >> 8) ^ t[(byte)(crc32 ^ (hi >> 8))]; if (hi != 0) { crcwn = (crcwn >> 8) ^ t[(byte)(crcwn ^ hi)]; crcwn = (crcwn >> 8) ^ t[(byte)(crcwn ^ (hi >> 8))]; } else crcnl++; pk = ((int)(hi << 16)) >> 16; peak = Math.Max(peak, (pk << 1) ^ (pk >> 31)); if (doSyn && hi != 0) CalcSyn8(exp, log, syn + i * 16 + 8, hi, n, npar); if (doPar) CalcPar8(exp, log, wr + i * 16 + 8, hi); } _CRCAR[_currentTrack, 0] = crcar; _CRCSM[_currentTrack, 0] = crcsm; _CRC32[_currentTrack, 0] = crc32; _CRCWN[_currentTrack, 0] = crcwn; _CRCNL[_currentTrack, 0] = crcnl; _Peak[_currentTrack] = peak; } private int _samplesRemTrack = 0; private int _samplesDoneTrack = 0; public long Position { get { return _sampleCount; } set { _sampleCount = value; int tempLocation = 0; // NOT (int)_toc[_toc.FirstAudio][0].Start * 588; for (int iTrack = 0; iTrack <= _toc.AudioTracks; iTrack++) { int tempLen = (int)(iTrack == 0 ? _toc[_toc.FirstAudio].Pregap : _toc[_toc.FirstAudio + iTrack - 1].Length) * 588; if (tempLocation + tempLen > _sampleCount) { _currentTrack = iTrack; _samplesRemTrack = tempLocation + tempLen - (int)_sampleCount; _samplesDoneTrack = (int)_sampleCount - tempLocation; return; } tempLocation += tempLen; } throw new ArgumentOutOfRangeException(); } } public unsafe void Write(AudioBuffer sampleBuffer) { sampleBuffer.Prepare(this); int pos = 0; fixed (uint* t = Crc32.table) fixed (ushort* exp = Galois16.instance.ExpTbl, log = Galois16.instance.LogTbl, synptr1 = syndrome) fixed (byte* pSampleBuff = &sampleBuffer.Bytes[0], bpar = parity) while (pos < sampleBuffer.Length) { // Process no more than there is in the buffer, no more than there is in this track, and no more than up to a sector boundary. int copyCount = Math.Min(Math.Min(sampleBuffer.Length - pos, (int)_samplesRemTrack), 588 - (int)_sampleCount % 588); int currentSector = _samplesDoneTrack / 588; int remaingSectors = (_samplesRemTrack - 1) / 588; uint* samples = ((uint*)pSampleBuff) + pos; int currentPart = ((int)_sampleCount * 2) % stride; ushort* synptr = synptr1 + npar * currentPart; ushort* wr = ((ushort*)bpar) + npar * currentPart; int currentStride = ((int)_sampleCount * 2) / stride; for (int i = 0; i < Math.Min(leadin.Length - (int)_sampleCount * 2, copyCount * 2); i++) leadin[_sampleCount * 2 + i] = ((ushort*)samples)[i]; for (int i = Math.Max(0, (int)(_finalSampleCount - _sampleCount) * 2 - leadout.Length); i < copyCount * 2; i++) //if (currentStride >= stridecount && leadout != null) //for (int i = 0; i < copyCount * 2; i++) { int remaining = (int)(_finalSampleCount - _sampleCount) * 2 - i - 1; leadout[remaining] = ((ushort*)samples)[i]; } if (currentSector < 10) CalculateCRCs(t, exp, log, synptr, wr, samples, copyCount, _samplesDoneTrack); else if (remaingSectors < 10) CalculateCRCs(t, exp, log, synptr, wr, samples, copyCount, 20 * 588 - _samplesRemTrack); else if (currentSector >= 445 && currentSector <= 455) CalculateCRCs(t, exp, log, synptr, wr, samples, copyCount, 20 * 588 + _samplesDoneTrack - 445 * 588); else CalculateCRCs(t, exp, log, synptr, wr, samples, copyCount, -1); pos += copyCount; _samplesRemTrack -= copyCount; _samplesDoneTrack += copyCount; _sampleCount += copyCount; while (_samplesRemTrack <= 0) { if (++_currentTrack > _toc.AudioTracks) return; _samplesRemTrack = (int)_toc[_currentTrack + _toc.FirstAudio - 1].Length * 588; _samplesDoneTrack = 0; } } } public void Combine(AccurateRipVerify part, int start, int end) { for (int i = 0; i < leadin.Length; i++) { int currentOffset = i / 2; if (currentOffset >= start && currentOffset < end) this.leadin[i] = part.leadin[i]; } for (int i = 0; i < leadout.Length; i++) { int currentOffset = (int)_finalSampleCount - i / 2 - 1; if (currentOffset >= start && currentOffset < end) this.leadout[i] = part.leadout[i]; } for (int iTrack = 0; iTrack <= _toc.AudioTracks; iTrack++) { // ??? int tempLocation = (int) (iTrack == 0 ? _toc[_toc.FirstAudio][0].Start : _toc[_toc.FirstAudio + iTrack - 1].Start) * 588; int tempLocation = (int) (iTrack == 0 ? 0 : _toc[_toc.FirstAudio + iTrack - 1].Start - _toc[_toc.FirstAudio][0].Start) * 588; int tempLen = (int) (iTrack == 0 ? _toc[_toc.FirstAudio].Pregap : _toc[_toc.FirstAudio + iTrack - 1].Length) * 588; int trStart = Math.Max(tempLocation, start); int trEnd = Math.Min(tempLocation + tempLen, end); if (trStart >= trEnd) continue; uint crcar = _CRCAR[iTrack, 0]; uint crcsm = _CRCSM[iTrack, 0]; uint crc32 = _CRC32[iTrack, 0]; uint crcwn = _CRCWN[iTrack, 0]; int crcnl = _CRCNL[iTrack, 0]; _CRCAR[iTrack, 0] = crcar + part._CRCAR[iTrack, 0]; _CRCSM[iTrack, 0] = crcsm + part._CRCSM[iTrack, 0]; _CRCNL[iTrack, 0] = crcnl + part._CRCNL[iTrack, 0]; _CRC32[iTrack, 0] = Crc32.Combine(crc32, part._CRC32[iTrack, 0], 4 * (trEnd - trStart)); _CRCWN[iTrack, 0] = Crc32.Combine(crcwn, part._CRCWN[iTrack, 0], 4 * (trEnd - trStart) - 2 * part._CRCNL[iTrack, 0]); for (int i = 1; i < 31 * 588; i++) { int currentOffset; if (i < 10 * 588) { currentOffset = tempLocation + i; } else if (i < 20 * 588) { currentOffset = tempLocation + tempLen + i - 20 * 588; } else { currentOffset = tempLocation + i - 20 * 588 + 445 * 588; } if (currentOffset <= trStart) continue; _CRCAR[iTrack, i] = crcar + part._CRCAR[iTrack, i]; _CRCSM[iTrack, i] = crcsm + part._CRCSM[iTrack, i]; _CRCNL[iTrack, i] = crcnl + part._CRCNL[iTrack, i]; _CRC32[iTrack, i] = Crc32.Combine(crc32, part._CRC32[iTrack, i], 4 * (currentOffset - trStart)); _CRCWN[iTrack, i] = Crc32.Combine(crcwn, part._CRCWN[iTrack, i], 4 * (currentOffset - trStart) - 2 * part._CRCNL[iTrack, i]); } _Peak[iTrack] = Math.Max(_Peak[iTrack], part._Peak[iTrack]); } } public void Init(CDImageLayout toc) { _toc = toc; _finalSampleCount = _toc.AudioLength * 588; _CRCMASK = new uint[_toc.AudioTracks + 1]; _CRCMASK[0] = 0xffffffff ^ Crc32.Combine(0xffffffff, 0, (int)_finalSampleCount * 4); for (int iTrack = 1; iTrack <= _toc.AudioTracks; iTrack++) _CRCMASK[iTrack] = 0xffffffff ^ Crc32.Combine(0xffffffff, 0, (int)_toc[iTrack + _toc.FirstAudio - 1].Length * 588 * 4); _CRCAR = new uint[_toc.AudioTracks + 1, 31 * 588]; _CRCSM = new uint[_toc.AudioTracks + 1, 31 * 588]; _CRC32 = new uint[_toc.AudioTracks + 1, 31 * 588]; _CacheCRC32 = new uint[_toc.AudioTracks + 1, 31 * 588]; _CRCWN = new uint[_toc.AudioTracks + 1, 31 * 588]; _CacheCRCWN = new uint[_toc.AudioTracks + 1, 31 * 588]; _CRCNL = new int[_toc.AudioTracks + 1, 31 * 588]; _Peak = new int[_toc.AudioTracks + 1]; syndrome = new ushort[calcSyn ? stride : 1, npar]; parity = new byte[stride * npar * 2]; int leadin_len = Math.Max(4096 * 4, (calcSyn || calcParity) ? stride * 2 : 0); int leadout_len = Math.Max(4096 * 4, (calcSyn || calcParity) ? stride + laststride : 0); leadin = new ushort[leadin_len]; leadout = new ushort[leadout_len]; _currentTrack = 0; Position = 0; // NOT _toc[_toc.FirstAudio][0].Start * 588; } private uint readIntLE(byte[] data, int pos) { return (uint)(data[pos] + (data[pos + 1] << 8) + (data[pos + 2] << 16) + (data[pos + 3] << 24)); } static DateTime last_accessed; static readonly TimeSpan min_interval = new TimeSpan (5000000); // 0.5 second static readonly object server_mutex = new object (); public void ContactAccurateRip(string accurateRipId) { // Calculate the three disc ids used by AR uint discId1 = 0; uint discId2 = 0; uint cddbDiscId = 0; string[] n = accurateRipId.Split('-'); if (n.Length != 3) { ExceptionStatus = WebExceptionStatus.RequestCanceled; throw new Exception("Invalid accurateRipId."); } discId1 = UInt32.Parse(n[0], NumberStyles.HexNumber); discId2 = UInt32.Parse(n[1], NumberStyles.HexNumber); cddbDiscId = UInt32.Parse(n[2], NumberStyles.HexNumber); string url = String.Format("http://www.accuraterip.com/accuraterip/{0:x}/{1:x}/{2:x}/dBAR-{3:d3}-{4:x8}-{5:x8}-{6:x8}.bin", discId1 & 0xF, discId1 >> 4 & 0xF, discId1 >> 8 & 0xF, _toc.AudioTracks, discId1, discId2, cddbDiscId); HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.Method = "GET"; req.Proxy = proxy; lock (server_mutex) { // Don't access the AR server twice within min_interval if (last_accessed != null) { TimeSpan time = DateTime.Now - last_accessed; if (min_interval > time) Thread.Sleep((min_interval - time).Milliseconds); } try { using (HttpWebResponse response = (HttpWebResponse)req.GetResponse()) { ExceptionStatus = WebExceptionStatus.ProtocolError; ResponseStatus = response.StatusCode; if (ResponseStatus == HttpStatusCode.OK) { ExceptionStatus = WebExceptionStatus.Success; // Retrieve response stream and wrap in StreamReader Stream respStream = response.GetResponseStream(); // Allocate byte buffer to hold stream contents byte[] urlData = new byte[13]; int urlDataLen, bytesRead; _accDisks.Clear(); while (true) { for (urlDataLen = 0; urlDataLen < 13; urlDataLen += bytesRead) { bytesRead = respStream.Read(urlData, urlDataLen, 13 - urlDataLen); if (0 == bytesRead) break; } if (urlDataLen == 0) break; if (urlDataLen < 13) { ExceptionStatus = WebExceptionStatus.ReceiveFailure; return; } AccDisk dsk = new AccDisk(); dsk.count = urlData[0]; dsk.discId1 = readIntLE(urlData, 1); dsk.discId2 = readIntLE(urlData, 5); dsk.cddbDiscId = readIntLE(urlData, 9); for (int i = 0; i < dsk.count; i++) { for (urlDataLen = 0; urlDataLen < 9; urlDataLen += bytesRead) { bytesRead = respStream.Read(urlData, urlDataLen, 9 - urlDataLen); if (0 == bytesRead) { ExceptionStatus = WebExceptionStatus.ReceiveFailure; return; } } AccTrack trk = new AccTrack(); trk.count = urlData[0]; trk.CRC = readIntLE(urlData, 1); trk.Frame450CRC = readIntLE(urlData, 5); dsk.tracks.Add(trk); } _accDisks.Add(dsk); } respStream.Close(); } } } catch (WebException ex) { ExceptionStatus = ex.Status; ExceptionMessage = ex.Message; if (ExceptionStatus == WebExceptionStatus.ProtocolError) ResponseStatus = (ex.Response as HttpWebResponse).StatusCode; } finally { last_accessed = DateTime.Now; } } } public void Close() { if (_sampleCount != _finalSampleCount) throw new Exception("_sampleCount != _finalSampleCount"); } public void Delete() { throw new Exception("unsupported"); } public int CompressionLevel { get { return 0; } set { } } public object Settings { get { return null; } set { if (value != null && value.GetType() != typeof(object)) throw new Exception("Unsupported options " + value); } } public long Padding { set { } } public AudioPCMConfig PCM { get { return AudioPCMConfig.RedBook; } } public long FinalSampleCount { get { return _finalSampleCount; } set { if (value != _finalSampleCount) throw new Exception("invalid FinalSampleCount"); } } public long BlockSize { set { throw new Exception("unsupported"); } } public string Path { get { throw new Exception("unsupported"); } } public void GenerateLog(TextWriter sw, int oi) { uint maxTotal = 0; for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) maxTotal = Math.Max(maxTotal, Total(iTrack)); string ifmt = maxTotal < 10 ? ":0" : maxTotal < 100 ? ":00" : ":000"; //string ifmt = maxTotal < 10 ? ",1" : maxTotal < 100 ? ",2" : ",3"; for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { uint count = 0; uint partials = 0; uint conf = 0; uint crcOI = CRC(iTrack, oi); uint crc450OI = CRC450(iTrack, oi); for (int di = 0; di < (int)AccDisks.Count; di++) { int trno = iTrack + _toc.FirstAudio - 1; if (trno >= AccDisks[di].tracks.Count) continue; count += AccDisks[di].tracks[trno].count; if (crcOI == AccDisks[di].tracks[trno].CRC && 0 != AccDisks[di].tracks[trno].CRC) conf += AccDisks[di].tracks[trno].count; if (crc450OI == AccDisks[di].tracks[trno].Frame450CRC && 0 != AccDisks[di].tracks[trno].Frame450CRC) partials ++; } string status; if (conf > 0) status = "Accurately ripped"; else if (count == 0 && CRC(iTrack, oi) == 0) status = "Silent track"; else if (partials > 0) status = "No match but offset"; else status = "No match"; sw.WriteLine(String.Format(" {0:00} [{1:x8}] ({3" + ifmt + "}/{2" + ifmt + "}) {4}", iTrack + 1, CRC(iTrack, oi), count, conf, status)); } } public void GenerateFullLog(TextWriter sw, bool verbose, string id) { sw.WriteLine("[AccurateRip ID: {0}] {1}.", id, ARStatus ?? "found"); if (ExceptionStatus == WebExceptionStatus.Success) { if (verbose) { sw.WriteLine("Track [ CRC ] Status"); GenerateLog(sw, 0); uint offsets_match = 0; for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) { uint matches = 0; for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { int trno = iTrack + _toc.FirstAudio - 1; for (int di = 0; di < (int)AccDisks.Count; di++) if (trno < AccDisks[di].tracks.Count && (CRC(iTrack, oi) == AccDisks[di].tracks[trno].CRC && AccDisks[di].tracks[trno].CRC != 0)) { matches++; break; } } if (matches == _toc.AudioTracks && oi != 0) { if (offsets_match++ > 16) { sw.WriteLine("More than 16 offsets match!"); break; } sw.WriteLine("Offsetted by {0}:", oi); GenerateLog(sw, oi); } } offsets_match = 0; for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) { uint matches = 0, partials = 0; for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { uint crcOI = CRC(iTrack, oi); uint crc450OI = CRC450(iTrack, oi); for (int di = 0; di < (int)AccDisks.Count; di++) { int trno = iTrack + _toc.FirstAudio - 1; if (trno >= AccDisks[di].tracks.Count) continue; if (crcOI == AccDisks[di].tracks[trno].CRC && AccDisks[di].tracks[trno].CRC != 0) { matches++; break; } if (crc450OI == AccDisks[di].tracks[trno].Frame450CRC && AccDisks[di].tracks[trno].Frame450CRC != 0) partials++; } } if (matches != _toc.AudioTracks && oi != 0 && matches + partials != 0) { if (offsets_match++ > 16) { sw.WriteLine("More than 16 offsets match!"); break; } sw.WriteLine("Offsetted by {0}:", oi); GenerateLog(sw, oi); } } } else { sw.WriteLine("Track Status"); for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { uint total = Total(iTrack); uint conf = 0; bool zeroOffset = false; StringBuilder pressings = new StringBuilder(); for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) for (int iDisk = 0; iDisk < AccDisks.Count; iDisk++) { int trno = iTrack + _toc.FirstAudio - 1; if (trno < AccDisks[iDisk].tracks.Count && CRC(iTrack, oi) == AccDisks[iDisk].tracks[trno].CRC && (AccDisks[iDisk].tracks[trno].CRC != 0 || oi == 0)) { conf += AccDisks[iDisk].tracks[trno].count; if (oi == 0) zeroOffset = true; pressings.AppendFormat("{0}{1}({2})", pressings.Length > 0 ? "," : "", oi, AccDisks[iDisk].tracks[trno].count); } } if (conf > 0 && zeroOffset && pressings.Length == 0) sw.WriteLine(String.Format(" {0:00} ({2:00}/{1:00}) Accurately ripped", iTrack + 1, total, conf)); else if (conf > 0 && zeroOffset) sw.WriteLine(String.Format(" {0:00} ({2:00}/{1:00}) Accurately ripped, all offset(s) {3}", iTrack + 1, total, conf, pressings)); else if (conf > 0) sw.WriteLine(String.Format(" {0:00} ({2:00}/{1:00}) Accurately ripped with offset(s) {3}", iTrack + 1, total, conf, pressings)); else if (total > 0) sw.WriteLine(String.Format(" {0:00} (00/{1:00}) NOT ACCURATE", iTrack + 1, total)); else sw.WriteLine(String.Format(" {0:00} (00/00) Track not present in database", iTrack + 1)); } } } if (CRC32(0) != 0 && (_hasLogCRC || verbose)) { sw.WriteLine(""); sw.WriteLine("Track Peak [ CRC32 ] [W/O NULL] {0:10}", _hasLogCRC ? "[ LOG ]" : ""); for (int iTrack = 0; iTrack <= _toc.AudioTracks; iTrack++) { string inLog, extra = ""; if (CRCLOG(iTrack) == 0) inLog = ""; else if (CRCLOG(iTrack) == CRC32(iTrack)) inLog = " CRC32 "; else if (CRCLOG(iTrack) == CRCWONULL(iTrack)) inLog = " W/O NULL "; else { inLog = String.Format("[{0:X8}]", CRCLOG(iTrack)); for (int jTrack = 1; jTrack <= _toc.AudioTracks; jTrack++) { if (CRCLOG(iTrack) == CRC32(jTrack)) { extra = string.Format(": CRC32 for track {0}", jTrack); break; } if (CRCLOG(iTrack) == CRCWONULL(jTrack)) { extra = string.Format(": W/O NULL for track {0}", jTrack); break; } } if (extra == "") for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) if (CRCLOG(iTrack) == CRC32(iTrack, oi)) { inLog = " CRC32 "; extra = string.Format(": offset {0}", oi); break; } if (extra == "") for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) if (CRCLOG(iTrack) == CRCWONULL(iTrack, oi)) { inLog = " W/O NULL "; if (extra == "") extra = string.Format(": offset {0}", oi); else { extra = string.Format(": with offset"); break; } } } sw.WriteLine(" {0} {5,5:F1} [{1:X8}] [{2:X8}] {3,10}{4}", iTrack == 0 ? "--" : string.Format("{0:00}", iTrack), CRC32(iTrack), CRCWONULL(iTrack), inLog, extra, ((iTrack == 0 ? PeakLevel() : PeakLevel(iTrack)) * 1000 / 65534) * 0.1); } } } private static uint sumDigits(uint n) { uint r = 0; while (n > 0) { r = r + (n % 10); n = n / 10; } return r; } static string CachePath { get { string cache = System.IO.Path.Combine(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CUE Tools"), "AccurateRipCache"); if (!Directory.Exists(cache)) Directory.CreateDirectory(cache); return cache; } } public static bool FindDriveReadOffset(string driveName, out int driveReadOffset) { string fileName = System.IO.Path.Combine(CachePath, "DriveOffsets.bin"); if (!File.Exists(fileName) || (DateTime.Now - File.GetLastWriteTime(fileName) > TimeSpan.FromDays(10)) ) { HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://www.accuraterip.com/accuraterip/DriveOffsets.bin"); req.Method = "GET"; try { HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); if (resp.StatusCode != HttpStatusCode.OK) { driveReadOffset = 0; return false; } Stream respStream = resp.GetResponseStream(); FileStream myOffsetsSaved = new FileStream(fileName, FileMode.Create, FileAccess.Write); byte[] buff = new byte[0x8000]; do { int count = respStream.Read(buff, 0, buff.Length); if (count == 0) break; myOffsetsSaved.Write(buff, 0, count); } while (true); respStream.Close(); myOffsetsSaved.Close(); } catch (WebException ex) { driveReadOffset = 0; return false; } } FileStream myOffsets = new FileStream(fileName, FileMode.Open, FileAccess.Read); BinaryReader offsetReader = new BinaryReader(myOffsets); do { short readOffset = offsetReader.ReadInt16(); byte[] name = offsetReader.ReadBytes(0x21); byte[] misc = offsetReader.ReadBytes(0x22); int len = name.Length; while (len > 0 && name[len - 1] == '\0') len--; string strname = Encoding.ASCII.GetString(name, 0, len); if (strname == driveName) { driveReadOffset = readOffset; return true; } } while (myOffsets.Position + 0x45 <= myOffsets.Length); offsetReader.Close(); driveReadOffset = 0; return false; } public static string CalculateCDDBQuery(CDImageLayout toc) { StringBuilder query = new StringBuilder(CalculateCDDBId(toc)); query.AppendFormat("+{0}", toc.TrackCount); for (int iTrack = 1; iTrack <= toc.TrackCount; iTrack++) query.AppendFormat("+{0}", toc[iTrack].Start + 150); query.AppendFormat("+{0}", toc.Length / 75 - toc[1].Start / 75); return query.ToString(); } public static string CalculateCDDBId(CDImageLayout toc) { uint cddbDiscId = 0; for (int iTrack = 1; iTrack <= toc.TrackCount; iTrack++) cddbDiscId += sumDigits(toc[iTrack].Start / 75 + 2); // !!!!!!!!!!!!!!!!! %255 !! return string.Format("{0:X8}", (((cddbDiscId % 255) << 24) + ((toc.Length / 75 - toc[1].Start / 75) << 8) + (uint)toc.TrackCount) & 0xFFFFFFFF); } public static string CalculateAccurateRipId(CDImageLayout toc) { // Calculate the three disc ids used by AR uint discId1 = 0; uint discId2 = 0; uint num = 0; for (int iTrack = 1; iTrack <= toc.TrackCount; iTrack++) if (toc[iTrack].IsAudio) { discId1 += toc[iTrack].Start; discId2 += Math.Max(toc[iTrack].Start, 1) * (++num); } discId1 += toc.Length; discId2 += Math.Max(toc.Length, 1) * (++num); discId1 &= 0xFFFFFFFF; discId2 &= 0xFFFFFFFF; return string.Format("{0:x8}-{1:x8}-{2}", discId1, discId2, CalculateCDDBId(toc).ToLower()); } public List AccDisks { get { return _accDisks; } } private string ExceptionMessage; public HttpStatusCode ResponseStatus { get; set; } public WebExceptionStatus ExceptionStatus { get; set; } public string ARStatus { get { return ExceptionStatus == WebExceptionStatus.Success ? null : ExceptionStatus != WebExceptionStatus.ProtocolError ? ("database access error: " + (ExceptionMessage ?? ExceptionStatus.ToString())) : ResponseStatus != HttpStatusCode.NotFound ? "database access error: " + ResponseStatus.ToString() : "disk not present in database"; } } CDImageLayout _toc; long _sampleCount, _finalSampleCount; int _currentTrack; private List _accDisks; internal uint[,] _CRCAR; internal uint[,] _CRCSM; internal uint[,] _CRC32; internal uint[,] _CRCWN; internal int[,] _CRCNL; private uint[,] _CacheCRCWN; private uint[,] _CacheCRC32; internal int[] _Peak; private uint[] _CRCLOG; private uint[] _CRCMASK; private IWebProxy proxy; private bool _hasLogCRC; private const int _arOffsetRange = 5 * 588 - 1; } public struct AccTrack { public uint count; public uint CRC; public uint Frame450CRC; } public class AccDisk { public uint count; public uint discId1; public uint discId2; public uint cddbDiscId; public List tracks; public AccDisk() { tracks = new List(); } } }