/// <summary> /// Merges the new mod files with the existing modded files and vanilla game files. /// The resulting file structure is the new _working0 folder to pack as 00.dat /// </summary> private static void InstallMods(List <string> modFilePaths, SettingsManager manager, List <string> pullFromVanillas, List <string> pullFromMods, ref HashSet <string> zeroFiles, ref List <string> oneFilesList, Dictionary <string, bool> pathUpdatesExist) { //Assumption: modded packs have already been extracted to _working0 directory - qarEntryEditList //Assumption: vanilla packs have already been extracted to _gameFpk directory (during AddToSettings) - fpkEntryRetrievalList //theoretically there should be no qar overlap between the _gamefpk(vanilla) and _working0(modded) files FastZip unzipper = new FastZip(); GameData gameData = manager.GetGameData(); foreach (string modFilePath in modFilePaths) { Debug.LogLine($"[Install] Installation started: {Path.GetFileName(modFilePath)}", Debug.LogLevel.Basic); Debug.LogLine($"[Install] Unzipping mod .mgsv ({Tools.GetFileSizeKB(modFilePath)} KB)", Debug.LogLevel.Basic); unzipper.ExtractZip(modFilePath, "_extr", "(.*?)"); Debug.LogLine("[Install] Load mod metadata", Debug.LogLevel.Basic); ModEntry extractedModEntry = new ModEntry("_extr\\metadata.xml"); if (pathUpdatesExist[extractedModEntry.Name]) { Debug.LogLine(string.Format("[Install] Checking for Qar path updates: {0}", extractedModEntry.Name), Debug.LogLevel.Basic); foreach (ModQarEntry modQar in extractedModEntry.ModQarEntries.Where(entry => !entry.FilePath.StartsWith("/Assets/"))) { string unhashedName = HashingExtended.UpdateName(modQar.FilePath); if (unhashedName != null) { Debug.LogLine(string.Format("[Install] Update successful: {0} -> {1}", modQar.FilePath, unhashedName), Debug.LogLevel.Basic); string workingOldPath = Path.Combine("_extr", Tools.ToWinPath(modQar.FilePath)); string workingNewPath = Path.Combine("_extr", Tools.ToWinPath(unhashedName)); if (!Directory.Exists(Path.GetDirectoryName(workingNewPath))) { Directory.CreateDirectory(Path.GetDirectoryName(workingNewPath)); } if (!File.Exists(workingNewPath)) { File.Move(workingOldPath, workingNewPath); } modQar.FilePath = unhashedName; } } } GzsLib.LoadModDictionary(extractedModEntry); ValidateModEntries(ref extractedModEntry); Debug.LogLine("[Install] Check mod FPKs against game .dat fpks", Debug.LogLevel.Basic); zeroFiles.UnionWith(MergePacks(extractedModEntry, pullFromVanillas, pullFromMods)); //foreach (string zeroFile in zeroFiles) Debug.LogLine(string.Format("Contained in zeroFiles: {0}", zeroFile)); Debug.LogLine("[Install] Copying loose textures to 01.", Debug.LogLevel.Basic); InstallLooseFtexs(extractedModEntry, ref oneFilesList); Debug.LogLine("[Install] Copying game dir files", Debug.LogLevel.Basic); InstallGameDirFiles(extractedModEntry, ref gameData); } manager.SetGameData(gameData); }
internal static bool ValidateDatHash() { string datHash = Tools.GetMd5Hash(ModManager.ZeroPath); string hashOld = SettingsManager.GetGameData().DatHash; if (datHash != hashOld) { return(false); } return(true); }
public static bool CheckConflicts(string ModFile) { //Morbid: Conflict check has been reworked as of 0.9.0. CheckConflicts is now split into PreinstallManager.FilterModValidity and PreinstallManager.FilterModConflicts. // CheckConflicts is only used for command-line installation. ModEntry metaData = Tools.ReadMetaData(ModFile); if (metaData == null) { return(false); } // check version conflicts var SBVersion = ModManager.GetSBVersion(); var MGSVersion = ModManager.GetMGSVersion(); Version modSBVersion = new Version(); Version modMGSVersion = new Version(); try { modSBVersion = metaData.SBVersion.AsVersion(); modMGSVersion = metaData.MGSVersion.AsVersion(); } catch { MessageBox.Show(String.Format("The selected version of {0} was created with an older version of SnakeBite and is no longer compatible, please download the latest version and try again.", metaData.Name), "Mod update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } // Check if mod requires SB update if (modSBVersion > SBVersion) { MessageBox.Show(String.Format("{0} requires SnakeBite version {1} or newer. Please follow the link on the Settings page to get the latest version.", metaData.Name, metaData.SBVersion), "Update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } if (modSBVersion < new Version(0, 8, 0, 0)) // 0.8.0.0 { MessageBox.Show(String.Format("The selected version of {0} was created with an older version of SnakeBite and is no longer compatible, please download the latest version and try again.", metaData.Name), "Mod update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } // Check MGS version compatibility if (MGSVersion != modMGSVersion && modMGSVersion != new Version(0, 0, 0, 0)) { if (MGSVersion > modMGSVersion && modMGSVersion > new Version(0, 0, 0, 0)) { var contInstall = MessageBox.Show(String.Format("{0} appears to be for an older version of MGSV. It is recommended that you at least check for an updated version before installing.\n\nContinue installation?", metaData.Name), "Game version mismatch", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (contInstall == DialogResult.No) { return(false); } } if (MGSVersion < modMGSVersion) { MessageBox.Show(String.Format("{0} requires MGSV version {1}, but your installation is version {2}. Please update MGSV and try again.", metaData.Name, modMGSVersion, MGSVersion), "Update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } } //end of validity checks Debug.LogLine(String.Format("[Mod] Checking conflicts for {0}", metaData.Name)); int confCounter = 0; // search installed mods for conflicts SettingsManager manager = new SettingsManager(GameDir); var mods = manager.GetInstalledMods(); List <string> conflictingMods = new List <string>(); int confIndex = -1; foreach (ModEntry mod in mods) // iterate through installed mods { foreach (ModFileEntry fileEntry in metaData.ModFileEntries) // iterate external files from new mod { ModFileEntry conflicts = mod.ModFileEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FilePath, fileEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in 00.dat: {1}", mod.Name, conflicts.FilePath)); confCounter++; } } foreach (ModQarEntry qarEntry in metaData.ModQarEntries) // iterate qar files from new mod { if (qarEntry.FilePath.Contains(".fpk")) { continue; } ModQarEntry conflicts = mod.ModQarEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FilePath, qarEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in 00.dat: {1}", mod.Name, conflicts.FilePath)); confCounter++; } } foreach (ModFpkEntry fpkEntry in metaData.ModFpkEntries) // iterate fpk files from new mod { ModFpkEntry conflicts = mod.ModFpkEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FpkFile, fpkEntry.FpkFile) && Tools.CompareHashes(entry.FilePath, fpkEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in {2}: {1}", mod.Name, conflicts.FilePath, Path.GetFileName(conflicts.FpkFile))); confCounter++; } } } // if the mod conflicts, display message if (conflictingMods.Count > 0) { Debug.LogLine(String.Format("[Mod] Found {0} conflicts", confCounter)); string msgboxtext = "The selected mod conflicts with these mods:\n"; foreach (string Conflict in conflictingMods) { msgboxtext += Conflict + "\n"; } msgboxtext += "\nMore information regarding the conflicts has been output to the logfile."; MessageBox.Show(msgboxtext, "Installation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } Debug.LogLine("[Mod] No conflicts found"); bool sysConflict = false; // check for system file conflicts var gameData = manager.GetGameData(); foreach (ModQarEntry gameQarFile in gameData.GameQarEntries.FindAll(entry => entry.SourceType == FileSource.System)) { if (metaData.ModQarEntries.Count(entry => Tools.ToQarPath(entry.FilePath) == Tools.ToQarPath(gameQarFile.FilePath)) > 0) { sysConflict = true; } } foreach (ModFpkEntry gameFpkFile in gameData.GameFpkEntries.FindAll(entry => entry.SourceType == FileSource.System)) { if (metaData.ModFpkEntries.Count(entry => entry.FilePath == gameFpkFile.FilePath && entry.FpkFile == gameFpkFile.FpkFile) > 0) { sysConflict = true; } } if (sysConflict) { //tex TODO: figure out what it's actually checking and how this can be corrupted string msgboxtext = "The selected mod conflicts with existing MGSV system files,\n"; msgboxtext += "or the snakebite.xml base entries has become corrupt.\n"; msgboxtext += "Please use the Restore Original Game Files option in Snakebite settings and re-run snakebite\n"; MessageBox.Show(msgboxtext, "SnakeBite", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } return(true); }
public static void FilterModConflicts(List <string> ModFiles)//checks if the mods in the list conflict with installed mods or with the game files { // asks user for input if a conflict is found. //return a list of mods that the user has OK'd int confCounter; int confIndex; SettingsManager manager = new SettingsManager(GameDir); var mods = manager.GetInstalledMods(); List <string> conflictingMods; ModEntry metaData; formModConflict conflictForm = new formModConflict(); // search installed mods for conflicts for (int i = ModFiles.Count() - 1; i >= 0; i--) { metaData = Tools.ReadMetaData(ModFiles[i]); confCounter = 0; confIndex = -1; conflictingMods = new List <string>(); Debug.LogLine(String.Format("[Mod] Checking conflicts for {0}", metaData.Name)); foreach (ModEntry mod in mods) // iterate through installed mods [Morbid: TODO iterate pftxs files as well] { foreach (ModFileEntry fileEntry in metaData.ModFileEntries) // iterate external files from new mod { ModFileEntry conflicts = mod.ModFileEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FilePath, fileEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in 00.dat: {1}", mod.Name, conflicts.FilePath)); confCounter++; } } foreach (ModQarEntry qarEntry in metaData.ModQarEntries) // iterate qar files from new mod { if (qarEntry.FilePath.Contains(".fpk")) { continue; } ModQarEntry conflicts = mod.ModQarEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FilePath, qarEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in 00.dat: {1}", mod.Name, conflicts.FilePath)); confCounter++; } } foreach (ModFpkEntry fpkEntry in metaData.ModFpkEntries) // iterate fpk files from new mod { ModFpkEntry conflicts = mod.ModFpkEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FpkFile, fpkEntry.FpkFile) && Tools.CompareHashes(entry.FilePath, fpkEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in {2}: {1}", mod.Name, conflicts.FilePath, Path.GetFileName(conflicts.FpkFile))); confCounter++; } } } // if the mod conflicts, prompt user for resolution if (conflictingMods.Count > 0) { Debug.LogLine(String.Format("[Mod] Found {0} conflicts", confCounter)); string msgboxtext = String.Format("\"{0}\" conflicts with the following pre-existing mods:\n\n", Tools.ReadMetaData(ModFiles[i]).Name); foreach (string Conflict in conflictingMods) { msgboxtext += String.Format("\"{0}\"\n", Conflict); } msgboxtext += "\n\nMore information regarding these conflicts can be found in the Debug Logs."; DialogResult userInput = conflictForm.ShowDialog(msgboxtext); if (userInput == DialogResult.Cancel) { ModFiles.RemoveAt(i); continue; } } Debug.LogLine("[Mod] No conflicts found"); bool sysConflict = false; // check for system file conflicts var gameData = manager.GetGameData(); foreach (ModQarEntry gameQarFile in gameData.GameQarEntries.FindAll(entry => entry.SourceType == FileSource.System)) { if (metaData.ModQarEntries.Count(entry => Tools.ToQarPath(entry.FilePath) == Tools.ToQarPath(gameQarFile.FilePath)) > 0) { sysConflict = true; } } foreach (ModFpkEntry gameFpkFile in gameData.GameFpkEntries.FindAll(entry => entry.SourceType == FileSource.System)) { if (metaData.ModFpkEntries.Count(entry => entry.FilePath == gameFpkFile.FilePath && entry.FpkFile == gameFpkFile.FpkFile) > 0) { sysConflict = true; } } if (sysConflict) { //tex TODO: figure out what it's actually checking and how this can be corrupted string msgboxtext = String.Format("\"{0}\" conflicts with existing MGSV system files,\n", Tools.ReadMetaData(ModFiles[i]).Name); msgboxtext += "or the snakebite.xml base entries has become corrupt.\n"; msgboxtext += "Please use the Restore Original Game Files option in Snakebite settings and re-run snakebite\n"; DialogResult userInput = conflictForm.ShowDialog(msgboxtext); if (userInput == DialogResult.Cancel) { ModFiles.RemoveAt(i); continue; } } } }
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
}//InstallGameDirFiles private static void AddToSettingsFpk(List <ModEntry> installEntryList, SettingsManager manager, List <Dictionary <ulong, GameFile> > allQarGameFiles, out List <string> PullFromVanillas, out List <string> pullFromMods, out Dictionary <string, bool> pathUpdatesExist) { GameData gameData = manager.GetGameData(); pathUpdatesExist = new Dictionary <string, bool>(); List <string> newModQarEntries = new List <string>(); List <string> modQarFiles = manager.GetModQarFiles(); pullFromMods = new List <string>(); foreach (ModEntry modToInstall in installEntryList) { Dictionary <string, string> newNameDictionary = new Dictionary <string, string>(); int foundUpdate = 0; foreach (ModQarEntry modQar in modToInstall.ModQarEntries.Where(entry => !entry.FilePath.StartsWith("/Assets/"))) { //Debug.LogLine(string.Format("Attempting to update Qar filename: {0}", modQar.FilePath), Debug.LogLevel.Basic); string unhashedName = HashingExtended.UpdateName(modQar.FilePath); if (unhashedName != null) { //Debug.LogLine(string.Format("Success: {0}", unhashedName), Debug.LogLevel.Basic); newNameDictionary.Add(modQar.FilePath, unhashedName); foundUpdate++; modQar.FilePath = unhashedName; if (!pathUpdatesExist.ContainsKey(modToInstall.Name)) { pathUpdatesExist.Add(modToInstall.Name, true); } else { pathUpdatesExist[modToInstall.Name] = true; } } } if (foundUpdate > 0) { foreach (ModFpkEntry modFpkEntry in modToInstall.ModFpkEntries) { string unHashedName; if (newNameDictionary.TryGetValue(modFpkEntry.FpkFile, out unHashedName)) { modFpkEntry.FpkFile = unHashedName; } } } else if (!pathUpdatesExist.ContainsKey(modToInstall.Name)) { pathUpdatesExist.Add(modToInstall.Name, false); } manager.AddMod(modToInstall); //foreach (ModQarEntry modqar in modToInstall.ModQarEntries) Debug.LogLine("Mod Qar in modToInstall: " + modqar.FilePath); foreach (ModQarEntry modQarEntry in modToInstall.ModQarEntries) // add qar entries (fpk, fpkd) to GameData if they don't already exist { string modQarFilePath = modQarEntry.FilePath; if (!(modQarFilePath.EndsWith(".fpk") || modQarFilePath.EndsWith(".fpkd"))) { continue; // only pull for Qar's with Fpk's } if (modQarFiles.Any(entry => entry == modQarFilePath)) { pullFromMods.Add(modQarFilePath); //Debug.LogLine("Pulling from 00.dat: {0} " + modQarFilePath); } else if (!newModQarEntries.Contains(modQarFilePath)) { newModQarEntries.Add(modQarFilePath); //Debug.LogLine("Pulling from base archives: {0} " + modQarFilePath); } } } //Debug.LogLine(string.Format("Foreach nest 1 complete")); List <ModFpkEntry> newModFpkEntries = new List <ModFpkEntry>(); foreach (ModEntry modToInstall in installEntryList) { foreach (ModFpkEntry modFpkEntry in modToInstall.ModFpkEntries) { //Debug.LogLine(string.Format("Checking out {0} from {1}", modFpkEntry.FilePath, modFpkEntry.FpkFile)); if (newModQarEntries.Contains(modFpkEntry.FpkFile)) // it isn't already part of the snakebite environment { //Debug.LogLine(string.Format("seeking repair files around {0}", modFpkEntry.FilePath)); newModFpkEntries.Add(modFpkEntry); } else { //Debug.LogLine(string.Format("Removing {0} from gameFpkEntries so it will only be listed in the mod's entries", modFpkEntry.FilePath)); int indexToRemove = gameData.GameFpkEntries.FindIndex(m => m.FilePath == Tools.ToWinPath(modFpkEntry.FilePath)); // this will remove the gamedata's listing of the file under fpkentries (repair entries), so the filepath will only be listed in the modentry if (indexToRemove >= 0) { gameData.GameFpkEntries.RemoveAt(indexToRemove); } } } } //Debug.LogLine(string.Format("Foreach nest 2 complete")); HashSet <ulong> mergeFpkHashes = new HashSet <ulong>(); PullFromVanillas = new List <string>(); var repairFpkEntries = new List <ModFpkEntry>(); foreach (ModFpkEntry newFpkEntry in newModFpkEntries) // this will add the fpkentry listings (repair entries) to the settings xml { //Debug.LogLine(string.Format("checking {0} for repairs", newFpkEntry.FilePath)); ulong packHash = Tools.NameToHash(newFpkEntry.FpkFile); if (mergeFpkHashes.Contains(packHash)) { continue; // the process has already plucked this particular qar file } foreach (var archiveQarGameFiles in allQarGameFiles) // check every archive (except 00) to see if the particular qar file already exists { //Debug.LogLine(string.Format("checking archive for an existing qar file")); if (archiveQarGameFiles.Count > 0) { GameFile existingPack = null; archiveQarGameFiles.TryGetValue(packHash, out existingPack); if (existingPack != null) // the qar file is found { //Debug.LogLine(string.Format("Qar file {0} found in {1}. adding to gameqarentries", newFpkEntry.FpkFile, existingPack.QarFile)); mergeFpkHashes.Add(packHash); gameData.GameQarEntries.Add(new ModQarEntry { FilePath = newFpkEntry.FpkFile, SourceType = FileSource.Merged, SourceName = existingPack.QarFile, Hash = existingPack.FileHash }); PullFromVanillas.Add(newFpkEntry.FpkFile); string windowsFilePath = Tools.ToWinPath(newFpkEntry.FpkFile); // Extract the pack file from the vanilla game files, place into _gamefpk for future use string sourceArchive = Path.Combine(GameDir, "master\\" + existingPack.QarFile); string workingPath = Path.Combine("_gameFpk", windowsFilePath); if (!Directory.Exists(Path.GetDirectoryName(workingPath))) { Directory.CreateDirectory(Path.GetDirectoryName(workingPath)); } GzsLib.ExtractFileByHash <QarFile>(sourceArchive, existingPack.FileHash, workingPath); // extracts the specific .fpk from the game data foreach (string listedFile in GzsLib.ListArchiveContents <FpkFile>(workingPath)) { repairFpkEntries.Add(new ModFpkEntry { FpkFile = newFpkEntry.FpkFile, FilePath = listedFile, SourceType = FileSource.Merged, SourceName = existingPack.QarFile }); //Debug.LogLine(string.Format("File Listed: {0} in {1}", extractedFile, newFpkEntry.FpkFile)); } break; } } } } //Debug.LogLine(string.Format("Foreach nest 3 complete")); foreach (ModFpkEntry newFpkEntry in newModFpkEntries) // finally, strip away the modded entries from the repair entries { //Debug.LogLine(string.Format("checking to remove {0} from gamefpkentries", Tools.ToWinPath(newFpkEntry.FilePath))); int indexToRemove = repairFpkEntries.FindIndex(m => m.FilePath == Tools.ToWinPath(newFpkEntry.FilePath)); if (indexToRemove >= 0) { repairFpkEntries.RemoveAt(indexToRemove); } } gameData.GameFpkEntries = gameData.GameFpkEntries.Union(repairFpkEntries).ToList(); manager.SetGameData(gameData); }
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); } }
/// <summary> /// Checks 00.dat files, indcluding fpk contents and adds the different mod entry types (if missing) to database (snakebite.xml) /// Slows down as number of fpks increase /// </summary> public static void CleanupDatabase() { Debug.LogLine("[Cleanup] Database cleanup started", Debug.LogLevel.Basic); // Retrieve installation data SettingsManager manager = new SettingsManager(SnakeBiteSettings); var mods = manager.GetInstalledMods(); var game = manager.GetGameData(); var zeroFiles = GzsLib.ListArchiveContents<QarFile>(ZeroPath); //Should only happen if user manually mods 00.dat Debug.LogLine("[Cleanup] Removing duplicate file entries", Debug.LogLevel.Debug); // Remove duplicate file entries var cleanFiles = zeroFiles.ToList(); foreach (string file in zeroFiles) { while (cleanFiles.Count(entry => entry == file) > 1) { cleanFiles.Remove(file); Debug.LogLine(String.Format("[Cleanup] Found duplicate file in 00.dat: {0}", file), Debug.LogLevel.Debug); } } Debug.LogLine("[Cleanup] Examining FPK archives", Debug.LogLevel.Debug); var GameFpks = game.GameFpkEntries.ToList(); // Search for FPKs in game data var fpkFiles = cleanFiles.FindAll(entry => entry.EndsWith(".fpk") || entry.EndsWith(".fpkd")); foreach (string fpkFile in fpkFiles) { string fpkName = Path.GetFileName(fpkFile); // Extract FPK from archive Debug.LogLine(String.Format("[Cleanup] Examining {0}", fpkName)); GzsLib.ExtractFile<QarFile>(ZeroPath, fpkFile, fpkName); // Read FPK contents var fpkContent = GzsLib.ListArchiveContents<FpkFile>(fpkName); // Add contents to game FPK list foreach (var c in fpkContent) { if (GameFpks.Count(entry => Tools.CompareNames(entry.FilePath, c) && Tools.CompareHashes(entry.FpkFile, fpkFile)) == 0) { GameFpks.Add(new ModFpkEntry() { FpkFile = fpkFile, FilePath = c }); } } try { File.Delete(fpkName); } catch { Console.WriteLine("[Uninstall] Could not delete: " + fpkName); } } Debug.LogLine("[Cleanup] Checking installed mods", Debug.LogLevel.Debug); Debug.LogLine("[Cleanup] Removing all installed mod data from game data list", Debug.LogLevel.Debug); foreach (var mod in mods) { foreach (var qarEntry in mod.ModQarEntries) { cleanFiles.RemoveAll(file => Tools.CompareHashes(file, qarEntry.FilePath)); } foreach (var fpkEntry in mod.ModFpkEntries) { GameFpks.RemoveAll(fpk => Tools.CompareHashes(fpk.FpkFile, fpkEntry.FpkFile) && Tools.ToQarPath(fpk.FilePath) == Tools.ToQarPath(fpkEntry.FilePath)); } } Debug.LogLine("[Cleanup] Checking mod QAR files against game files", Debug.LogLevel.Debug); foreach (var s in cleanFiles) { if (game.GameQarEntries.Count(entry => Tools.CompareHashes(entry.FilePath, s)) == 0) { Debug.LogLine($"[Cleanup] Adding missing {s}", Debug.LogLevel.Debug); game.GameQarEntries.Add(new ModQarEntry() { FilePath = Tools.ToQarPath(s), SourceType = FileSource.System, Hash = Tools.NameToHash(s) }); } } game.GameFpkEntries = GameFpks; manager.SetGameData(game); }
public static bool CheckConflicts(string ModFile, bool ignoreConflicts = false) { var metaData = Tools.ReadMetaData(ModFile); if (metaData == null) { return(false); } if (!SettingsManager.DisableConflictCheck && !ignoreConflicts) { // check version conflicts var SBVersion = ModManager.GetSBVersion(); var MGSVersion = ModManager.GetMGSVersion(); Version modSBVersion = new Version(); Version modMGSVersion = new Version(); try { modSBVersion = metaData.SBVersion.AsVersion(); modMGSVersion = metaData.MGSVersion.AsVersion(); } catch { MessageBox.Show(String.Format("The selected version of {0} was created with an older version of SnakeBite and is no longer compatible, please download the latest version and try again.", metaData.Name), "Mod update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } // Check if mod requires SB update if (modSBVersion > SBVersion) { MessageBox.Show(String.Format("{0} requires a newer version of SnakeBite. Please follow the link on the Settings page to get the latest version.", metaData.Name), "Update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } if (modSBVersion < new Version(0, 8, 0, 0)) // 0.8.0.0 { MessageBox.Show(String.Format("The selected version of {0} was created with an older version of SnakeBite and is no longer compatible, please download the latest version and try again.", metaData.Name), "Mod update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } // Check MGS version compatibility if (MGSVersion != modMGSVersion && modMGSVersion != new Version(0, 0, 0, 0)) { if (MGSVersion > modMGSVersion && modMGSVersion > new Version(0, 0, 0, 0)) { var contInstall = MessageBox.Show(String.Format("{0} appears to be for an older version of MGSV. It is recommended that you at least check for an updated version before installing.\n\nContinue installation?", metaData.Name, modMGSVersion, MGSVersion), "Game version mismatch", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (contInstall == DialogResult.No) { return(false); } } if (MGSVersion < modMGSVersion) { MessageBox.Show(String.Format("{0} requires MGSV version {1}, but your installation is version {2}. Please update MGSV and try again.", metaData.Name, modMGSVersion, MGSVersion), "Update required", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } } Debug.LogLine(String.Format("[Mod] Checking conflicts for {0}", metaData.Name)); int confCounter = 0; // search installed mods for conflicts var mods = SettingsManager.GetInstalledMods(); List <string> conflictingMods = new List <string>(); int confIndex = -1; foreach (ModEntry mod in mods) // iterate through installed mods { foreach (ModQarEntry qarEntry in metaData.ModQarEntries) // iterate qar files from new mod { if (qarEntry.FilePath.Contains(".fpk")) { continue; } ModQarEntry conflicts = mod.ModQarEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FilePath, qarEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in 00.dat: {1}", mod.Name, conflicts.FilePath)); confCounter++; } } foreach (ModFpkEntry fpkEntry in metaData.ModFpkEntries) // iterate fpk files from new mod { ModFpkEntry conflicts = mod.ModFpkEntries.FirstOrDefault(entry => Tools.CompareHashes(entry.FpkFile, fpkEntry.FpkFile) && Tools.CompareHashes(entry.FilePath, fpkEntry.FilePath)); if (conflicts != null) { if (confIndex == -1) { confIndex = mods.IndexOf(mod); } if (!conflictingMods.Contains(mod.Name)) { conflictingMods.Add(mod.Name); } Debug.LogLine(String.Format("[{0}] Conflict in {2}: {1}", mod.Name, conflicts.FilePath, Path.GetFileName(conflicts.FpkFile))); confCounter++; } } } // if the mod conflicts, display message if (conflictingMods.Count > 0) { Debug.LogLine(String.Format("[Mod] Found {0} conflicts", confCounter)); string msgboxtext = "The selected mod conflicts with these mods:\n"; foreach (string Conflict in conflictingMods) { msgboxtext += Conflict + "\n"; } msgboxtext += "\nMore information regarding the conflicts has been output to the logfile. Double click the version number shown in the Launcher to view the current logfile."; MessageBox.Show(msgboxtext, "Installation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } Debug.LogLine("[Mod] No conflicts found"); bool sysConflict = false; // check for system file conflicts var gameData = SettingsManager.GetGameData(); foreach (ModQarEntry gameQarFile in gameData.GameQarEntries.FindAll(entry => entry.SourceType == FileSource.System)) { if (metaData.ModQarEntries.Count(entry => Tools.ToQarPath(entry.FilePath) == Tools.ToQarPath(gameQarFile.FilePath)) > 0) { sysConflict = true; } } foreach (ModFpkEntry gameFpkFile in gameData.GameFpkEntries.FindAll(entry => entry.SourceType == FileSource.System)) { if (metaData.ModFpkEntries.Count(entry => entry.FilePath == gameFpkFile.FilePath && entry.FpkFile == gameFpkFile.FpkFile) > 0) { sysConflict = true; } } if (sysConflict) { MessageBox.Show("The selected mod conflicts with existing MGSV system files.", "SnakeBite", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } DialogResult confirmInstall = MessageBox.Show(String.Format("You are about to install {0}, continue?", metaData.Name), "SnakeBite", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (confirmInstall == DialogResult.No) { return(false); } } return(true); }
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); }
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); }