/******************************************************
 *     ROMVault2 is written by Gordon J.              *
 *     Contact gordon@romvault.com                    *
 *     Copyright 2014                                 *
 ******************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using ROMVault2.SupportedFiles.Zip.ZLib;

// UInt16 = ushort
// UInt32 = uint
// ULong = ulong

namespace ROMVault2.SupportedFiles.Zip
{

    public class ZipFile
    {
        const int Buffersize = 4096 * 1024;
        private static byte[] _buffer0;
        private static byte[] _buffer1;

        private const uint LocalFileHeaderSignature = 0x04034b50;
        private const uint CentralDirectoryHeaderSigniature = 0x02014b50;
        private const uint EndOfCentralDirSignature = 0x06054b50;
        private const uint Zip64EndOfCentralDirSignatue = 0x06064b50;
        private const uint Zip64EndOfCentralDirectoryLocator = 0x07064b50;

        class LocalFile
        {
            private readonly Stream _zipFs;
            public string FileName { get; private set; }
            public ushort _generalPurposeBitFlag { get; private set; }
            private ushort _compressionMethod;
            private ushort _lastModFileTime;
            private ushort _lastModFileDate;
            public byte[] CRC { get; private set; }
            private ulong _compressedSize;
            public ulong UncompressedSize { get; private set; }
            public ulong RelativeOffsetOfLocalHeader; // only in centeral directory

            private ulong _crc32Location;
            private ulong _extraLocation;
            private ulong _dataLocation;

            public bool Zip64 { get; private set; }
            public bool TrrntZip { get; private set; }

            public byte[] sha1 { get; private set; }
            public byte[] md5 { get; private set; }

            public ZipReturn FileStatus = ZipReturn.ZipUntested;
            public LocalFile(Stream zipFs)
            {
                _zipFs = zipFs;
            }
            public LocalFile(Stream zipFs, string filename)
            {
                Zip64 = false;
                _zipFs = zipFs;
                _generalPurposeBitFlag = 2;  // Maximum Compression Deflating
                _compressionMethod = 8;      // Compression Method Deflate
                _lastModFileTime = 48128;
                _lastModFileDate = 8600;

                FileName = filename;
            }


            public ZipReturn CenteralDirectoryRead()
            {
                try
                {
                    BinaryReader br = new BinaryReader(_zipFs);

                    uint thisSignature = br.ReadUInt32();
                    if (thisSignature != CentralDirectoryHeaderSigniature)
                        return ZipReturn.ZipCenteralDirError;

                    br.ReadUInt16(); // Version Made By

                    br.ReadUInt16(); // Version Needed To Extract


                    _generalPurposeBitFlag = br.ReadUInt16();
                    _compressionMethod = br.ReadUInt16();
                    if (_compressionMethod != 8 && _compressionMethod != 0)
                        return ZipReturn.ZipUnsupportedCompression;

                    _lastModFileTime = br.ReadUInt16();
                    _lastModFileDate = br.ReadUInt16();
                    CRC = ReadCRC(br);

                    _compressedSize = br.ReadUInt32();
                    UncompressedSize = br.ReadUInt32();

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

                    br.ReadUInt16(); // diskNumberStart
                    br.ReadUInt16(); // internalFileAttributes
                    br.ReadUInt32(); // externalFileAttributes

                    RelativeOffsetOfLocalHeader = br.ReadUInt32();

                    byte[] bFileName = br.ReadBytes(fileNameLength);
                    FileName = (_generalPurposeBitFlag & (1 << 11)) == 0 ?
                        GetString(bFileName) :
                        Encoding.UTF8.GetString(bFileName, 0, fileNameLength);

                    Byte[] extraField = br.ReadBytes(extraFieldLength);
                    br.ReadBytes(fileCommentLength); // File Comments

                    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 (RelativeOffsetOfLocalHeader == 0xffffffff)
                                {
                                    RelativeOffsetOfLocalHeader = 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(bFileName, 0, fileNameLength);
                                uint fCRC = crcTest.Crc32ResultU;

                                if (nameCRC32 != fCRC) return ZipReturn.ZipCenteralDirError;

                                int charLen = blockLength - 5;

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

                                break;
                            default:
                                pos += blockLength;
                                break;
                        }
                    }

                    return ZipReturn.ZipGood;
                }
                catch
                {
                    return ZipReturn.ZipCenteralDirError;
                }

            }
            public void CenteralDirectoryWrite(Stream crcStream)
            {
                BinaryWriter bw = new BinaryWriter(crcStream);

                const uint header = 0x2014B50;

                List<Byte> extraField = new List<byte>();

                uint cdUncompressedSize;
                if (UncompressedSize >= 0xffffffff)
                {
                    Zip64 = true;
                    cdUncompressedSize = 0xffffffff;
                    extraField.AddRange(BitConverter.GetBytes(UncompressedSize));
                }
                else
                    cdUncompressedSize = (uint)UncompressedSize;

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

                uint cdRelativeOffsetOfLocalHeader;
                if (RelativeOffsetOfLocalHeader >= 0xffffffff)
                {
                    Zip64 = true;
                    cdRelativeOffsetOfLocalHeader = 0xffffffff;
                    extraField.AddRange(BitConverter.GetBytes(RelativeOffsetOfLocalHeader));
                }
                else
                    cdRelativeOffsetOfLocalHeader = (uint)RelativeOffsetOfLocalHeader;


                if (extraField.Count > 0)
                {
                    ushort exfl = (ushort)extraField.Count;
                    extraField.InsertRange(0, BitConverter.GetBytes((ushort)0x0001));
                    extraField.InsertRange(2, BitConverter.GetBytes(exfl));
                }
                ushort extraFieldLength = (ushort)extraField.Count;

                byte[] bFileName;
                if (IsUnicode(FileName))
                {
                    _generalPurposeBitFlag |= 1 << 11;
                    bFileName = Encoding.UTF8.GetBytes(FileName);
                }
                else
                    bFileName = GetBytes(FileName);
                ushort fileNameLength = (ushort)bFileName.Length;

                ushort versionNeededToExtract = (ushort)(Zip64 ? 45 : 20);

                bw.Write(header);
                bw.Write((ushort)0);
                bw.Write(versionNeededToExtract);
                bw.Write(_generalPurposeBitFlag);
                bw.Write(_compressionMethod);
                bw.Write(_lastModFileTime);
                bw.Write(_lastModFileDate);
                bw.Write(CRC[3]);
                bw.Write(CRC[2]);
                bw.Write(CRC[1]);
                bw.Write(CRC[0]);
                bw.Write(cdCompressedSize);
                bw.Write(cdUncompressedSize);
                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(cdRelativeOffsetOfLocalHeader);

                bw.Write(bFileName, 0, fileNameLength);
                bw.Write(extraField.ToArray(), 0, extraFieldLength);
                // No File Comment
            }


            public ZipReturn LocalFileHeaderRead()
            {
                try
                {
                    TrrntZip = true;

                    BinaryReader br = new BinaryReader(_zipFs);

                    _zipFs.Position = (long)RelativeOffsetOfLocalHeader;
                    uint thisSignature = br.ReadUInt32();
                    if (thisSignature != LocalFileHeaderSignature)
                        return ZipReturn.ZipLocalFileHeaderError;

                    br.ReadUInt16();  // version needed to extract
                    ushort generalPurposeBitFlagLocal = br.ReadUInt16();
                    if (generalPurposeBitFlagLocal != _generalPurposeBitFlag) TrrntZip = false;

                    ushort tshort = br.ReadUInt16();
                    if (tshort != _compressionMethod) return ZipReturn.ZipLocalFileHeaderError;

                    tshort = br.ReadUInt16();
                    if (tshort != _lastModFileTime) return ZipReturn.ZipLocalFileHeaderError;

                    tshort = br.ReadUInt16();
                    if (tshort != _lastModFileDate) return ZipReturn.ZipLocalFileHeaderError;

                    byte[] tCRC = ReadCRC(br);
                    if (((_generalPurposeBitFlag & 8) == 0) && !ByteArrCompare(tCRC, CRC)) return ZipReturn.ZipLocalFileHeaderError;

                    uint tCompressedSize = br.ReadUInt32();
                    if (Zip64 && tCompressedSize != 0xffffffff && tCompressedSize != _compressedSize)   // if Zip64 File then the compressedSize should be 0xffffffff
                        return ZipReturn.ZipLocalFileHeaderError;
                    if ((_generalPurposeBitFlag & 8) == 8 && tCompressedSize != 0)   // if bit 4 set then no compressedSize is set yet
                        return ZipReturn.ZipLocalFileHeaderError;
                    if (!Zip64 && (_generalPurposeBitFlag & 8) != 8 && tCompressedSize != _compressedSize) // check the compressedSize
                        return ZipReturn.ZipLocalFileHeaderError;



                    uint tUnCompressedSize = br.ReadUInt32();
                    if (Zip64 && tUnCompressedSize != 0xffffffff && tUnCompressedSize != UncompressedSize)   // if Zip64 File then the unCompressedSize should be 0xffffffff
                        return ZipReturn.ZipLocalFileHeaderError;
                    if ((_generalPurposeBitFlag & 8) == 8 && tUnCompressedSize != 0)   // if bit 4 set then no unCompressedSize is set yet
                        return ZipReturn.ZipLocalFileHeaderError;
                    if (!Zip64 && (_generalPurposeBitFlag & 8) != 8 && tUnCompressedSize != UncompressedSize) // check the unCompressedSize
                        return ZipReturn.ZipLocalFileHeaderError;

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


                    byte[] bFileName = br.ReadBytes(fileNameLength);
                    string tFileName = (generalPurposeBitFlagLocal & (1 << 11)) == 0 ?
                        GetString(bFileName) :
                        Encoding.UTF8.GetString(bFileName, 0, fileNameLength);

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


                    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 (tUnCompressedSize == 0xffffffff)
                                {
                                    ulong tLong = BitConverter.ToUInt64(extraField, pos);
                                    if (tLong != UncompressedSize) return ZipReturn.ZipLocalFileHeaderError;
                                    pos += 8;
                                }
                                if (tCompressedSize == 0xffffffff)
                                {
                                    ulong tLong = BitConverter.ToUInt64(extraField, pos);
                                    if (tLong != _compressedSize) return ZipReturn.ZipLocalFileHeaderError;
                                    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(bFileName, 0, fileNameLength);
                                uint fCRC = crcTest.Crc32ResultU;

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

                                int charLen = blockLength - 5;

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

                                break;
                            default:
                                pos += blockLength;
                                break;
                        }
                    }

                    if (!CompareString(FileName, tFileName)) return ZipReturn.ZipLocalFileHeaderError;

                    _dataLocation = (ulong)_zipFs.Position;

                    if ((_generalPurposeBitFlag & 8) == 0) return ZipReturn.ZipGood;

                    _zipFs.Position += (long)_compressedSize;

                    tCRC = ReadCRC(br);
                    if (!ByteArrCompare(tCRC, new byte[] { 0x50, 0x4b, 0x07, 0x08 }))
                        tCRC = ReadCRC(br);

                    if (!ByteArrCompare(tCRC, CRC)) return ZipReturn.ZipLocalFileHeaderError;

                    uint tint = br.ReadUInt32();
                    if (tint != _compressedSize) return ZipReturn.ZipLocalFileHeaderError;

                    tint = br.ReadUInt32();
                    if (tint != UncompressedSize) return ZipReturn.ZipLocalFileHeaderError;

                    return ZipReturn.ZipGood;
                }
                catch
                {
                    return ZipReturn.ZipLocalFileHeaderError;
                }


            }
            public ZipReturn LocalFileHeaderReadQuick()
            {
                try
                {
                    TrrntZip = true;

                    BinaryReader br = new BinaryReader(_zipFs);

                    _zipFs.Position = (long)RelativeOffsetOfLocalHeader;
                    uint thisSignature = br.ReadUInt32();
                    if (thisSignature != LocalFileHeaderSignature)
                        return ZipReturn.ZipLocalFileHeaderError;

                    br.ReadUInt16();  // version needed to extract
                    _generalPurposeBitFlag = br.ReadUInt16();
                    if ((_generalPurposeBitFlag & 8) == 8)
                        return ZipReturn.ZipCannotFastOpen;

                    _compressionMethod = br.ReadUInt16();
                    _lastModFileTime = br.ReadUInt16();
                    _lastModFileDate = br.ReadUInt16();
                    CRC = ReadCRC(br);
                    _compressedSize = br.ReadUInt32();
                    UncompressedSize = br.ReadUInt32();

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

                    byte[] bFileName = br.ReadBytes(fileNameLength);

                    FileName = (_generalPurposeBitFlag & (1 << 11)) == 0 ?
                        GetString(bFileName) :
                        Encoding.UTF8.GetString(bFileName, 0, fileNameLength);

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

                    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(bFileName, 0, fileNameLength);
                                uint fCRC = crcTest.Crc32ResultU;

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

                                int charLen = blockLength - 5;

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

                                pos += charLen;

                                break;
                            default:
                                pos += blockLength;
                                break;
                        }
                    }

                    _dataLocation = (ulong)_zipFs.Position;
                    return ZipReturn.ZipGood;

                }
                catch
                {
                    return ZipReturn.ZipLocalFileHeaderError;
                }


            }



            private void LocalFileHeaderWrite()
            {
                BinaryWriter bw = new BinaryWriter(_zipFs);

                List<Byte> extraField = new List<byte>();
                Zip64 = UncompressedSize >= 0xffffffff;

                byte[] bFileName;
                if (IsUnicode(FileName))
                {
                    _generalPurposeBitFlag |= 1 << 11;
                    bFileName = Encoding.UTF8.GetBytes(FileName);
                }
                else
                    bFileName = GetBytes(FileName);

                ushort versionNeededToExtract = (ushort)(Zip64 ? 45 : 20);

                RelativeOffsetOfLocalHeader = (ulong)_zipFs.Position;
                const uint header = 0x4034B50;
                bw.Write(header);
                bw.Write(versionNeededToExtract);
                bw.Write(_generalPurposeBitFlag);
                bw.Write(_compressionMethod);
                bw.Write(_lastModFileTime);
                bw.Write(_lastModFileDate);

                _crc32Location = (ulong)_zipFs.Position;

                // these 3 values will be set correctly after the file data has been written
                bw.Write(0xffffffff);
                bw.Write(0xffffffff);
                bw.Write(0xffffffff);



                if (Zip64)
                {
                    for (int i = 0; i < 20; i++)
                        extraField.Add(0);
                }

                ushort fileNameLength = (ushort)bFileName.Length;
                bw.Write(fileNameLength);

                ushort extraFieldLength = (ushort)extraField.Count;
                bw.Write(extraFieldLength);

                bw.Write(bFileName, 0, fileNameLength);

                _extraLocation = (ulong)_zipFs.Position;
                bw.Write(extraField.ToArray(), 0, extraFieldLength);

            }


            private Stream _readStream;
            public ZipReturn LocalFileOpenReadStream(bool raw, out Stream stream, out ulong streamSize, out ushort compressionMethod)
            {
                streamSize = 0;
                compressionMethod = _compressionMethod;

                _readStream = null;
                _zipFs.Seek((long)_dataLocation, SeekOrigin.Begin);

                switch (_compressionMethod)
                {
                    case 8:
                        if (raw)
                        {
                            _readStream = _zipFs;
                            streamSize = _compressedSize;
                        }
                        else
                        {
                            _readStream = new DeflateStream(_zipFs, CompressionMode.Decompress, true);
                            streamSize = UncompressedSize;

                        }
                        break;
                    case 0:
                        _readStream = _zipFs;
                        streamSize = _compressedSize;  // same as UncompressedSize
                        break;
                }
                stream = _readStream;
                return stream == null ? ZipReturn.ZipErrorGettingDataStream : ZipReturn.ZipGood;
            }
            public ZipReturn LocalFileCloseReadStream()
            {
                DeflateStream dfStream = _readStream as DeflateStream;
                if (dfStream != null)
                {
                    dfStream.Close();
                    dfStream.Dispose();
                }
                return ZipReturn.ZipGood;
            }

            private Stream _writeStream;
            public ZipReturn LocalFileOpenWriteStream(bool raw, bool trrntZip, ulong uncompressedSize, ushort compressionMethod, out Stream stream)
            {
                UncompressedSize = uncompressedSize;
                _compressionMethod = compressionMethod;

                LocalFileHeaderWrite();
                _dataLocation = (ulong)_zipFs.Position;

                if (raw)
                {
                    _writeStream = _zipFs;
                    TrrntZip = trrntZip;
                }
                else
                {
                    if (compressionMethod == 0)
                    {
                        _writeStream = _zipFs;
                        TrrntZip = false;
                    }
                    else
                    {
                        _writeStream = new DeflateStream(_zipFs, CompressionMode.Compress, CompressionLevel.BestCompression, true);
                        TrrntZip = true;
                    }
                }

                stream = _writeStream;
                return stream == null ? ZipReturn.ZipErrorGettingDataStream : ZipReturn.ZipGood;
            }
            public ZipReturn LocalFileCloseWriteStream(byte[] crc32)
            {
                DeflateStream dfStream = _writeStream as DeflateStream;
                if (dfStream != null)
                {
                    dfStream.Flush();
                    dfStream.Close();
                    dfStream.Dispose();
                }

                _compressedSize = (ulong)_zipFs.Position - _dataLocation;

                if (_compressedSize == 0 && UncompressedSize == 0)
                {
                    LocalFileAddDirectory();
                    _compressedSize = (ulong)_zipFs.Position - _dataLocation;
                }

                CRC = crc32;
                WriteCompressedSize();

                return ZipReturn.ZipGood;
            }
            private void WriteCompressedSize()
            {
                long posNow = _zipFs.Position;
                _zipFs.Seek((long)_crc32Location, SeekOrigin.Begin);
                BinaryWriter bw = new BinaryWriter(_zipFs);

                uint tCompressedSize;
                uint tUncompressedSize;
                if (Zip64)
                {
                    tCompressedSize = 0xffffffff;
                    tUncompressedSize = 0xffffffff;
                }
                else
                {
                    tCompressedSize = (uint)_compressedSize;
                    tUncompressedSize = (uint)UncompressedSize;
                }

                bw.Write(CRC[3]);
                bw.Write(CRC[2]);
                bw.Write(CRC[1]);
                bw.Write(CRC[0]);
                bw.Write(tCompressedSize);
                bw.Write(tUncompressedSize);

                // also need to write extradata
                if (Zip64)
                {
                    _zipFs.Seek((long)_extraLocation, SeekOrigin.Begin);
                    bw.Write((ushort)0x0001); // id
                    bw.Write((ushort)16); // data length
                    bw.Write(UncompressedSize);
                    bw.Write(_compressedSize);
                }

                _zipFs.Seek(posNow, SeekOrigin.Begin);

            }


            public void LocalFileCheck()
            {
                if (FileStatus != ZipReturn.ZipUntested) return;

                try
                {
                    Stream sInput = null;
                    _zipFs.Seek((long)_dataLocation, SeekOrigin.Begin);

                    switch (_compressionMethod)
                    {
                        case 8:
                            sInput = new DeflateStream(_zipFs, CompressionMode.Decompress, true);
                            break;
                        case 0:
                            sInput = _zipFs;
                            break;
                    }

                    if (sInput == null)
                    {
                        FileStatus = ZipReturn.ZipErrorGettingDataStream;
                        return;
                    }

                    CRC32Hash crc32 = new CRC32Hash();
                    MD5 lmd5 = System.Security.Cryptography.MD5.Create();
                    SHA1 lsha1 = System.Security.Cryptography.SHA1.Create();

                    if (_buffer0 == null)
                    {
                        _buffer0 = new byte[Buffersize];
                        _buffer1 = new byte[Buffersize];
                    }

                    ulong sizetogo = UncompressedSize;

                    // Pre load the first buffer0
                    int sizeNext = sizetogo > Buffersize ? Buffersize : (int)sizetogo;
                    sInput.Read(_buffer0, 0, sizeNext);
                    int sizebuffer = sizeNext;
                    sizetogo -= (ulong)sizeNext;
                    bool whichBuffer = true;

                    while (sizebuffer > 0)
                    {
                        sizeNext = sizetogo > Buffersize ? Buffersize : (int)sizetogo;

                        Thread t0 = null;
                        if (sizeNext > 0)
                        {
                            t0 = new Thread(() => { sInput.Read(whichBuffer ? _buffer1 : _buffer0, 0, sizeNext); });
                            t0.Start();
                        }

                        byte[] buffer = whichBuffer ? _buffer0 : _buffer1;
                        Thread t1 = new Thread(() => { crc32.TransformBlock(buffer, 0, sizebuffer, null, 0); });
                        Thread t2 = new Thread(() => { lmd5.TransformBlock(buffer, 0, sizebuffer, null, 0); });
                        Thread t3 = new Thread(() => { lsha1.TransformBlock(buffer, 0, sizebuffer, null, 0); });
                        t1.Start();
                        t2.Start();
                        t3.Start();
                        if (t0 != null)
                            t0.Join();
                        t1.Join();
                        t2.Join();
                        t3.Join();

                        sizebuffer = sizeNext;
                        sizetogo -= (ulong)sizeNext;
                        whichBuffer = !whichBuffer;
                    }

                    crc32.TransformFinalBlock(_buffer0, 0, 0);
                    lmd5.TransformFinalBlock(_buffer0, 0, 0);
                    lsha1.TransformFinalBlock(_buffer0, 0, 0);

                    byte[] testcrc = crc32.Hash;
                    md5 = lmd5.Hash;
                    sha1 = lsha1.Hash;

                    if (_compressionMethod == 8)
                    {
                        sInput.Close();
                        sInput.Dispose();
                    }

                    FileStatus = ByteArrCompare(CRC, testcrc) ? ZipReturn.ZipGood : ZipReturn.ZipCRCDecodeError;
                }
                catch
                {
                    FileStatus = ZipReturn.ZipDecodeError;
                }
            }


            public void LocalFileAddDirectory()
            {
                Stream ds = _zipFs;
                ds.WriteByte(03);
                ds.WriteByte(00);
            }



            public ulong LocalFilePos
            {
                get { return RelativeOffsetOfLocalHeader; }
                set { RelativeOffsetOfLocalHeader = value; }
            }
            private static byte[] ReadCRC(BinaryReader br)
            {
                byte[] tCRC = new byte[4];
                tCRC[3] = br.ReadByte();
                tCRC[2] = br.ReadByte();
                tCRC[1] = br.ReadByte();
                tCRC[0] = br.ReadByte();
                return tCRC;
            }


        }


        private IO.FileInfo _zipFileInfo;

        public string ZipFilename
        {
            get { return _zipFileInfo != null ? _zipFileInfo.FullName : ""; }
        }
        public long TimeStamp
        {
            get { return _zipFileInfo != null ? _zipFileInfo.LastWriteTime : 0; }
        }

        private ulong _centerDirStart;
        private ulong _centerDirSize;
        private ulong _endOfCenterDir64;

        byte[] _fileComment;
        private Stream _zipFs;

        private uint _localFilesCount;
        private readonly List<LocalFile> _localFiles = new List<LocalFile>();

        private ZipStatus _pZipStatus;
        private bool _zip64;
        public ZipOpenType ZipOpen;

        public ZipStatus ZipStatus { get { return _pZipStatus; } }

        public int LocalFilesCount() { return _localFiles.Count; }

        public string Filename(int i) { return _localFiles[i].FileName; }
        public ulong UncompressedSize(int i) { return _localFiles[i].UncompressedSize; }
        public ulong? LocalHeader(int i)
        {
            return ((_localFiles[i]._generalPurposeBitFlag & 8) == 0) ? (ulong?)_localFiles[i].RelativeOffsetOfLocalHeader : null;
        }
        public ZipReturn FileStatus(int i) { return _localFiles[i].FileStatus; }
        public byte[] CRC32(int i) { return _localFiles[i].CRC; }
        public byte[] MD5(int i) { return _localFiles[i].md5; }
        public byte[] SHA1(int i) { return _localFiles[i].sha1; }


        ~ZipFile()
        {
            if (_zipFs != null)
            {
                _zipFs.Close();
                _zipFs.Dispose();
            }
        }


        private ZipReturn FindEndOfCentralDirSignature()
        {
            long fileSize = _zipFs.Length;
            long maxBackSearch = 0xffff;

            if (_zipFs.Length < maxBackSearch)
                maxBackSearch = fileSize;

            const long buffSize = 0x400;

            byte[] buffer = new byte[buffSize + 4];

            long backPosition = 4;
            while (backPosition < maxBackSearch)
            {
                backPosition += buffSize;
                if (backPosition > maxBackSearch) backPosition = maxBackSearch;

                long readSize = backPosition > (buffSize + 4) ? (buffSize + 4) : backPosition;

                _zipFs.Position = fileSize - backPosition;

                _zipFs.Read(buffer, 0, (int)readSize);


                for (long i = readSize - 4; i >= 0; i--)
                {
                    if ((buffer[i] != 0x50) || (buffer[i + 1] != 0x4b) || (buffer[i + 2] != 0x05) || (buffer[i + 3] != 0x06)) continue;

                    _zipFs.Position = (fileSize - backPosition) + i;
                    return ZipReturn.ZipGood;
                }
            }
            return ZipReturn.ZipCenteralDirError;
        }


        private ZipReturn EndOfCentralDirRead()
        {
            BinaryReader zipBr = new BinaryReader(_zipFs);

            uint thisSignature = zipBr.ReadUInt32();
            if (thisSignature != EndOfCentralDirSignature)
                return ZipReturn.ZipEndOfCentralDirectoryError;

            ushort tushort = zipBr.ReadUInt16();     // NumberOfThisDisk
            if (tushort != 0) return ZipReturn.ZipEndOfCentralDirectoryError;

            tushort = zipBr.ReadUInt16();     // NumberOfThisDiskCenterDir
            if (tushort != 0) return ZipReturn.ZipEndOfCentralDirectoryError;

            _localFilesCount = zipBr.ReadUInt16();     // TotalNumberOfEnteriesDisk

            tushort = zipBr.ReadUInt16();     // TotalNumber of enteries in the central directory 
            if (tushort != _localFilesCount) return ZipReturn.ZipEndOfCentralDirectoryError;

            _centerDirSize = zipBr.ReadUInt32();     // SizeOfCenteralDir
            _centerDirStart = zipBr.ReadUInt32();     // Offset

            ushort zipFileCommentLength = zipBr.ReadUInt16();

            _fileComment = zipBr.ReadBytes(zipFileCommentLength);

            if (_zipFs.Position != _zipFs.Length) _pZipStatus |= ZipStatus.ExtraData;

            return ZipReturn.ZipGood;
        }
        private void EndOfCentralDirWrite()
        {

            BinaryWriter bw = new BinaryWriter(_zipFs);
            bw.Write(EndOfCentralDirSignature);
            bw.Write((ushort)0); // NumberOfThisDisk
            bw.Write((ushort)0); // NumberOfThisDiskCenterDir
            bw.Write((ushort)(_localFiles.Count >= 0xffff ? 0xffff : _localFiles.Count));  // TotalNumberOfEnteriesDisk
            bw.Write((ushort)(_localFiles.Count >= 0xffff ? 0xffff : _localFiles.Count));  // TotalNumber of enteries in the central directory 
            bw.Write((uint)(_centerDirSize >= 0xffffffff ? 0xffffffff : _centerDirSize));
            bw.Write((uint)(_centerDirStart >= 0xffffffff ? 0xffffffff : _centerDirStart));
            bw.Write((ushort)_fileComment.Length);
            bw.Write(_fileComment, 0, _fileComment.Length);
        }

        private ZipReturn Zip64EndOfCentralDirRead()
        {
            _zip64 = true;
            BinaryReader zipBr = new BinaryReader(_zipFs);

            uint thisSignature = zipBr.ReadUInt32();
            if (thisSignature != Zip64EndOfCentralDirSignatue)
                return ZipReturn.ZipEndOfCentralDirectoryError;

            ulong tulong = zipBr.ReadUInt64(); // Size of zip64 end of central directory record
            if (tulong != 44) return ZipReturn.Zip64EndOfCentralDirError;

            zipBr.ReadUInt16(); // version made by

            ushort tushort = zipBr.ReadUInt16(); // version needed to extract
            if (tushort != 45) return ZipReturn.Zip64EndOfCentralDirError;

            uint tuint = zipBr.ReadUInt32(); // number of this disk
            if (tuint != 0) return ZipReturn.Zip64EndOfCentralDirError;

            tuint = zipBr.ReadUInt32(); // number of the disk with the start of the central directory
            if (tuint != 0) return ZipReturn.Zip64EndOfCentralDirError;

            _localFilesCount = (uint)zipBr.ReadUInt64(); // total number of entries in the central directory on this disk

            tulong = zipBr.ReadUInt64(); // total number of entries in the central directory
            if (tulong != _localFilesCount) return ZipReturn.Zip64EndOfCentralDirError;

            _centerDirSize = zipBr.ReadUInt64(); // size of central directory

            _centerDirStart = zipBr.ReadUInt64(); // offset of start of central directory with respect to the starting disk number

            return ZipReturn.ZipGood;
        }

        private void Zip64EndOfCentralDirWrite()
        {
            BinaryWriter bw = new BinaryWriter(_zipFs);
            bw.Write(Zip64EndOfCentralDirSignatue);
            bw.Write((ulong)44); // Size of zip64 end of central directory record
            bw.Write((ushort)45); // version made by
            bw.Write((ushort)45); // version needed to extract
            bw.Write((uint)0);    // number of this disk
            bw.Write((uint)0);    // number of the disk with the start of the central directroy
            bw.Write((ulong)_localFiles.Count); // total number of entries in the central directory on this disk
            bw.Write((ulong)_localFiles.Count); // total number of entries in the central directory
            bw.Write(_centerDirSize);  // size of central directory
            bw.Write(_centerDirStart); // offset of start of central directory with respect to the starting disk number
        }

        private ZipReturn Zip64EndOfCentralDirectoryLocatorRead()
        {
            _zip64 = true;
            BinaryReader zipBr = new BinaryReader(_zipFs);

            uint thisSignature = zipBr.ReadUInt32();
            if (thisSignature != Zip64EndOfCentralDirectoryLocator)
                return ZipReturn.ZipEndOfCentralDirectoryError;

            uint tuint = zipBr.ReadUInt32();  // number of the disk with the start of the zip64 end of centeral directory
            if (tuint != 0) return ZipReturn.Zip64EndOfCentralDirectoryLocatorError;

            _endOfCenterDir64 = zipBr.ReadUInt64(); // relative offset of the zip64 end of central directroy record

            tuint = zipBr.ReadUInt32();  // total number of disks
            if (tuint != 1) return ZipReturn.Zip64EndOfCentralDirectoryLocatorError;

            return ZipReturn.ZipGood;
        }
        private void Zip64EndOfCentralDirectoryLocatorWrite()
        {
            BinaryWriter bw = new BinaryWriter(_zipFs);
            bw.Write(Zip64EndOfCentralDirectoryLocator);
            bw.Write((uint)0);    // number of the disk with the start of the zip64 end of centeral directory
            bw.Write(_endOfCenterDir64); // relative offset of the zip64 end of central directroy record
            bw.Write((uint)1);  // total number of disks
        }



        public ZipReturn ZipFileOpen(string newFilename, long timestamp, bool readHeaders)
        {
            ZipFileClose();
            _pZipStatus = ZipStatus.None;
            _zip64 = false;
            _centerDirStart = 0;
            _centerDirSize = 0;
            _zipFileInfo = null;

            try
            {
                if (!IO.File.Exists(newFilename))
                {
                    ZipFileClose();
                    return ZipReturn.ZipErrorFileNotFound;
                }
                _zipFileInfo = new IO.FileInfo(newFilename);
                if (_zipFileInfo.LastWriteTime != timestamp)
                {
                    ZipFileClose();
                    return ZipReturn.ZipErrorTimeStamp;
                }
                int errorCode = IO.FileStream.OpenFileRead(newFilename, out _zipFs);
                if (errorCode != 0)
                {
                    ZipFileClose();
                    if (errorCode == 32)
                        return ZipReturn.ZipFileLocked;
                    return ZipReturn.ZipErrorOpeningFile;
                }
            }
            catch (PathTooLongException)
            {
                ZipFileClose();
                return ZipReturn.ZipFileNameToLong;
            }
            catch (IOException)
            {
                ZipFileClose();
                return ZipReturn.ZipErrorOpeningFile;
            }
            ZipOpen = ZipOpenType.OpenRead;

            if (!readHeaders)
                return ZipReturn.ZipGood;


            try
            {
                ZipReturn zRet = FindEndOfCentralDirSignature();
                if (zRet != ZipReturn.ZipGood)
                {
                    ZipFileClose();
                    return zRet;
                }

                long endOfCentralDir = _zipFs.Position;
                zRet = EndOfCentralDirRead();
                if (zRet != ZipReturn.ZipGood)
                {
                    ZipFileClose();
                    return zRet;
                }

                // check if this is a ZIP64 zip and if it is read the Zip64 End Of Central Dir Info
                if (_centerDirStart == 0xffffffff || _centerDirSize == 0xffffffff || _localFilesCount == 0xffff)
                {
                    _zip64 = true;
                    _zipFs.Position = endOfCentralDir - 20;
                    zRet = Zip64EndOfCentralDirectoryLocatorRead();
                    if (zRet != ZipReturn.ZipGood)
                    {
                        ZipFileClose();
                        return zRet;
                    }
                    _zipFs.Position = (long)_endOfCenterDir64;
                    zRet = Zip64EndOfCentralDirRead();
                    if (zRet != ZipReturn.ZipGood)
                    {
                        ZipFileClose();
                        return zRet;
                    }
                }

                bool trrntzip = false;

                // check if the ZIP has a valid TorrentZip file comment
                if (_fileComment.Length == 22)
                {
                    if (GetString(_fileComment).Substring(0, 14) == "TORRENTZIPPED-")
                    {
                        CrcCalculatorStream crcCs = new CrcCalculatorStream(_zipFs, true);
                        byte[] buffer = new byte[_centerDirSize];
                        _zipFs.Position = (long)_centerDirStart;
                        crcCs.Read(buffer, 0, (int)_centerDirSize);
                        crcCs.Flush();
                        crcCs.Close();

                        uint r = (uint)crcCs.Crc;
                        crcCs.Dispose();

                        string tcrc = GetString(_fileComment).Substring(14, 8);
                        string zcrc = r.ToString("X8");
                        if (String.Compare(tcrc, zcrc, StringComparison.Ordinal) == 0)
                            trrntzip = true;

                    }
                }


                // now read the central directory
                _zipFs.Position = (long)_centerDirStart;

                _localFiles.Clear();
                _localFiles.Capacity = (int)_localFilesCount;
                for (int i = 0; i < _localFilesCount; i++)
                {
                    LocalFile lc = new LocalFile(_zipFs);
                    zRet = lc.CenteralDirectoryRead();
                    if (zRet != ZipReturn.ZipGood)
                    {
                        ZipFileClose();
                        return zRet;
                    }
                    _zip64 |= lc.Zip64;
                    _localFiles.Add(lc);
                }

                for (int i = 0; i < _localFilesCount; i++)
                {
                    zRet = _localFiles[i].LocalFileHeaderRead();
                    if (zRet != ZipReturn.ZipGood)
                    {
                        ZipFileClose();
                        return zRet;
                    }
                    trrntzip &= _localFiles[i].TrrntZip;
                }

                // check trrntzip file order
                if (trrntzip)
                    for (int i = 0; i < _localFilesCount - 1; i++)
                    {
                        if (TrrntZipStringCompare(_localFiles[i].FileName, _localFiles[i + 1].FileName) < 0) continue;
                        trrntzip = false;
                        break;
                    }

                // check trrntzip directories
                if (trrntzip)
                    for (int i = 0; i < _localFilesCount - 1; i++)
                    {
                        // see if we found a directory
                        string filename0 = _localFiles[i].FileName;
                        if (filename0.Substring(filename0.Length - 1, 1) != "/") continue;

                        // see if the next file is in that directory
                        string filename1 = _localFiles[i + 1].FileName;
                        if (filename1.Length <= filename0.Length) continue;
                        if (TrrntZipStringCompare(filename0, filename1.Substring(0, filename0.Length)) != 0) continue;

                        // if we found a file in the directory then we do not need the directory entry
                        trrntzip = false;
                        break;
                    }

                if (trrntzip)
                    _pZipStatus |= ZipStatus.TrrntZip;

                return ZipReturn.ZipGood;
            }
            catch
            {
                ZipFileClose();
                return ZipReturn.ZipErrorReadingFile;
            }

        }

        public ZipReturn ZipFileCreate(string newFilename)
        {
            if (ZipOpen != ZipOpenType.Closed)
                return ZipReturn.ZipFileAlreadyOpen;

            CreateDirForFile(newFilename);
            _zipFileInfo = new IO.FileInfo(newFilename);

            int errorCode = IO.FileStream.OpenFileWrite(newFilename, out _zipFs);
            if (errorCode != 0)
            {
                ZipFileClose();
                return ZipReturn.ZipErrorOpeningFile;
            }
            ZipOpen = ZipOpenType.OpenWrite;
            return ZipReturn.ZipGood;
        }

        public void ZipFileClose()
        {
            if (ZipOpen == ZipOpenType.Closed)
            {
                return;
            }

            if (ZipOpen == ZipOpenType.OpenRead)
            {
                if (_zipFs != null)
                {
                    _zipFs.Close();
                    _zipFs.Dispose();
                }
                ZipOpen = ZipOpenType.Closed;
                return;
            }

            _zip64 = false;
            bool lTrrntzip = true;

            _centerDirStart = (ulong)_zipFs.Position;
            if (_centerDirStart >= 0xffffffff)
                _zip64 = true;

            CrcCalculatorStream crcCs = new CrcCalculatorStream(_zipFs, true);

            foreach (LocalFile t in _localFiles)
            {
                t.CenteralDirectoryWrite(crcCs);
                _zip64 |= t.Zip64;
                lTrrntzip &= t.TrrntZip;
            }

            crcCs.Flush();
            crcCs.Close();

            _centerDirSize = (ulong)_zipFs.Position - _centerDirStart;

            _fileComment = lTrrntzip ? GetBytes("TORRENTZIPPED-" + crcCs.Crc.ToString("X8")) : new byte[0];
            _pZipStatus = lTrrntzip ? ZipStatus.TrrntZip : ZipStatus.None;

            crcCs.Dispose();

            if (_zip64)
            {
                _endOfCenterDir64 = (ulong)_zipFs.Position;
                Zip64EndOfCentralDirWrite();
                Zip64EndOfCentralDirectoryLocatorWrite();
            }
            EndOfCentralDirWrite();

            _zipFs.SetLength(_zipFs.Position);
            _zipFs.Flush();
            _zipFs.Close();
            _zipFs.Dispose();
            _zipFileInfo = new IO.FileInfo(_zipFileInfo.FullName);
            ZipOpen = ZipOpenType.Closed;

        }

        public void ZipFileCloseFailed()
        {
            if (ZipOpen == ZipOpenType.Closed)
            {
                return;
            }

            if (ZipOpen == ZipOpenType.OpenRead)
            {
                if (_zipFs != null)
                {
                    _zipFs.Close();
                    _zipFs.Dispose();
                }
                ZipOpen = ZipOpenType.Closed;
                return;
            }

            _zipFs.Flush();
            _zipFs.Close();
            _zipFs.Dispose();
            IO.File.Delete(_zipFileInfo.FullName);
            _zipFileInfo = null;
            ZipOpen = ZipOpenType.Closed;
        }


        private int _readIndex;
        public ZipReturn ZipFileOpenReadStream(int index, bool raw, out Stream stream, out ulong streamSize, out ushort compressionMethod)
        {
            streamSize = 0;
            compressionMethod = 0;
            _readIndex = index;
            stream = null;
            if (ZipOpen != ZipOpenType.OpenRead)
                return ZipReturn.ZipReadingFromOutputFile;

            ZipReturn zRet = _localFiles[index].LocalFileHeaderRead();
            if (zRet != ZipReturn.ZipGood)
            {
                ZipFileClose();
                return zRet;
            }

            return _localFiles[index].LocalFileOpenReadStream(raw, out stream, out streamSize, out compressionMethod);
        }

        public ZipReturn ZipFileOpenReadStreamQuick(ulong pos, bool raw, out Stream stream, out ulong streamSize, out ushort compressionMethod)
        {
            LocalFile tmpFile = new LocalFile(_zipFs) { LocalFilePos = pos };
            _localFiles.Clear();
            _localFiles.Add(tmpFile);
            ZipReturn zr = tmpFile.LocalFileHeaderReadQuick();
            if (zr != ZipReturn.ZipGood)
            {
                stream = null;
                streamSize = 0;
                compressionMethod = 0;
                return zr;
            }
            _readIndex = 0;

            return tmpFile.LocalFileOpenReadStream(raw, out stream, out streamSize, out compressionMethod);
        }

        public ZipReturn ZipFileCloseReadStream()
        {
            return _localFiles[_readIndex].LocalFileCloseReadStream();
        }

        public ZipReturn ZipFileOpenWriteStream(bool raw, bool trrntzip, string filename, ulong uncompressedSize, ushort compressionMethod, out Stream stream)
        {
            stream = null;
            if (ZipOpen != ZipOpenType.OpenWrite)
                return ZipReturn.ZipWritingToInputFile;

            LocalFile lf = new LocalFile(_zipFs, filename);

            ZipReturn retVal = lf.LocalFileOpenWriteStream(raw, trrntzip, uncompressedSize, compressionMethod, out stream);

            _localFiles.Add(lf);

            return retVal;
        }
        public ZipReturn ZipFileCloseWriteStream(byte[] crc32)
        {
            return _localFiles[_localFiles.Count - 1].LocalFileCloseWriteStream(crc32);
        }

        public ZipReturn ZipFileRollBack()
        {
            if (ZipOpen != ZipOpenType.OpenWrite)
                return ZipReturn.ZipWritingToInputFile;

            int fileCount = _localFiles.Count;
            if (fileCount == 0)
                return ZipReturn.ZipErrorRollBackFile;

            LocalFile lf = _localFiles[fileCount - 1];

            _localFiles.RemoveAt(fileCount - 1);
            _zipFs.Position = (long)lf.LocalFilePos;
            return ZipReturn.ZipGood;
        }

        public void ZipFileAddDirectory()
        {
            _localFiles[_localFiles.Count - 1].LocalFileAddDirectory();
        }


        /*
        public void BreakTrrntZip(string filename)
        {
            _zipFs = new FileStream(filename, FileMode.Open, FileAccess.ReadWrite);
            BinaryReader zipBr = new BinaryReader(_zipFs);
            _zipFs.Position = _zipFs.Length - 22;
            byte[] fileComment = zipBr.ReadBytes(22);
            if (GetString(fileComment).Substring(0, 14) == "TORRENTZIPPED-")
            {
                _zipFs.Position = _zipFs.Length - 8;
                _zipFs.WriteByte(48); _zipFs.WriteByte(48); _zipFs.WriteByte(48); _zipFs.WriteByte(48);
                _zipFs.WriteByte(48); _zipFs.WriteByte(48); _zipFs.WriteByte(48); _zipFs.WriteByte(48);
            }

            zipBr.Close();
            _zipFs.Flush();
            _zipFs.Close();
        }
        */

        public void DeepScan()
        {
            foreach (LocalFile lfile in _localFiles)
                lfile.LocalFileCheck();
        }

        private static void CreateDirForFile(string sFilename)
        {
            string strTemp = IO.Path.GetDirectoryName(sFilename);

            if (String.IsNullOrEmpty(strTemp)) return;

            if (IO.Directory.Exists(strTemp)) return;


            while (strTemp.Length > 0 && !IO.Directory.Exists(strTemp))
            {
                int pos = strTemp.LastIndexOf(IO.Path.DirectorySeparatorChar);
                if (pos < 0) pos = 0;
                strTemp = strTemp.Substring(0, pos);
            }

            while (sFilename.IndexOf(IO.Path.DirectorySeparatorChar, strTemp.Length + 1) > 0)
            {
                strTemp = sFilename.Substring(0, sFilename.IndexOf(IO.Path.DirectorySeparatorChar, strTemp.Length + 1));
                IO.Directory.CreateDirectory(strTemp);
            }
        }


        public static string ZipErrorMessageText(ZipReturn zS)
        {
            string ret = "Unknown";
            switch (zS)
            {
                case ZipReturn.ZipGood:
                    ret = "";
                    break;
                case ZipReturn.ZipFileCountError:
                    ret = "The number of file in the Zip does not mach the number of files in the Zips Centeral Directory";
                    break;
                case ZipReturn.ZipSignatureError:
                    ret = "An unknown Signature Block was found in the Zip";
                    break;
                case ZipReturn.ZipExtraDataOnEndOfZip:
                    ret = "Extra Data was found on the end of the Zip";
                    break;
                case ZipReturn.ZipUnsupportedCompression:
                    ret = "An unsupported Compression method was found in the Zip, if you recompress this zip it will be usable";
                    break;
                case ZipReturn.ZipLocalFileHeaderError:
                    ret = "Error reading a zipped file header information";
                    break;
                case ZipReturn.ZipCenteralDirError:
                    ret = "There is an error in the Zip Centeral Directory";
                    break;
                case ZipReturn.ZipReadingFromOutputFile:
                    ret = "Trying to write to a Zip file open for output only";
                    break;
                case ZipReturn.ZipWritingToInputFile:
                    ret = "Tring to read from a Zip file open for input only";
                    break;
                case ZipReturn.ZipErrorGettingDataStream:
                    ret = "Error creating Data Stream";
                    break;
                case ZipReturn.ZipCRCDecodeError:
                    ret = "CRC error";
                    break;
                case ZipReturn.ZipDecodeError:
                    ret = "Error unzipping a file";
                    break;
            }

            return ret;
        }

        private static byte[] GetBytes(String s)
        {
            char[] c = s.ToCharArray();
            byte[] b = new byte[c.Length];
            for (int i = 0; i < c.Length; i++)
            {
                char t = c[i];
                b[i] = t > 255 ? (byte)'?' : (byte)c[i];
            }
            return b;
        }
        private static bool IsUnicode(string s)
        {
            char[] c = s.ToCharArray();
            for (int i = 0; i < c.Length; i++)
                if (c[i] > 255) return true;
            return false;
        }

        private static string GetString(byte[] b)
        {
            string s = "";
            for (int i = 0; i < b.Length; i++)
                s += (char)b[i];
            return s;
        }
        private static bool CompareString(string s1, string s2)
        {
            char[] c1 = s1.ToCharArray();
            char[] c2 = s2.ToCharArray();

            if (c1.Length != c2.Length)
                return false;

            for (int i = 0; i < c1.Length; i++)
                if (c1[i] != c2[i]) return false;
            return true;
        }


        private static bool ByteArrCompare(byte[] b0, byte[] b1)
        {
            if (b0 == null || b1 == null)
                return false;
            if (b0.Length != b1.Length)
                return false;

            for (int i = 0; i < b0.Length; i++)
            {
                if (b0[i] != b1[i])
                    return false;
            }
            return true;
        }

        private static int TrrntZipStringCompare(string string1, string string2)
        {
            char[] bytes1 = string1.ToCharArray();
            char[] bytes2 = string2.ToCharArray();

            int pos1 = 0;
            int pos2 = 0;

            for (;;)
            {
                if (pos1 == bytes1.Length)
                    return ((pos2 == bytes2.Length) ? 0 : -1);
                if (pos2 == bytes2.Length)
                    return 1;

                int byte1 = bytes1[pos1++];
                int byte2 = bytes2[pos2++];

                if (byte1 >= 65 && byte1 <= 90) byte1 += 0x20;
                if (byte2 >= 65 && byte2 <= 90) byte2 += 0x20;

                if (byte1 < byte2)
                    return -1;
                if (byte1 > byte2)
                    return 1;
            }
        }
    }

}
