Example #1
0
        public static bool UninstallMods(CheckedListBox.CheckedIndexCollection modIndices, bool skipCleanup = false) // Uninstalls mods based on their indices in the list
        {
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();
            Debug.LogLine("[Uninstall] Start", Debug.LogLevel.Basic);

            // initial cleanup
            ModManager.ClearBuildFiles(ZeroPath, OnePath, SnakeBiteSettings, SavePresetPath);
            ModManager.ClearSBGameDir();
            ModManager.CleanupFolders();

            // backup preset build
            if (Properties.Settings.Default.AutosaveRevertPreset == true)
            {
                PresetManager.SavePreset(SavePresetPath + build_ext);
            }
            else
            {
                Debug.LogLine("[Uninstall] Skipping RevertChanges.MGSVPreset Save", Debug.LogLevel.Basic);
            }

            GzsLib.LoadDictionaries();
            File.Copy(SnakeBiteSettings, SnakeBiteSettings + build_ext, true);
            List <ModEntry> mods = SBBuildManager.GetInstalledMods();

            List <ModEntry> selectedMods = new List <ModEntry>();

            foreach (int index in modIndices)
            {
                ModEntry mod = mods[index];
                selectedMods.Add(mod);
            }

            List <string> zeroFiles  = new List <string>();
            bool          hasQarZero = ModManager.hasQarZeroFiles(selectedMods);

            if (hasQarZero)
            {
                // if necessary, extracts 00.dat and creates a list of filenames, which is pruned throughout the uninstall process and repacked at the end.
                zeroFiles = GzsLib.ExtractArchive <QarFile>(ZeroPath, "_working0");
                zeroFiles.RemoveAll(file => file.EndsWith("_unknown"));
            }

            List <string> oneFiles = null;
            bool          hasFtexs = ModManager.foundLooseFtexs(selectedMods);

            if (hasFtexs)
            {
                // if necessary, extracts 01.dat and creates a list of filenames similar to zeroFiles. only textures are pruned from the list.
                oneFiles = GzsLib.ExtractArchive <QarFile>(OnePath, "_working1");
                oneFiles.RemoveAll(file => file.EndsWith("_unknown"));
            }

            //end of qar extraction
            GameData gameData = SBBuildManager.GetGameData();

            ModManager.ValidateGameData(ref gameData, ref zeroFiles);

            Debug.LogLine("[Uninstall] Building gameFiles lists", Debug.LogLevel.Basic);
            var baseGameFiles = GzsLib.ReadBaseData();

            try
            {
                ModManager.PrepGameDirFiles();
                // begin uninstall
                UninstallMods(selectedMods, ref zeroFiles, ref oneFiles);

                if (hasQarZero)
                {
                    zeroFiles.Sort();
                    GzsLib.WriteQarArchive(ZeroPath + build_ext, "_working0", zeroFiles, GzsLib.zeroFlags);
                }

                if (hasFtexs)
                {
                    oneFiles.Sort();
                    GzsLib.WriteQarArchive(OnePath + build_ext, "_working1", oneFiles, GzsLib.oneFlags);
                }
                // end of qar rebuild

                // overwrite old mod data
                ModManager.PromoteGameDirFiles();
                ModManager.PromoteBuildFiles(ZeroPath, OnePath, SnakeBiteSettings, SavePresetPath);

                if (!skipCleanup)
                {
                    ModManager.CleanupFolders();
                    ModManager.ClearSBGameDir();
                }

                Debug.LogLine("[Uninstall] Uninstall complete", Debug.LogLevel.Basic);
                stopwatch.Stop();
                Debug.LogLine($"[Uninstall] Uninstall took {stopwatch.ElapsedMilliseconds} ms", Debug.LogLevel.Basic);
                return(true);
            }
            catch (Exception e)
            {
                Debug.LogLine("[Uninstall] Exception: " + e, Debug.LogLevel.Basic);
                stopwatch.Stop();
                Debug.LogLine($"[Uninstall] Uninstall failed at {stopwatch.ElapsedMilliseconds} ms", Debug.LogLevel.Basic);
                MessageBox.Show("An error has occurred during the uninstallation process and SnakeBite could not uninstall the selected mod(s).\nException: " + e);

                // clean up failed files
                ModManager.ClearBuildFiles(ZeroPath, OnePath, SnakeBiteSettings, SavePresetPath);
                ModManager.CleanupFolders();

                bool restoreRetry = false;
                do
                {
                    try
                    {
                        ModManager.RestoreBackupGameDir(SBBuildManager);
                    }
                    catch (Exception f)
                    {
                        Debug.LogLine("[Uninstall] Exception: " + f, Debug.LogLevel.Basic);
                        restoreRetry = DialogResult.Retry == MessageBox.Show("SnakeBite could not restore Game Directory mod files due to the following exception: {f} \nWould you like to retry?", "Exception Occurred", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
                    }
                } while (restoreRetry);

                ModManager.ClearSBGameDir();
                return(false);
            }
        }//UninstallMod batch
Example #2
0
        public static bool UninstallMod(ModEntry mod)
        {
            Debug.LogLine(String.Format("[Mod] Uninstall started: {0}", mod.Name), Debug.LogLevel.Basic);

            CleanupFolders();

            // Extract game archive
            var zeroFiles = GzsLib.ExtractArchive <QarFile>(ZeroPath, "_working");

            // List all FPKs in mod
            List <string> modFpks = new List <string>();

            foreach (ModFpkEntry fpkEntry in mod.ModFpkEntries)
            {
                if (!modFpks.Contains(fpkEntry.FpkFile))
                {
                    modFpks.Add(fpkEntry.FpkFile);
                }
            }

            var gameData = SettingsManager.GetGameData();

            // Extract FPK
            foreach (string fpk in modFpks)
            {
                string fpkName    = Path.GetFileName(fpk);
                string fpkDatPath = zeroFiles.FirstOrDefault(file => Tools.CompareHashes(file, fpk));
                if (fpkDatPath == null)
                {
                    continue;
                }
                var fpkFile = GzsLib.ExtractArchive <FpkFile>(Path.Combine("_working", Tools.ToWinPath(fpkDatPath)), "_modfpk");

                // Remove all mod fpk files from fpk
                foreach (ModFpkEntry fpkEntry in mod.ModFpkEntries)
                {
                    Debug.LogLine(String.Format("[Mod] Removing {1}\\{0}", Tools.ToWinPath(fpkEntry.FilePath), fpkName), Debug.LogLevel.Debug);
                    fpkFile.RemoveAll(file => Tools.ToQarPath(file) == Tools.ToQarPath(fpkEntry.FilePath));
                }

                var gameFpks = gameData.GameFpkEntries.ToList();

                // remove all merged files from fpk
                foreach (ModFpkEntry gameFpkFile in gameFpks)
                {
                    if (Tools.ToQarPath(gameFpkFile.FpkFile) == Tools.ToQarPath(fpk) && gameFpkFile.SourceType == FileSource.Merged)
                    {
                        Debug.LogLine(String.Format("[Mod] Removing merged file {0}", gameFpkFile.FilePath));
                        fpkFile.RemoveAll(entry => entry == gameFpkFile.FilePath);
                        gameData.GameFpkEntries.Remove(gameFpkFile);
                    }
                }

                // remove fpk if no files left
                if (fpkFile.Count == 0)
                {
                    Debug.LogLine(String.Format("[Mod] {0} is empty, removing", fpkName), Debug.LogLevel.Debug);
                    zeroFiles.RemoveAll(file => Tools.CompareHashes(file, fpk));
                    gameData.GameQarEntries.RemoveAll(file => Tools.CompareHashes(file.FilePath, fpk));
                }
                else
                {
                    Debug.LogLine(String.Format("[Mod] Rebuilding {0}", fpk), Debug.LogLevel.Debug);
                    // rebuild fpk from base file
                    var baseData = GzsLib.ReadBaseData();

                    var oneFiles  = baseData.FileList.FindAll(entry => entry.QarFile == "01.dat");
                    var baseFiles = baseData.FileList.FindAll(entry => entry.QarFile != "01.dat");

                    // Check for FPKs in 00.dat first
                    GameFile file = oneFiles.FirstOrDefault(entry => entry.FileHash == Tools.NameToHash(fpk));
                    if (file != null)
                    {
                        // Extract base FPK files
                        GzsLib.ExtractFileByHash <QarFile>(Path.Combine(GameDir, "master\\0\\01.dat"), file.FileHash, "_working\\temp.fpk");
                        var gameFpk = GzsLib.ExtractArchive <FpkFile>("_working\\temp.fpk", "_gamefpk");

                        // Add merged base files to game file database
                        var mCount = 0;
                        foreach (var fpkF in gameFpk)
                        {
                            if (!fpkFile.Contains(fpkF))
                            {
                                gameData.GameFpkEntries.Add(new ModFpkEntry()
                                {
                                    FpkFile = fpk, FilePath = fpkF, SourceType = FileSource.Merged, SourceName = file.QarFile
                                });
                                mCount++;
                            }
                        }

                        Debug.LogLine(String.Format("[Mod] {0} files restored from {1}", mCount, file.QarFile), Debug.LogLevel.Debug);

                        // Copy remaining files over base FPK
                        foreach (string mFile in fpkFile)
                        {
                            string fDir = Path.GetDirectoryName(mFile);
                            if (!Directory.Exists(Path.Combine("_gamefpk", fDir)))
                            {
                                Directory.CreateDirectory(Path.Combine("_gamefpk", fDir));
                            }
                            Debug.LogLine(String.Format("[Mod] Merging existing file: {0}", mFile));
                            File.Copy(Path.Combine("_modfpk", mFile), Path.Combine(Path.Combine("_gamefpk", mFile)), true);
                            if (!gameFpk.Contains(mFile))
                            {
                                gameFpk.Add(mFile);
                            }
                        }

                        // Rebuild FPK
                        GzsLib.WriteFpkArchive(Path.Combine("_working", Tools.ToWinPath(fpkDatPath)), "_gamefpk", gameFpk);
                        Directory.Delete("_gamefpk", true);
                        Directory.Delete("_modfpk", true);
                        continue; // don't check base data if it's in 01
                    }

                    // check base files for FPK
                    file = baseFiles.FirstOrDefault(entry => entry.FileHash == Tools.NameToHash(fpk));
                    if (file != null)
                    {
                        // Extract base FPK files
                        GzsLib.ExtractFileByHash <QarFile>(Path.Combine(GameDir, "master\\" + file.QarFile), file.FileHash, "_working\\temp.fpk");
                        var gameFpk = GzsLib.ExtractArchive <FpkFile>("_working\\temp.fpk", "_gamefpk");

                        // Add merged base files to game file database
                        var mCount = 0;
                        foreach (var fpkF in gameFpk)
                        {
                            if (!fpkFile.Contains(fpkF))
                            {
                                gameData.GameFpkEntries.Add(new ModFpkEntry()
                                {
                                    FpkFile = fpk, FilePath = fpkF, SourceType = FileSource.Merged, SourceName = file.QarFile
                                });
                                mCount++;
                            }
                        }

                        Debug.LogLine(String.Format("[Mod] {0} files restored from {1}", mCount, file.QarFile), Debug.LogLevel.Debug);
                        // Copy remaining files over base FPK
                        foreach (string mFile in fpkFile)
                        {
                            string fDir = Path.GetDirectoryName(mFile);
                            if (!Directory.Exists(Path.Combine("_gamefpk", fDir)))
                            {
                                Directory.CreateDirectory(Path.Combine("_gamefpk", fDir));
                            }
                            Debug.LogLine(String.Format("[Mod] Merging existing file: {0}", mFile));
                            File.Copy(Path.Combine("_modfpk", mFile), Path.Combine(Path.Combine("_gamefpk", mFile)), true);
                            if (!gameFpk.Contains(mFile))
                            {
                                gameFpk.Add(mFile);
                            }
                        }

                        // Rebuild FPK
                        GzsLib.WriteFpkArchive(Path.Combine("_working", Tools.ToWinPath(fpk)), "_gamefpk", gameFpk);
                        Directory.Delete("_gamefpk", true);
                        Directory.Delete("_modfpk", true);
                    }
                }
            }

            SettingsManager.SetGameData(gameData);

            // Remove all mod files from 01.dat
            foreach (ModQarEntry qarEntry in mod.ModQarEntries)
            {
                string fExt = Path.GetExtension(qarEntry.FilePath);
                if (!fExt.Contains(".fpk"))
                {
                    zeroFiles.RemoveAll(file => Tools.CompareHashes(file, qarEntry.FilePath));
                }
            }

            Debug.LogLine("[Mod] Rebuilding game archive", Debug.LogLevel.Basic);
            // Rebuild 01.dat
            GzsLib.WriteQarArchive(ZeroPath, "_working", zeroFiles, 3150048);
            SettingsManager.UpdateDatHash();

            SettingsManager.RemoveMod(mod);

            CleanupDatabase();

            CleanupFolders();
            Debug.LogLine("[Mod] Uninstall complete", Debug.LogLevel.Basic);

            return(true);
        }
Example #3
0
        public static bool InstallMods(List <string> ModFiles, bool skipCleanup = false)
        {
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            Debug.LogLine("[Install] Start", Debug.LogLevel.Basic);
            ModManager.ClearBuildFiles(ZeroPath, OnePath, SnakeBiteSettings, SavePresetPath); // deletes any leftover sb_build files that might still be in the directory (ie from a mid-process shutdown)
            ModManager.ClearSBGameDir();                                                      // deletes the game directory sb_build
            ModManager.CleanupFolders();                                                      // deletes the work folders which contain extracted files from 00/01

            if (Properties.Settings.Default.AutosaveRevertPreset == true)
            {
                PresetManager.SavePreset(SavePresetPath + build_ext); // creates a backup preset file sb_build
            }
            else
            {
                Debug.LogLine("[Install] Skipping RevertChanges.MGSVPreset Save", Debug.LogLevel.Basic);
            }
            File.Copy(SnakeBiteSettings, SnakeBiteSettings + build_ext, true); // creates a settings sb_build

            GzsLib.LoadDictionaries();
            List <ModEntry> installEntryList = new List <ModEntry>();

            foreach (string modFile in ModFiles)
            {
                installEntryList.Add(Tools.ReadMetaData(modFile));
            }


            List <string> zeroFiles  = new List <string>();
            bool          hasQarZero = ModManager.hasQarZeroFiles(installEntryList);

            if (hasQarZero)
            {
                zeroFiles = GzsLib.ExtractArchive <QarFile>(ZeroPath, "_working0");
            }

            List <string> oneFiles = null;
            bool          hasFtexs = ModManager.foundLooseFtexs(installEntryList);

            if (hasFtexs)
            {
                oneFiles = GzsLib.ExtractArchive <QarFile>(OnePath, "_working1");
            }

            SettingsManager SBBuildManager = new SettingsManager(SnakeBiteSettings + build_ext);
            var             gameData       = SBBuildManager.GetGameData();

            ModManager.ValidateGameData(ref gameData, ref zeroFiles);

            var zeroFilesHashSet = new HashSet <string>(zeroFiles);

            Debug.LogLine("[Install] Building gameFiles lists", Debug.LogLevel.Basic);
            var baseGameFiles   = GzsLib.ReadBaseData();
            var allQarGameFiles = new List <Dictionary <ulong, GameFile> >();

            allQarGameFiles.AddRange(baseGameFiles);


            try
            {
                ModManager.PrepGameDirFiles();
                List <string> pullFromVanillas; List <string> pullFromMods; Dictionary <string, bool> pathUpdatesExist;

                Debug.LogLine("[Install] Writing FPK data to Settings", Debug.LogLevel.Basic);
                AddToSettingsFpk(installEntryList, SBBuildManager, allQarGameFiles, out pullFromVanillas, out pullFromMods, out pathUpdatesExist);
                InstallMods(ModFiles, SBBuildManager, pullFromVanillas, pullFromMods, ref zeroFilesHashSet, ref oneFiles, pathUpdatesExist);

                if (hasQarZero)
                {
                    zeroFiles = zeroFilesHashSet.ToList();
                    zeroFiles.Sort();
                    GzsLib.WriteQarArchive(ZeroPath + build_ext, "_working0", zeroFiles, GzsLib.zeroFlags);
                }
                if (hasFtexs)
                {
                    oneFiles.Sort();
                    GzsLib.WriteQarArchive(OnePath + build_ext, "_working1", oneFiles, GzsLib.oneFlags);
                }

                ModManager.PromoteGameDirFiles();
                ModManager.PromoteBuildFiles(ZeroPath, OnePath, SnakeBiteSettings, SavePresetPath);

                if (!skipCleanup)
                {
                    ModManager.CleanupFolders();
                    ModManager.ClearSBGameDir();
                }

                stopwatch.Stop();
                Debug.LogLine($"[Install] Installation finished in {stopwatch.ElapsedMilliseconds} ms", Debug.LogLevel.Basic);
                return(true);
            }
            catch (Exception e)
            {
                stopwatch.Stop();
                Debug.LogLine($"[Install] Installation failed at {stopwatch.ElapsedMilliseconds} ms", Debug.LogLevel.Basic);
                Debug.LogLine("[Install] Exception: " + e, Debug.LogLevel.Basic);
                MessageBox.Show("An error has occurred during the installation process and SnakeBite could not install the selected mod(s).\nException: " + e, "Mod(s) could not be installed", MessageBoxButtons.OK, MessageBoxIcon.Error);

                ModManager.ClearBuildFiles(ZeroPath, OnePath, SnakeBiteSettings, SavePresetPath);
                ModManager.CleanupFolders();

                bool restoreRetry = false;
                do
                {
                    try
                    {
                        ModManager.RestoreBackupGameDir(SBBuildManager);
                    }
                    catch (Exception f)
                    {
                        Debug.LogLine("[Uninstall] Exception: " + f, Debug.LogLevel.Basic);
                        restoreRetry = DialogResult.Retry == MessageBox.Show("SnakeBite could not restore Game Directory mod files due to the following exception: {f} \nWould you like to retry?", "Exception Occurred", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
                    }
                } while (restoreRetry);

                ModManager.ClearSBGameDir();
                return(false);
            }
        }
Example #4
0
        public static bool InstallMod(string ModFile)
        {
            CleanupFolders();

            Debug.LogLine(String.Format("[Mod] Installation started: {0}", ModFile), Debug.LogLevel.Basic);

            // Extract game archive
            var zeroFiles = GzsLib.ExtractArchive <QarFile>(ZeroPath, "_working");

            // Extract mod data
            FastZip unzipper = new FastZip();

            unzipper.ExtractZip(ModFile, "_extr", "(.*?)");

            // Load mod metadata
            ModEntry metaData = new ModEntry("_extr\\metadata.xml");

            // Build a list of FPKs contained in mod
            List <string> modFpks = new List <string>();

            foreach (ModFpkEntry fpkEntry in metaData.ModFpkEntries)
            {
                if (!modFpks.Contains(fpkEntry.FpkFile))
                {
                    modFpks.Add(fpkEntry.FpkFile);
                }
            }

            List <string>      mergeFpks  = new List <string>();
            List <ModQarEntry> MergeFiles = new List <ModQarEntry>();

            Debug.LogLine("[Mod] Checking existing game data", Debug.LogLevel.Basic);

            // Check for FPKs in 00.dat
            foreach (string fpk in modFpks)
            {
                string datFile = zeroFiles.FirstOrDefault(file => Tools.CompareHashes(file, fpk));
                if (datFile != null)
                {
                    if (mergeFpks.Contains(Tools.ToQarPath(datFile)))
                    {
                        continue;
                    }
                    mergeFpks.Add(fpk);

                    MergeFiles.Add(new ModQarEntry()
                    {
                        FilePath = fpk, SourceType = FileSource.Merged, SourceName = "00.dat"
                    });
                }
            }

            var gameData = GzsLib.ReadBaseData();

            var oneFiles  = gameData.FileList.FindAll(entry => entry.QarFile == "01.dat");
            var baseFiles = gameData.FileList.FindAll(entry => entry.QarFile != "01.dat");

            // Check for FPKs in 01.dat
            foreach (string fpk in modFpks)
            {
                GameFile file = oneFiles.FirstOrDefault(entry => entry.FileHash == Tools.NameToHash(fpk));
                if (file != null)
                {
                    if (mergeFpks.Contains(Tools.ToQarPath(file.FilePath)))
                    {
                        continue;
                    }

                    // Create destination directory
                    string destDirectory = Path.Combine("_working", Path.GetDirectoryName(Tools.ToWinPath(file.FilePath)));
                    if (!Directory.Exists(destDirectory))
                    {
                        Directory.CreateDirectory(destDirectory);
                    }

                    // Extract file into dat directory
                    var ex = GzsLib.ExtractFileByHash <QarFile>(Path.Combine(GameDir, "master\\0\\01.dat"), file.FileHash, Path.Combine("_working", Tools.ToWinPath(file.FilePath)));
                    mergeFpks.Add(Tools.ToQarPath(file.FilePath));

                    MergeFiles.Add(new ModQarEntry()
                    {
                        FilePath = file.FilePath, SourceType = FileSource.Merged, SourceName = "01.dat"
                    });

                    if (zeroFiles.FirstOrDefault(datFile => Tools.CompareHashes(datFile, file.FilePath)) == null)
                    {
                        zeroFiles.Add(Tools.ToWinPath(file.FilePath));
                    }
                }
            }

            // Check for FPKs in base data
            foreach (string fpk in modFpks)
            {
                GameFile file = baseFiles.FirstOrDefault(entry => entry.FileHash == Tools.NameToHash(fpk));
                if (file != null)
                {
                    if (mergeFpks.Contains(Tools.ToQarPath(file.FilePath)))
                    {
                        continue;
                    }

                    // Create destination directory
                    string destDirectory = Path.Combine("_working", Path.GetDirectoryName(Tools.ToWinPath(file.FilePath)));
                    if (!Directory.Exists(destDirectory))
                    {
                        Directory.CreateDirectory(destDirectory);
                    }

                    // Extract file into dat directory
                    var ex = GzsLib.ExtractFileByHash <QarFile>(Path.Combine(GameDir, "master\\" + file.QarFile), file.FileHash, Path.Combine("_working", Tools.ToWinPath(file.FilePath)));
                    mergeFpks.Add(Tools.ToQarPath(file.FilePath));
                    MergeFiles.Add(new ModQarEntry()
                    {
                        FilePath = file.FilePath, SourceType = FileSource.Merged, SourceName = file.QarFile
                    });

                    if (zeroFiles.FirstOrDefault(datFile => Tools.CompareHashes(datFile, file.FilePath)) == null)
                    {
                        zeroFiles.Add(Tools.ToWinPath(file.FilePath));
                    }
                }
            }

            Debug.LogLine(String.Format("[Mod] Merging {0} FPK files", MergeFiles.Count), Debug.LogLevel.Basic);

            var g = SettingsManager.GetGameData();

            // Merge FPK files
            foreach (ModQarEntry gf in MergeFiles)
            {
                Debug.LogLine(String.Format("[Mod] Starting merge: {0} ({1})", gf.FilePath, gf.SourceName), Debug.LogLevel.Debug);
                // Extract game FPK
                string fpkDatPath = zeroFiles.FirstOrDefault(file => Tools.CompareHashes(file, gf.FilePath));
                string fpkPath    = Path.Combine("_working", Tools.ToWinPath(fpkDatPath));
                var    gameFpk    = GzsLib.ExtractArchive <FpkFile>(fpkPath, "_gamefpk");

                // Extract mod FPK
                var exFpk = GzsLib.ExtractArchive <FpkFile>(Path.Combine("_extr", Tools.ToWinPath(gf.FilePath)), "_modfpk");

                // Add file to gamedata info
                var q = g.GameQarEntries.FirstOrDefault(entry => entry.FilePath == gf.FilePath);
                if (q == null)
                {
                    g.GameQarEntries.Add(new ModQarEntry()
                    {
                        FilePath = Tools.ToQarPath(gf.FilePath), SourceType = gf.SourceType, SourceName = gf.SourceName, Hash = Tools.NameToHash(gf.FilePath)
                    });
                }

                foreach (string f in gameFpk)
                {
                    var c = exFpk.FirstOrDefault(entry => Tools.CompareHashes(entry, f));
                    if (c == null)
                    {
                        if (g.GameFpkEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FpkFile, gf.FilePath) && Tools.CompareNames(entry.FilePath, f)) == null)
                        {
                            g.GameFpkEntries.Add(new ModFpkEntry()
                            {
                                FpkFile = Tools.ToQarPath(gf.FilePath), FilePath = f, SourceType = gf.SourceType, SourceName = gf.SourceName
                            });
                        }
                    }
                }

                // Merge contents
                foreach (string fileName in exFpk)
                {
                    string fileDir    = (Path.Combine("_gamefpk", Path.GetDirectoryName(fileName)));
                    string sourceFile = Path.Combine("_modfpk", fileName);
                    string destFile   = Path.Combine("_gamefpk", fileName);

                    Debug.LogLine(String.Format("[Mod] Copying file: {0}", fileName), Debug.LogLevel.All);

                    if (!Directory.Exists(fileDir))
                    {
                        Directory.CreateDirectory(fileDir);
                    }
                    File.Copy(sourceFile, destFile, true);
                    if (!gameFpk.Contains(fileName))
                    {
                        gameFpk.Add(Tools.ToQarPath(fileName));
                    }
                }

                // Rebuild game FPK
                GzsLib.WriteFpkArchive(fpkPath, "_gamefpk", gameFpk);
                if (!zeroFiles.Contains(Tools.ToWinPath(gf.FilePath)))
                {
                    zeroFiles.Add(Tools.ToWinPath(gf.FilePath));
                }
                Directory.Delete("_modfpk", true);
                Directory.Delete("_gamefpk", true);
                Debug.LogLine(String.Format("[Mod] Merge complete"), Debug.LogLevel.Debug);
            }

            SettingsManager.SetGameData(g);

            Debug.LogLine("[Mod] Copying remaining mod files", Debug.LogLevel.Basic);

            // Copy files for 01.dat, ignoring merged FPKs
            foreach (ModQarEntry modEntry in metaData.ModQarEntries)
            {
                if (!zeroFiles.Contains(Tools.ToWinPath(modEntry.FilePath)))
                {
                    zeroFiles.Add(Tools.ToWinPath(modEntry.FilePath));
                }

                if (modEntry.FilePath.Contains(".fpk"))
                {
                    if (mergeFpks.Count(fpk => Tools.CompareHashes(fpk, modEntry.FilePath)) > 0)
                    {
                        continue;
                    }
                }

                string sourceFile = Path.Combine("_extr", Tools.ToWinPath(modEntry.FilePath));
                string destFile   = Path.Combine("_working", Tools.ToWinPath(modEntry.FilePath));
                string destDir    = Path.GetDirectoryName(destFile);

                Debug.LogLine(String.Format("[Mod] Copying file: {0}", modEntry.FilePath), Debug.LogLevel.All);
                if (!Directory.Exists(destDir))
                {
                    Directory.CreateDirectory(destDir);
                }
                File.Copy(sourceFile, destFile, true);
            }

            // Rebuild 01.dat
            Debug.LogLine("[Mod] Rebuilding game archive", Debug.LogLevel.Basic);
            GzsLib.WriteQarArchive(ZeroPath, "_working", zeroFiles, 3150048);

            SettingsManager.UpdateDatHash();

            SettingsManager.AddMod(metaData);

            Debug.LogLine("[Mod] Running database cleanup", Debug.LogLevel.Debug);
            CleanupDatabase();

            CleanupFolders();

            Debug.LogLine("[Mod] Installation finished", Debug.LogLevel.Basic);

            return(true);
        }