コード例 #1
0
ファイル: Mp3LameBlock.cs プロジェクト: kaosborn/Filebert
        public static Mp3XingBlock.Model Create(byte[] buf, Mp3Header header)
        {
            System.Diagnostics.Debug.Assert(header.IsMpegLayer3);

            int    xix        = header.XingOffset;
            string xingString = ConvertTo.FromAsciiToString(buf, xix, 4);

            if (xingString == "Info" || xingString == "Xing")
            {
                string lameString = ConvertTo.FromAsciiToString(buf, xix + 0x78, 9);

                if (lameString.StartsWith("LAME"))
                {
                    return(new Mp3LameBlock.Model(buf, xix, header, xingString, lameString));
                }
                else
                {
                    return(new Mp3XingBlock.Model(buf, xix, header, xingString));
                }
            }

            return(null);
        }
コード例 #2
0
        public override void GetReportDetail(IList <string> report)
        {
            if (report.Count > 0)
            {
                report.Add(String.Empty);
            }

            report.Add("Meta header:");

            report.Add($"  Minimum block size = {MinBlockSize}");
            report.Add($"  Maximum block size = {MaxBlockSize}");
            report.Add($"  Minimum frame size = {MinFrameSize}");
            report.Add($"  Maximum frame size = {MaxFrameSize}");

            report.Add($"  Sample rate = {MetaSampleRate} Hz");
            report.Add($"  Number of channels = {ChannelCount}");
            report.Add($"  Bits per sample = {BitsPerSample}");

            report.Add("  Total samples = " + (TotalSamples != 0? TotalSamples.ToString() : " (unknown)"));

            report.Add(String.Empty);
            report.Add("Raw audio header: " + ConvertTo.ToBitString(aHdr, 1));

            report.Add(String.Empty);
            report.Add("Cooked audio header:");
            report.Add($"  Blocking strategy = {BlockingStrategyText}");
            report.Add($"  Block size = {BlockSize} samples");
            report.Add($"  Sample rate = {SampleRateText}");
            report.Add($"  Channel assignment = {ChannelAssignmentText}");
            report.Add($"  Sample size = {SampleSizeText}");
            report.Add($"  Sample/frame number = {SampleOrFrameNumber}");

            report.Add(String.Empty);
            report.Add("Checks:");

            report.Add($"  Stored audio header CRC-8 = {StoredAudioHeaderCRC8ToHex}");
            if (ActualAudioHeaderCRC8 != null)
            {
                report.Add($"  Actual audio header CRC-8 = {ActualAudioHeaderCRC8ToHex}");
            }

            report.Add($"  Stored audio block CRC-16 = {StoredAudioBlockCRC16ToHex}");
            if (ActualAudioBlockCRC16 != null)
            {
                report.Add($"  Actual audio block CRC-16 = {ActualAudioBlockCRC16ToHex}");
            }

            report.Add($"  Stored PCM MD5 = {StoredAudioDataMD5ToHex}");
            if (actualAudioDataMD5 != null)
            {
                report.Add($"  Actual PCM MD5 = {ActualAudioDataMD5ToHex}");
            }

            if (ActualPcmCRC32 != null)
            {
                report.Add($"  Actual PCM CRC-32 = {ActualPcmCRC32ToHex}");
            }

            report.Add(String.Empty);
            report.Add($"Layout = {Layout}");

            if (Blocks.Tags != null)
            {
                report.Add(String.Empty);
                report.Add("Tags:");
                report.Add($"  Vendor: {Blocks.Tags.Vendor}");
                foreach (var item in Blocks.Tags.Lines)
                {
                    report.Add($"  {item}");
                }
            }
        }
