Example #1
0
        internal IEnumerable <RarFilePart> GetVolumeFileParts()
        {
            MarkHeader lastMarkHeader = null;

            foreach (var header in _headerFactory.ReadHeaders(Stream))
            {
                switch (header.HeaderType)
                {
                case HeaderType.Mark:
                {
                    lastMarkHeader = header as MarkHeader;
                }
                break;

                case HeaderType.Archive:
                {
                    ArchiveHeader = header as ArchiveHeader;
                }
                break;

                case HeaderType.File:
                {
                    var fh = header as FileHeader;
                    yield return(CreateFilePart(lastMarkHeader, fh));
                }
                break;
                }
            }
        }
        internal static IEnumerable <RarArchiveVolume> GetParts(FileInfo fileInfo, RarOptions options)
        {
            var part = new FileInfoRarArchiveVolume(fileInfo, options);

            yield return(part);

            if (!part.ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME))
            {
                yield break; //if file isn't volume then there is no reason to look
            }
            ArchiveHeader ah = part.ArchiveHeader;

            fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart);
            //we use fileinfo because rar is dumb and looks at file names rather than archive info for another volume
            while (fileInfo != null)
            {
                part     = new FileInfoRarArchiveVolume(fileInfo, options);
                fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart);
                if (!fileInfo.Exists)
                {
                    yield break;
                }
                yield return(part);
            }
        }
Example #3
0
 public static bool IsArchiveFile(Stream stream)
 {
     using (EndianReader reader = new EndianReader(stream, EndianType.BigEndian))
     {
         return(ArchiveHeader.IsArchiveHeader(reader));
     }
 }
Example #4
0
 public static ArchiveHeader Create(FileSplitInfo fileSplitInfo)
 {
     var header = new ArchiveHeader(fileSplitInfo.ChunkCount);
     for (int id = 0; id < fileSplitInfo.ChunkCount; id++)
     {
         header.ChunkLengths[id] = fileSplitInfo.Chunks[id].Length;
     }
     return header;
 }
Example #5
0
 public static bool IsArchiveFile(Stream stream, long offset, long size)
 {
     using (PartialStream bundleStream = new PartialStream(stream, offset, size))
     {
         using (EndianReader reader = new EndianReader(bundleStream, EndianType.BigEndian))
         {
             return(ArchiveHeader.IsArchiveHeader(reader));
         }
     }
 }
Example #6
0
 public NARC()
 {
     Header = new ArchiveHeader();
     FATB   = new FileAllocationTableBlock();
     FNTB   = new FilenameTableBlock();
     FIMG   = new FileImageBlock();
     FromFileSystem(new SFSDirectory("/", true)
     {
         DirectoryID = 0xF000
     });
 }
