/// <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); }
public static void AddModsToXml(params PreinstallEntry[] modsArrary) { HashingExtended.ReadDictionary(); foreach (PreinstallEntry mod in modsArrary) { FastZip unzipper = new FastZip(); unzipper.ExtractZip(mod.filename, "_extr", "metadata.xml"); ModEntry metaData = new ModEntry("_extr\\metadata.xml"); Dictionary <string, string> newNameDictionary = new Dictionary <string, string>(); int foundUpdate = 0; Debug.LogLine(string.Format("[PreinstallCheck] Checking for Qar path updates: {0}", metaData.Name), Debug.LogLevel.Basic); foreach (ModQarEntry modQar in metaData.ModQarEntries.Where(entry => !entry.FilePath.StartsWith("/Assets/"))) { string unhashedName = HashingExtended.UpdateName(modQar.FilePath); if (unhashedName != null) { Debug.LogLine(string.Format("[PreinstallCheck] Update successful: {0} -> {1}", modQar.FilePath, unhashedName), Debug.LogLevel.Basic); newNameDictionary.Add(modQar.FilePath, unhashedName); modQar.FilePath = unhashedName; foundUpdate++; } } if (foundUpdate > 0) { foreach (ModFpkEntry modFpkEntry in metaData.ModFpkEntries) { string unHashedName; if (newNameDictionary.TryGetValue(modFpkEntry.FpkFile, out unHashedName)) { modFpkEntry.FpkFile = unHashedName; } } } new SettingsManager("_extr\\buildInfo.xml").AddMod(metaData); mod.modInfo = metaData; } }
}//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); }
internal void updateQarFileNames() // snakebite supports automatically updating filenames before they're installed, but will need to update old game settings from the prior version. 1-time-check per SB update { Settings settings = new Settings(); settings.LoadFrom(xmlFilePath); HashingExtended.ReadDictionary(); List <string> noUpdateQars = new List <string>(); Dictionary <string, string> newNameDictionary = new Dictionary <string, string>(); int foundUpdate = 0; foreach (ModQarEntry QarEntry in settings.GameData.GameQarEntries) { if (QarEntry.FilePath.StartsWith("/Assets/")) { noUpdateQars.Add(QarEntry.FilePath); continue; } string unhashedName = HashingExtended.UpdateName(QarEntry.FilePath); if (unhashedName != null) { newNameDictionary.Add(QarEntry.FilePath, unhashedName); foundUpdate++; QarEntry.FilePath = unhashedName; } else { noUpdateQars.Add(QarEntry.FilePath); } } if (foundUpdate > 0) { foreach (ModFpkEntry modFpkEntry in settings.GameData.GameFpkEntries.Where(entry => !noUpdateQars.Contains(entry.FpkFile))) { string unHashedName; if (newNameDictionary.TryGetValue(modFpkEntry.FpkFile, out unHashedName)) { modFpkEntry.FpkFile = unHashedName; } } } foreach (ModEntry mod in settings.ModEntries) { noUpdateQars.Clear(); foreach (ModQarEntry modQar in mod.ModQarEntries) { if (modQar.FilePath.StartsWith("/Assets/")) { noUpdateQars.Add(modQar.FilePath); continue; } string unHashedName; if (newNameDictionary.TryGetValue(modQar.FilePath, out unHashedName)) { modQar.FilePath = unHashedName; } else { unHashedName = HashingExtended.UpdateName(modQar.FilePath); if (unHashedName != null) { newNameDictionary.Add(modQar.FilePath, unHashedName); modQar.FilePath = unHashedName; foundUpdate++; } else { noUpdateQars.Add(modQar.FilePath); } } } if (foundUpdate > 0) { foreach (ModFpkEntry modFpkEntry in mod.ModFpkEntries.Where(entry => !noUpdateQars.Contains(entry.FpkFile))) { string unHashedName; if (newNameDictionary.TryGetValue(modFpkEntry.FpkFile, out unHashedName)) { modFpkEntry.FpkFile = unHashedName; } } } } settings.SaveTo(xmlFilePath); }
public static void BuildArchive(string SourceDir, ModEntry metaData, string outputFilePath) { Debug.LogLine($"[BuildArchive] {SourceDir}."); HashingExtended.ReadDictionary(); string buildDir = Directory.GetCurrentDirectory() + "\\_build"; try { if (Directory.Exists(buildDir)) { Directory.Delete(buildDir, true); } } catch { Debug.LogLine(string.Format("[BuildArchive] preexisting _build directory could not be deleted: {0}", buildDir)); } Directory.CreateDirectory("_build"); List <string> fpkFiles = Directory.GetFiles(SourceDir, "*.fpk*", SearchOption.AllDirectories).ToList(); for (int i = fpkFiles.Count - 1; i >= 0; i--) { string fpkFile = fpkFiles[i].Substring(SourceDir.Length + 1); if (!fpkFile.StartsWith("Assets")) { string updatedFileName = HashingExtended.UpdateName(fpkFile); if (updatedFileName != null) { updatedFileName = SourceDir + updatedFileName.Replace('/', '\\'); if (fpkFiles.Contains(updatedFileName)) { fpkFiles.Remove(fpkFiles[i]); } } } } List <string> fpkFolders = ListFpkFolders(SourceDir); for (int i = fpkFolders.Count - 1; i >= 0; i--) { string fpkFolder = fpkFolders[i].Substring(SourceDir.Length + 1); if (!fpkFolder.StartsWith("Assets")) { string updatedFileName = HashingExtended.UpdateName(fpkFolder.Replace("_fpk", ".fpk")); if (updatedFileName != null) { updatedFileName = SourceDir + updatedFileName.Replace('/', '\\'); if (fpkFolders.Contains(updatedFileName.Replace(".fpk", "_fpk")) || fpkFiles.Contains(updatedFileName)) { MessageBox.Show(string.Format("{0} was not packed or added to the build, because {1} (the unhashed filename of {0}) already exists in the mod directory.", Path.GetFileName(fpkFolders[i]), Path.GetFileName(updatedFileName))); fpkFolders.Remove(fpkFolders[i]); } } } } // check for FPKs that must be built and build metaData.ModFpkEntries = new List <ModFpkEntry>(); List <string> builtFpks = new List <string>(); foreach (string FpkFullDir in fpkFolders) { foreach (ModFpkEntry fpkEntry in BuildFpk(FpkFullDir, SourceDir)) { metaData.ModFpkEntries.Add(fpkEntry); if (!builtFpks.Contains(fpkEntry.FpkFile)) { builtFpks.Add(fpkEntry.FpkFile); } } } // check for other FPKs and build fpkentry data foreach (string SourceFile in Directory.GetFiles(SourceDir, "*.fpk*", SearchOption.AllDirectories)) { //tex chunk0\Assets\tpp\pack\collectible\common\col_common_tpp_fpk\Assets\tpp\pack\resident\resident00.fpkl is the only fpkl, don't know what a fpkl is, but gzcore crashes on it. if (SourceFile.EndsWith(".fpkl") || SourceFile.EndsWith(".xml")) { continue; } string FileName = Tools.ToQarPath(SourceFile.Substring(SourceDir.Length)); if (!builtFpks.Contains(FileName)) { // unpack FPK and build FPK list string fpkDir = Tools.ToWinPath(FileName.Replace(".fpk", "_fpk")); string fpkFullDir = Path.Combine(SourceDir, fpkDir); if (!Directory.Exists(fpkFullDir)) { GzsLib.ExtractArchive <FpkFile>(SourceFile, fpkFullDir); } var fpkContents = GzsLib.ListArchiveContents <FpkFile>(SourceFile); foreach (string file in fpkContents) { if (!GzsLib.IsExtensionValidForArchive(file, fpkDir)) { Debug.LogLine($"[BuildArchive] {file} is not a valid file for a {Path.GetExtension(fpkDir)} archive."); continue; } metaData.ModFpkEntries.Add(new ModFpkEntry() { FilePath = file, FpkFile = FileName, ContentHash = Tools.GetMd5Hash(Path.Combine(SourceDir, fpkDir, Tools.ToWinPath(file))) }); } } } // build QAR entries List <string> qarFiles = ListQarFiles(SourceDir); for (int i = qarFiles.Count - 1; i >= 0; i--) { string qarFile = qarFiles[i].Substring(SourceDir.Length + 1); if (!qarFile.StartsWith("Assets")) { string updatedQarName = HashingExtended.UpdateName(qarFile); if (updatedQarName != null) { updatedQarName = SourceDir + updatedQarName.Replace('/', '\\'); if (qarFiles.Contains(updatedQarName)) { MessageBox.Show(string.Format("{0} was not added to the build, because {1} (the unhashed filename of {0}) already exists in the mod directory.", Path.GetFileName(qarFiles[i]), Path.GetFileName(updatedQarName))); qarFiles.Remove(qarFiles[i]); } } } } metaData.ModQarEntries = new List <ModQarEntry>(); foreach (string qarFile in qarFiles) { string subDir = qarFile.Substring(0, qarFile.LastIndexOf("\\")).Substring(SourceDir.Length).TrimStart('\\'); // the subdirectory for XML output string qarFilePath = Tools.ToQarPath(qarFile.Substring(SourceDir.Length)); if (!Directory.Exists(Path.Combine("_build", subDir))) { Directory.CreateDirectory(Path.Combine("_build", subDir)); // create file structure } File.Copy(qarFile, Path.Combine("_build", Tools.ToWinPath(qarFilePath)), true); ulong hash = Tools.NameToHash(qarFilePath); metaData.ModQarEntries.Add(new ModQarEntry() { FilePath = qarFilePath, Compressed = qarFile.EndsWith(".fpk") || qarFile.EndsWith(".fpkd") ? true : false, ContentHash = Tools.GetMd5Hash(qarFile), Hash = hash }); } //tex build external entries metaData.ModFileEntries = new List <ModFileEntry>(); var externalFiles = ListExternalFiles(SourceDir); foreach (string externalFile in externalFiles) { string subDir = externalFile.Substring(0, externalFile.LastIndexOf("\\")).Substring(SourceDir.Length).TrimStart('\\'); // the subdirectory for XML output string externalFilePath = Tools.ToQarPath(externalFile.Substring(SourceDir.Length)); if (!Directory.Exists(Path.Combine("_build", subDir))) { Directory.CreateDirectory(Path.Combine("_build", subDir)); // create file structure } File.Copy(externalFile, Path.Combine("_build", Tools.ToWinPath(externalFilePath)), true); string strip = "/" + ExternalDirName; if (externalFilePath.StartsWith(strip)) { externalFilePath = externalFilePath.Substring(strip.Length); } //ulong hash = Tools.NameToHash(qarFilePath); metaData.ModFileEntries.Add(new ModFileEntry() { FilePath = externalFilePath, ContentHash = Tools.GetMd5Hash(externalFile) }); } metaData.SBVersion.Version = Application.ProductVersion; metaData.SaveToFile("_build\\metadata.xml"); // build archive FastZip zipper = new FastZip(); zipper.CreateZip(outputFilePath, "_build", true, "(.*?)"); try { Directory.Delete("_build", true); } catch (Exception e) { Debug.LogLine(string.Format("[BuildArchive] _build directory could not be deleted: {0}", e.ToString())); } }