コード例 #3
0
            public Model(Stream stream, byte[] hdr, string path)
            {
                base._data = Data = new FlacFormat(this, stream, path);

                Data.MetadataBlockStreamInfoSize = ConvertTo.FromBig24ToInt32(hdr, 5);
                if (Data.MetadataBlockStreamInfoSize < 34)
                {
                    IssueModel.Add($"Bad metablock size of {Data.MetadataBlockStreamInfoSize}", Severity.Fatal);
                    return;
                }

                var bb = new byte[Data.MetadataBlockStreamInfoSize];

                Data.ValidSize = 8;

                Data.fbs.Position = Data.ValidSize;
                var got = Data.fbs.Read(bb, 0, Data.MetadataBlockStreamInfoSize);

                if (got != Data.MetadataBlockStreamInfoSize)
                {
                    IssueModel.Add("File truncated", Severity.Fatal);
                    return;
                }

                Data.MinBlockSize = ConvertTo.FromBig16ToInt32(bb, 0);
                Data.MinBlockSize = ConvertTo.FromBig16ToInt32(bb, 2);
                Data.MinFrameSize = ConvertTo.FromBig24ToInt32(bb, 4);
                Data.MaxFrameSize = ConvertTo.FromBig24ToInt32(bb, 7);

                Data.MetaSampleRate = bb[10] << 12 | bb[11] << 4 | bb[12] >> 4;
                Data.ChannelCount   = ((bb[12] & 0x0E) >> 1) + 1;
                Data.BitsPerSample  = (((bb[12] & 1) << 4) | (bb[13] >> 4)) + 1;
                Data.TotalSamples   = ((bb[13] & 0x0F) << 32) | bb[14] << 24 | bb[15] << 16 | bb[16] << 8 | bb[17];

                Data.storedAudioDataMD5 = new byte[16];
                Array.Copy(bb, 18, Data.storedAudioDataMD5, 0, 16);

                Data.ValidSize += Data.MetadataBlockStreamInfoSize;

                for (;;)
                {
                    bb = new byte[12];
                    try
                    {
                        Data.fbs.Position = Data.ValidSize;
                    }
                    catch (EndOfStreamException)
                    {
                        IssueModel.Add("File truncated near meta data", Severity.Fatal);
                        return;
                    }

                    Data.fbs.Position = Data.ValidSize;
                    got = Data.fbs.Read(bb, 0, 4);
                    if (got != 4)
                    {
                        IssueModel.Add("File truncated near meta data", Severity.Fatal);
                        return;
                    }

                    if (bb[0] == 0xFF)
                    {
                        break;
                    }

                    int blockSize = ConvertTo.FromBig24ToInt32(bb, 1);
                    Data.ValidSize += 4;

                    switch ((FlacBlockType)(bb[0] & 0x7F))
                    {
                    case FlacBlockType.Padding:
                        Data.Blocks.AddPad((int)Data.ValidSize, blockSize);
                        break;

                    case FlacBlockType.Application:
                        got = Data.fbs.Read(bb, 0, 4);
                        if (got != 4)
                        {
                            IssueModel.Add("File truncated near tags", Severity.Fatal);
                            return;
                        }
                        int appId = ConvertTo.FromBig32ToInt32(bb, 0);
                        Data.Blocks.AddApp((int)Data.ValidSize, blockSize, appId);
                        break;

                    case FlacBlockType.SeekTable:
                        var st = new byte[blockSize];
                        got = Data.fbs.Read(st, 0, blockSize);
                        if (got != blockSize)
                        {
                            IssueModel.Add("File truncated near seek table", Severity.Fatal);
                            return;
                        }
                        Data.Blocks.AddSeekTable((int)Data.ValidSize, blockSize, st);
                        break;

                    case FlacBlockType.Tags:
                        bb = new byte[blockSize];
                        Data.fbs.Position = Data.ValidSize;
                        got = Data.fbs.Read(bb, 0, blockSize);
                        if (got != blockSize)
                        {
                            IssueModel.Add("File truncated near tags", Severity.Fatal);
                            return;
                        }
                        if (Data.Blocks.Tags != null)
                        {
                            IssueModel.Add("Contains multiple tag blocks", Severity.Error);
                        }
                        else
                        {
                            Data.Blocks.AddTags((int)Data.ValidSize, blockSize, bb);
                        }
                        break;

                    case FlacBlockType.CueSheet:
                        var sb = new byte[284];
                        got = Data.fbs.Read(sb, 0, 284);
                        if (got != 284)
                        {
                            IssueModel.Add("File truncated near cuesheet", Severity.Fatal);
                            return;
                        }
                        var isCD       = (sb[24] & 0x80) != 0;
                        int trackCount = sb[283];
                        Data.Blocks.AddCuesheet((int)Data.ValidSize, blockSize, isCD, trackCount);
                        break;

                    case FlacBlockType.Picture:
                        var pb = new byte[blockSize];
                        got = Data.fbs.Read(pb, 0, blockSize);
                        if (got != blockSize)
                        {
                            IssueModel.Add("File truncated near picture", Severity.Fatal);
                            return;
                        }
                        var picType = (PicType)ConvertTo.FromBig32ToInt32(pb, 0);
                        var mimeLen = ConvertTo.FromBig32ToInt32(pb, 4);
                        var mime    = Encoding.UTF8.GetString(pb, 8, mimeLen);
                        var descLen = ConvertTo.FromBig32ToInt32(pb, mimeLen + 8);
                        var desc    = Encoding.UTF8.GetString(pb, mimeLen + 12, descLen);
                        var width   = ConvertTo.FromBig32ToInt32(pb, mimeLen + descLen + 12);
                        var height  = ConvertTo.FromBig32ToInt32(pb, mimeLen + descLen + 16);
                        Data.Blocks.AddPic((int)Data.ValidSize, blockSize, picType, width, height);
                        break;

                    case FlacBlockType.Invalid:
                        IssueModel.Add("Encountered invalid block type", Severity.Fatal);
                        return;

                    default:
                        IssueModel.Add($"Encountered reserved block type '{bb[0]}'", Severity.Warning);
                        break;
                    }

                    Data.ValidSize += blockSize;
                }

                try
                {
                    Data.fbs.Position = Data.ValidSize;
                }
                catch (EndOfStreamException)
                {
                    IssueModel.Add("File truncated near frame header", Severity.Fatal);
                    return;
                }
                got = Data.fbs.Read(bb, 0, 4);
                if (got != 4)
                {
                    IssueModel.Add("File truncated", Severity.Fatal);
                    return;
                }

                // Detect frame header sync code
                if (bb[0] != 0xFF || (bb[1] & 0xFC) != 0xF8)
                {
                    IssueModel.Add("Audio data not found", Severity.Fatal);
                    return;
                }

                Data.mediaPosition = Data.ValidSize;

                Data.SampleOrFrameNumber = Data.fbs.ReadWobbly(out byte[] wtfBuf);
                if (Data.SampleOrFrameNumber < 0)
                {
                    IssueModel.Add("File truncated or badly formed sample/frame number.", Severity.Fatal);
                    return;
                }
                Array.Copy(wtfBuf, 0, bb, 4, wtfBuf.Length);
                int bPos = 4 + wtfBuf.Length;

                Data.RawBlockingStrategy = bb[1] & 1;

                Data.RawBlockSize = bb[2] >> 4;
                if (Data.RawBlockSize == 0)
                {
                    Data.BlockSize = 0;
                }
                else if (Data.RawBlockSize == 1)
                {
                    Data.BlockSize = 192;
                }
                else if (Data.RawBlockSize >= 2 && Data.RawBlockSize <= 5)
                {
                    Data.BlockSize = 576 * (1 << (Data.RawBlockSize - 2));
                }
                else if (Data.RawBlockSize == 6)
                {
                    got            = Data.fbs.Read(bb, bPos, 1);
                    Data.BlockSize = bb[bPos] + 1;
                    bPos          += 1;
                }
                else if (Data.RawBlockSize == 7)
                {
                    got            = Data.fbs.Read(bb, bPos, 2);
                    Data.BlockSize = (bb[bPos] << 8) + bb[bPos + 1] + 1;
                    bPos          += 2;
                }
                else
                {
                    Data.BlockSize = 256 * (1 << (Data.RawBlockSize - 8));
                }


                Data.RawSampleRate = bb[2] & 0xF;
                if (Data.RawSampleRate == 0xC)
                {
                    got = Data.fbs.Read(bb, bPos, 1);
                    Data.SampleRateText = bb[bPos] + "kHz";
                    bPos += 1;
                }
                else if (Data.RawSampleRate == 0xD || Data.RawSampleRate == 0xE)
                {
                    got = Data.fbs.Read(bb, bPos, 2);
                    Data.SampleRateText = (bb[bPos] << 8).ToString() + bb[bPos + 1] + (Data.RawSampleRate == 0xD? " Hz" : " kHz");
                    bPos += 2;
                }
                else if (Data.RawSampleRate == 0)
                {
                    Data.SampleRateText = Data.MetaSampleRate.ToString() + " Hz";
                }
                else
                {
                    Data.SampleRateText = SampleRateMap[Data.RawSampleRate];
                }

                Data.RawChannelAssignment = bb[3] >> 4;

                Data.RawSampleSize = (bb[3] & 0xE) >> 1;
                if (Data.RawSampleSize == 0)
                {
                    Data.SampleSizeText = Data.BitsPerSample.ToString() + " bits";
                }
                else
                {
                    Data.SampleSizeText = SampleSizeMap[Data.RawSampleSize];
                }

                Data.aHdr = new byte[bPos];
                Array.Copy(bb, Data.aHdr, bPos);

                Data.ValidSize   += bPos;
                Data.fbs.Position = Data.ValidSize;
                int octet = Data.fbs.ReadByte();

                if (octet < 0)
                {
                    IssueModel.Add("File truncated near CRC-8", Severity.Fatal);
                    return;
                }
                Data.StoredAudioHeaderCRC8 = (Byte)octet;

                try
                {
                    Data.fbs.Position = Data.mediaPosition;
                }
                catch (EndOfStreamException)
                {
                    IssueModel.Add("File truncated near audio data", Severity.Fatal);
                    return;
                }

                try
                {
                    Data.fbs.Position = Data.FileSize - 2;
                }
                catch (EndOfStreamException)
                {
                    IssueModel.Add("File truncated looking for end", Severity.Fatal);
                    return;
                }

                bb = new byte[2];
                if (Data.fbs.Read(bb, 0, 2) != 2)
                {
                    IssueModel.Add("Read failed on audio block CRC-16", Severity.Fatal);
                    return;
                }

                Data.StoredAudioBlockCRC16 = (UInt16)(bb[0] << 8 | bb[1]);
                Data.MediaCount            = Data.FileSize - Data.mediaPosition;

                GetDiagnostics();
            }
コード例 #4
0
 public EbmlNodeCRC(EbmlSig element, byte[] payload, long start, long count) : base(element)
 {
     this.Start       = start;
     this.Count       = count;
     this.StoredCRC32 = ConvertTo.FromLit32ToUInt32(payload, 0);
 }
