/// <summary>
        /// The load.
        /// </summary>
        /// <param name="bundleId">
        /// The bundle id.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public bool Load(string bundleId)
        {
            string headerFile = bundleId + "_h.bundle";
            if (!File.Exists(headerFile))
            {
                Console.WriteLine("Bundle header file does not exist.");
                return false;
            }

            try
            {
                var fs = new FileStream(headerFile, FileMode.Open);
                var br = new BinaryReader(fs);
                this.Header.Add(br.ReadUInt32());
                this.Header.Add(br.ReadUInt32());
                uint itemCount = br.ReadUInt32();
                this.Header.Add(itemCount);
                this.Header.Add(br.ReadUInt32());
                this.Header.Add(br.ReadUInt32());
                this.HasLengthField = this.Header[4] == 24;
                if (this.HasLengthField)
                {
                    this.Header.Add(br.ReadUInt32());
                    this.Header.Add(br.ReadUInt32());
                }

                for (int i = 0; i < itemCount; ++i)
                {
                    var be = new BundleEntry { Id = br.ReadUInt32(), Address = br.ReadUInt32() };
                    if (this.HasLengthField)
                    {
                        be.Length = br.ReadInt32();
                    }

                    this.entries.Add(be);
                    if (this.HasLengthField || i <= 0)
                    {
                        continue;
                    }

                    BundleEntry pbe = this.entries[i - 1];
                    pbe.Length = (int)be.Address - (int)pbe.Address;
                }

                if (itemCount > 0 && !this.HasLengthField)
                {
                    this.entries[this.entries.Count - 1].Length = -1;
                }

                this.Footer = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
                br.Close();
                fs.Close();
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }
        /// <summary>
        /// The load.
        /// </summary>
        /// <param name="bundleId">
        /// The bundle id.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public bool Load(string bundleId)
        {
            string headerFile = bundleId + "_h.bundle";
            if (!File.Exists(headerFile))
            {
                Console.WriteLine("Bundle header file does not exist.");
                return false;
            }

            try
            {
                var fs = new FileStream(headerFile, FileMode.Open);
                var br = new BinaryReader(fs);
                this.Header.Add(br.ReadUInt32()); // offset to footer
                this.Header.Add(br.ReadUInt32()); // tag
                uint itemCount = br.ReadUInt32(); //count
                this.Header.Add(itemCount);
                this.Header.Add(br.ReadUInt32()); //count
                this.Header.Add(br.ReadUInt32()); //offset

                uint offset;
                if (this.Header[1] == this.Header[2])
                {
                    offset = this.Header[3];
                }
                else
                {
                    if (this.Header[1] == 0)
                        offset = this.Header[4] + 4;
                    else
                        offset = this.Header[4];
                }

                this.HasLengthField = offset == 24;
                if (this.HasLengthField)
                {
                    this.Header.Add(br.ReadUInt32());
                }

                br.BaseStream.Position = offset;

                this.Header.Add(br.ReadUInt32());

                for (int i = 0; i < itemCount; ++i)
                {
                    var be = new BundleEntry { Id = br.ReadUInt32(), Address = br.ReadUInt32() };
                    if (this.HasLengthField)
                    {
                        be.Length = br.ReadInt32();
                    }

                    this.entries.Add(be);
                    if (this.HasLengthField || i <= 0)
                    {
                        continue;
                    }

                    BundleEntry pbe = this.entries[i - 1];
                    pbe.Length = (int)be.Address - (int)pbe.Address;
                }

                if (itemCount > 0 && !this.HasLengthField)
                {
                    this.entries[this.entries.Count - 1].Length = -1;
                }

                //Footer breakdown
                /*
                 * uint32 - tag
                 * uint32 - section size
                 * uint32 - count
                 * uint32 - unknown
                 * uint32 - unknown
                 * uint32 - tag?
                 * foreach (count):
                 *  uint64 - hash (extension)
                 *  uint64 - hash (path)
                 * uint32 - end?
                 * uint32 (0) - end
                */
                this.Footer = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
                br.Close();
                fs.Close();
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }
        /// <summary>
        /// The write zip entry.
        /// </summary>
        /// <param name="output">
        /// The output.
        /// </param>
        /// <param name="rewriteItem">
        /// Replacement parameters in form of BundleRewriteItem
        /// </param>
        /// <returns>
        /// How many bytes were written to the stream.
        /// </returns>
        private int WriteZipEntry(BundleEntry bundleEntry, Stream input, Stream output, BundleRewriteItem rewriteItem)
        {
            if (rewriteItem.SourceFile != null)
            {
                if(this.Zip == null)
                    this.Zip = new ZipFile(rewriteItem.SourceFile);
                else if (!this.Zip.Name.Equals(rewriteItem.SourceFile))
                {
                    this.Zip.Dispose();
                    this.Zip = new ZipFile(rewriteItem.SourceFile);
                }
            }

            int extractedLength = 0;

            ZipEntry zip_entry = this.Zip[rewriteItem.ReplacementFile];
            if (zip_entry != null)
            {
                if (zip_entry.UsesEncryption)
                {
                    zip_entry.Password = "******";
                    //zip_entry.Encryption = EncryptionAlgorithm.WinZipAes256;
                }

                var br = new BinaryReader(input);
                var bw = new BinaryWriter(output);
                var data = new MemoryStream();
                zip_entry.Extract(data);
                data.Seek(0, SeekOrigin.Begin);

                if (zip_entry.FileName.EndsWith(".script"))
                {
                    StreamReader scriptStream = new StreamReader(data);

                    List<BundleRewriteScriptAction> scriptActions = ParseScriptActions(ref scriptStream, zip_entry.FileName);
                    data.Close();
                    scriptStream.Close();

                    //apply script functions here
                    if (input != output)
                        input.Seek(bundleEntry.Address, SeekOrigin.Begin);
                    else
                        input.Seek(0, SeekOrigin.Begin);
                    long entryLength = bundleEntry.Length == -1 ? input.Length - input.Position : bundleEntry.Length;

                    List<byte> entryBytes = br.ReadBytes((int)entryLength).ToList();

                    output.Seek(0, SeekOrigin.Begin);
                    if (!ApplyScriptActions(ref entryBytes, ref scriptActions))
                    {
                        bw.Write(br.ReadBytes((int)entryLength));
                        return (int)(entryLength);
                    }

                    bw.Write(entryBytes.ToArray());
                    return (int)entryBytes.ToArray().Length;
                }

                output.Seek(0, SeekOrigin.Begin);

                zip_entry.Extract(output);
                extractedLength = (int)zip_entry.UncompressedSize;

                data.Close();
            }

            return extractedLength;
        }
        /// <summary>
        /// The restore entry.
        /// </summary>
        /// <param name="output">
        /// The output.
        /// </param>
        /// <param name="rewriteItem">
        /// Replacement parameters in form of BundleRewriteItem
        /// </param>
        /// <returns>
        /// How many bytes were written to the stream.
        /// </returns>
        private int RestoreEntry(BundleEntry restoreBundleEntry, uint currentAddress, Stream input, Stream output)
        {
            var br = new BinaryReader(input);
            var bw = new BinaryWriter(output);

            input.Seek(restoreBundleEntry.Address, SeekOrigin.Begin);
            long entryLength = restoreBundleEntry.Length == -1 ? input.Length - input.Position : restoreBundleEntry.Length;

            output.Seek(currentAddress, SeekOrigin.Begin);
            bw.Write(br.ReadBytes((int)entryLength));

            return (int)entryLength;
        }
        /// <summary>
        /// The patch bundle restore.
        /// </summary>
        /// <param name="bundleId">
        /// The bundle id.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private bool PatchBundleRestore(BundleMod mod, string bundleId, bool method)
        {
            if (method)
            {
                if (!File.Exists(Path.Combine(this._assetFolder, bundleId + ".bundle")) || !File.Exists(Path.Combine(this._assetFolder, bundleId + "_h.bundle")) || !File.Exists(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle")) || !File.Exists(Path.Combine(this._assetFolder, "asset_backups", bundleId + "_h.bundle")))
                    return false;

                File.Delete(Path.Combine(this._assetFolder, bundleId + ".bundle"));
                File.Delete(Path.Combine(this._assetFolder, bundleId + "_h.bundle"));
                File.Copy(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle"), Path.Combine(this._assetFolder, bundleId + ".bundle"), true);
                File.Copy(Path.Combine(this._assetFolder, "asset_backups", bundleId + "_h.bundle"), Path.Combine(this._assetFolder, bundleId + "_h.bundle"), true);
                this.SetBundleProgress("Done", 100);
                return true;
            }

            var inHeader = new BundleHeader();
            var inRestoreHeader = new BundleHeader();
            Dictionary<uint, BundleEntry> inRestoreDictionary = new Dictionary<uint, BundleEntry>();
            if (!inHeader.Load(Path.Combine(this._assetFolder, bundleId)))
            {
                return false;
            }

            if (!inRestoreHeader.Load(Path.Combine(this._assetFolder, "asset_backups", bundleId)))
                return false;

            foreach (BundleEntry entry in inRestoreHeader.Entries)
            {
                if (!inRestoreDictionary.ContainsKey(entry.Id))
                    inRestoreDictionary.Add(entry.Id, entry);
            }

            var outHeader = new BundleHeader();
            outHeader.Footer = inHeader.Footer;
            outHeader.Header = inHeader.Header;
            outHeader.HasLengthField = inHeader.HasLengthField;
            var inRestoreBundle = new FileStream(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle"), FileMode.Open, FileAccess.Read);
            var inBundle = new FileStream(Path.Combine(this._assetFolder, bundleId + ".bundle"), FileMode.Open, FileAccess.Read);
            var outBundle = new FileStream(
                Path.Combine(this._assetFolder, bundleId + ".bundle.new"),
                FileMode.OpenOrCreate,
                FileAccess.ReadWrite);
            int currentAddress = 0;
            int currentEntry = 1;
            int entryCount = inHeader.Entries.Count;
            foreach (BundleEntry entry in inHeader.Entries)
            {
                if (currentEntry % 100 == 0)
                {
                    this.SetBundleProgress(
                        string.Format("Writing entry {0}/{1}", currentEntry, entryCount),
                        (int)((currentEntry / (float)entryCount) * 100.0f));
                }

                var newEntry = new BundleEntry();
                newEntry.Id = entry.Id;
                newEntry.Length = entry.Length;
                newEntry.Address = (uint)currentAddress;
                bool replaced = false;

                foreach (int length in from rewriteItem in mod.ItemQueue
                                       where rewriteItem.Ids.Contains(entry.Id)
                                       select this.RestoreEntry(inRestoreDictionary[entry.Id], (uint)currentAddress, inRestoreBundle, outBundle))
                {
                    currentAddress += length;
                    newEntry.Length = length;
                    replaced = true;
                    break;
                }

                outHeader.Entries.Add(newEntry);

                if (!replaced)
                {
                    inBundle.Seek(entry.Address, SeekOrigin.Begin);
                    long entryLength = entry.Length == -1 ? inBundle.Length - inBundle.Position : entry.Length;
                    var br = new BinaryReader(inBundle);
                    var bw = new BinaryWriter(outBundle);
                    bw.Write(br.ReadBytes((int)entryLength));
                    currentAddress += (int)entryLength;
                }

                currentEntry += 1;
            }

            inBundle.Close();
            inRestoreBundle.Close();
            outBundle.Close();
            var outHeaderStream = new FileStream(
                Path.Combine(this._assetFolder, bundleId + "_h.bundle"),
                FileMode.OpenOrCreate,
                FileAccess.Write);
            var outHeaderBr = new BinaryWriter(outHeaderStream);
            outHeader.WriteHeader(outHeaderBr);
            foreach (BundleEntry entry in outHeader.Entries)
            {
                entry.WriteEntry(outHeaderBr, outHeader.HasLengthField);
            }

            outHeader.WriteFooter(outHeaderBr);
            outHeaderBr.Close();
            outHeaderStream.Close();
            File.Delete(Path.Combine(this._assetFolder, bundleId + ".bundle"));
            File.Move(Path.Combine(this._assetFolder, bundleId + ".bundle.new"), Path.Combine(this._assetFolder, bundleId + ".bundle"));
            this.SetBundleProgress("Done", 100);
            return true;
        }
        /// <summary>
        /// The patch bundle.
        /// </summary>
        /// <param name="bundleId">
        /// The bundle id.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private bool PatchBundle(string bundleId)
        {
            System.Diagnostics.Stopwatch st_total = new System.Diagnostics.Stopwatch();
            System.Diagnostics.Stopwatch st_entry = new System.Diagnostics.Stopwatch();
            System.Diagnostics.Stopwatch st_writing = new System.Diagnostics.Stopwatch();

            bool isAll = bundleId.Contains("all_");
            var inHeader = new BundleHeader();
            if (!inHeader.Load(Path.Combine(this._assetFolder, bundleId)))
            {
                return false;
            }

            var outHeader = new BundleHeader();
            outHeader.Footer = inHeader.Footer;
            outHeader.Header = inHeader.Header;
            outHeader.HasLengthField = inHeader.HasLengthField;
            var inBundle = new FileStream(Path.Combine(this._assetFolder, bundleId + ".bundle"), FileMode.Open, FileAccess.Read);
            var outBundle = new FileStream(
                Path.Combine(this._assetFolder, bundleId + ".bundle.new"),
                FileMode.Create,
                FileAccess.ReadWrite);
            //BufferedStream bsin = new BufferedStream(inBundle);
            //BufferedStream bsout = new BufferedStream(outBundle);
            var br = new BinaryReader(inBundle);
            var bw = new BinaryWriter(outBundle);
            long inFileLength = inBundle.Length;
            int currentAddress = 0;
            int currentEntry = 1;
            int entryCount = inHeader.Entries.Count;

            //stream buffers
            //int bufferSize = 4096; //1024^2
            byte[] buffer = new byte[this.bufferSize];

            if (isAll)
                inHeader.SortEntriesAddress();

            foreach (BundleEntry entry in inHeader.Entries)
            {
                st_total.Restart();
                st_entry.Restart();

                if (currentEntry % 100 == 0)
                {
                    this.SetBundleProgress(
                        string.Format("Writing entry {0}/{1}", currentEntry, entryCount),
                        (int)((currentEntry / (float)entryCount) * 100.0f));
                }

                var newEntry = new BundleEntry();
                newEntry.Id = entry.Id;
                newEntry.Length = entry.Length;
                newEntry.Address = (uint)currentAddress;
                bool replaced = false;
                bool firstpatched = false;
                bool restore = false;

                if (this._rewriteItems.ContainsKey(entry.Id))
                {
                    if (this._rewriteItems[entry.Id].toRestore)
                        restore = true;

                    MemoryStream newData = new MemoryStream();

                    if (restore)
                    {
                        if (this._backupType == 0)
                        {
                            var inRestoreBundle = new FileStream(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle"), FileMode.Open, FileAccess.Read);
                            var inRestoreHeader = new BundleHeader();
                            if (!inRestoreHeader.Load(Path.Combine(this._assetFolder, "asset_backups", bundleId)))
                                return false;

                            foreach (BundleEntry restoreEntry in inRestoreHeader.Entries)
                            {
                                if (restoreEntry.Id == entry.Id)
                                {
                                    newEntry.Length = this.RestoreEntry(restoreEntry, 0, inRestoreBundle, newData);
                                    replaced = true;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            NameEntry ne = StaticStorage.Index.Id2Name(entry.Id);
                            if (File.Exists(Path.Combine(this._assetFolder, "asset_backups", ne.ToString())))
                            {
                                using (FileStream importFS = new FileStream(Path.Combine(this._assetFolder, "asset_backups", ne.ToString()), FileMode.Open, FileAccess.Read))
                                {
                                    using (BinaryReader importBR = new BinaryReader(importFS))
                                    {
                                        newEntry.Length = (int)importFS.Length;

                                        newData.Seek(0, SeekOrigin.Begin);
                                        newData.Write(importBR.ReadBytes((int)importFS.Length), 0, (int)importFS.Length);
                                        replaced = true;
                                    }
                                }

                            }
                            else
                            {
                                return false;
                            }
                        }
                    }

                    if (this._rewriteItems[entry.Id].priorityBundleRewriteItem != new BundleRewriteItem())
                    {
                        newEntry.Length = this.WriteZipEntry(entry, inBundle, newData, this._rewriteItems[entry.Id].priorityBundleRewriteItem);
                        firstpatched = true;
                        replaced = true;
                    }

                    foreach (var rewriteItem in this._rewriteItems[entry.Id].BundleRewriteItem_queue)
                    {
                        if (rewriteItem.toRemove)
                            continue;

                        if (!firstpatched)
                        {
                            newEntry.Length = this.WriteZipEntry(entry, inBundle, newData, rewriteItem);
                            firstpatched = true;
                        }
                        else
                        {
                            newEntry.Length = this.WriteZipEntry(newEntry, newData, newData, rewriteItem);
                        }

                        replaced = true;
                    }

                    if (newData.Length > 0L)
                    {

                        //buffer = new byte[megabyte];
                        newData.Position = 0L;
                        int bytesRead = newData.Read(buffer, 0, this.bufferSize);
                        while (bytesRead > 0)
                        {
                            outBundle.Write(buffer, 0, bytesRead);
                            bytesRead = newData.Read(buffer, 0, this.bufferSize);
                        }

                        //outBundle.Seek(currentAddress, SeekOrigin.Begin);
                        //newData.CopyTo(outBundle);
                    }

                }

                st_entry.Stop();
                st_writing.Restart();

                if (replaced)
                    currentAddress += newEntry.Length;

                outHeader.Entries.Add(newEntry);

                if (!replaced)
                {
                    /*
                        inBundle.Seek(entry.Address, SeekOrigin.Begin);
                        long entryLength = entry.Length == -1 ? inBundle.Length - inBundle.Position : entry.Length;
                        var br = new BinaryReader(inBundle);
                        var bw = new BinaryWriter(outBundle);
                        bw.Write(br.ReadBytes((int)entryLength));
                        currentAddress += (int)entryLength;
                     */

                    //inBundle.Seek(entry.Address, SeekOrigin.Begin);
                    inBundle.Seek((long)entry.Address, SeekOrigin.Begin);
                    long entryLength = (entry.Length == -1 ? inFileLength - (long)entry.Address : (long)entry.Length);
                    int remaining = (int)entryLength;
                    int totalread = 0;
                    //buffer = new byte[megabyte];
                    //bw.Write(br.ReadBytes((int)entryLength));

                    int bytesRead = inBundle.Read(buffer, 0, (((remaining - totalread) / this.bufferSize) > 0 ? this.bufferSize : remaining - totalread));
                    totalread += bytesRead;
                    while (bytesRead > 0 && totalread <= remaining)
                    {
                        bw.Write(buffer, 0, bytesRead);
                        bytesRead = inBundle.Read(buffer, 0, (((remaining - totalread) / this.bufferSize) > 0 ? this.bufferSize : remaining - totalread));
                        totalread += bytesRead;
                    }
                    //bw.Flush();

                    currentAddress += (int)entryLength;
                }

                currentEntry += 1;
                CurrentEntryCount++;
                SpeedEntryCount++;

                st_writing.Stop();
                st_total.Stop();
                if (st_total.ElapsedMilliseconds > 200)
                    Console.WriteLine(bundleId + " - T: " + st_total.ElapsedMilliseconds + " ms. [entry: " + st_entry.ElapsedMilliseconds + " ms. writing: " + st_writing.ElapsedMilliseconds + " ms.] with " + newEntry.Length + " for replaced " + replaced);
            }

            bw.Flush();
            inBundle.Close();
            outBundle.Close();

            if (isAll)
                outHeader.SortEntriesId();
            var outHeaderStream = new FileStream(
                Path.Combine(this._assetFolder, bundleId + "_h.bundle"),
                FileMode.OpenOrCreate,
                FileAccess.Write);
            var outHeaderBr = new BinaryWriter(outHeaderStream);
            outHeader.WriteHeader(outHeaderBr);
            foreach (BundleEntry entry in outHeader.Entries)
            {
                entry.WriteEntry(outHeaderBr, outHeader.HasLengthField);
            }

            outHeader.WriteFooter(outHeaderBr);
            outHeaderBr.Close();
            outHeaderStream.Close();
            File.Delete(Path.Combine(this._assetFolder, bundleId + ".bundle"));
            File.Move(Path.Combine(this._assetFolder, bundleId + ".bundle.new"), Path.Combine(this._assetFolder, bundleId + ".bundle"));
            this.SetBundleProgress("Done", 100);
            return true;
        }