/// <summary>
        /// Create a new ZipFileEntry from a stream and a filename
        /// </summary>
        /// <param name="zipstream">Stream representing the entry</param>
        /// <param name="filename">Internal filename to use</param>
        public ZipFileEntry(Stream zipstream, string filename, uint lastMod = Constants.TorrentZipFileDateTime)
        {
            _zip64                 = false;
            _zipstream             = zipstream;
            _generalPurposeBitFlag = GeneralPurposeBitFlag.DeflatingMaximumCompression;
            _compressionMethod     = CompressionMethod.Deflated;
            _lastMod               = lastMod;

            FileName = filename;
        }
        /// <summary>
        /// Write the local file header entry to the included stream
        /// </summary>
        public void WriteHeader()
        {
            // Open the stream for writing
            BinaryWriter bw = new BinaryWriter(_zipstream);

            // Create an empty extra field to start out with
            List <byte> extraField = new List <byte>();

            // Figure out if we're in Zip64 based on the size
            _zip64 = _uncompressedSize >= 0xffffffff;

            // Now check for a unicode filename and set the flag accordingly
            byte[] fileNameBytes;
            if (Utilities.IsUnicode(_fileName))
            {
                _generalPurposeBitFlag |= GeneralPurposeBitFlag.LanguageEncodingFlag;
                fileNameBytes           = Encoding.UTF8.GetBytes(_fileName);
            }
            else
            {
                fileNameBytes = Encoding.GetEncoding(858).GetBytes(_fileName);
            }

            // Set the version needed to extract according to if it's Zip64
            ushort versionNeededToExtract = (ushort)(_zip64 ? ArchiveVersion.TorrentZip64 : ArchiveVersion.TorrentZip);

            // Now save the relative offset and write
            _relativeOffset = (ulong)_zipstream.Position;
            bw.Write(Constants.LocalFileHeaderSignature);
            bw.Write(versionNeededToExtract);
            bw.Write((ushort)_generalPurposeBitFlag);
            bw.Write((ushort)_compressionMethod);
            bw.Write(_lastMod);

            _crc32Location = (ulong)_zipstream.Position;

            // Now, write dummy bytes for crc, compressed size, and uncompressed size
            bw.Write(0xffffffff);
            bw.Write(0xffffffff);
            bw.Write(0xffffffff);

            // If we have Zip64, add the right things to the extra field
            if (_zip64)
            {
                for (int i = 0; i < 20; i++)
                {
                    extraField.Add(0);
                }
            }

            // Write out the lengths and their associated fields
            ushort fileNameLength = (ushort)fileNameBytes.Length;

            bw.Write(fileNameLength);

            ushort extraFieldLength = (ushort)extraField.Count;

            bw.Write(extraFieldLength);

            bw.Write(fileNameBytes, 0, fileNameLength);

            _extraLocation = (ulong)_zipstream.Position;
            bw.Write(extraField.ToArray(), 0, extraFieldLength);
        }
        /// <summary>
        /// Read the local file header from the input stream, assuming correctness
        /// </summary>
        /// <returns>Status of the underlying stream</returns>
        public ZipReturn ReadHeaderQuick()
        {
            try
            {
                // We assume that the file is torrentzip until proven otherwise
                _torrentZip = true;

                // Open the stream for reading
                BinaryReader br = new BinaryReader(_zipstream);

                // Set the position of the writer based on the entry information
                br.BaseStream.Seek((long)_relativeOffset, SeekOrigin.Begin);

                // If the first bytes aren't a local file header, log and return
                if (br.ReadUInt32() != Constants.LocalFileHeaderSignature)
                {
                    return(ZipReturn.ZipLocalFileHeaderError);
                }

                // Now read in available information, ignoring unneeded
                _versionNeeded         = (ArchiveVersion)br.ReadUInt16();
                _generalPurposeBitFlag = (GeneralPurposeBitFlag)br.ReadUInt16();

                // If the flag says there's no hash data, then we can't use quick mode
                if ((_generalPurposeBitFlag & GeneralPurposeBitFlag.ZeroedCRCAndSize) == GeneralPurposeBitFlag.ZeroedCRCAndSize)
                {
                    return(ZipReturn.ZipCannotFastOpen);
                }

                _compressionMethod = (CompressionMethod)br.ReadUInt16();
                _lastMod           = br.ReadUInt32();
                _crc              = br.ReadUInt32();
                _compressedSize   = br.ReadUInt32();
                _uncompressedSize = br.ReadUInt32();

                ushort fileNameLength   = br.ReadUInt16();
                ushort extraFieldLength = br.ReadUInt16();

                byte[] fileNameBytes = br.ReadBytes(fileNameLength);
                _fileName = ((_generalPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0
                                        ? Encoding.GetEncoding(858).GetString(fileNameBytes)
                                        : Encoding.UTF8.GetString(fileNameBytes, 0, fileNameLength));

                byte[] extraField = br.ReadBytes(extraFieldLength);

                /*
                 * Full disclosure: this next section is in GordonJ's work but I honestly
                 * have no idea everything that it does. It seems to do something to figure
                 * out if it's Zip64, or possibly check for random things but it uses the
                 * extra field for this, which I do not fully understand. It's copied in
                 * its entirety below in the hope that it makes things better...
                 */

                _zip64 = false;
                int pos = 0;
                while (extraFieldLength > pos)
                {
                    ushort type = BitConverter.ToUInt16(extraField, pos);
                    pos += 2;
                    ushort blockLength = BitConverter.ToUInt16(extraField, pos);
                    pos += 2;
                    switch (type)
                    {
                    case 0x0001:
                        Zip64 = true;
                        if (_uncompressedSize == 0xffffffff)
                        {
                            _uncompressedSize = BitConverter.ToUInt64(extraField, pos);
                            pos += 8;
                        }
                        if (_compressedSize == 0xffffffff)
                        {
                            _compressedSize = BitConverter.ToUInt64(extraField, pos);
                            pos            += 8;
                        }
                        break;

                    case 0x7075:
                        pos += 1;
                        uint nameCRC32 = BitConverter.ToUInt32(extraField, pos);
                        pos += 4;

                        CRC32 crcTest = new CRC32();
                        crcTest.SlurpBlock(fileNameBytes, 0, fileNameLength);
                        uint fCRC = (uint)crcTest.Crc32Result;

                        if (nameCRC32 != fCRC)
                        {
                            return(ZipReturn.ZipLocalFileHeaderError);
                        }

                        int charLen = blockLength - 5;

                        FileName = Encoding.UTF8.GetString(extraField, pos, charLen);

                        pos += charLen;

                        break;

                    default:
                        pos += blockLength;
                        break;
                    }
                }

                // Set the position of the data
                _dataLocation = (ulong)_zipstream.Position;
            }
            catch
            {
                return(ZipReturn.ZipLocalFileHeaderError);
            }

            return(ZipReturn.ZipGood);
        }
        /// <summary>
        /// Write the central directory entry from the included stream
        /// </summary>
        /// <param name="output">Write out the data from the internal stream to the output stream</param>
        public void WriteCentralDirectory(Stream output)
        {
            // Open the output stream for writing
            BinaryWriter bw = new BinaryWriter(output);

            // Create an empty extra field to start out with
            List <byte> extraField = new List <byte>();

            // Now get the uncompressed size (for Zip64 compatibility)
            uint uncompressedSize32;

            if (_uncompressedSize >= 0xffffffff)
            {
                _zip64             = true;
                uncompressedSize32 = 0xffffffff;
                extraField.AddRange(BitConverter.GetBytes(_uncompressedSize));
            }
            else
            {
                uncompressedSize32 = (uint)_uncompressedSize;
            }

            // Now get the compressed size (for Zip64 compatibility)
            uint compressedSize32;

            if (_compressedSize >= 0xffffffff)
            {
                _zip64           = true;
                compressedSize32 = 0xffffffff;
                extraField.AddRange(BitConverter.GetBytes(_compressedSize));
            }
            else
            {
                compressedSize32 = (uint)_compressedSize;
            }

            // Now get the relative offset (for Zip64 compatibility)
            uint relativeOffset32;

            if (_relativeOffset >= 0xffffffff)
            {
                _zip64           = true;
                relativeOffset32 = 0xffffffff;
                extraField.AddRange(BitConverter.GetBytes(_relativeOffset));
            }
            else
            {
                relativeOffset32 = (uint)_relativeOffset;
            }

            // If we wrote anything to the extra field, set the flag and size
            if (extraField.Count > 0)
            {
                ushort extraFieldLengthInternal = (ushort)extraField.Count;
                extraField.InsertRange(0, BitConverter.GetBytes((ushort)0x0001));                 // id
                extraField.InsertRange(2, BitConverter.GetBytes(extraFieldLengthInternal));       // data length
            }
            ushort extraFieldLength = (ushort)extraField.Count;

            // Now check for a unicode filename and set the flag accordingly
            byte[] fileNameBytes;
            if (Utilities.IsUnicode(_fileName))
            {
                _generalPurposeBitFlag |= GeneralPurposeBitFlag.LanguageEncodingFlag;
                fileNameBytes           = Encoding.UTF8.GetBytes(_fileName);
            }
            else
            {
                fileNameBytes = Encoding.GetEncoding(858).GetBytes(_fileName);
            }
            ushort fileNameLength = (ushort)fileNameBytes.Length;

            // Set the version needed to extract according to if it's Zip64
            ushort versionNeededToExtract = (ushort)(_zip64 ? ArchiveVersion.TorrentZip64 : ArchiveVersion.TorrentZip);

            // Now, write all of the data to the stream
            bw.Write(Constants.CentralDirectoryHeaderSignature);
            bw.Write((ushort)ArchiveVersion.MSDOSandOS2);
            bw.Write(versionNeededToExtract);
            bw.Write((ushort)_generalPurposeBitFlag);
            bw.Write((ushort)_compressionMethod);
            bw.Write(_lastMod);
            bw.Write(_crc);
            bw.Write(compressedSize32);
            bw.Write(uncompressedSize32);
            bw.Write(fileNameLength);
            bw.Write(extraFieldLength);
            bw.Write((ushort)0);                                 // File comment length
            bw.Write((ushort)0);                                 // Disk number start
            bw.Write((ushort)0);                                 // Internal file attributes
            bw.Write((uint)0);                                   // External file attributes
            bw.Write(relativeOffset32);
            bw.Write(fileNameBytes, 0, fileNameLength);          // Only write first bytes if longer than allowed
            bw.Write(extraField.ToArray(), 0, extraFieldLength); // Only write the first bytes if longer than allowed
            // We have no file comment, so we don't have to write more
        }
        /// <summary>
        /// Read the central directory entry from the input stream
        /// </summary>
        /// <returns>Status of the underlying stream</returns>
        public ZipReturn ReadCentralDirectory()
        {
            try
            {
                // Open the stream for reading
                BinaryReader br = new BinaryReader(_zipstream);

                // If the first bytes aren't a central directory header, log and return
                if (br.ReadUInt32() != Constants.CentralDirectoryHeaderSignature)
                {
                    return(ZipReturn.ZipCentralDirError);
                }

                // Now read in available information, skipping the unnecessary
                _versionMadeBy         = (ArchiveVersion)br.ReadUInt16();
                _versionNeeded         = (ArchiveVersion)br.ReadUInt16();
                _generalPurposeBitFlag = (GeneralPurposeBitFlag)br.ReadUInt16();
                _compressionMethod     = (CompressionMethod)br.ReadUInt16();

                // If we have an unsupported compression method, log and return
                if (_compressionMethod != CompressionMethod.Stored && _compressionMethod != CompressionMethod.Deflated)
                {
                    return(ZipReturn.ZipCentralDirError);
                }

                // Keep reading available information, skipping the unnecessary
                _lastMod          = br.ReadUInt32();
                _crc              = br.ReadUInt32();
                _compressedSize   = br.ReadUInt32();
                _uncompressedSize = br.ReadUInt32();

                // Now store some temp vars to find the filename, extra field, and comment
                ushort fileNameLength    = br.ReadUInt16();
                ushort extraFieldLength  = br.ReadUInt16();
                ushort fileCommentLength = br.ReadUInt16();

                // Even more reading available information, skipping the unnecessary
                br.ReadUInt16();                 // Disk number start
                _internalFileAttributes = (InternalFileAttributes)br.ReadUInt16();
                _externalFileAttributes = br.ReadUInt32();
                _relativeOffset         = br.ReadUInt32();
                byte[] fileNameBytes = br.ReadBytes(fileNameLength);
                _fileName = ((_generalPurposeBitFlag & GeneralPurposeBitFlag.LanguageEncodingFlag) == 0
                                        ? Encoding.GetEncoding(858).GetString(fileNameBytes)
                                        : Encoding.UTF8.GetString(fileNameBytes, 0, fileNameLength));
                _extraField = br.ReadBytes(extraFieldLength);
                _comment    = br.ReadBytes(fileCommentLength);

                /*
                 * Full disclosure: this next section is in GordonJ's work but I honestly
                 * have no idea everything that it does. It seems to do something to figure
                 * out if it's Zip64, or possibly check for random things but it uses the
                 * extra field for this, which I do not fully understand. It's copied in
                 * its entirety below in the hope that it makes things better...
                 */

                int pos = 0;
                while (extraFieldLength > pos)
                {
                    ushort type = BitConverter.ToUInt16(_extraField, pos);
                    pos += 2;
                    ushort blockLength = BitConverter.ToUInt16(_extraField, pos);
                    pos += 2;
                    switch (type)
                    {
                    case 0x0001:
                        Zip64 = true;
                        if (UncompressedSize == 0xffffffff)
                        {
                            UncompressedSize = BitConverter.ToUInt64(_extraField, pos);
                            pos += 8;
                        }
                        if (_compressedSize == 0xffffffff)
                        {
                            _compressedSize = BitConverter.ToUInt64(_extraField, pos);
                            pos            += 8;
                        }
                        if (_relativeOffset == 0xffffffff)
                        {
                            _relativeOffset = BitConverter.ToUInt64(_extraField, pos);
                            pos            += 8;
                        }
                        break;

                    case 0x7075:
                        //byte version = extraField[pos];
                        pos += 1;
                        uint nameCRC32 = BitConverter.ToUInt32(_extraField, pos);
                        pos += 4;

                        CRC32 crcTest = new CRC32();
                        crcTest.SlurpBlock(fileNameBytes, 0, fileNameLength);
                        uint fCRC = (uint)crcTest.Crc32Result;

                        if (nameCRC32 != fCRC)
                        {
                            return(ZipReturn.ZipCentralDirError);
                        }

                        int charLen = blockLength - 5;

                        _fileName = Encoding.UTF8.GetString(_extraField, pos, charLen);
                        pos      += charLen;

                        break;

                    default:
                        pos += blockLength;
                        break;
                    }
                }
            }
            catch
            {
                return(ZipReturn.ZipCentralDirError);
            }

            return(ZipReturn.ZipGood);
        }