コード例 #5
0
ファイル: Mp3Format.cs プロジェクト: kaosborn/Filebert
        public override void GetReportDetail(IList <string> report)
        {
            if (Issues.HasFatal)
            {
                return;
            }

            report.Add($"MPEG size = {MediaCount}");
            if (Xing != null)
            {
                report.Add($"XING size = {Xing.XingSize}");
            }
            if (Lame != null)
            {
                report.Add($"LAME size = {Lame.LameSize}");
            }

            report.Add(String.Empty);
            report.Add($"Raw audio header: {ConvertTo.ToBitString (Header.Bits, 32)}");
            report.Add(String.Empty);

            report.Add("Cooked audio header:");
            report.Add($"  Codec = {Header.Codec}");
            report.Add($"  Bit rate = {Header.BitRateText}");
            report.Add($"  Frequency = {Header.SampleRateText}");
            report.Add($"  Mode = {Header.ModeText}");

            report.Add($"  CRC protection bit = {Header.CrcProtectedBit}");
            report.Add($"  Padding bit = {Header.PaddingBit}");
            report.Add($"  Private bit = {Header.PrivateBit}");
            report.Add($"  Copyright bit = {Header.CopyrightBit}");
            report.Add($"  Original bit = {Header.OriginalBit}");
            report.Add($"  Emphasis = {Header.EmphasisText}");

            if (Xing != null)
            {
                report.Add(String.Empty);
                report.Add("XING:");
                report.Add($"  String = {Xing.XingString}");
                report.Add($"  Layout = {Xing.Layout}");
            }

            if (Lame != null)
            {
                report.Add(String.Empty);

                report.Add("LAME:");
                report.Add($"  Version string = {Lame.LameVersion}");
                report.Add($"  Profile string = {Lame.Profile}");
                report.Add($"  Profile detail = {Lame.Method}");

                report.Add($"  Tag revision = {Lame.TagRevision}");
                report.Add($"  Lowpass filter = {Lame.LowpassFilter}");
                report.Add($"  Replay Gain: Peak = {Lame.ReplayGainPeak}, Radio = {Lame.RadioReplayGain:X4}, Audiophile = {Lame.AudiophileReplayGain:X4}");
                report.Add($"  Lame encoding flags = {ConvertTo.ToBitString (Lame.LameFlags, 8)}");
                report.Add($"  Encoder delay: Start = {Lame.EncoderDelayStart}, End = {Lame.EncoderDelayEnd}");
                report.Add($"  LAME surround = {Lame.Surround}, LAME preset = {Lame.Preset}");
                report.Add($"  MP3 gain = {Lame.Mp3Gain}");
                report.Add($"  Minimum bit rate = {Lame.MinBitRateText}");
                report.Add("  Checks:");
                report.Add($"    Stored: audio header CRC-16 = {Lame.StoredHeaderCrcText}, audio data CRC-16 = {Lame.StoredDataCrcText}");

                if (Lame.ActualHeaderCrc != null)
                {
                    report.Add($"    Actual: audio header CRC-16 = {Lame.ActualHeaderCrcText}, audio data CRC-16 = {Lame.ActualDataCrcText}");
                }
            }

            report.Add(String.Empty);
            report.Add($"Layout = {Layout}");
        }
コード例 #6
0
ファイル: OggFormat.cs プロジェクト: kaosborn/Filebert
            public override void CalcHashes(Hashes hashFlags, Validations validationFlags)
            {
                if (Data.Issues.HasFatal)
                {
                    return;
                }

                if ((hashFlags & Hashes.Intrinsic) != 0)
                {
                    var    buf1 = new byte[27 + 256];
                    byte[] buf2 = null;
                    Data.PageCount = 0;

                    while (Data.ValidSize < Data.FileSize)
                    {
                        Data.fbs.Position = Data.ValidSize;
                        ++Data.PageCount;
                        var got = Data.fbs.Read(buf1, 0, buf1.Length);
                        if (got < buf1.Length)
                        {
                            IssueModel.Add("Read failed near header", Severity.Fatal);
                            return;
                        }

                        int    segmentCount      = buf1[26];
                        UInt32 storedHeaderCRC32 = ConvertTo.FromLit32ToUInt32(buf1, 22);

                        int pageSize = 27 + segmentCount;
                        for (int ix = 27; ix < 27 + segmentCount; ++ix)
                        {
                            pageSize += buf1[ix];
                        }

                        Data.fbs.Position = Data.ValidSize;

                        if (buf2 == null || buf2.Length < pageSize)
                        {
                            buf2 = new byte[pageSize];
                        }
                        got = Data.fbs.Read(buf2, 0, pageSize);
                        if (got < pageSize)
                        {
                            IssueModel.Add("Read failed near page " + Data.PageCount, Severity.Fatal);
                            return;
                        }

                        buf2[22] = 0; buf2[23] = 0; buf2[24] = 0; buf2[25] = 0;

                        UInt32 actualHeaderCRC32;
                        var    hasher = new Crc32n0Hasher();
                        hasher.Append(buf2, 0, pageSize);
                        var hash = hasher.GetHashAndReset();
                        actualHeaderCRC32 = BitConverter.ToUInt32(hash, 0);

                        if (actualHeaderCRC32 != storedHeaderCRC32)
                        {
                            Data.badPage.Add(Data.PageCount.Value);
                        }

                        Data.ValidSize += pageSize;
                    }

                    if (Data.badPage.Count == 0)
                    {
                        Data.CdIssue = IssueModel.Add($"CRC-32 checks successful on {Data.PageCount} pages.", Severity.Advisory, IssueTags.Success);
                    }
                    else
                    {
                        var err = $"CRC-32 checks failed on {Data.badPage.Count} of {Data.PageCount} pages.";
                        Data.CdIssue = IssueModel.Add(err, Severity.Error, IssueTags.Failure);
                    }
                }

                base.CalcHashes(hashFlags, validationFlags);
            }
コード例 #7
0
            public Model(Stream stream, string path) : base(path)
            {
                base._data = Data = new CueFormat(this, stream, path);
                SetIgnoredName("Range.wav");

                if (Data.FileSize > 512 * 1024)
                {
                    IssueModel.Add("Oversized file", Severity.Fatal);
                    return;
                }

                Data.fBuf         = new byte[Data.FileSize];
                Data.fbs.Position = 0;
                if (Data.fbs.Read(Data.fBuf, 0, (int)Data.FileSize) != Data.FileSize)
                {
                    IssueModel.Add("Read error", Severity.Fatal);
                    return;
                }

                Data.Codepage = Encoding.GetEncoding(1252);

                int fIx = 0, fIx1 = 0, fIx2 = 0, bIxNS = -1, quoteIx1 = -1, quoteIx2 = -1;

                for (int line = 1;;)
                {
                    if (fIx < Data.fBuf.Length)
                    {
                        byte ch = Data.fBuf[fIx];
                        ++fIx;
                        if (ch == (byte)'\r')
                        {
                            fIx2 = fIx < Data.fBuf.Length && Data.fBuf[fIx] == (byte)'\n' ? fIx + 1 : fIx;
                        }
                        else if (ch == (byte)'\n')
                        {
                            fIx2 = fIx;
                        }
                        else
                        {
                            if (ch == '\"')
                            {
                                if (quoteIx1 < 0)
                                {
                                    quoteIx1 = fIx;
                                }
                                else if (quoteIx2 < 0)
                                {
                                    quoteIx2 = fIx - 1;
                                }
                            }
                            else if (bIxNS < 0 && ch != ' ')
                            {
                                bIxNS = fIx - 1;
                            }
                            continue;
                        }
                    }
                    else
                    {
                        fIx2 = fIx;
                    }

                    if (ConvertTo.StartsWithAscii(Data.fBuf, bIxNS, "CATALOG "))
                    {
                        Data.Catalog = FormatBase.Cp1252.GetString(Data.fBuf, bIxNS + 8, fIx2 - bIxNS - 8).Trim(null);
                        if (Data.Catalog.Length != 13)
                        {
                            IssueModel.Add("Invalid CATALOG.");
                        }
                    }
                    else if (ConvertTo.StartsWithAscii(Data.fBuf, bIxNS, "FILE "))
                    {
                        if (quoteIx2 <= quoteIx1)
                        {
                            IssueModel.Add("Malformed FILE.");
                        }
                        else
                        {
                            string quoted = FormatBase.Cp1252.GetString(Data.fBuf, quoteIx1, quoteIx2 - quoteIx1);
                            FilesModel.Add1252(quoted, quoteIx1, quoteIx2 - quoteIx1);
                        }
                    }

                    fIx      = fIx1 = bIxNS = fIx2;
                    quoteIx1 = quoteIx2 = -1;
                    ++line;
                    if (fIx >= Data.fBuf.Length)
                    {
                        break;
                    }
                }
            }
