private void parseCompressedFragmentFiles(string rootFolder, FileInfo fi, MemoryStream ms)
        {
            try
            {
                List<FileInfo> compFr2 = null;
                int fnameLen;
                long fsize;
                string fname;

                while (ms.Position < ms.Length)
                {
                    if (previousMs != null) // recovery mode
                    {
                        byte[] buff = new byte[24];
                        int a = previousMs.Read(buff, 0, buff.Length);
                        if (a < buff.Length) ms.Read(buff, a, buff.Length - a);

                        fnameLen = BitConverter.ToInt32(buff, 0);

                        if (fnameLen > 2048) // out of memory prevention
                        {
                            throw new ApplicationException(string.Format("Invalid filename length: {0}", fnameLen));
                        }

                        fsize = BitConverter.ToUInt32(buff, 4);

                        buff = new byte[fnameLen * 2];
                        a = previousMs.Read(buff, 0, buff.Length);
                        if (a < buff.Length) ms.Read(buff, a, buff.Length - a);

                        fname = System.Text.Encoding.Unicode.GetString(buff);
                        previousMs.Dispose();
                        previousMs = null; // leave recovery mode
                        addLine("leaving recovery mode with filename: " + fname);
                    }
                    else
                    {

                        if ((ms.Position + 24) > ms.Length) // 24 = sizeof(fnameLen + fsize) + 16
                        {
                            previousMs = ms;
                            addLine("file header behind end of fragment - entering recovery mode");
                            return;
                        }

                        fnameLen = StreamUtils.ReadUInt32asInt(ms);

                        if (fnameLen > 2048) // out of memory prevention
                        {
                            throw new ApplicationException(string.Format("Invalid filename length: {0}", fnameLen));
                        }

                        fsize = StreamUtils.ReadUInt32(ms);
                        ms.Seek(16, SeekOrigin.Current); // 8 bytes?? + 8bytes date and time??

                        if ((ms.Position + 2 * fnameLen) > ms.Length)
                        {
                            ms.Seek(-24, SeekOrigin.Current);
                            previousMs = ms; // enter recovery mode
                            addLine("file name behind end of fragment - entering recovery mode");
                            return;
                        }

                        fname = StreamUtils.ReadString(ms, fnameLen);
                    }

                    addLine(fname);

                    string dir = Path.GetDirectoryName(fname);
                    bool canBeMessage = ExpectMessage(dir);

                    compFr2 = findOrCreateFileInfoList(rootFolder + "\\" + dir);
                    fname = Path.GetFileName(fname).Trim();
                    if (fname.Length > 0)
                    {
                        if (ms.Length >= ms.Position + fsize)
                        {
                            FileInfoCfPart ficp = new FileInfoCfPart(fname, fi.Start, fi.RawSize, DateTime.MinValue, ms.Position, fsize);
                            if (canBeMessage) parseSymbianMessage(ficp);
                            compFr2.Add(ficp);
                            StreamUtils.Counter += fsize;
                        }
                        else
                        {
                            currentIncompleteMultipartFile = new FileInfoCfMultiPart(fname, DateTime.MinValue, fsize, compFr2, canBeMessage);
                            currentIncompleteMultipartFile.Parts.Add(new FileInfoCfPart("", fi.Start, fi.RawSize, DateTime.MinValue, ms.Position, ms.Length - ms.Position));
                            StreamUtils.Counter += (ms.Length - ms.Position);
                            addLine("incomplete file, continue on next fragment...");
                        }
                    }

                    ms.Seek(fsize, SeekOrigin.Current);
                }
            }
            catch (Exception exc)
            {
                previousMs = null; // reset recovery
                addLine(string.Format("Parsing ERROR at position {0}: {1}", ms.Position.ToString("X"), exc.Message));
            }
        }
        private void parseCompressedFragment(string rootFolder, FileinfoCf fi, long lenUncomp)
        {
            long initCounter = StreamUtils.Counter;

            MemoryStream ms = new MemoryStream();
            try
            {
                fi.CopyToStream(currentFileName, ms);
                if (ms.Length != lenUncomp)
                {
                    addLine(string.Format("WARNING: uncompressed size ({0}) differs from expected ({1})", ms.Length, lenUncomp));
                }
            }
            catch (Exception exc)
            {
                previousMs = null; // reset recovery mode
                addLine("ERROR decompressing fragment: " + exc.Message);
                if (currentIncompleteMultipartFile != null)
                {
                    if (currentIncompleteMultipartFile.MissingLength >= lenUncomp)
                    {
                        addLine(string.Format("Incomplete multipart file '{0}' shortened", currentIncompleteMultipartFile.Filename));
                        currentIncompleteMultipartFile.Shorten(lenUncomp);
                        return;
                    }
                    else
                    {
                        addLine(string.Format("Incomplete multipart file '{0}' corrupted and lost", currentIncompleteMultipartFile.Filename));
                        currentIncompleteMultipartFile = null;
                        return;
                    }
                }
            }

            ms.Seek(0, SeekOrigin.Begin);

            if (previousMs != null)
            {
                // continue recovery mode started by previous fragment
                parseCompressedFragmentFiles(rootFolder, fi, ms);
            }
            else if (currentHeaderLengthToSkip > 0)
            {
                // continnue skipping useless header
                if (currentHeaderLengthToSkip > ms.Length)
                {
                    currentHeaderLengthToSkip -= ms.Length;
                    addLine("still incomplete header, continue on next fragment...");
                }
                else
                {
                    ms.Seek(currentHeaderLengthToSkip, SeekOrigin.Begin);
                    currentHeaderLengthToSkip = 0;
                    addLine("end of header reached, looking for files in rest of fragment...");
                    parseCompressedFragmentFiles(rootFolder, fi, ms);
                }
            }
            else if (currentIncompleteMultipartFile != null)
            {
                // continue collecting parts of multifragment file
                long missingLength = currentIncompleteMultipartFile.MissingLength;
                if (missingLength > ms.Length)
                {
                    currentIncompleteMultipartFile.Parts.Add(new FileInfoCfPart("", fi.Start, fi.RawSize, fi.FileTime, 0, ms.Length));
                    StreamUtils.Counter += ms.Length;
                    addLine("still incomplete file, continue on next fragment...");
                }
                else
                {
                    if (currentIncompleteMultipartFile.Shortened)
                    {
                        addLine(string.Format("multipart file '{0}' complete, but corrupted (shortened) - skipping", currentIncompleteMultipartFile.Filename));
                    }
                    else
                    {
                        addLine(string.Format("multipart file '{0}' complete.", currentIncompleteMultipartFile.Filename));
                        currentIncompleteMultipartFile.Parts.Add(new FileInfoCfPart("", fi.Start, fi.RawSize, fi.FileTime, 0, missingLength));
                        if (currentIncompleteMultipartFile.CanBeMessage)
                        {
                            parseSymbianMessage(currentIncompleteMultipartFile);
                        }
                        currentIncompleteMultipartFile.Finish();
                    }
                    currentIncompleteMultipartFile = null;

                    StreamUtils.Counter += missingLength;
                    ms.Seek(missingLength, SeekOrigin.Begin);

                    if (ms.Length > missingLength)
                    {
                        addLine("looking for next files in rest of fragment...");
                        parseCompressedFragmentFiles(rootFolder, fi, ms);
                    }
                }
            }
            else
            {
                if (ms.Length <= 16)
                {
                    addLine("empty fragment\r\n");
                    return;
                }

                // detection of fragment type
                ms.Seek(0, SeekOrigin.Begin);
                UInt32 test1 = StreamUtils.ReadUInt32(ms);
                UInt32 test2 = StreamUtils.ReadUInt32(ms);
                UInt32 test3 = StreamUtils.ReadUInt32(ms);
                UInt32 test4 = StreamUtils.ReadUInt32(ms);

                if (test3 == 0x20 || test3 == 0 || (test3 == 1 && test4 == 8))
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    parseCompressedFragmentFiles(rootFolder, fi, ms);
                }
                else if (test3 == 1 || test3 == 2)
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    UInt32 headerLength = StreamUtils.ReadUInt32(ms) + 4;
                    if (headerLength > ms.Length)
                    {
                        currentHeaderLengthToSkip = headerLength - ms.Length;
                        addLine("header fragment start, continue on next fragment...");
                    }
                    else
                    {
                        ms.Seek(headerLength, SeekOrigin.Begin);
                        parseCompressedFragmentFiles(rootFolder, fi, ms);
                    }
                }
                else if (test1 == 1 && test2 == 1 && test4 == 0x10202be9)
                {
                    addLine("phone settings fragment type\r\n");
                    return;
                }
                else if (test1 == 0 && (test2 & 0xFFF00F) == 4) // test2 == 0x0224 || 0x24 || 0x0464 || 0x0444 || 0x0434
                {
                    addLine("ignored fragment type\r\n");
                    return;
                }
                else
                {
                    addLine(string.Format("{0} - unknown fragment type\r\n", fi.Filename));
                }
            }

            long uncompressedCoverage = StreamUtils.Counter - initCounter;

            if (uncompressedCoverage > 0)
            {
                double ratio = (double)uncompressedCoverage / ms.Length;
                StreamUtils.Counter = initCounter + (long)(fi.RawSize * ratio);
                addLine(string.Format("fragment coverage {0:0.##}%", 100 * ratio));
            }

            addLine("");
        }