Example #7
0
        public Archive(Stream stream)
        {
            byte[] header = new byte[4];
            stream.Read(header, 0, 4);
            stream.Seek(-4, SeekOrigin.Current);

            if (header[0] == 'Y')
            {
                archiveFileStream = new Yaz0Stream(stream);
            }
            else
            {
                archiveFileStream = stream;
            }

            using (EndianBinaryReader er = new EndianBinaryReader(archiveFileStream, Encoding.ASCII, true, Endian.Big))
            {
                archiveHeader = new ArchiveHeader(er);
                sfatHeader    = new SFATHeader(er);

                sfatEntries = new SFATEntry[sfatHeader.NodeCount];
                for (int i = 0; i < sfatHeader.NodeCount; i++)
                {
                    sfatEntries[i] = new SFATEntry(er);
                }

                er.Skip(8); // SFNT, 0x00, 0x08, 0x00, 0x00

                sfatStringTable = new string[sfatHeader.NodeCount];
                for (int i = 0; i < sfatHeader.NodeCount; i++)
                {
                    // These strings are aligned to 4 bytes.
                    //while (er.PeekReadByte() == 0)
                    //    er.ReadByte();

                    sfatStringTable[i] = Encoding.ASCII.GetString(er.ReadBytesUntil(0));
                }
            }

            for (int i = 0; i < sfatEntries.Length; i++)
            {
                sfatEntries[i].FileName = sfatStringTable[sfatEntries[i].FileNameTableOffset / 4];
            }

            fileDictionary = new Dictionary <string, ArchiveEntry>(sfatEntries.Length);

            for (int i = 0; i < sfatEntries.Length; i++)
            {
                string name = sfatStringTable[sfatEntries[i].FileNameTableOffset / 4];
                uint   size = sfatEntries[i].DataOffsetEnd - sfatEntries[i].DataOffsetStart;

                fileDictionary.Add(name, new ArchiveEntry(name, sfatEntries[i].DataOffsetStart + archiveHeader.DataOffset, size, sfatEntries[i].FileType));
            }
        }
        private static FileInfo GetNextFileInfo(ArchiveHeader ah, FileInfoRarFilePart currentFilePart)
        {
            if (currentFilePart == null)
            {
                return(null);
            }
            bool oldNumbering = !ah.ArchiveHeaderFlags.HasFlag(ArchiveFlags.NEWNUMBERING) ||
                                currentFilePart.MarkHeader.OldFormat;

            if (oldNumbering)
            {
                return(FindNextFileWithOldNumbering(currentFilePart.FileInfo));
            }
            else
            {
                return(FindNextFileWithNewNumbering(currentFilePart.FileInfo));
            }
        }
        private static FileInfo GetNextFileInfo(ArchiveHeader ah, FileInfoRarFilePart currentFilePart)
        {
            if (currentFilePart == null)
            {
                return(null);
            }
            bool oldNumbering = ah.OldNumberingFormat ||
                                currentFilePart.MarkHeader.OldNumberingFormat;

            if (oldNumbering)
            {
                return(FindNextFileWithOldNumbering(currentFilePart.FileInfo));
            }
            else
            {
                return(FindNextFileWithNewNumbering(currentFilePart.FileInfo));
            }
        }
Example #10
0
        public NARC(byte[] Data)
        {
            EndianBinaryReader er = new EndianBinaryReader(new MemoryStream(Data), Endianness.LittleEndian);

            try
            {
                Header = new ArchiveHeader(er);
                FATB   = new FileAllocationTableBlock(er);
                FNTB   = new FilenameTableBlock(er);
                FIMG   = new FileImageBlock(er);
            }
            catch (SignatureNotCorrectException e)
            {
                MessageBox.Show(e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                er.Close();
            }
        }
        internal static IEnumerable <RarVolume> GetParts(FileInfo fileInfo, ReaderOptions options)
        {
            FileInfoRarArchiveVolume part = new FileInfoRarArchiveVolume(fileInfo, options);

            yield return(part);

            ArchiveHeader ah = part.ArchiveHeader;

            if (!ah.IsVolume)
            {
                yield break; //if file isn't volume then there is no reason to look
            }
            fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart);
            //we use fileinfo because rar is dumb and looks at file names rather than archive info for another volume
            while (fileInfo != null && fileInfo.Exists)
            {
                part = new FileInfoRarArchiveVolume(fileInfo, options);

                fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart);
                yield return(part);
            }
        }
        public bool MoveNext()
        {
            var result = files.MoveNext();

            if (result)
            {
                var fi = new FileInfo(files.Current);
                Current = new ArchiveHeader
                {
                    ArchiveName    = archivename,
                    FileAttributes = fi.Attributes,
                    FileName       = fi.Name,
                    FileTime       = fi.LastWriteTime,
                    UnpackedSize   = fi.Length,
                    PackedSize     = fi.Length,
                };
            }
            else
            {
                Current = null;
            }
            return(result);
        }
        internal static IEnumerable <RarVolume> GetParts(FileInfo fileInfo, string password, Options options)
        {
            FileInfoRarArchiveVolume part = new FileInfoRarArchiveVolume(fileInfo, password, options);

            yield return(part);

            ArchiveFlags af = part.ArchiveHeader.ArchiveHeaderFlags;

            if (!af_HasFlag(af, ArchiveFlags.VOLUME))
            {
                yield break; //if file isn't volume then there is no reason to look
            }
            ArchiveHeader ah = part.ArchiveHeader;

            fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault <RarFilePart>() as FileInfoRarFilePart);
            //we use fileinfo because rar is dumb and looks at file names rather than archive info for another volume
            while (fileInfo != null && fileInfo.Exists)
            {
                part = new FileInfoRarArchiveVolume(fileInfo, password, options);

                fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault <RarFilePart>() as FileInfoRarFilePart);
                yield return(part);
            }
        }