コード例 #8
0
            public Model(Stream stream, string path)
            {
                base._data = Data = new IcoFormat(this, stream, path);

                // Arbitrary sanity limit.
                if (Data.FileSize > 50000000)
                {
                    IssueModel.Add("File insanely large", Severity.Fatal);
                    return;
                }

                var buf = new byte[Data.FileSize];

                stream.Position = 0;
                var got = stream.Read(buf, 0, (int)Data.FileSize);

                if (got != Data.FileSize)
                {
                    IssueModel.Add("Read error", Severity.Fatal);
                    return;
                }

                int headerCount = ConvertTo.FromLit16ToInt32(buf, 4);

                if (headerCount <= 0)
                {
                    IssueModel.Add("Corrupt header count", Severity.Fatal);
                    return;
                }

                int pos         = 6;
                int stop        = pos + 16 * headerCount;
                int actualStart = stop;

                for (pos = 6; pos < stop; pos += 16)
                {
                    int width, height, bpp, paletteSize;
                    int storedSize  = ConvertTo.FromLit32ToInt32(buf, pos + 8);
                    int storedStart = ConvertTo.FromLit32ToInt32(buf, pos + 12);

                    if (storedStart != actualStart || storedSize <= 0)
                    {
                        IssueModel.Add($"Corrupt header near byte {Data.ValidSize}", Severity.Fatal);
                        return;
                    }

                    bool isPNG = buf[actualStart] == 0x89 && buf[actualStart + 1] == 'P' && buf[actualStart + 2] == 'N' && buf[actualStart + 3] == 'G';

                    if (isPNG)
                    {
                        width       = ConvertTo.FromBig32ToInt32(buf, actualStart + 16);
                        height      = ConvertTo.FromBig32ToInt32(buf, actualStart + 20);
                        paletteSize = 0;
                        bpp         = buf[actualStart + 24];
                    }
                    else
                    {
                        width = buf[pos];
                        if (width == 0)
                        {
                            width = 256;
                        }

                        height = buf[pos + 1];
                        if (height == 0)
                        {
                            height = 256;
                        }

                        paletteSize = buf[pos + 2];
                        bpp         = buf[pos + 6];
                    }

                    Data.icons.Add(new IconItem(width, height, paletteSize, bpp, storedStart, storedSize, isPNG));

                    actualStart += storedSize;
                }

                if (actualStart != Data.FileSize)
                {
                    IssueModel.Add("Incorrect file size");
                    return;
                }
            }
コード例 #9
0
ファイル: FlvFormat.cs プロジェクト: kaosborn/Filebert
            public Model(Stream stream, byte[] hdr, string path)
            {
                base._data = Data = new FlvFormat(this, stream, path);

                var bb = new byte[15];

                Data.flags = hdr[4];
                if ((Data.flags & 0xA) != 0)
                {
                    IssueModel.Add("Unexpected flags.");
                }
                if ((Data.flags & 5) == 0)
                {
                    IssueModel.Add("Missing audio and video.");
                }

                UInt32 hdrSize = ConvertTo.FromBig32ToUInt32(hdr, 5);

                if (hdrSize != 9)
                {
                    IssueModel.Add("Wrong header size.");
                }

                Data.mediaPosition = 9;
                UInt32 actualPrevSize = 0;

                while (Data.mediaPosition < Data.FileSize)
                {
                    if (Data.mediaPosition + 15 > Data.FileSize)
                    {
                        IssueModel.Add("File truncated near packet header.", Severity.Fatal); return;
                    }

                    Data.fbs.Position = Data.mediaPosition;
                    var got = Data.fbs.Read(bb, 0, bb.Length);
                    if (got < bb.Length)
                    {
                        IssueModel.Add("Read error", Severity.Fatal); return;
                    }

                    Data.mediaPosition += 15;

                    UInt32 storedPrevSize = ConvertTo.FromBig32ToUInt32(bb, 0);
                    if (storedPrevSize != actualPrevSize)
                    {
                        IssueModel.Add("Bad previous packet size.");
                    }

                    byte   packetType = bb[4];
                    UInt32 packetSize = ConvertTo.FromBig24ToUInt32(bb, 5);
                    actualPrevSize = packetSize + 11;

                    ++Data.PacketCount;
                    Data.mediaPosition += packetSize;
                }

                if (Data.mediaPosition > Data.FileSize)
                {
                    IssueModel.Add("File truncated.", Severity.Fatal);
                }
            }
コード例 #10
0
ファイル: AifFormat.cs プロジェクト: kaosborn/Filebert
            private void ParseAif(Stream stream, byte[] hdr)
            {
                Data.GroupId  = ConvertTo.FromAsciiToString(hdr, 0, 4);
                Data.FormType = ConvertTo.FromAsciiToString(hdr, 8, 4);

                UInt32 gSize = ConvertTo.FromBig32ToUInt32(hdr, 4);

                if (gSize < 12 || gSize > stream.Length - 8)
                {
                    IssueModel.Add("File truncated or corrupt.", Severity.Fatal); return;
                }

                int    hPos = 12;
                string id0  = ConvertTo.FromAsciiToString(hdr, 12, 4);

                if (Data.IsCompressed)
                {
                    if (id0 != "FVER")
                    {
                        IssueModel.Add("Missing 'FVER' chunk."); return;
                    }

                    UInt32 vSize = ConvertTo.FromBig32ToUInt32(hdr, 16);
                    if (vSize != 4)
                    {
                        IssueModel.Add("Bad 'FVER' chunk."); return;
                    }

                    hPos = 24;
                    id0  = ConvertTo.FromAsciiToString(hdr, 24, 4);
                }

                if (id0 != "COMM")
                {
                    IssueModel.Add("Missing 'COMM' chunk."); return;
                }

                UInt32 cSize = ConvertTo.FromBig32ToUInt32(hdr, hPos + 4);

                if (cSize < 18 || cSize > stream.Length - 8)
                {
                    IssueModel.Add("Bad 'COMM' chunk."); return;
                }

                Data.ChannelCount = ConvertTo.FromBig16ToInt32(hdr, hPos + 8);
                Data.SampleSize   = ConvertTo.FromBig16ToInt32(hdr, hPos + 14);

                ++Data.IffChunkCount;
                Data.IffSize       = gSize;
                Data.mediaPosition = 0;
                Data.MediaCount    = gSize + 8;
                Data.ValidSize     = hPos + cSize + 8;

                var hasSSND = false;
                var buf     = new byte[8];

                while (Data.ValidSize < Data.MediaCount)
                {
                    stream.Position = Data.ValidSize;

                    int got = stream.Read(buf, 0, 8);
                    if (got != 8)
                    {
                        IssueModel.Add("Read failed."); return;
                    }

                    cSize = ConvertTo.FromBig32ToUInt32(buf, 4);
                    if (cSize < 8 || Data.ValidSize + cSize > Data.MediaCount - 8)
                    {
                        IssueModel.Add("Bad chunk size or truncated file."); return;
                    }

                    string id = ConvertTo.FromAsciiToString(buf, 0, 4);
                    if (id == "SSND")
                    {
                        if (hasSSND)
                        {
                            IssueModel.Add("Many 'SSND' chunks."); return;
                        }
                        hasSSND = true;
                    }
                    else if (id != "(c) " && id != "ANNO" && id != "AUTH" && id != "NAME")
                    {
                        IssueModel.Add($"Unexpected '{id}' chunk.", Severity.Trivia, IssueTags.StrictWarn); return;
                    }

                    Data.ValidSize += cSize + 8;
                    if ((cSize & 1) != 0)
                    {
                        ++Data.ValidSize;
                    }
                }

                if (!hasSSND)
                {
                    IssueModel.Add("Missing 'SSND' chunk.", Severity.Warning);
                }
            }
