private void BulkExtractTPFButton_Click(object sender, EventArgs e)
        {
            string outputPath = null;

            var dialog = new CommonOpenFileDialog();
            dialog.IsFolderPicker = true;
            dialog.EnsurePathExists = true;
            dialog.Title = "Select folder containing multiple TPF's to extract.";
            if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
                outputPath = dialog.FileName;
            else
                return;


            OverallProgressBar.Value = 0;
            var tpfs = Directory.EnumerateFiles(outputPath);
            OverallProgressBar.Maximum = tpfs.Count();
            foreach (var item in tpfs)
            {
                if (!item.EndsWith(".tpf", StringComparison.OrdinalIgnoreCase))  // KFreon: TPF's only.
                    continue;

                SaltTPF.ZipReader zippy = new SaltTPF.ZipReader(item);

                OverallStatusLabel.Text = "Extracting " + Path.GetFileName(zippy._filename);

                // KFreon: Create individual directory
                string extractPath = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(zippy._filename));
                Directory.CreateDirectory(extractPath);

                List<string> hashes = GetHashesFromTPF(zippy, false);

                for (int i = 0; i < zippy.Entries.Count - 1; i++) 
                {
                    var entry = zippy.Entries[i];
                    string filename = entry.Filename;
                    string hash = hashes[i].Split('|').First();

                    if (!filename.Contains(hash))
                    {
                        string tempname = Path.GetFileNameWithoutExtension(filename);
                        filename = filename.Replace(tempname, tempname + "_" + hash);
                    }

                    if (File.Exists(Path.Combine(extractPath, filename)))
                        continue;

                    entry.Extract(false, Path.Combine(extractPath, filename));
                }

                OverallProgressBar.Value++;
            }

            OverallProgressBar.Value = OverallProgressBar.Maximum;
            MessageBox.Show("Done");
        }
        private void LoadTPF(string file)
        {
            EnableSecondProgressBar(true);

            // KFreon: Open TPF and set some properties
            SaltTPF.ZipReader zippy = new SaltTPF.ZipReader(file);
            zippy.Description = "TPF Details\n\nFilename:  \n" + zippy._filename + "\n\nComment:  \n" + zippy.EOFStrct.Comment + "\nNumber of stored files:  " + zippy.Entries.Count;
            zippy.Scanned = false;
            int zippyInd = zippys.Count;
            zippys.Add(zippy);
            int numEntries = zippy.Entries.Count;

            // KFreon: Setup nodes and GUI elements
            this.Invoke(new Action(() =>
            {
                CurrentProg.ChangeProgressBar(0, numEntries);
                CurrentStatusLabel.Text = "Processing file: " + Path.GetFileName(file);
            }));
            DebugOutput.PrintLn("Loading file: " + Path.GetFileName(file));

            // KFreon: Get hash info from TPF
            // KFreon: Get individual hashes without duplicate lines
            // Heff: Fix weird uppercase X
            List<string> parts = GetHashesFromTPF(zippy);
            if (parts == null)
                return;


            // KFreon: Thread TPF loading
            ParallelOptions po = new ParallelOptions();
            po.MaxDegreeOfParallelism = Properties.Settings.Default.NumThreads;
            DebugOutput.PrintLn("Reading TPF using " + po.MaxDegreeOfParallelism + " threads.");
            List<TPFTexInfo> temptexes = new List<TPFTexInfo>();
            for (int i = 0; i < numEntries; i++)
            {
                temptexes.Add(new TPFTexInfo());
            }
            Parallel.For(0, numEntries, po, i =>
            {
                // KFreon: Add TPF entries to TotalTexes list
                TPFTexInfo tmpTex = new TPFTexInfo(zippy.Entries[i].Filename, i, null, zippy, WhichGame);

                // KFreon: Find and set hash
                foreach (string line in parts)
                    if (line.ToLowerInvariant().Contains(tmpTex.FileName.ToLowerInvariant()))
                    {
                        tmpTex.Hash = KFreonLib.Textures.Methods.FormatTexmodHashAsUint(line);
                        tmpTex.OriginalHash = tmpTex.Hash;
                        tmpTex.FileName = line.Split('|')[1].Replace("\r", "");
                        break;
                    }

                // KFreon: If hash gen failed, notify
                if (!tmpTex.isDef && tmpTex.Hash == 0)
                    DebugOutput.PrintLn("Failure to get hash for entry " + i + " in " + file);

                // KFreon: Get details
                if (!tmpTex.isDef)
                    tmpTex.EnumerateDetails();

                temptexes[i] = tmpTex;

                // Heff: cancel background loaders
                if (cts.IsCancellationRequested)
                    return;

                CurrentProg.IncrementBar();
            });

            // KFreon: Load textures into list and treeview
            LoadedTexes.AddRange(temptexes);
            EnableSecondProgressBar(false);
        }
        private void LoadTPF(string file)
        {
            EnableSecondProgressBar(true);

            // KFreon: Open TPF and set some properties
            SaltTPF.ZipReader zippy = new SaltTPF.ZipReader(file);
            zippy.Description = "TPF Details\n\nFilename:  \n" + zippy._filename + "\n\nComment:  \n" + zippy.EOFStrct.Comment + "\nNumber of stored files:  " + zippy.Entries.Count;
            zippy.Scanned = false;
            int zippyInd = zippys.Count;
            zippys.Add(zippy);
            int numEntries = zippy.Entries.Count;

            // KFreon: Setup nodes and GUI elements
            this.Invoke(new Action(() =>
            {
                CurrentProg.ChangeProgressBar(0, numEntries);
                CurrentStatusLabel.Text = "Processing file: " + Path.GetFileName(file);
            }));
            DebugOutput.PrintLn("Loading file: " + Path.GetFileName(file));

            // KFreon: Get hash info from TPF
            string alltext = "";
            try
            {
                byte[] data = zippy.Entries[numEntries - 1].Extract(true);
                char[] chars = new char[data.Length];
                for (int i = 0; i < data.Length; i++)
                    chars[i] = (char)data[i];
                alltext = new string(chars);
            }
            catch (Exception e)
            {
                MessageBox.Show("An error occurred during extraction: " + e.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // KFreon: Get individual hashes without duplicate lines
            List<string> parts = alltext.Replace("\r", "").Split('\n').ToList();
            parts.RemoveAll(s => s == "\0");
            List<string> tempparts = new List<string>();
            foreach (string part in parts)
                if (!tempparts.Contains(part))
                    tempparts.Add(part);
            parts = tempparts;


            // KFreon: Thread TPF loading
            ParallelOptions po = new ParallelOptions();
            po.MaxDegreeOfParallelism = 1;//Properties.Settings.Default.NumThreads;
            DebugOutput.PrintLn("Reading TPF using " + po.MaxDegreeOfParallelism + " threads.");
            List<TPFTexInfo> temptexes = new List<TPFTexInfo>();
            for (int i = 0; i < numEntries; i++)
            {
                temptexes.Add(new TPFTexInfo());
            }

            Parallel.For(0, numEntries, po, i =>
            {
                // KFreon: Add TPF entries to TotalTexes list
                TPFTexInfo tmpTex = new TPFTexInfo(zippy.Entries[i].Filename, i, null, zippy, WhichGame);

                // KFreon: Find and set hash
                foreach (string line in parts)
                    if (line.ToLowerInvariant().Contains(tmpTex.FileName.ToLowerInvariant()))
                    {
                        tmpTex.Hash = KFreonLib.Textures.Methods.FormatTexmodHashAsUint(line);
                        tmpTex.OriginalHash = tmpTex.Hash;
                        tmpTex.FileName = line.Split('|')[1].Replace("\r", "");
                        break;
                    }

                // KFreon: If hash gen failed, notify
                if (!tmpTex.isDef && tmpTex.Hash == 0)
                    DebugOutput.PrintLn("Failure to get hash for entry " + i + " in " + file);

                // KFreon: Get details
                if (!tmpTex.isDef)
                    tmpTex.EnumerateDetails();

                temptexes[i] = tmpTex;
                
                CurrentProg.IncrementBar();
            });

            // KFreon: Load textures into list and treeview
            LoadedTexes.AddRange(temptexes);
            EnableSecondProgressBar(false);
        }