Example #14
0
        public static void Main(string[] args)
        {
            bool showHelp       = false;
            bool overwriteFiles = false;
            bool noCrypto       = false;
            bool verbose        = false;

            var options = new OptionSet()
            {
                { "no-crypto", "don't use any encryption", v => noCrypto = v != null },
                { "o|overwrite", "overwrite existing files", v => overwriteFiles = v != null },
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
            };

            List <string> extras;

            try
            {
                extras = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extras.Count < 1 || extras.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_ipf [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            var inputPath  = Path.GetFullPath(extras[0]);
            var outputPath = extras.Count > 1 ? extras[1] : Path.ChangeExtension(inputPath, null) + "_unpack";

            const Endian endian = Endian.Little;

            using (var input = File.OpenRead(inputPath))
            {
                if (input.Length < ArchiveHeader.Size)
                {
                    throw new FormatException();
                }

                input.Seek(-ArchiveHeader.Size, SeekOrigin.End);

                var header = ArchiveHeader.Read(input, endian);
                if (header.Magic != ArchiveHeader.Signature)
                {
                    throw new FormatException();
                }

                var fileEntries = new ArchiveFileEntry[header.FileTableCount];
                if (header.FileTableCount > 0)
                {
                    input.Position = header.FileTableOffset;
                    for (int i = 0; i < header.FileTableCount; i++)
                    {
                        fileEntries[i] = ArchiveFileEntry.Read(input, endian);
                    }
                }

                var deletionEntries = new ArchiveDeletionEntry[header.DeletionTableCount];
                if (header.DeletionTableCount > 0)
                {
                    input.Position = header.DeletionTableOffset;
                    for (int i = 0; i < header.DeletionTableCount; i++)
                    {
                        deletionEntries[i] = ArchiveDeletionEntry.Read(input, endian);
                    }
                }

                long current = 0;
                long total   = fileEntries.Length;
                var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

                if (header.DeletionTableCount > 0)
                {
                    Directory.CreateDirectory(outputPath);

                    using (var output = File.Create(Path.Combine(outputPath, "deletions.json")))
                        using (var streamWriter = new StreamWriter(output))
                            using (var writer = new JsonTextWriter(streamWriter))
                            {
                                writer.Indentation = 2;
                                writer.IndentChar  = ' ';
                                writer.Formatting  = Formatting.Indented;
                                writer.WriteStartArray();
                                foreach (var deletionEntry in deletionEntries)
                                {
                                    writer.WriteStartObject();
                                    writer.WritePropertyName("archive");
                                    writer.WriteValue(deletionEntry.Archive);
                                    writer.WritePropertyName("name");
                                    writer.WriteValue(deletionEntry.Name);
                                    writer.WriteEndObject();
                                }
                                writer.WriteEndArray();
                            }
                }

                foreach (var entry in fileEntries)
                {
                    current++;

                    var entryPath = Path.Combine(outputPath,
                                                 entry.Archive.Replace('/', Path.DirectorySeparatorChar),
                                                 entry.Name.Replace('/', Path.DirectorySeparatorChar));
                    if (overwriteFiles == false && File.Exists(entryPath) == true)
                    {
                        continue;
                    }

                    if (verbose == true)
                    {
                        Console.WriteLine("[{0}/{1}] {2}",
                                          current.ToString(CultureInfo.InvariantCulture).PadLeft(padding),
                                          total,
                                          entry.Name);
                    }

                    input.Seek(entry.Offset, SeekOrigin.Begin);

                    var entryDirectory = Path.GetDirectoryName(entryPath);
                    if (entryDirectory != null)
                    {
                        Directory.CreateDirectory(entryDirectory);
                    }

                    using (var output = File.Create(entryPath))
                    {
                        input.Seek(entry.Offset, SeekOrigin.Begin);

                        if (entry.ShouldCompress == false)
                        {
                            output.WriteFromStream(input, entry.CompressedSize);
                        }
                        else
                        {
                            var bytes = input.ReadBytes(entry.CompressedSize);

                            if (noCrypto == false)
                            {
                                var crypto = new ArchiveCrypto();
                                crypto.Decrypt(bytes, 0, bytes.Length);
                            }

                            using (var temp = new MemoryStream(bytes, false))
                            {
                                var zlib = new InflaterInputStream(temp, new Inflater(true));
                                output.WriteFromStream(zlib, entry.UncompressedSize);
                            }
                        }
                    }
                }
            }
        }
Example #15
0
 public Archive(ArchiveHeader ArchiveHeader = default, slice <ptr <Member> > Members = default, io.Closer closer = default)
 {
     this.m_ArchiveHeaderRef = new ptr <ArchiveHeader>(ArchiveHeader);
     this.Members            = Members;
     this.closer             = closer;
 }
Example #16
0
        public static IEnumerable <ImportInfo> GetImportInfos(string libPath)
        {
            using FileStream libStream   = new FileStream(libPath, FileMode.Open, FileAccess.Read);
            using BinaryReader libReader = new BinaryReader(libStream);

            byte[] libHeader = libReader.ReadBytes(expectedLibHeader.Length);
            if (!libHeader.SequenceEqual(expectedLibHeader))
            {
                throw new InvalidOperationException($"{libPath} is not a valid .lib.");
            }

            long nextArchPos = libStream.Position;

            while (libStream.Position != libStream.Length)
            {
                libStream.Position = nextArchPos;

                // Make sure it starts on an even boundary
                if ((libStream.Position % 2) == 1)
                {
                    libStream.Position += 1;
                }

                byte[]        headerBytes = libReader.ReadBytes(Marshal.SizeOf(typeof(ArchiveHeader)));
                ArchiveHeader header      = GetStructFromBytes <ArchiveHeader>(headerBytes);
                if (!header.IsValid)
                {
                    yield break;
                }

                nextArchPos = libStream.Position + header.ArchiveSize;

                string name = header.Name;

                // We don't need these archives
                if (name == "/" || name == "//")
                {
                    continue;
                }

                if (header.ArchiveSize < Marshal.SizeOf(typeof(ImportHeader)))
                {
                    continue;
                }

                byte[]       importHeaderBytes = libReader.ReadBytes(Marshal.SizeOf(typeof(ImportHeader)));
                ImportHeader importHeader      = GetStructFromBytes <ImportHeader>(importHeaderBytes);

                // Is this really an import header?
                if (importHeader.sig1 != 0 || importHeader.sig2 != 0xFFFF)
                {
                    continue;
                }

                // Make sure it's a code import
                if (importHeader.ImportType != ImportType.Code)
                {
                    continue;
                }

                string symbolName = ReadNullTermString(libReader);
                string dllName    = ReadNullTermString(libReader);
                string procName   = symbolName;

                // If it's x86, try to undecorate it
                if (importHeader.MachineType == MachineType.X86)
                {
                    if (procName.StartsWith('_'))
                    {
                        procName = procName.Substring(1);

                        int atIndex = procName.IndexOf('@');
                        if (atIndex != -1)
                        {
                            procName = procName.Substring(0, atIndex);
                        }
                    }
                }

                yield return(new ImportInfo(dllName, procName));
            }
        }
Example #17
0
        public static void Main(string[] args)
        {
            bool showHelp = false;
            bool verbose  = false;
            bool noCrypto = false;

            var options = new OptionSet()
            {
                { "no-crypto", "don't use any encryption", v => noCrypto = v != null },
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
            };

            List <string> extras;

            try
            {
                extras = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extras.Count < 1 || extras.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_dir [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            const Endian endian = Endian.Little;

            var inputPath  = Path.GetFullPath(extras[0]);
            var outputPath = extras.Count > 1 ? extras[1] : Path.ChangeExtension(inputPath, null) + "_unpack";

            var archiveInfosUnsorted = new List <ArchiveInfo>();

            var basePathInfo = new List <KeyValuePair <string, string> >();

            basePathInfo.Add(new KeyValuePair <string, string>(Path.Combine(inputPath, "data"), "*.ipf"));
            basePathInfo.Add(new KeyValuePair <string, string>(Path.Combine(inputPath, "patch"), "*_001001.ipf"));

            foreach (var kv in basePathInfo)
            {
                var basePath = kv.Key;
                var filter   = kv.Value;

                if (Directory.Exists(basePath) == false)
                {
                    continue;
                }

                foreach (var archivePath in Directory.GetFiles(basePath, filter))
                {
                    using (var input = File.OpenRead(archivePath))
                    {
                        if (input.Length < ArchiveHeader.Size)
                        {
                            throw new FormatException();
                        }

                        input.Seek(-ArchiveHeader.Size, SeekOrigin.End);

                        var header = ArchiveHeader.Read(input, endian);
                        if (header.Magic != ArchiveHeader.Signature)
                        {
                            throw new FormatException();
                        }

                        archiveInfosUnsorted.Add(
                            new ArchiveInfo(archivePath,
                                            header.BaseRevision,
                                            header.Revision,
                                            header.FileTableCount + header.DeletionTableCount));
                    }
                }
            }

            var archiveInfos = archiveInfosUnsorted.OrderBy(ai => ai.BaseRevision)
                               .ThenBy(ai => ai.Revision)
                               .ThenBy(ai => ai.Path)
                               .ToList();

            long current = 0;
            long total   = archiveInfos.Sum(ai => ai.TotalCount);
            var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

            var dataPaths = new Dictionary <string, List <string> >();

            foreach (var archiveInfo in archiveInfos)
            {
                using (var input = File.OpenRead(archiveInfo.Path))
                {
                    if (input.Length < ArchiveHeader.Size)
                    {
                        throw new FormatException();
                    }

                    input.Seek(-ArchiveHeader.Size, SeekOrigin.End);

                    var header = ArchiveHeader.Read(input, endian);
                    if (header.Magic != ArchiveHeader.Signature)
                    {
                        throw new FormatException();
                    }

                    var fileEntries = new ArchiveFileEntry[header.FileTableCount];
                    if (header.FileTableCount > 0)
                    {
                        input.Position = header.FileTableOffset;
                        for (int i = 0; i < header.FileTableCount; i++)
                        {
                            fileEntries[i] = ArchiveFileEntry.Read(input, endian);
                        }
                    }

                    var deletionEntries = new ArchiveDeletionEntry[header.DeletionTableCount];
                    if (header.DeletionTableCount > 0)
                    {
                        input.Position = header.DeletionTableOffset;
                        for (int i = 0; i < header.DeletionTableCount; i++)
                        {
                            deletionEntries[i] = ArchiveDeletionEntry.Read(input, endian);
                        }
                    }

                    foreach (var entry in deletionEntries)
                    {
                        current++;

                        if (entry.Archive == "data")
                        {
                            var dataPath = entry.Name.ToLowerInvariant();

                            if (dataPaths.ContainsKey(dataPath) == false)
                            {
                                // probably an incorrect entry pointing to a directory
                                continue;
                            }

                            foreach (var entryPath in dataPaths[dataPath])
                            {
                                if (File.Exists(entryPath) == true)
                                {
                                    File.Delete(entryPath);
                                }
                            }

                            dataPaths.Remove(entry.Name);
                        }
                        else
                        {
                            throw new NotImplementedException();

                            var entryPath = Path.Combine(outputPath, entry.Archive, entry.Name);
                            if (File.Exists(entryPath) == true)
                            {
                                File.Delete(entryPath);
                            }
                        }
                    }

                    foreach (var entry in fileEntries)
                    {
                        current++;

                        var entryPath = Path.Combine(outputPath,
                                                     entry.Archive.Replace('/', Path.DirectorySeparatorChar),
                                                     entry.Name.Replace('/', Path.DirectorySeparatorChar));

                        var dataPath = entry.Name.ToLowerInvariant();
                        if (dataPaths.ContainsKey(dataPath) == false)
                        {
                            dataPaths[dataPath] = new List <string>();
                        }

                        if (dataPaths[dataPath].Contains(entryPath) == false)
                        {
                            dataPaths[dataPath].Add(entryPath);
                        }

                        if (verbose == true)
                        {
                            Console.WriteLine("[{0}/{1}] {2}/{3}",
                                              current.ToString(CultureInfo.InvariantCulture).PadLeft(padding),
                                              total,
                                              entry.Archive,
                                              entry.Name);
                        }

                        input.Seek(entry.Offset, SeekOrigin.Begin);

                        var entryDirectory = Path.GetDirectoryName(entryPath);
                        if (entryDirectory != null)
                        {
                            Directory.CreateDirectory(entryDirectory);
                        }

                        using (var output = File.Create(entryPath))
                        {
                            input.Seek(entry.Offset, SeekOrigin.Begin);

                            if (entry.ShouldCompress == false)
                            {
                                output.WriteFromStream(input, entry.CompressedSize);
                            }
                            else
                            {
                                var bytes = input.ReadBytes(entry.CompressedSize);

                                if (noCrypto == false)
                                {
                                    var crypto = new ArchiveCrypto();
                                    crypto.Decrypt(bytes, 0, bytes.Length);
                                }

                                using (var temp = new MemoryStream(bytes, false))
                                {
                                    var zlib = new InflaterInputStream(temp, new Inflater(true));
                                    output.WriteFromStream(zlib, entry.UncompressedSize);
                                }
                            }
                        }
                    }
                }
            }
        }
Example #18
0
        private ArchiveHeader ReadArchiveHeader(BinaryReader rd)
        {
            UInt16 id = rd.ReadUInt16();
            byte[] BasicHeader;
            ArchiveHeader result = new ArchiveHeader();
            BinaryReader hr;

            if (id != 0xea60)
                return null;

            result.BasicHeaderSize = rd.ReadUInt16();
            if (result.BasicHeaderSize > 2600)
                return null;

            BasicHeader = new byte[result.BasicHeaderSize];
            rd.Read(BasicHeader, 0, result.BasicHeaderSize);
            result.BasicHeaderCRC = rd.ReadUInt32();
            // read/skip extended headers
            UInt16 extHdrSize = rd.ReadUInt16();
            // TODO: check if extended header occurs at all in any archive
            if (extHdrSize > 0)
            {
                for (int i = 0; i < extHdrSize; i++)
                    rd.ReadByte();
            }
            hr = new BinaryReader(new MemoryStream(BasicHeader));
            result.FirstHeaderSize = hr.ReadByte();
            result.ArchiverVersion = hr.ReadByte();
            result.MinVersionToExtract = hr.ReadByte();
            result.HostOS = hr.ReadByte();
            result.ARJFlags = hr.ReadByte();
            if ((result.ARJFlags & 0x4d) != 0)
                return null; // unsupported archive flags (protected, multi-volume, garbled, secured)
            result.SecurityVersion = hr.ReadByte();
            result.FileType = hr.ReadByte();
            if (result.FileType != 2)
                return null;
            hr.ReadByte(); // reserved
            result.ArchiveCreated = FromDOSDateTime(hr.ReadUInt32());
            result.ArchiveModified = FromDOSDateTime(hr.ReadUInt32());
            result.ArchiveSize = hr.ReadUInt32();
            result.SecurityEnvelopePosition = hr.ReadUInt32();
            result.FilespecPosition = hr.ReadUInt16();
            result.SecurityEnvelopeLength = hr.ReadUInt16();
            result.Encryption = hr.ReadByte();
            if (result.Encryption != 0)
                return null; // encrypted archives not supported
            result.LastChapter = hr.ReadByte();
            // skip over "extra data"
            for (int i = 0; i < result.FirstHeaderSize - 0x1e; i++)
                hr.ReadByte(); // ignore extra data

            result.OriginalFileName = ReadASCIIZ(hr);
            result.Comment = ReadASCIIZ(hr);
            if (result.BasicHeaderSize != result.FirstHeaderSize + result.OriginalFileName.Length + result.Comment.Length + 2)
                return null;

            ulong crc = CRCCalculator.CalculateCRC(BasicHeader, BasicHeader.Length);
            // in the end, this should catch all false positives :)
            if (crc != result.BasicHeaderCRC)
                return null;

            return result;
        }