コード例 #11
0
            protected void ParseHashes()
            {
                Data.fbs.Position = 0;
                TextReader tr = new StreamReader(Data.fbs, Data.encoding);

                for (int line = 1;; ++line)
                {
                    var lx = tr.ReadLine();
                    if (lx == null)
                    {
                        return;
                    }

                    lx = lx.TrimStart();
                    if (lx.Length == 0 || lx[0] == ';')
                    {
                        continue;
                    }

                    if (lx.Length < Data.HashedFiles.HashLength * 2 + 3)
                    {
                        IssueModel.Add($"Too short, line {line}.", Severity.Fatal);
                        return;
                    }

                    // Try typical format with hash first.
                    byte[] hash = null;
                    if (lx[Data.HashedFiles.HashLength * 2] == ' ')
                    {
                        var modeChar = lx[Data.HashedFiles.HashLength * 2 + 1];

                        for (int modeIx = 1; modeIx < modeChars.Length; ++modeIx)
                        {
                            if (modeChar == modeChars[modeIx])
                            {
                                hash = ConvertTo.FromHexStringToBytes(lx, 0, Data.HashedFiles.HashLength);
                                if (hash != null)
                                {
                                    var targetName = lx.Substring(Data.HashedFiles.HashLength * 2 + 2);
                                    HashedModel.Add(targetName, hash, (HashMode)modeIx);
                                    break;
                                }
                            }
                        }

                        if (hash != null)
                        {
                            continue;
                        }
                    }

                    // Fall back to layout with name followed by hash.
                    if (lx[lx.Length - Data.HashedFiles.HashLength * 2 - 1] == ' ')
                    {
                        hash = ConvertTo.FromHexStringToBytes(lx, lx.Length - Data.HashedFiles.HashLength * 2, Data.HashedFiles.HashLength);
                        if (hash != null)
                        {
                            var targetName = lx.Substring(0, lx.Length - Data.HashedFiles.HashLength * 2 - 1);
                            HashedModel.Add(targetName, hash, HashMode.Binary);
                            continue;
                        }
                    }

                    IssueModel.Add($"Badly formed, line {line}.", Severity.Fatal);
                }
            }
コード例 #12
0
ファイル: PngFormat.cs プロジェクト: kaosborn/Filebert
            public Model(Stream stream, string path)
            {
                ChunksModel = new PngChunk.Vector.Model();
                base._data  = Data = new PngFormat(this, stream, path);

                // Arbitrary sanity limit.
                if (Data.FileSize > 100000000)
                {
                    IssueModel.Add("File size insanely huge.", Severity.Fatal);
                    return;
                }

                Data.fBuf = new byte[(int)Data.FileSize];
                var fBuf = Data.fBuf;

                stream.Position = 0;
                int got = stream.Read(fBuf, 0, (int)Data.FileSize);

                if (got < Data.FileSize)
                {
                    IssueModel.Add("Read failed.", Severity.Fatal);
                    return;
                }

                Data.ValidSize = 8;
                do
                {
                    UInt32 chunkSize = ConvertTo.FromBig32ToUInt32(fBuf, (int)Data.ValidSize);
                    if (Data.ValidSize + chunkSize + 12 > Data.FileSize)
                    {
                        IssueModel.Add("File is corrupt or truncated.", Severity.Fatal);
                        return;
                    }

                    string type      = Encoding.ASCII.GetString(fBuf, (int)Data.ValidSize + 4, 4);
                    UInt32 storedCRC = ConvertTo.FromBig32ToUInt32(fBuf, (int)(Data.ValidSize + chunkSize + 8));
                    ChunksModel.Add(type, chunkSize, storedCRC);

                    var typeLow = type.ToLower();
                    switch (typeLow)
                    {
                    case "idat":
                        if (Data.mediaPosition <= 0)
                        {
                            Data.mediaPosition = Data.ValidSize;
                        }
                        break;

                    case "iend":
                        if (Data.MediaCount > 0)
                        {
                            IssueModel.Add("Multiple IEND chunks.");
                        }
                        else
                        {
                            Data.MediaCount = Data.ValidSize - Data.mediaPosition + chunkSize + 0xC;
                        }
                        break;

                    case "ihdr":
                        if (chunkSize < 13)
                        {
                            IssueModel.Add("IHDR chunk is short.");
                        }
                        else if (Data.Width != null)
                        {
                            IssueModel.Add("Multiple IHDR chunks.");
                        }
                        else
                        {
                            Data.Width             = ConvertTo.FromBig32ToInt32(fBuf, (int)Data.ValidSize + 8);
                            Data.Height            = ConvertTo.FromBig32ToInt32(fBuf, (int)Data.ValidSize + 12);
                            Data.BitDepth          = fBuf[(int)Data.ValidSize + 16];
                            Data.ColorType         = fBuf[(int)Data.ValidSize + 17];
                            Data.CompressionMethod = fBuf[(int)Data.ValidSize + 18];
                            Data.FilterMethod      = fBuf[(int)Data.ValidSize + 19];
                            Data.InterlaceMethod   = fBuf[(int)Data.ValidSize + 20];
                        }
                        break;

                    case "phys":
                        if (chunkSize < 9)
                        {
                            IssueModel.Add("PHYS chunk is short.");
                        }
                        else if (Data.DotsPerMeter1 != null)
                        {
                            IssueModel.Add("Multiple PHYS chunks.");
                        }
                        else
                        {
                            Data.DotsPerMeter1 = ConvertTo.FromBig32ToInt32(fBuf, (int)Data.ValidSize + 8);
                            Data.DotsPerMeter2 = ConvertTo.FromBig32ToInt32(fBuf, (int)Data.ValidSize + 12);
                            Data.Units         = fBuf[(int)Data.ValidSize + 9];
                        }
                        break;

                    case "text":
                        if (chunkSize > 0x7FFF)
                        {
                            IssueModel.Add("String size too large.");
                        }
                        else
                        {
                            var escaped = new StringBuilder();
                            for (int ix = (int)Data.ValidSize + 8; ix < (int)Data.ValidSize + 8 + chunkSize; ++ix)
                            {
                                if (fBuf[ix] < ' ' || fBuf[ix] > 0x7F)
                                {
                                    escaped.Append($"\\{fBuf[ix]:x2}");
                                }
                                else
                                {
                                    escaped.Append((char)fBuf[ix]);
                                }
                            }

                            Data.texts.Add(escaped.ToString());
                        }
                        break;

                    case "gama":
                        if (chunkSize < 4)
                        {
                            IssueModel.Add("GAMA chunk is short.");
                        }
                        else if (Data.Gamma != null)
                        {
                            IssueModel.Add("Multiple GAMA chunks.");
                        }
                        else
                        {
                            Data.Gamma = ConvertTo.FromBig32ToUInt32(fBuf, (int)Data.ValidSize + 8) / 100000f;
                        }
                        break;
                    }

                    Data.ValidSize += chunkSize + 0xC;
                }while (Data.ValidSize < Data.FileSize);

                if (Data.Width == null)
                {
                    IssueModel.Add("Missing IHDR chunk.");
                }

                if (Data.Chunks.Items[Data.Chunks.Items.Count - 1].Type != "IEND")
                {
                    IssueModel.Add("Missing IEND chunk.");
                }

                if (Data.Width <= 0 || Data.Height <= 0)
                {
                    IssueModel.Add("Invalid dimensions.");
                }

                if (Data.DotsPerMeter1 != Data.DotsPerMeter2)
                {
                    IssueModel.Add("Density aspect not 1.", Severity.Warning);
                }

                if (Data.BitDepth != 1 && Data.BitDepth != 2 && Data.BitDepth != 4 && Data.BitDepth != 8 && Data.BitDepth != 16)
                {
                    IssueModel.Add($"Invalid bit depth '{Data.BitDepth}'.");
                }

                if (Data.CompressionMethod != 0)
                {
                    IssueModel.Add($"Invalid compression '{Data.CompressionMethod}'.");
                }

                if (Data.FilterMethod != 0)
                {
                    IssueModel.Add($"Invalid filter '{Data.FilterMethod}'.");
                }

                if (Data.InterlaceMethod != 0 && Data.InterlaceMethod != 1)
                {
                    IssueModel.Add($"Invalid interlace '{Data.InterlaceMethod}'.");
                }
            }
