From e7eeeeb9a2e21a25c799f1103a3f0446c1e9bad3 Mon Sep 17 00:00:00 2001 From: chudov Date: Thu, 18 Feb 2010 21:12:44 +0000 Subject: [PATCH] CD parity information database test --- CUETools.CDRepair/CDRepair.cs | 499 ++++++++++++++++++ CUETools.CDRepair/CUETools.CDRepair.csproj | 62 +++ CUETools.CDRepair/Properties/AssemblyInfo.cs | 36 ++ CUETools.CTDB/CUETools.CTDB.csproj | 70 +++ CUETools.CTDB/CUEToolsDB.cs | 376 +++++++++++++ CUETools.CTDB/Properties/AssemblyInfo.cs | 36 ++ .../UploadHelper/HttpUploadHelper.cs | 114 ++++ CUETools.CTDB/UploadHelper/MimePart.cs | 52 ++ .../UploadHelper/Properties/AssemblyInfo.cs | 35 ++ CUETools.CTDB/UploadHelper/StreamMimePart.cs | 31 ++ CUETools.CTDB/UploadHelper/StringMimePart.cs | 34 ++ CUETools.CTDB/UploadHelper/UploadFile.cs | 61 +++ .../UploadHelper/UploadHelper.csproj | 59 +++ CUETools.CTDB/UploadHelper/uploader.php | 77 +++ CUETools.CTDB/uploader.php | 14 + CUETools.Parity/CUETools.Parity.csproj | 54 ++ CUETools.Parity/Galois.cs | Bin 0 -> 12622 bytes CUETools.Parity/Properties/AssemblyInfo.cs | 36 ++ CUETools.Parity/RsDecode.cs | Bin 0 -> 19232 bytes CUETools.Parity/RsEncode.cs | Bin 0 -> 5960 bytes 20 files changed, 1646 insertions(+) create mode 100644 CUETools.CDRepair/CDRepair.cs create mode 100644 CUETools.CDRepair/CUETools.CDRepair.csproj create mode 100644 CUETools.CDRepair/Properties/AssemblyInfo.cs create mode 100644 CUETools.CTDB/CUETools.CTDB.csproj create mode 100644 CUETools.CTDB/CUEToolsDB.cs create mode 100644 CUETools.CTDB/Properties/AssemblyInfo.cs create mode 100644 CUETools.CTDB/UploadHelper/HttpUploadHelper.cs create mode 100644 CUETools.CTDB/UploadHelper/MimePart.cs create mode 100644 CUETools.CTDB/UploadHelper/Properties/AssemblyInfo.cs create mode 100644 CUETools.CTDB/UploadHelper/StreamMimePart.cs create mode 100644 CUETools.CTDB/UploadHelper/StringMimePart.cs create mode 100644 CUETools.CTDB/UploadHelper/UploadFile.cs create mode 100644 CUETools.CTDB/UploadHelper/UploadHelper.csproj create mode 100644 CUETools.CTDB/UploadHelper/uploader.php create mode 100644 CUETools.CTDB/uploader.php create mode 100644 CUETools.Parity/CUETools.Parity.csproj create mode 100644 CUETools.Parity/Galois.cs create mode 100644 CUETools.Parity/Properties/AssemblyInfo.cs create mode 100644 CUETools.Parity/RsDecode.cs create mode 100644 CUETools.Parity/RsEncode.cs diff --git a/CUETools.CDRepair/CDRepair.cs b/CUETools.CDRepair/CDRepair.cs new file mode 100644 index 0000000..7fb88b7 --- /dev/null +++ b/CUETools.CDRepair/CDRepair.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Text; +using CUETools.Codecs; +using CUETools.Parity; + +namespace CUETools.CDRepair +{ + public class CDRepair : IAudioDest + { + protected int sampleCount; + protected int finalSampleCount; + protected Galois galois; + protected RsDecode rs; + protected Crc32 crc32; + protected uint crc; + protected int[] encodeGx; + protected int stride; + protected int laststride; + protected int stridecount; + protected int npar; + + public CDRepair(int finalSampleCount, int stride, int npar) + { + this.npar = npar; + this.stride = stride; + this.finalSampleCount = finalSampleCount; + sampleCount = 0; + galois = Galois16.instance; + rs = new RsDecode16(npar, galois); + crc32 = new Crc32(); + crc = 0xffffffff; + encodeGx = galois.makeEncodeGxLog(npar); + laststride = stride + (finalSampleCount * 2) % stride; + stridecount = (finalSampleCount * 2) / stride - 2; // minus one for leadin and one for leadout + if ((finalSampleCount * 2 + stride - 1) / stride + npar > galois.Max) + throw new Exception("invalid stride"); + } + + public CDRepair(CDRepair src) + : this(src.finalSampleCount, src.stride, src.npar) + { + } + + public unsafe void Write(AudioBuffer sampleBuffer) + { + throw new Exception("unsupported"); + } + + public unsafe 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 string Options + { + set + { + if (value == null || value == "") return; + throw new Exception("Unsupported options " + value); + } + } + + public AudioPCMConfig PCM + { + get { return AudioPCMConfig.RedBook; } + } + + public long FinalSampleCount + { + set + { + if (value < 0) // != _toc.Length? + throw new Exception("invalid FinalSampleCount"); + finalSampleCount = (int)value; + } + } + public long BlockSize + { + set { throw new Exception("unsupported"); } + } + + public string Path + { + get { throw new Exception("unsupported"); } + } + + public uint CRC + { + get + { + return crc ^ 0xffffffff; + } + } + } + + public class CDRepairEncode : CDRepair + { + protected byte[] parity; + protected ushort[,] syndrome; + protected ushort[] leadin; + protected ushort[] leadout; + protected bool verify; + protected bool hasErrors = false, canRecover = true; + protected int actualOffset = 0; + + internal int[,] sigma; + internal int[,] omega; + internal int[,] errpos; + internal int[,] erroff; + internal int[] errors; + + public CDRepairEncode(int finalSampleCount, int stride, int npar, bool verify) + : base (finalSampleCount, stride, npar) + { + this.verify = verify; + parity = new byte[stride * npar * 2]; + if (verify) + { + syndrome = new ushort[stride, npar]; + leadin = new ushort[stride * 2]; + leadout = new ushort[stride + laststride]; + } else + syndrome = new ushort[1, npar]; + } + + new public unsafe void Write(AudioBuffer sampleBuffer) + { + sampleBuffer.Prepare(this); + + if ((sampleBuffer.ByteLength & 1) != 0) + throw new Exception("never happens"); + + int firstPos = Math.Max(0, stride - sampleCount * 2); + int lastPos = Math.Min(sampleBuffer.ByteLength >> 1, (finalSampleCount - sampleCount) * 2 - laststride); + + fixed (byte* bytes = sampleBuffer.Bytes, par = parity) + fixed (int* gx = encodeGx) + fixed (uint* t = crc32.table) + fixed (ushort* exp = galois.ExpTbl, log = galois.LogTbl, synptr = syndrome) + { + ushort* data = (ushort*)bytes; + + if (verify) + for (int pos = 0; pos < (sampleBuffer.ByteLength >> 1); pos++) + { + ushort dd = data[pos]; + if (sampleCount * 2 + pos < 2 * stride) + leadin[sampleCount * 2 + pos] = dd; + int remaining = (finalSampleCount - sampleCount) * 2 - pos - 1; + if (remaining < stride + laststride) + leadout[remaining] = dd; + } + + if (npar == 8) + { + for (int pos = firstPos; pos < lastPos; pos++) + { + int part = (sampleCount * 2 + pos) % stride; + ushort* wr = ((ushort*)par) + part * 8; + ushort dd = data[pos]; + + crc = (crc >> 8) ^ t[(byte)(crc ^ dd)]; + crc = (crc >> 8) ^ t[(byte)(crc ^ (dd >> 8))]; + + int ib = wr[0] ^ dd; + 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; + } + + // syn[i] += data[pos] * α^(pos*i) + if (verify && dd != 0) + { + ushort* syn = synptr + part * 8; + ushort* myexp = exp + log[dd]; + int offs = stridecount - (sampleCount * 2 + pos) / stride; + syn[0] ^= dd; + syn[1] ^= myexp[offs]; + syn[2] ^= myexp[(offs * 2) % 65535]; + syn[3] ^= myexp[(offs * 3) % 65535]; + syn[4] ^= myexp[(offs * 4) % 65535]; + syn[5] ^= myexp[(offs * 5) % 65535]; + syn[6] ^= myexp[(offs * 6) % 65535]; + syn[7] ^= myexp[(offs * 7) % 65535]; + //ushort logdd = log[dd]; + //syn[1] ^= exp[(logdd + offs) % 65535]; + //syn[2] ^= exp[(logdd + offs * 2) % 65535]; + //syn[3] ^= exp[(logdd + offs * 3) % 65535]; + //syn[4] ^= exp[(logdd + offs * 4) % 65535]; + //syn[5] ^= exp[(logdd + offs * 5) % 65535]; + //syn[6] ^= exp[(logdd + offs * 6) % 65535]; + //syn[7] ^= exp[(logdd + offs * 7) % 65535]; + } + } + } + else + { + for (int pos = firstPos; pos < lastPos; pos++) + { + int part = (sampleCount * 2 + pos) % stride; + ushort* wr = ((ushort*)par) + part * npar; + ushort dd = data[pos]; + + crc = (crc >> 8) ^ t[(byte)(crc ^ dd)]; + crc = (crc >> 8) ^ t[(byte)(crc ^ (dd >> 8))]; + + if (verify) + { + ushort* syn = synptr + part * npar; + syn[0] ^= dd; // wk += data + for (int i = 1; i < npar; i++) + syn[i] = (ushort)(dd ^ galois.mulExp(syn[i], i)); // wk = data + wk * α^i + } + + int ib = wr[0] ^ dd; + if (ib != 0) + { + ushort* myexp = exp + log[ib]; + for (int i = 0; i < npar - 1; i++) + wr[i] = (ushort)(wr[i + 1] ^ myexp[gx[i]]); + wr[npar - 1] = myexp[gx[npar - 1]]; + } + else + { + for (int i = 0; i < npar - 1; i++) + wr[i] = wr[i + 1]; + wr[npar - 1] = 0; + } + } + } + } + sampleCount += sampleBuffer.Length; + } + + public unsafe bool VerifyParity(byte[] parity2) + { + return VerifyParity(parity2, 0, parity2.Length); + } + + public unsafe bool VerifyParity(byte[] parity2, int pos, int len) + { + if (!verify) + throw new Exception("verify was not enabled"); + if (sampleCount != finalSampleCount) + throw new Exception("sampleCount != finalSampleCount"); + if (len != stride * npar * 2) + throw new Exception("wrong size"); + + sigma = new int[stride, npar / 2 + 2]; + omega = new int[stride, npar / 2 + 1]; + errpos = new int[stride, npar / 2]; + erroff = new int[stride, npar / 2]; + errors = new int[stride]; + + actualOffset = 0; + + // find offset + fixed (byte* par2ptr = &parity2[pos]) + { + ushort* par2 = (ushort*)par2ptr; + int* syn = stackalloc int[npar]; + int* _sigma = stackalloc int[npar]; + int* _omega = stackalloc int[npar]; + int* _errpos = stackalloc int[npar]; + int bestErrors = npar; + + // We can only use offset if Abs(offset * 2) < stride, + // else we might need to add/remove more than one sample + // from syndrome calculations, and that would be too difficult + // and will probably require longer leadin/leadout. + for (int offset = 1 - stride / 2; offset < stride / 2; offset++) + { + int err = 0; + + for (int i = 0; i < npar; i++) + { + int part = (stride - 1) % stride; + int part2 = (part + offset * 2 + stride) % stride; + ushort* wr = par2 + part2 * npar; + + syn[i] = syndrome[part, i]; + + // offset < 0 + if (part < -offset * 2) + { + syn[i] ^= galois.mulExp(leadin[stride + part], (i * (stridecount - 1)) % galois.Max); + syn[i] = leadout[laststride - part - 1] ^ galois.mulExp(syn[i], i); + } + // offset > 0 + if (part >= stride - offset * 2) + { + syn[i] = galois.divExp(syn[i] ^ leadout[laststride + stride - part - 1], i); + syn[i] ^= galois.mulExp(leadin[part], (i * (stridecount - 1)) % galois.Max); + } + + for (int j = 0; j < npar; j++) + syn[i] = wr[j] ^ galois.mulExp(syn[i], i); + + err |= syn[i]; + } + if (err == 0) + { + actualOffset = offset; + bestErrors = 0; + break; + } + int err_count = rs.calcSigmaMBM(_sigma, _omega, syn); + if (err_count > 0 && rs.chienSearch(_errpos, stridecount + npar, err_count, _sigma)) + { + if (err_count < bestErrors) + { + actualOffset = offset; + bestErrors = err_count; + } + } + } + } + + hasErrors = false; + fixed (byte* par = &parity2[pos]) + fixed (ushort* exp = galois.ExpTbl, log = galois.LogTbl) + { + int* syn = stackalloc int[npar]; + int offset = actualOffset; + + for (int part = 0; part < stride; part++) + { + int part2 = (part + offset * 2 + stride) % stride; + ushort* wr = (ushort*)par + part2 * npar; + int err = 0; + + for (int i = 0; i < npar; i++) + { + syn[i] = syndrome[part, i]; + + // offset < 0 + if (part < -offset * 2) + { + syn[i] ^= galois.mulExp(leadin[stride + part], (i * (stridecount - 1)) % galois.Max); + syn[i] = leadout[laststride - part - 1] ^ galois.mulExp(syn[i], i); + } + // offset > 0 + if (part >= stride - offset * 2) + { + syn[i] = galois.divExp(syn[i] ^ leadout[laststride + stride - part - 1], i); + syn[i] ^= galois.mulExp(leadin[part], (i * (stridecount - 1)) % galois.Max); + } + + //syn[i] = galois.mulExp(syn[i], i * npar); + + for (int j = 0; j < npar; j++) + syn[i] = wr[j] ^ galois.mulExp(syn[i], i); // wk = data + wk * α^i + + err |= syn[i]; + } + + //for (int j = 0; j < npar; j++) + // if (wr[j] != 0) + // { + // ushort* myexp = exp + log[wr[j]]; + // syn[0] ^= wr[j]; + // for (int i = 1; i < npar; i++) + // syn[i] ^= myexp[(npar - j - 1) * i]; + // } + + //for (int i = 0; i < npar; i++) + // err |= syn[i]; + + if (err != 0) + { + hasErrors = true; + fixed (int* s = &sigma[part, 0], o = &omega[part, 0], e = &errpos[part, 0], f = &erroff[part, 0]) + { + errors[part] = rs.calcSigmaMBM(s, o, syn); + if (errors[part] <= 0 || !rs.chienSearch(e, stridecount + npar, errors[part], s)) + canRecover = false; + else + { + for (int i = 0; i < errors[part]; i++) + f[i] = galois.toPos(stridecount + npar, e[i]); + } + } + } + else + errors[part] = 0; + } + } + + return !hasErrors; + } + + public byte[] Parity + { + get + { + return parity; + } + } + + public bool HasErrors + { + get + { + return hasErrors; + } + } + + public bool CanRecover + { + get + { + return canRecover; + } + } + + public int ActualOffset + { + get + { + return actualOffset; + } + } + } + + public class CDRepairFix : CDRepair + { + CDRepairEncode decode; + + public CDRepairFix(CDRepairEncode decode) + : base(decode) + { + this.decode = decode; + } + + new public unsafe void Write(AudioBuffer sampleBuffer) + { + sampleBuffer.Prepare(this); + + if ((sampleBuffer.ByteLength & 1) != 0) + throw new Exception("never happens"); + + int firstPos = Math.Max(0, stride - sampleCount * 2 - decode.ActualOffset * 2); + int lastPos = Math.Min(sampleBuffer.ByteLength >> 1, (finalSampleCount - sampleCount) * 2 - laststride - decode.ActualOffset * 2); + + fixed (byte* bytes = sampleBuffer.Bytes) + fixed (uint* t = crc32.table) + { + ushort* data = (ushort*)bytes; + for (int pos = firstPos; pos < lastPos; pos++) + { + int part = (sampleCount * 2 + pos) % stride; + int errors = decode.errors[part]; + fixed (int* s = &decode.sigma[part, 0], o = &decode.omega[part, 0], f = &decode.erroff[part, 0]) + for (int i = 0; i < errors; i++) + if (f[i] == (sampleCount * 2 + decode.ActualOffset * 2 + pos) / stride - 1) + data[pos] ^= (ushort)rs.doForney(errors, decode.errpos[part, i], s, o); + + ushort dd = data[pos]; + + crc = (crc >> 8) ^ t[(byte)(crc ^ dd)]; + crc = (crc >> 8) ^ t[(byte)(crc ^ (dd >> 8))]; + } + } + sampleCount += sampleBuffer.Length; + } + } +} diff --git a/CUETools.CDRepair/CUETools.CDRepair.csproj b/CUETools.CDRepair/CUETools.CDRepair.csproj new file mode 100644 index 0000000..74b1385 --- /dev/null +++ b/CUETools.CDRepair/CUETools.CDRepair.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {C4869B37-EBB1-47BB-9406-B1209BEAB84B} + Library + Properties + CUETools.CDRepair + CUETools.CDRepair + v2.0 + 512 + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + {6458A13A-30EF-45A9-9D58-E5031B17BEE2} + CUETools.Codecs + + + {ECEB839C-171B-4535-958F-9899310A0342} + CUETools.Parity + + + + + \ No newline at end of file diff --git a/CUETools.CDRepair/Properties/AssemblyInfo.cs b/CUETools.CDRepair/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4e46e90 --- /dev/null +++ b/CUETools.CDRepair/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CUETools.CDRepair")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("CUETools.CDRepair")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5e9cf235-c412-4f5f-bf0e-29acda362306")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CUETools.CTDB/CUETools.CTDB.csproj b/CUETools.CTDB/CUETools.CTDB.csproj new file mode 100644 index 0000000..36e0677 --- /dev/null +++ b/CUETools.CTDB/CUETools.CTDB.csproj @@ -0,0 +1,70 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {AA2A9A7E-45FB-4632-AD85-85B0E556F818} + Library + Properties + CUETools.CTDB + CUETools.CTDB + v2.0 + 512 + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + {1DD41038-D885-46C5-8DDE-E0B82F066584} + CUETools.CDImage + + + {C4869B37-EBB1-47BB-9406-B1209BEAB84B} + CUETools.CDRepair + + + {6458A13A-30EF-45A9-9D58-E5031B17BEE2} + CUETools.Codecs + + + + + \ No newline at end of file diff --git a/CUETools.CTDB/CUEToolsDB.cs b/CUETools.CTDB/CUEToolsDB.cs new file mode 100644 index 0000000..0fadd90 --- /dev/null +++ b/CUETools.CTDB/CUEToolsDB.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using CUETools.CDImage; +using CUETools.CDRepair; +using Krystalware.UploadHelper; + +namespace CUETools.CTDB +{ + public class CUEToolsDB + { + private CDRepairEncode verify; + private CDImageLayout toc; + private HttpStatusCode accResult; + private string id; + private byte[] contents; + private int pos; + private int length; + private int confidence; + private int total; + List entries = new List(); + + public CUEToolsDB(CDImageLayout toc) + { + this.toc = toc; + this.length = (int)toc.AudioLength * 588; + } + + public void ContactDB(string id) + { + this.id = id; + + // Calculate the three disc ids used by AR + uint discId1 = 0; + uint discId2 = 0; + uint cddbDiscId = 0; + + string[] n = id.Split('-'); + if (n.Length != 3) + 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://db.cuetools.net/parity/{0:x}/{1:x}/{2:x}/dBCT-{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 = WebRequest.GetSystemWebProxy(); + + try + { + HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); + accResult = resp.StatusCode; + + if (accResult == HttpStatusCode.OK) + { + using (Stream responseStream = resp.GetResponseStream()) + { + using(MemoryStream memoryStream = new MemoryStream()) + { + byte[] buffer = new byte[16536]; + int count = 0; + do + { + count = responseStream.Read(buffer, 0, buffer.Length); + memoryStream.Write(buffer, 0, count); + } while (count != 0); + contents = memoryStream.ToArray(); + } + } + } + Parse(); + } + catch (WebException ex) + { + if (ex.Status == WebExceptionStatus.ProtocolError) + accResult = ((HttpWebResponse)ex.Response).StatusCode; + else + accResult = HttpStatusCode.BadRequest; + } + } + + /// + /// Database entry format: + /// 'CTDB' version(0x00000100) size + /// 'HEAD' version(0x00000100) size disc-count total-submissions + /// 'DISC' version(0x00000100) size + /// 'TOC ' version(0x00000100) size track-count preGap + /// 'TRAK' ersion(0x00000100) size flags(1==isAudio) length(frames) + /// .... track-count + /// 'ARDB' version(0x00000100) size pressings-count + /// 'ARCD' version(0x00000100) size CRC1 ... CRCN + /// .... pressings-count + /// 'PAR8' version(0x00000100) size parity-data + /// 'CONF' version(0x00000100) size confidence + /// 'CRC ' version(0x00000100) size CRC32 min-offset max-offset ... + /// .... disc-count + /// + public string Submit(int confidence, int total) + { + if (id == null) + throw new Exception("no id"); + // Calculate the three disc ids used by AR + uint discId1 = 0; + uint discId2 = 0; + uint cddbDiscId = 0; + + string[] n = id.Split('-'); + if (n.Length != 3) + 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); + + UploadFile[] files = new UploadFile[1]; + MemoryStream newcontents = new MemoryStream(); + using (DBHDR CTDB = new DBHDR(newcontents, "CTDB", 0x100)) + { + using (DBHDR HEAD = new DBHDR(newcontents, "HEAD", 0x100)) + { + DBHDR.WriteInt(newcontents, 1); // disc count + DBHDR.WriteInt(newcontents, total); + } + using (DBHDR DISC = new DBHDR(newcontents, "DISC", 0x100)) + { + using (DBHDR TOC = new DBHDR(newcontents, "TOC ", 0x100)) + { + DBHDR.WriteInt(newcontents, toc.TrackCount); + DBHDR.WriteInt(newcontents, toc.Pregap); + for (int i = 1; i <= toc.TrackCount; i++) + using (DBHDR TRAK = new DBHDR(newcontents, "TRAK", 0x100)) + { + DBHDR.WriteInt(newcontents, toc[i].IsAudio ? 1 : 0); + DBHDR.WriteInt(newcontents, toc[i].Length); + } + } + using (DBHDR PAR8 = new DBHDR(newcontents, "CONF", 0x100)) + { + DBHDR.WriteInt(newcontents, confidence); + } + using (DBHDR PAR8 = new DBHDR(newcontents, "CRC ", 0x100)) + { + DBHDR.WriteInt(newcontents, verify.CRC); + } + using (DBHDR PAR8 = new DBHDR(newcontents, "PAR8", 0x100)) + { + newcontents.Write(verify.Parity, 0, verify.Parity.Length); + } + } + } + newcontents.Position = 0; + files[0] = new UploadFile(newcontents, "uploadedfile", "data.bin", "image/binary"); + HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://db.cuetools.net/uploader.php"); + req.Proxy = WebRequest.GetSystemWebProxy(); + NameValueCollection form = new NameValueCollection(); + form.Add("id", String.Format("{0:d3}-{1:x8}-{2:x8}-{3:x8}", toc.AudioTracks, discId1, discId2, cddbDiscId)); + HttpWebResponse resp = HttpUploadHelper.Upload(req, files, form); + string errtext; + using (Stream s = resp.GetResponseStream()) + using (StreamReader sr = new StreamReader(s)) + { + errtext = sr.ReadToEnd(); + } + return errtext; + } + + private string ReadHDR(out int end) + { + string res = Encoding.ASCII.GetString(contents, pos, 4); + pos += 4; + int version = ReadInt(); + if (version >= 0x200) + throw new Exception("unsupported CTDB version"); + int size = ReadInt(); + end = pos + size; + return res; + } + + private int ReadInt() + { + int value = + (contents[pos] + + (contents[pos + 1] << 8) + + (contents[pos + 2] << 16) + + (contents[pos + 3] << 24)); + pos += 4; + return value; + } + + private uint ReadUInt() + { + uint value = + ((uint)contents[pos] + + ((uint)contents[pos + 1] << 8) + + ((uint)contents[pos + 2] << 16) + + ((uint)contents[pos + 3] << 24)); + pos += 4; + return value; + } + + private void Parse() + { + if (accResult != HttpStatusCode.OK) + return; + + pos = 0; + int end; + string hdr = ReadHDR(out end); + if (hdr != "CTDB") throw new Exception("invalid CTDB file"); + if (end != contents.Length) throw new Exception("incomplete CTDB file"); + hdr = ReadHDR(out end); + if (hdr != "HEAD") throw new Exception("invalid CTDB file"); + int discCount = ReadInt(); + total = ReadInt(); + pos = end; + while (pos < contents.Length) + { + hdr = ReadHDR(out end); + if (hdr != "DISC") + { + pos = end; + continue; + } + int endDisc = end; + uint crc = 0; + int parPos = 0, parLen = 0, conf = 0; + while (pos < endDisc) + { + hdr = ReadHDR(out end); + if (hdr == "PAR8") + { + parPos = pos; + parLen = end - pos; + } + else if (hdr == "CRC ") + crc = ReadUInt(); + else if (hdr == "CONF") + conf = ReadInt(); + pos = end; + } + if (parPos != 0) + entries.Add(new DBEntry(parPos, parLen, conf, crc)); + } + } + + public void DoVerify() + { + foreach (DBEntry entry in entries) + { + verify.VerifyParity(contents, entry.pos, entry.len); + if (!verify.HasErrors || verify.CanRecover) + confidence = entry.conf; + break; + } + } + + public void Init() + { + verify = new CDRepairEncode(length, 10 * 588 * 2, 8, accResult == HttpStatusCode.OK); + } + + public int Total + { + get + { + return total; + } + } + + public HttpStatusCode AccResult + { + get + { + return accResult; + } + } + + public CDRepairEncode Verify + { + get + { + return verify; + } + } + + public string DBStatus + { + get + { + return accResult == HttpStatusCode.NotFound ? "disk not present in database" : + accResult == HttpStatusCode.OK ? null + : accResult.ToString(); + } + } + + public string Status + { + get + { + //sw.WriteLine("CUETools DB CRC: {0:x8}", Verify.CRC); + if (DBStatus != null) + return DBStatus; + if (!verify.HasErrors) + return string.Format("verified OK, confidence {0}/{1}", confidence, total); + if (verify.CanRecover) + return string.Format("contains correctable errors, confidence {0}/{1}", confidence, total); + return "could not be verified"; + } + } + } + + internal class DBEntry + { + public int pos; + public int len; + public int conf; + public uint crc; + + public DBEntry(int pos, int len, int conf, uint crc) + { + this.pos = pos; + this.len = len; + this.conf = conf; + this.crc = crc; + } + } + + internal class DBHDR : IDisposable + { + private long lenOffs; + private MemoryStream stream; + + public DBHDR(MemoryStream stream, string name, int version) + { + this.stream = stream; + stream.Write(Encoding.ASCII.GetBytes(name), 0, 4); + WriteInt(stream, version); + lenOffs = stream.Position; + WriteInt(stream, 0); + } + + public void Dispose() + { + long fin = stream.Position; + stream.Position = lenOffs; + WriteInt(stream, (int)(fin - lenOffs - 4)); + stream.Position = fin; + } + + public static void WriteInt(MemoryStream stream, int value) + { + byte[] temp = new byte[4]; + temp[0] = (byte)(value & 0xff); + temp[1] = (byte)((value >> 8) & 0xff); + temp[2] = (byte)((value >> 16) & 0xff); + temp[3] = (byte)((value >> 24) & 0xff); + stream.Write(temp, 0, 4); + } + + public static void WriteInt(MemoryStream stream, uint value) + { + byte[] temp = new byte[4]; + temp[0] = (byte)(value & 0xff); + temp[1] = (byte)((value >> 8) & 0xff); + temp[2] = (byte)((value >> 16) & 0xff); + temp[3] = (byte)((value >> 24) & 0xff); + stream.Write(temp, 0, 4); + } + } +} diff --git a/CUETools.CTDB/Properties/AssemblyInfo.cs b/CUETools.CTDB/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..389e16d --- /dev/null +++ b/CUETools.CTDB/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CUETools.CTDB")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("CUETools.CTDB")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ac77f009-6d70-4a1c-9675-c5fec3bfe20b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CUETools.CTDB/UploadHelper/HttpUploadHelper.cs b/CUETools.CTDB/UploadHelper/HttpUploadHelper.cs new file mode 100644 index 0000000..aa950e7 --- /dev/null +++ b/CUETools.CTDB/UploadHelper/HttpUploadHelper.cs @@ -0,0 +1,114 @@ +// http://aspnetupload.com +// Copyright © 2009 Krystalware, Inc. +// +// This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License +// http://creativecommons.org/licenses/by-sa/3.0/us/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Collections.Specialized; +using System.Net; +using System.IO; + +namespace Krystalware.UploadHelper +{ + public class HttpUploadHelper + { + private HttpUploadHelper() + { } + + public static string Upload(string url, UploadFile[] files, NameValueCollection form) + { + HttpWebResponse resp = Upload((HttpWebRequest)WebRequest.Create(url), files, form); + + using (Stream s = resp.GetResponseStream()) + using (StreamReader sr = new StreamReader(s)) + { + return sr.ReadToEnd(); + } + } + + public static HttpWebResponse Upload(HttpWebRequest req, UploadFile[] files, NameValueCollection form) + { + List mimeParts = new List(); + + try + { + foreach (string key in form.AllKeys) + { + StringMimePart part = new StringMimePart(); + + part.Headers["Content-Disposition"] = "form-data; name=\"" + key + "\""; + part.StringData = form[key]; + + mimeParts.Add(part); + } + + int nameIndex = 0; + + foreach (UploadFile file in files) + { + StreamMimePart part = new StreamMimePart(); + + if (string.IsNullOrEmpty(file.FieldName)) + file.FieldName = "file" + nameIndex++; + + part.Headers["Content-Disposition"] = "form-data; name=\"" + file.FieldName + "\"; filename=\"" + file.FileName + "\""; + part.Headers["Content-Type"] = file.ContentType; + + part.SetStream(file.Data); + + mimeParts.Add(part); + } + + string boundary = "----------" + DateTime.Now.Ticks.ToString("x"); + + req.ContentType = "multipart/form-data; boundary=" + boundary; + req.Method = "POST"; + + long contentLength = 0; + + byte[] _footer = Encoding.UTF8.GetBytes("--" + boundary + "--\r\n"); + + foreach (MimePart part in mimeParts) + { + contentLength += part.GenerateHeaderFooterData(boundary); + } + + req.ContentLength = contentLength + _footer.Length; + + byte[] buffer = new byte[8192]; + byte[] afterFile = Encoding.UTF8.GetBytes("\r\n"); + int read; + + using (Stream s = req.GetRequestStream()) + { + foreach (MimePart part in mimeParts) + { + s.Write(part.Header, 0, part.Header.Length); + + while ((read = part.Data.Read(buffer, 0, buffer.Length)) > 0) + s.Write(buffer, 0, read); + + part.Data.Dispose(); + + s.Write(afterFile, 0, afterFile.Length); + } + + s.Write(_footer, 0, _footer.Length); + } + + return (HttpWebResponse)req.GetResponse(); + } + catch + { + foreach (MimePart part in mimeParts) + if (part.Data != null) + part.Data.Dispose(); + + throw; + } + } + } +} \ No newline at end of file diff --git a/CUETools.CTDB/UploadHelper/MimePart.cs b/CUETools.CTDB/UploadHelper/MimePart.cs new file mode 100644 index 0000000..e744d12 --- /dev/null +++ b/CUETools.CTDB/UploadHelper/MimePart.cs @@ -0,0 +1,52 @@ +// http://aspnetupload.com +// Copyright © 2009 Krystalware, Inc. +// +// This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License +// http://creativecommons.org/licenses/by-sa/3.0/us/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Collections.Specialized; + +namespace Krystalware.UploadHelper +{ + public abstract class MimePart + { + NameValueCollection _headers = new NameValueCollection(); + byte[] _header; + + public NameValueCollection Headers + { + get { return _headers; } + } + + public byte[] Header + { + get { return _header; } + } + + public long GenerateHeaderFooterData(string boundary) + { + StringBuilder sb = new StringBuilder(); + + sb.Append("--"); + sb.Append(boundary); + sb.AppendLine(); + foreach (string key in _headers.AllKeys) + { + sb.Append(key); + sb.Append(": "); + sb.AppendLine(_headers[key]); + } + sb.AppendLine(); + + _header = Encoding.UTF8.GetBytes(sb.ToString()); + + return _header.Length + Data.Length + 2; + } + + public abstract Stream Data { get; } + } +} \ No newline at end of file diff --git a/CUETools.CTDB/UploadHelper/Properties/AssemblyInfo.cs b/CUETools.CTDB/UploadHelper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8616d05 --- /dev/null +++ b/CUETools.CTDB/UploadHelper/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UploadHelper")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("UploadHelper")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("13eac2b4-c1d5-4317-a17a-5389ad4c6005")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CUETools.CTDB/UploadHelper/StreamMimePart.cs b/CUETools.CTDB/UploadHelper/StreamMimePart.cs new file mode 100644 index 0000000..d2e765b --- /dev/null +++ b/CUETools.CTDB/UploadHelper/StreamMimePart.cs @@ -0,0 +1,31 @@ +// http://aspnetupload.com +// Copyright © 2009 Krystalware, Inc. +// +// This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License +// http://creativecommons.org/licenses/by-sa/3.0/us/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace Krystalware.UploadHelper +{ + public class StreamMimePart : MimePart + { + Stream _data; + + public void SetStream(Stream stream) + { + _data = stream; + } + + public override Stream Data + { + get + { + return _data; + } + } + } +} diff --git a/CUETools.CTDB/UploadHelper/StringMimePart.cs b/CUETools.CTDB/UploadHelper/StringMimePart.cs new file mode 100644 index 0000000..0304fcc --- /dev/null +++ b/CUETools.CTDB/UploadHelper/StringMimePart.cs @@ -0,0 +1,34 @@ +// http://aspnetupload.com +// Copyright © 2009 Krystalware, Inc. +// +// This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License +// http://creativecommons.org/licenses/by-sa/3.0/us/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace Krystalware.UploadHelper +{ + public class StringMimePart : MimePart + { + Stream _data; + + public string StringData + { + set + { + _data = new MemoryStream(Encoding.UTF8.GetBytes(value)); + } + } + + public override Stream Data + { + get + { + return _data; + } + } + } +} diff --git a/CUETools.CTDB/UploadHelper/UploadFile.cs b/CUETools.CTDB/UploadHelper/UploadFile.cs new file mode 100644 index 0000000..27ed84b --- /dev/null +++ b/CUETools.CTDB/UploadHelper/UploadFile.cs @@ -0,0 +1,61 @@ +// http://aspnetupload.com +// Copyright © 2009 Krystalware, Inc. +// +// This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License +// http://creativecommons.org/licenses/by-sa/3.0/us/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace Krystalware.UploadHelper +{ + public class UploadFile + { + Stream _data; + string _fieldName; + string _fileName; + string _contentType; + + public UploadFile(Stream data, string fieldName, string fileName, string contentType) + { + _data = data; + _fieldName = fieldName; + _fileName = fileName; + _contentType = contentType; + } + + public UploadFile(string fileName, string fieldName, string contentType) + : this(File.OpenRead(fileName), fieldName, Path.GetFileName(fileName), contentType) + { } + + public UploadFile(string fileName) + : this(fileName, null, "application/octet-stream") + { } + + public Stream Data + { + get { return _data; } + set { _data = value; } + } + + public string FieldName + { + get { return _fieldName; } + set { _fieldName = value; } + } + + public string FileName + { + get { return _fileName; } + set { _fileName = value; } + } + + public string ContentType + { + get { return _contentType; } + set { _contentType = value; } + } + } +} diff --git a/CUETools.CTDB/UploadHelper/UploadHelper.csproj b/CUETools.CTDB/UploadHelper/UploadHelper.csproj new file mode 100644 index 0000000..025e3ae --- /dev/null +++ b/CUETools.CTDB/UploadHelper/UploadHelper.csproj @@ -0,0 +1,59 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {02FBD52C-B997-4D18-9E2C-81B5AE8EFB30} + Library + Properties + Krystalware.UploadHelper + Krystalware.UploadHelper + + + + + + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CUETools.CTDB/UploadHelper/uploader.php b/CUETools.CTDB/UploadHelper/uploader.php new file mode 100644 index 0000000..d480d00 --- /dev/null +++ b/CUETools.CTDB/UploadHelper/uploader.php @@ -0,0 +1,77 @@ + + * @link http://de3.php.net/manual/en/ini.core.php#79564 + */ +function php_to_byte($v){ + $l = substr($v, -1); + $ret = substr($v, 0, -1); + switch(strtoupper($l)){ + case 'P': + $ret *= 1024; + case 'T': + $ret *= 1024; + case 'G': + $ret *= 1024; + case 'M': + $ret *= 1024; + case 'K': + $ret *= 1024; + break; + } + return $ret; +} + +// Return the human readable size of a file +// @param int $size a file size +// @param int $dec a number of decimal places + +function filesize_h($size, $dec = 1) +{ + $sizes = array('byte(s)', 'kb', 'mb', 'gb'); + $count = count($sizes); + $i = 0; + + while ($size >= 1024 && ($i < $count - 1)) { + $size /= 1024; + $i++; + } + + return round($size, $dec) . ' ' . $sizes[$i]; +} + + +$file = $_FILES['uploadedfile']; + +//echo $file['name'], ini_get('upload_max_filesize'); + + // give info on PHP catched upload errors + if($file['error']) switch($file['error']){ + case 1: + case 2: + echo sprintf($lang['uploadsize'], + filesize_h(php_to_byte(ini_get('upload_max_filesize')))); + echo "Error ", $file['error']; + return; + default: + echo $lang['uploadfail']; + echo "Error ", $file['error']; + } + +$id = $_POST['id']; +$err = sscanf($id, "%d-%x-%x-%x", $tracks, $id1, $id2, $cddbid); +$target_path = sprintf("parity/%x/%x/%x", $id1 & 15, ($id1 >> 4) & 15, ($id1 >> 8) & 15); +$target_file = sprintf("%s/dBCT-%03d-%08x-%08x-%08x.bin", $target_path, $tracks, $id1, $id2, $cddbid); + +@mkdir($target_path, 0777, true); + +if(move_uploaded_file($file['tmp_name'], $target_file)) { + echo "The file ". $target_file. " has been uploaded"; +} else{ + echo "There was an error uploading the file, please try again!"; +} + +?> diff --git a/CUETools.CTDB/uploader.php b/CUETools.CTDB/uploader.php new file mode 100644 index 0000000..a8f0ed5 --- /dev/null +++ b/CUETools.CTDB/uploader.php @@ -0,0 +1,14 @@ + diff --git a/CUETools.Parity/CUETools.Parity.csproj b/CUETools.Parity/CUETools.Parity.csproj new file mode 100644 index 0000000..3257358 --- /dev/null +++ b/CUETools.Parity/CUETools.Parity.csproj @@ -0,0 +1,54 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {ECEB839C-171B-4535-958F-9899310A0342} + Library + Properties + CUETools.Parity + CUETools.Parity + v2.0 + 512 + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CUETools.Parity/Galois.cs b/CUETools.Parity/Galois.cs new file mode 100644 index 0000000000000000000000000000000000000000..cefad0d03f34c531940923975385f5df93cb4a87 GIT binary patch literal 12622 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 soSeKLo!1N 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