コード例 #13
0
ファイル: WavFormat.cs プロジェクト: kaosborn/Filebert
            public Model(Stream stream, byte[] hdr, string path)
            {
                base._data = Data = new WavFormat(this, stream, path);

                ParseRiff(hdr);

                Data.ActualCRC32 = null;

                if (Data.Issues.HasFatal)
                {
                    return;
                }

                if (hdr.Length < 0x2C)
                {
                    IssueModel.Add("File truncated near header", Severity.Fatal);
                    return;
                }

                if (Data.IffChunkCount > 1)
                {
                    IssueModel.Add("Contains multiple RIFF chunks", Severity.Fatal);
                    return;
                }

                int hPos = 0x0C;

                if (hdr[hPos] != 'f' || hdr[hPos + 1] != 'm' || hdr[hPos + 2] != 't' || hdr[hPos + 3] != 0x20)
                {
                    IssueModel.Add("Missing 'fmt' section", Severity.Fatal);
                    return;
                }

                Data.CompCode      = hdr[hPos + 8] | hdr[hPos + 9] << 8;
                Data.ChannelCount  = hdr[hPos + 0x0A] | hdr[hPos + 0x0B] << 8;
                Data.SampleRate    = ConvertTo.FromLit32ToUInt32(hdr, hPos + 0x0C);
                Data.AverageBPS    = ConvertTo.FromLit32ToUInt32(hdr, hPos + 0x10);
                Data.BlockAlign    = hdr[hPos + 0x14] | hdr[hPos + 0x15] << 8;
                Data.BitsPerSample = hdr[hPos + 0x16] | hdr[hPos + 0x17] << 8;

                if ((hdr[hPos] & 0x80) != 0)
                {
                    IssueModel.Add("Header size insanely huge", Severity.Fatal);
                    return;
                }

                long hdrDataSize = ConvertTo.FromLit32ToInt32(hdr, hPos + 4);
                long dataPos     = hPos + 8 + hdrDataSize;

                stream.Position = dataPos;
                var dHdr = new byte[8];

                if (stream.Read(dHdr, 0, 8) != 8)
                {
                    IssueModel.Add("Read failed", Severity.Fatal);
                    return;
                }

                if (dHdr[0] != 'd' || dHdr[1] != 'a' || dHdr[2] != 't' || dHdr[3] != 'a')
                {
                    IssueModel.Add("Missing 'data' section", Severity.Fatal);
                    return;
                }

                Data.mediaPosition = dataPos + 8;
                Data.MediaCount    = ConvertTo.FromLit32ToUInt32(dHdr, 4);
                if (Data.mediaPosition + Data.MediaCount > Data.IffSize)
                {
                    IssueModel.Add("Invalid data size", Severity.Fatal);
                    return;
                }

                Data.HasTags = Data.mediaPosition + Data.MediaCount < Data.IffSize;
                GetDiagnostics();
            }
コード例 #14
0
ファイル: JpegFormat.cs プロジェクト: kaosborn/Filebert
            public Model(Stream stream, string path)
            {
                base._data = Data = new JpegFormat(this, stream, path);

                // Arbitrary choice of 50MB cutoff.
                if (Data.FileSize > 50000000)
                {
                    IssueModel.Add("File insanely huge", Severity.Fatal);
                    return;
                }

                byte[] buf = new byte[(int)Data.FileSize];

                stream.Position = 0;
                int got = stream.Read(buf, 0, (int)Data.FileSize);

                if (got != Data.FileSize)
                {
                    IssueModel.Add("Read error", Severity.Fatal);
                    return;
                }

                for (int len = 0, pos = 2; ; pos = pos + len + 2)
                {
                    if (pos > Data.FileSize - 4)
                    {
                        IssueModel.Add("File truncated", Severity.Fatal);
                        return;
                    }

                    if (buf[pos] != 0xFF)
                    {
                        IssueModel.Add("Missing marker.", Severity.Fatal);
                        return;
                    }

                    if (buf[pos + 1] == 0xD9)
                    {
                        IssueModel.Add("Unexpected EOI marker", Severity.Fatal);
                        return;
                    }

                    // Detect SOS (Start of Stream) marker.
                    if (buf[pos + 1] == 0xDA)
                    {
                        Data.sosPos = pos;
                        break;
                    }

                    len = (buf[pos + 2] << 8) + buf[pos + 3];
                    if (len < 2)
                    {
                        IssueModel.Add($"Invalid segment length '{len}'.", Severity.Fatal);
                        return;
                    }

                    if (pos + len + 2 >= Data.FileSize)
                    {
                        IssueModel.Add("File truncated.", Severity.Fatal);
                        return;
                    }

                    if (len == 2)
                    {
                        IssueModel.Add("Contains empty segment.", Severity.Trivia);
                    }

                    ++Data.SegmentCount;

                    // Detect application marker:
                    if (buf[pos + 1] >= 0xE0 && buf[pos + 1] <= 0xEF)
                    {
                        string appName = ConvertTo.FromAsciizToString(buf, pos + 4);
                        Data.appNames.Add(appName);

                        if (buf[pos + 1] == 0xE0)
                        {
                            if (buf[pos + 4] != 'J' || buf[pos + 5] != 'F' || buf[pos + 6] != 'I' || buf[pos + 7] != 'F' || buf[pos + 8] != 0)
                            {
                                continue;
                            }

                            if ((Data.Apps & JpegApps.Jfif) != 0)
                            {
                                IssueModel.Add("Contains ghost JFIF segment.", Severity.Advisory);
                                continue;
                            }

                            if (len < 13)
                            {
                                IssueModel.Add("JFIF segment too small.", Severity.Fatal);
                                return;
                            }

                            Data.Apps        |= JpegApps.Jfif;
                            Data.VersionMajor = buf[pos + 0x09];
                            Data.VersionMinor = buf[pos + 0x0A];
                            Data.Units        = (JpegUnits)buf[pos + 0x0B];
                            Data.DensityX     = (buf[pos + 0x0C] << 8) + buf[pos + 0x0D];
                            Data.DensityY     = (buf[pos + 0x0E] << 8) + buf[pos + 0x0F];
                            Data.ThumbXLen    = buf[pos + 0x10];
                            Data.ThumbYLen    = buf[pos + 0x11];
                        }
                        else if (buf[pos + 1] == 0xE1)
                        {
                            if (buf[pos + 4] == 'E' && buf[pos + 5] == 'x' && buf[pos + 6] == 'i' && buf[pos + 7] == 'f' && buf[pos + 8] == 0)
                            {
                                if ((Data.Apps & JpegApps.Exif) != 0)
                                {
                                    IssueModel.Add("Contains ghost Exif segment", Severity.Trivia);
                                    continue;
                                }

                                Data.Apps |= JpegApps.Exif;
                                // Additional Exif parsing goes here...
                            }
                        }
                    }
                }

                // Detect EOI (end of image) marker: FFD9
                for (int pos = (int)Data.sosPos; pos < Data.FileSize - 1; ++pos)
                {
                    if (buf[pos] == 0xFF && buf[pos + 1] == 0xD9)
                    {
                        Data.ValidSize = pos + 2;
                        Data.eoiPos    = pos;
                        break;
                    }
                }

                CalcMark();
                GetDiagnostics();
            }
コード例 #15
0
ファイル: LogEacFormat.cs プロジェクト: kaosborn/Filebert
            string Parse(LogBuffer parser)
            {
                string lx = null;

                ParseHeader();

                if (!Data.IsRangeRip)
                {
                    lx = ParseTracks();
                    if (Data.Issues.HasFatal)
                    {
                        return(null);
                    }
                }

                while (!parser.EOF && !lx.Contains("errors") && !lx.StartsWith("==== "))
                {
                    lx = parser.ReadLineLTrim();
                }

                if (lx == "No errors occured" || lx == "No errors occurred")
                {
                    lx = parser.ReadLineLTrim();
                }
                else if (lx == "There were errors")
                {
                    if (Data.Issues.MaxSeverity < Severity.Error)
                    {
                        IssueModel.Add("There were errors.");
                    }
                    lx = parser.ReadLineLTrim();
                }
                else
                {
                    IssueModel.Add("Missing 'errors' line.");
                }

                if (Data.IsRangeRip)
                {
                    if (lx == "AccurateRip summary")
                    {
                        for (;;)
                        {
                            lx = parser.ReadLineLTrim();
                            if (parser.EOF || !lx.StartsWith("Track "))
                            {
                                break;
                            }

                            if (lx.Contains("ccurately ripped (confidence "))
                            {
                                int arVersion    = lx.Contains("AR v2") ? 2 : 1;
                                int advanced     = ConvertTo.FromStringToInt32(lx, 40, out int val);
                                int arConfidence = advanced > 0 && val > 0 ? val : -1;
                                lx = parser.ReadLineLTrim();

                                if (Data.AccurateRipConfidence == null || Data.AccurateRipConfidence.Value > arConfidence)
                                {
                                    Data.AccurateRipConfidence = arConfidence;
                                }
                                if (Data.AccurateRip == null || Data.AccurateRip.Value > arVersion)
                                {
                                    Data.AccurateRip = arVersion;
                                }
                            }
                        }
                    }
                }

                if (lx == "All tracks accurately ripped")
                {
                    lx = parser.ReadLineLTrim();
                }

                if (lx.StartsWith("End of status report"))
                {
                    lx = parser.ReadLineLTrim();
                }

                if (lx.StartsWith("---- CUETools"))
                {
                    lx = ParseCueTools();
                }

                while (!parser.EOF && !lx.StartsWith("==== "))
                {
                    lx = parser.ReadLine();
                }

                if (lx.StartsWith("==== Log checksum ") && lx.Length >= 82)
                {
                    Data.storedHash = ConvertTo.FromHexStringToBytes(lx, 18, 32);

                    lx = parser.ReadLine();
                    if (!parser.EOF || !String.IsNullOrEmpty(lx))
                    {
                        IssueModel.Add("Unexpected content at end of file.", Severity.Warning, IssueTags.StrictErr);
                    }
                }

                return(lx);

                string ParseHeader()
                {
                    lx = parser.ReadLine();
                    if (lx.StartsWith("Exact Audio Copy V"))
                    {
                        var spacePos = lx.IndexOf(' ', 18);
                        if (spacePos > 0)
                        {
                            Data.EacVersionString = lx.Substring(18, spacePos - 18);
                        }

                        lx = parser.ReadLine();
                        lx = parser.ReadLine();
                    }

                    if (!lx.StartsWith("EAC extraction logfile from "))
                    {
                        IssueModel.Add("Unexpected contents, " + parser.GetPlace() + ": Expecting 'EAC extraction logfile'.");
                        return(lx);
                    }

                    lx = lx.Substring(28);
                    if (lx.Length < 12)
                    {
                        IssueModel.Add("Missing rip date, " + parser.GetPlace() + ".");
                        return(lx);
                    }
                    Data.RipDate = lx.EndsWith(" for CD") ? lx.Substring(0, lx.Length - 7) : lx;

                    lx = parser.ReadLine();
                    if (String.IsNullOrWhiteSpace(lx))
                    {
                        lx = parser.ReadLine();
                    }

                    int slashPos = lx.IndexOf('/');

                    if (slashPos < 0)
                    {
                        IssueModel.Add("Missing '<artist> / <album>', " + parser.GetPlace() + ".");
                        return(lx);
                    }
                    Data.RipArtist = lx.Substring(0, slashPos).Trim();
                    Data.RipAlbum  = lx.Substring(slashPos + 1).Trim();

                    for (;;)
                    {
TOP:
                        if (parser.EOF)
                        {
                            lx = String.Empty;
                            return(lx);
                        }

                        lx = parser.ReadLine();
                        if (lx.StartsWith("Track ") || lx.StartsWith("===="))
                        {
                            return(lx);
                        }

                        if (lx == "Range status and errors")
                        {
                            Data.IsRangeRip = true;
                            lx = parser.ReadLine();
                            return(lx);
                        }

                        if (lx == "TOC of the extracted CD")
                        {
                            for (Data.TocTrackCount = 0;;)
                            {
                                lx = parser.ReadLine();
                                if (parser.EOF || lx.StartsWith("==== ") || (lx.StartsWith("Track") && !lx.Contains("|")))
                                {
                                    return(lx);
                                }

                                if (lx == "Range status and errors")
                                {
                                    Data.IsRangeRip = true;
                                    lx = parser.ReadLine();
                                    return(lx);
                                }

                                if (lx.Length >= 60)
                                {
                                    if (int.TryParse(lx.Substring(0, 9), out int tn))
                                    {
                                        ++Data.TocTrackCount;
                                    }
                                }
                            }
                        }

                        int ik0 = 0, ik1 = 0, ii = 0, iv0 = 0, iv1;
                        for (;; ++ik0)
                        {
                            if (ik0 >= lx.Length)
                            {
                                goto TOP;
                            }
                            else if (lx[ik0] != ' ')
                            {
                                for (ii = ik0, ik1 = ii;;)
                                {
                                    ++ii;
                                    if (ii >= lx.Length)
                                    {
                                        string kk = lx.Substring(ik0, ik1 - ik0 + 1);
                                        if (kk == "Installed external ASPI interface" || kk == "Native Win32 interface for Win NT & 2000")
                                        {
                                            Data.Interface = kk;
                                        }
                                        goto TOP;
                                    }
                                    if (lx[ii] == ':')
                                    {
                                        break;
                                    }
                                    if (lx[ii] != ' ')
                                    {
                                        ik1 = ii;
                                    }
                                }

                                for (iv0 = ii + 1;; ++iv0)
                                {
                                    if (iv0 >= lx.Length)
                                    {
                                        goto TOP;
                                    }
                                    else if (lx[iv0] != ' ')
                                    {
                                        for (ii = iv0 + 1, iv1 = iv0;; ++ii)
                                        {
                                            if (ii == lx.Length)
                                            {
                                                break;
                                            }
                                            else if (lx[ii] != ' ')
                                            {
                                                iv1 = ii;
                                            }
                                        }

                                        string optKey = lx.Substring(ik0, ik1 - ik0 + 1),
                                               optVal = lx.Substring(iv0, iv1 - iv0 + 1);
                                        if (optKey == "Used drive")
                                        {
                                            Data.Drive = optVal;
                                        }
                                        else if (optKey == "Utilize accurate stream")
                                        {
                                            Data.AccurateStream = optVal;
                                        }
                                        else if (optKey == "Defeat audio cache")
                                        {
                                            Data.DefeatCache = optVal;
                                        }
                                        else if (optKey == "Make use of C2 pointers")
                                        {
                                            Data.UseC2 = optVal;
                                        }
                                        else if (optKey == "Read mode")
                                        {
                                            Data.ReadMode = optVal;
                                        }
                                        else if (optKey == "Read offset correction" || optKey == "Combined read/write offset correction")
                                        {
                                            Data.ReadOffset = optVal;
                                        }
                                        else if (optKey == "Overread into Lead-In and Lead-Out")
                                        {
                                            Data.Overread = optVal;
                                        }
                                        else if (optKey == "Fill up missing offset samples with silence")
                                        {
                                            Data.FillWithSilence = optVal;
                                        }
                                        else if (optKey == "Delete leading and trailing silent blocks")
                                        {
                                            Data.TrimSilence = optVal;
                                        }
                                        else if (optKey == "Null samples used in CRC calculations")
                                        {
                                            Data.CalcWithNulls = optVal;
                                        }
                                        else if (optKey == "Normalize to")
                                        {
                                            Data.NormalizeTo = optVal;
                                        }
                                        else if (optKey == "Used interface")
                                        {
                                            Data.Interface = optVal;
                                        }
                                        else if (optKey == "Gap handling")
                                        {
                                            Data.GapHandling = optVal;
                                        }
                                        else if (optKey == "Sample format")
                                        {
                                            Data.SampleFormat = optVal;
                                        }
                                        else if (optKey == "Quality")
                                        {
                                            Data.Quality = optVal;
                                        }
                                        else if (optKey == "Add ID3 tag")
                                        {
                                            Data.Id3Tag = optVal;
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }

                string ParseTracks()
                {
                    for (;;)
                    {
                        if (parser.EOF ||
                            lx == "No errors occured" ||
                            lx == "No errors occurred" ||
                            lx == "There were errors" ||
                            lx.Contains("accurate"))
                        {
                            break;
                        }

                        if (!lx.StartsWith("Track") || lx.StartsWith("Track quality"))
                        {
                            lx = parser.ReadLineLTrim();
                            continue;
                        }

                        bool success = Int32.TryParse(lx.Substring(6), out int num);
                        if (!success)
                        {
                            Data.TkIssue = IssueModel.Add("Invalid track " + parser.GetPlace(), Severity.Fatal, IssueTags.Failure);
                            break;
                        }

                        lx = parser.ReadLineLTrim();
                        if (!lx.StartsWith("Filename "))
                        {
                            Data.TkIssue = IssueModel.Add($"Track {num}: Missing 'Filename'.", Severity.Error, IssueTags.Failure);
                        }
                        else
                        {
                            lx = ParseTrack(num);
                        }
                    }
                    return(lx);
                }

                string ParseTrack(int num)
                {
                    string name = "", peak = "", speed = "", pregap = "", qual = "";
                    int?   arVersion = null;
                    int?   arConfidence = null;
                    uint?  testCrc = null, copyCrc = null;
                    bool   trackErr = false;
                    uint   word;

                    name = lx.Substring(9);
                    if (name.Length < 10 || (lx.Length < 252 && !lx.EndsWith(".wav")))
                    {
                        IssueModel.Add("Unexpected extension " + parser.GetPlace());
                    }

                    lx = parser.ReadLineLTrim();
                    if (lx.StartsWith("Pre-gap length  "))
                    {
                        pregap = lx.Substring(16); lx = parser.ReadLineLTrim();
                    }

                    for (;;)
                    {
                        if (parser.EOF)
                        {
                            return(lx);
                        }

                        if (lx.StartsWith("Track quality") || lx.StartsWith("Peak level "))
                        {
                            break;
                        }

                        if (lx.StartsWith("Track "))
                        {
                            return(lx);  // Unexpected start of next track.
                        }
                        if (!trackErr)
                        {
                            trackErr = true;
                            IssueModel.Add($"Track {num}: '{lx}'.");
                        }
                        lx = parser.ReadLineLTrim();
                    }

                    if (lx.StartsWith("Peak level "))
                    {
                        peak = lx.Substring(11); lx = parser.ReadLineLTrim();
                    }

                    if (lx.StartsWith("Extraction speed "))
                    {
                        speed = lx.Substring(17); lx = parser.ReadLineLTrim();
                    }

                    if (lx.StartsWith("Track quality "))
                    {
                        qual = lx.Substring(14); lx = parser.ReadLineLTrim();
                    }

                    if (lx.StartsWith("Test CRC "))
                    {
                        if (uint.TryParse(lx.Substring(9), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out word))
                        {
                            testCrc = word;
                        }
                        else
                        {
                            Data.TpIssue = IssueModel.Add($"Track {num}: Invalid test CRC-32.", Severity.Error, IssueTags.Failure);
                        }
                        lx = parser.ReadLineLTrim();
                    }

                    if (!lx.StartsWith("Copy CRC "))
                    {
                        IssueModel.Add($"Track {num}: Missing copy CRC-32.", Severity.Warning);
                    }
                    else
                    {
                        if (uint.TryParse(lx.Substring(9), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out word))
                        {
                            copyCrc = word;
                        }
                        else
                        {
                            Data.TkIssue = IssueModel.Add($"Track {num}: Invalid copy CRC-32.", Severity.Error, IssueTags.Failure);
                        }
                        lx = parser.ReadLineLTrim();
                    }

                    if (lx.StartsWith("Accurately ripped (confidence "))
                    {
                        arVersion = lx.Contains("AR v2") ? 2 : 1;
                        int advanced = ConvertTo.FromStringToInt32(lx, 30, out int val);
                        arConfidence = advanced > 0 && val > 0 ? val : -1;
                        lx           = parser.ReadLineLTrim();
                    }
                    else if (lx.StartsWith("Cannot be verified"))
                    {
                        arVersion    = lx.Contains("AR v2") ? 2 : 1;
                        arConfidence = -1;
                        lx           = parser.ReadLineLTrim();
                    }
                    else if (lx.StartsWith("Track not present"))
                    {
                        if (peak != "0.0 %")
                        {
                            arConfidence = 0;
                        }
                        lx = parser.ReadLineLTrim();
                    }

                    if (arConfidence != null)
                    {
                        if (Data.AccurateRipConfidence == null || Data.AccurateRipConfidence > arConfidence)
                        {
                            Data.AccurateRipConfidence = arConfidence;
                        }
                        if (arVersion != null)
                        {
                            if (Data.AccurateRip == null || Data.AccurateRip.Value > arVersion.Value)
                            {
                                Data.AccurateRip = arVersion;
                            }
                        }
                    }

                    bool hasOK = false;

                    if (lx == "Copy OK")
                    {
                        hasOK = true;
                        lx    = parser.ReadLineLTrim();
                    }

                    TracksModel.Add(num, name, pregap, peak, speed, qual, testCrc, copyCrc, hasOK, arVersion, arConfidence);
                    return(lx);
                }

                string ParseCueTools()
                {
                    do
                    {
                        lx = parser.ReadLineLTrim();
                        if (parser.EOF || lx.StartsWith("==== "))
                        {
                            return(lx);
                        }
                    } while (!lx.StartsWith("[CTDB"));

                    if (lx.Contains("not present"))
                    {
                        Data.CueToolsConfidence = 0;
                        lx = parser.ReadLineLTrim();
                        if (!parser.EOF && lx.StartsWith("Submit"))
                        {
                            lx = parser.ReadLineLTrim();
                        }
                        return(lx);
                    }

                    if (!lx.Contains(" found"))
                    {
                        return(lx);
                    }

                    do
                    {
                        lx = parser.ReadLineLTrim();
                        if (parser.EOF || lx.StartsWith("==== "))
                        {
                            return(lx);
                        }

                        if (lx.StartsWith("["))
                        {
                            int ctConfidence = -1;
                            if (lx.Contains("Accurately ripped"))
                            {
                                int advanced = ConvertTo.FromStringToInt32(lx, 12, out ctConfidence);
                            }
                            Data.CueToolsConfidence = ctConfidence;
                            lx = parser.ReadLineLTrim();
                            return(lx);
                        }
                    } while (!lx.StartsWith("Track"));

                    for (int tx = 0; ; ++tx)
                    {
                        lx = parser.ReadLine();
                        if (parser.EOF || lx.StartsWith("==== ") || lx.StartsWith("All tracks"))
                        {
                            return(lx);
                        }

                        int ctConfidence;
                        if (lx.Contains("Accurately ripped"))
                        {
                            int advanced = ConvertTo.FromStringToInt32(lx, 9, out ctConfidence);
                            if (advanced == 0)
                            {
                                Data.CueToolsConfidence = -1; return(lx);
                            }
                        }
                        else if (lx.Contains("Differs"))
                        {
                            ctConfidence = -1;
                        }
                        else
                        {
                            break;
                        }

                        if (tx < TracksModel.Data.Items.Count)
                        {
                            TracksModel.SetCtConfidence(tx, ctConfidence);
                        }
                        if (Data.CueToolsConfidence == null || Data.CueToolsConfidence > ctConfidence)
                        {
                            Data.CueToolsConfidence = ctConfidence;
                        }
                    }
                    return(parser.ReadLineLTrim());
                }
            }