private static bool AddModEntryToVersionManifest(VersionManifest manifest, ModDef.ManifestEntry modEntry, bool addToDB = false) { if (modEntry.Path == null) { return(false); } VersionManifestAddendum addendum = null; if (!string.IsNullOrEmpty(modEntry.AddToAddendum)) { addendum = manifest.GetAddendumByName(modEntry.AddToAddendum); // create the addendum if it doesn't exist if (addendum == null) { Log($"\t\tCreated addendum: {modEntry.AddToAddendum}"); addendum = new VersionManifestAddendum(modEntry.AddToAddendum); manifest.ApplyAddendum(addendum); } } // add to DB if (addToDB && Path.GetExtension(modEntry.Path).ToLower() == ".json") { var type = (BattleTechResourceType)Enum.Parse(typeof(BattleTechResourceType), modEntry.Type); using (var metadataDatabase = new MetadataDatabase()) { VersionManifestHotReload.InstantiateResourceAndUpdateMDDB(type, modEntry.Path, metadataDatabase); Log($"\t\tAdding to MDDB! {type} {modEntry.Path}"); } } // add assetbundle path so it can be changed when the assetbundle path is requested if (modEntry.Type == "AssetBundle") { ModAssetBundlePaths[modEntry.Id] = modEntry.Path; } // add to addendum instead of adding to manifest if (addendum != null) { Log($"\t\tAddOrUpdate => {modEntry.Id} ({modEntry.Type}) to addendum {addendum.Name}"); addendum.AddOrUpdate(modEntry.Id, modEntry.Path, modEntry.Type, DateTime.Now, modEntry.AssetBundleName, modEntry.AssetBundlePersistent); return(true); } // not added to addendum, not added to jsonmerges Log($"\t\tAddOrUpdate => {modEntry.Id} ({modEntry.Type})"); manifest.AddOrUpdate(modEntry.Id, modEntry.Path, modEntry.Type, DateTime.Now, modEntry.AssetBundleName, modEntry.AssetBundlePersistent); return(true); }
// ADDING TO VERSION MANIFEST private static bool AddModEntry(VersionManifest manifest, ModDef.ManifestEntry modEntry) { if (modEntry.Path == null) { return(false); } VersionManifestAddendum addendum = null; if (!string.IsNullOrEmpty(modEntry.AddToAddendum)) { addendum = manifest.GetAddendumByName(modEntry.AddToAddendum); // create the addendum if it doesn't exist if (addendum == null) { Log($"\t\tCreated addendum: {modEntry.AddToAddendum}"); addendum = new VersionManifestAddendum(modEntry.AddToAddendum); manifest.ApplyAddendum(addendum); } } // add special handling for particular types switch (modEntry.Type) { case "AssetBundle": ModAssetBundlePaths[modEntry.Id] = modEntry.Path; break; case "Texture2D": ModTexture2Ds.Add(modEntry.Id); break; } // add to addendum instead of adding to manifest if (addendum != null) { Log($"\t\tAddOrUpdate => {modEntry.Id} ({modEntry.Type}) to addendum {addendum.Name}"); addendum.AddOrUpdate(modEntry.Id, modEntry.Path, modEntry.Type, DateTime.Now, modEntry.AssetBundleName, modEntry.AssetBundlePersistent); return(true); } // not added to addendum, not added to jsonmerges Log($"\t\tAddOrUpdate => {modEntry.Id} ({modEntry.Type})"); manifest.AddOrUpdate(modEntry.Id, modEntry.Path, modEntry.Type, DateTime.Now, modEntry.AssetBundleName, modEntry.AssetBundlePersistent); return(true); }
// ADDING MOD CONTENT TO THE GAME private static void AddModEntry(VersionManifest manifest, ModDef.ManifestEntry modEntry) { if (modEntry.Path == null) { return; } VersionManifestAddendum addendum = null; if (!string.IsNullOrEmpty(modEntry.AddToAddendum)) { addendum = manifest.GetAddendumByName(modEntry.AddToAddendum); if (addendum == null) { Log($"\tCannot add {modEntry.Id} to {modEntry.AddToAddendum} because addendum doesn't exist in the manifest."); return; } } // add special handling for particular types switch (modEntry.Type) { case "AssetBundle": ModAssetBundlePaths[modEntry.Id] = modEntry.Path; break; case "Texture2D": ModTexture2Ds.Add(modEntry.Id); break; } // add to addendum instead of adding to manifest if (addendum != null) { Log($"\tAdd/Replace: \"{GetRelativePath(modEntry.Path, ModsDirectory)}\" ({modEntry.Type}) [{addendum.Name}]"); } else { Log($"\tAdd/Replace: \"{GetRelativePath(modEntry.Path, ModsDirectory)}\" ({modEntry.Type})"); } // not added to addendum, not added to jsonmerges BTRLEntries.Add(modEntry); return; }
internal static void TryAddToVersionManifest(VersionManifest manifest) { if (!hasLoadedMods) { LoadMods(); } // there are no mods loaded, just return if (modLoadOrder == null || modLoadOrder.Count == 0) { return; } if (modEntries != null) { LogWithDate("Loading another manifest with already setup mod manifests."); foreach (var modEntry in modEntries) { AddModEntryToVersionManifest(manifest, modEntry); } LogWithDate("Done."); return; } modEntries = new List <ModDef.ManifestEntry>(); LogWithDate("Setting up mod manifests..."); var breakMyGame = File.Exists(Path.Combine(ModDirectory, "break.my.game")); if (breakMyGame) { var mddPath = Path.Combine(Path.Combine(StreamingAssetsDirectory, "MDD"), "MetadataDatabase.db"); var mddBackupPath = mddPath + ".orig"; Log($"\tBreak my game mode enabled! All new modded content (doesn't currently support merges) will be added to the DB."); if (!File.Exists(mddBackupPath)) { Log($"\t\tBacking up metadata database to {Path.GetFileName(mddBackupPath)}"); File.Copy(mddPath, mddBackupPath); } } var jsonMerges = new Dictionary <string, List <string> >(); foreach (var modName in modLoadOrder) { if (!ModManifest.ContainsKey(modName)) { continue; } Log($"\t{modName}:"); foreach (var modEntry in ModManifest[modName]) { // type being null means we have to figure out the type from the path (StreamingAssets) if (modEntry.Type == null) { // TODO: + 16 is a little bizzare looking, it's the length of the substring + 1 because we want to get rid of it and the \ var relPath = modEntry.Path.Substring(modEntry.Path.LastIndexOf("StreamingAssets", StringComparison.Ordinal) + 16); var fakeStreamingAssetsPath = Path.GetFullPath(Path.Combine(StreamingAssetsDirectory, relPath)); List <string> types; if (TypeCache.ContainsKey(fakeStreamingAssetsPath)) { types = TypeCache[fakeStreamingAssetsPath]; } else { // get the type from the manifest var matchingEntries = manifest.FindAll(x => Path.GetFullPath(x.FilePath) == fakeStreamingAssetsPath); if (matchingEntries == null || matchingEntries.Count == 0) { Log($"\t\tCould not find an existing VersionManifest entry for {modEntry.Id}. Is this supposed to be a new entry? Don't put new entries in StreamingAssets!"); continue; } types = new List <string>(); foreach (var existingEntry in matchingEntries) { types.Add(existingEntry.Type); } TypeCache[fakeStreamingAssetsPath] = types; } if (Path.GetExtension(modEntry.Path).ToLower() == ".json" && modEntry.ShouldMergeJSON) { if (!TypeCache.ContainsKey(fakeStreamingAssetsPath)) { Log($"\t\tUnable to determine type of {modEntry.Id}. Is there someone screwy with your this mod.json?"); continue; } if (!jsonMerges.ContainsKey(fakeStreamingAssetsPath)) { jsonMerges[fakeStreamingAssetsPath] = new List <string>(); } if (jsonMerges[fakeStreamingAssetsPath].Contains(modEntry.Path)) { continue; } // this assumes that .json can only have a single type modEntry.Type = TypeCache[fakeStreamingAssetsPath][0]; Log($"\t\tMerge => {modEntry.Id} ({modEntry.Type})"); jsonMerges[fakeStreamingAssetsPath].Add(modEntry.Path); continue; } foreach (var type in types) { var subModEntry = new ModDef.ManifestEntry(modEntry, modEntry.Path, modEntry.Id); subModEntry.Type = type; if (AddModEntryToVersionManifest(manifest, subModEntry, breakMyGame)) { modEntries.Add(modEntry); } } continue; } // non-streamingassets json merges if (Path.GetExtension(modEntry.Path).ToLower() == ".json" && modEntry.ShouldMergeJSON) { // have to find the original path for the manifest entry that we're merging onto var matchingEntry = manifest.Find(x => x.Id == modEntry.Id); if (matchingEntry == null) { Log($"\t\tCould not find an existing VersionManifest entry for {modEntry.Id}!"); continue; } if (!jsonMerges.ContainsKey(matchingEntry.FilePath)) { jsonMerges[matchingEntry.FilePath] = new List <string>(); } if (jsonMerges[matchingEntry.FilePath].Contains(modEntry.Path)) { continue; } // this assumes that .json can only have a single type modEntry.Type = matchingEntry.Type; Log($"\t\tMerge => {modEntry.Id} ({modEntry.Type})"); jsonMerges[matchingEntry.FilePath].Add(modEntry.Path); continue; } if (AddModEntryToVersionManifest(manifest, modEntry, breakMyGame)) { modEntries.Add(modEntry); } } } LogWithDate("Doing merges..."); foreach (var jsonMerge in jsonMerges) { var cachePath = JsonMergeCache.GetOrCreateCachedEntry(jsonMerge.Key, jsonMerge.Value); var cacheEntry = new ModDef.ManifestEntry(cachePath); cacheEntry.ShouldMergeJSON = false; cacheEntry.Type = TypeCache[jsonMerge.Key][0]; cacheEntry.Id = InferIDFromFileAndType(cachePath, cacheEntry.Type); if (AddModEntryToVersionManifest(manifest, cacheEntry, breakMyGame)) { modEntries.Add(cacheEntry); } } // write merge cache to disk JsonMergeCache.WriteCacheToDisk(Path.Combine(CacheDirectory, MERGE_CACHE_FILE_NAME)); // write type cache to disk File.WriteAllText(Path.Combine(CacheDirectory, TYPE_CACHE_FILE_NAME), JsonConvert.SerializeObject(TypeCache, Formatting.Indented)); LogWithDate("Done."); Log(""); }
private static void LoadMod(ModDef modDef) { var potentialAdditions = new List <ModDef.ManifestEntry>(); LogWithDate($"Loading {modDef.Name}"); // load out of the manifest if (modDef.LoadImplicitManifest && modDef.Manifest.All(x => Path.GetFullPath(Path.Combine(modDef.Directory, x.Path)) != Path.GetFullPath(Path.Combine(modDef.Directory, "StreamingAssets")))) { modDef.Manifest.Add(new ModDef.ManifestEntry("StreamingAssets", true)); } foreach (var entry in modDef.Manifest) { // handle prefabs; they have potential internal path to assetbundle if (entry.Type == "Prefab" && !string.IsNullOrEmpty(entry.AssetBundleName)) { if (!potentialAdditions.Any(x => x.Type == "AssetBundle" && x.Id == entry.AssetBundleName)) { Log($"\t{modDef.Name} has a Prefab that's referencing an AssetBundle that hasn't been loaded. Put the assetbundle first in the manifest!"); return; } entry.Id = Path.GetFileNameWithoutExtension(entry.Path); potentialAdditions.Add(entry); continue; } if (string.IsNullOrEmpty(entry.Path) && string.IsNullOrEmpty(entry.Type) && entry.Path != "StreamingAssets") { Log($"\t{modDef.Name} has a manifest entry that is missing its path or type! Aborting load."); return; } var entryPath = Path.Combine(modDef.Directory, entry.Path); if (Directory.Exists(entryPath)) { // path is a directory, add all the files there var files = Directory.GetFiles(entryPath, "*", SearchOption.AllDirectories); foreach (var filePath in files) { var childModDef = new ModDef.ManifestEntry(entry, filePath, InferIDFromFileAndType(filePath, entry.Type)); potentialAdditions.Add(childModDef); } } else if (File.Exists(entryPath)) { // path is a file, add the single entry entry.Id = entry.Id ?? InferIDFromFileAndType(entryPath, entry.Type); entry.Path = entryPath; potentialAdditions.Add(entry); } else if (entry.Path != "StreamingAssets") { // path is not streamingassets and it's missing Log($"\tMissing Entry: Manifest specifies file/directory of {entry.Type} at path {entry.Path}, but it's not there. Continuing to load."); } } // load mod dll if (modDef.DLL != null) { var dllPath = Path.Combine(modDef.Directory, modDef.DLL); string typeName = null; var methodName = "Init"; if (!File.Exists(dllPath)) { Log($"\t{modDef.Name} has a DLL specified ({dllPath}), but it's missing! Aborting load."); return; } if (modDef.DLLEntryPoint != null) { var pos = modDef.DLLEntryPoint.LastIndexOf('.'); if (pos == -1) { methodName = modDef.DLLEntryPoint; } else { typeName = modDef.DLLEntryPoint.Substring(0, pos); methodName = modDef.DLLEntryPoint.Substring(pos + 1); } } Log($"\tUsing BTML to load dll {Path.GetFileName(dllPath)} with entry path {typeName ?? "NoNameSpecified"}.{methodName}"); BTModLoader.LoadDLL(dllPath, methodName, typeName, new object[] { modDef.Directory, modDef.Settings.ToString(Formatting.None) }); } // actually add the additions, since we successfully got through loading the other stuff if (potentialAdditions.Count > 0) { foreach (var addition in potentialAdditions) { Log($"\tNew Entry: {addition.Path.Replace(ModDirectory, "")}"); } ModManifest[modDef.Name] = potentialAdditions; } }
public static void LoadMod(ModDef modDef) { var potentialAdditions = new List <ModDef.ManifestEntry>(); LogWithDate($"Loading {modDef.Name}"); // load out of the manifest if (modDef.LoadImplicitManifest && modDef.Manifest.All(x => Path.GetFullPath(Path.Combine(modDef.Directory, x.Path)) != Path.GetFullPath(Path.Combine(modDef.Directory, "StreamingAssets")))) { modDef.Manifest.Add(new ModDef.ManifestEntry("StreamingAssets", true)); } foreach (var entry in modDef.Manifest) { var entryPath = Path.Combine(modDef.Directory, entry.Path); if (string.IsNullOrEmpty(entry.Path) && (string.IsNullOrEmpty(entry.Type) || entry.Path == "StreamingAssets")) { Log($"\t{modDef.Name} has a manifest entry that is missing its path! Aborting load."); return; } if (Directory.Exists(entryPath)) { // path is a directory, add all the files there var files = Directory.GetFiles(entryPath, "*", SearchOption.AllDirectories); foreach (var filePath in files) { var childModDef = new ModDef.ManifestEntry(entry, filePath, InferIDFromFileAndType(filePath, entry.Type)); potentialAdditions.Add(childModDef); } } else if (File.Exists(entryPath)) { // path is a file, add the single entry entry.Id = entry.Id ?? InferIDFromFileAndType(entryPath, entry.Type); entry.Path = entryPath; potentialAdditions.Add(entry); } else { // wasn't a file and wasn't a path, must not exist // TODO: what to do with manifest entries that aren't implicit that are missing? //Log($"\t{modDef.Name} has a manifest entry {entryPath}, but it's missing! Aborting load."); //return; } } // load mod dll if (modDef.DLL != null) { var dllPath = Path.Combine(modDef.Directory, modDef.DLL); string typeName = null; var methodName = "Init"; if (!File.Exists(dllPath)) { Log($"\t{modDef.Name} has a DLL specified ({dllPath}), but it's missing! Aborting load."); return; } if (modDef.DLLEntryPoint != null) { var pos = modDef.DLLEntryPoint.LastIndexOf('.'); if (pos == -1) { methodName = modDef.DLLEntryPoint; } else { typeName = modDef.DLLEntryPoint.Substring(0, pos); methodName = modDef.DLLEntryPoint.Substring(pos + 1); } } Log($"\tUsing BTML to load dll {Path.GetFileName(dllPath)} with entry path {typeName ?? "NoNameSpecified"}.{methodName}"); BTModLoader.LoadDLL(dllPath, methodName, typeName, new object[] { modDef.Directory, modDef.Settings.ToString(Formatting.None) }); } // actually add the additions, since we successfully got through loading the other stuff if (potentialAdditions.Count > 0) { foreach (var addition in potentialAdditions) { Log($"\tNew Entry: {addition.Path.Replace(ModDirectory, "")}"); } ModManifest[modDef.Name] = potentialAdditions; } }
internal static IEnumerator <ProgressReport> BuildCachedManifestLoop(VersionManifest manifest) { stopwatch.Start(); // there are no mods loaded, just return if (modLoadOrder == null || modLoadOrder.Count == 0) { yield break; } string loadingModText = "Loading Mod Manifests"; yield return(new ProgressReport(0.0f, loadingModText, "Setting up mod manifests...")); LogWithDate("Setting up mod manifests..."); var jsonMerges = new Dictionary <string, List <string> >(); modEntries = new List <ModDef.ManifestEntry>(); int modCount = 0; var manifestMods = modLoadOrder.Where(name => modManifest.ContainsKey(name)).ToList(); foreach (var modName in manifestMods) { Log($"\t{modName}:"); yield return(new ProgressReport((float)modCount++ / (float)manifestMods.Count, loadingModText, string.Format("Loading manifest for {0}", modName))); foreach (var modEntry in modManifest[modName]) { // type being null means we have to figure out the type from the path (StreamingAssets) if (modEntry.Type == null) { // TODO: + 16 is a little bizzare looking, it's the length of the substring + 1 because we want to get rid of it and the \ var relPath = modEntry.Path.Substring(modEntry.Path.LastIndexOf("StreamingAssets", StringComparison.Ordinal) + 16); var fakeStreamingAssetsPath = Path.GetFullPath(Path.Combine(StreamingAssetsDirectory, relPath)); var types = GetTypesFromCacheOrManifest(manifest, fakeStreamingAssetsPath); if (types == null) { Log($"\t\tCould not find an existing VersionManifest entry for {modEntry.Id}. Is this supposed to be a new entry? Don't put new entries in StreamingAssets!"); continue; } if (Path.GetExtension(modEntry.Path).ToLower() == ".json" && modEntry.ShouldMergeJSON) { if (!jsonMerges.ContainsKey(fakeStreamingAssetsPath)) { jsonMerges[fakeStreamingAssetsPath] = new List <string>(); } if (jsonMerges[fakeStreamingAssetsPath].Contains(modEntry.Path)) { continue; } // this assumes that .json can only have a single type // typeCache will always contain this path modEntry.Type = typeCache[fakeStreamingAssetsPath][0]; Log($"\t\tMerge => {modEntry.Id} ({modEntry.Type})"); jsonMerges[fakeStreamingAssetsPath].Add(modEntry.Path); continue; } foreach (var type in types) { var subModEntry = new ModDef.ManifestEntry(modEntry, modEntry.Path, modEntry.Id); subModEntry.Type = type; if (AddModEntry(manifest, subModEntry)) { modEntries.Add(subModEntry); } } continue; } // get "fake" entries that don't actually go into the game's VersionManifest // add videos to be loaded from an external path switch (modEntry.Type) { case "Video": var fileName = Path.GetFileName(modEntry.Path); if (fileName != null && File.Exists(modEntry.Path)) { Log($"\t\tVideo => {fileName}"); ModVideos.Add(fileName, modEntry.Path); } continue; case "AdvancedJSONMerge": var targetFileRelative = AdvancedJSONMerger.GetTargetFile(modEntry.Path); var targetFile = ResolvePath(targetFileRelative); // need to add the types of the file to the typeCache, so that they can be used later // this actually returns the type, but we don't actually care about that right now GetTypesFromCacheOrManifest(manifest, targetFile); if (!jsonMerges.ContainsKey(targetFile)) { jsonMerges[targetFile] = new List <string>(); } if (jsonMerges[targetFile].Contains(modEntry.Path)) { continue; } Log($"\t\tAdvancedJSONMerge => {modEntry.Id} ({modEntry.Type})"); jsonMerges[targetFile].Add(modEntry.Path); continue; } // non-streamingassets json merges if (Path.GetExtension(modEntry.Path)?.ToLower() == ".json" && modEntry.ShouldMergeJSON) { // have to find the original path for the manifest entry that we're merging onto var matchingEntry = manifest.Find(x => x.Id == modEntry.Id); if (matchingEntry == null) { Log($"\t\tCould not find an existing VersionManifest entry for {modEntry.Id}!"); continue; } if (!jsonMerges.ContainsKey(matchingEntry.FilePath)) { jsonMerges[matchingEntry.FilePath] = new List <string>(); } if (jsonMerges[matchingEntry.FilePath].Contains(modEntry.Path)) { continue; } // this assumes that .json can only have a single type modEntry.Type = matchingEntry.Type; if (!typeCache.ContainsKey(matchingEntry.FilePath)) { typeCache[matchingEntry.FilePath] = new List <string>(); typeCache[matchingEntry.FilePath].Add(modEntry.Type); } Log($"\t\tMerge => {modEntry.Id} ({modEntry.Type})"); jsonMerges[matchingEntry.FilePath].Add(modEntry.Path); continue; } if (AddModEntry(manifest, modEntry)) { modEntries.Add(modEntry); } } } yield return(new ProgressReport(100.0f, "JSON", "Writing JSON file to disk")); // write type cache to disk WriteJsonFile(TypeCachePath, typeCache); // perform merges into cache LogWithDate("Doing merges..."); yield return(new ProgressReport(0.0f, "Merges", "Doing Merges...")); int mergeCount = 0; foreach (var jsonMerge in jsonMerges) { yield return(new ProgressReport((float)mergeCount++ / jsonMerges.Count, "Merges", string.Format("Merging {0}", jsonMerge.Key))); var cachePath = jsonMergeCache.GetOrCreateCachedEntry(jsonMerge.Key, jsonMerge.Value); // something went wrong (the parent json prob had errors) if (cachePath == null) { continue; } var cacheEntry = new ModDef.ManifestEntry(cachePath); cacheEntry.ShouldMergeJSON = false; cacheEntry.Type = typeCache[jsonMerge.Key][0]; // this assumes only one type for each json file cacheEntry.Id = InferIDFromFile(cachePath); if (AddModEntry(manifest, cacheEntry)) { modEntries.Add(cacheEntry); } } yield return(new ProgressReport(100.0f, "Merge Cache", "Writing Merge Cache to disk")); // write merge cache to disk jsonMergeCache.WriteCacheToDisk(Path.Combine(CacheDirectory, MERGE_CACHE_FILE_NAME)); LogWithDate("Adding to DB..."); // check if files removed from DB cache var rebuildDB = false; var replacementEntries = new List <VersionManifestEntry>(); var removeEntries = new List <string>(); string dbText = "Syncing Database"; yield return(new ProgressReport(0.0f, dbText, "")); foreach (var kvp in dbCache) { var path = kvp.Key; if (File.Exists(path)) { continue; } Log($"\tNeed to remove DB entry from file in path: {path}"); // file is missing, check if another entry exists with same filename in manifest var fileName = Path.GetFileName(path); var existingEntry = manifest.Find(x => Path.GetFileName(x.FilePath) == fileName); if (existingEntry == null) { Log("\t\tHave to rebuild DB, no existing entry in VersionManifest matches removed entry"); rebuildDB = true; break; } replacementEntries.Add(existingEntry); removeEntries.Add(path); } // add removed entries replacements to db dbText = "Cleaning Database"; yield return(new ProgressReport(100.0f, dbText, "")); if (!rebuildDB) { // remove old entries foreach (var removeEntry in removeEntries) { dbCache.Remove(removeEntry); } using (var metadataDatabase = new MetadataDatabase()) { foreach (var replacementEntry in replacementEntries) { if (AddModEntryToDB(metadataDatabase, Path.GetFullPath(replacementEntry.FilePath), replacementEntry.Type)) { Log($"\t\tReplaced DB entry with an existing entry in path: {Path.GetFullPath(replacementEntry.FilePath)}"); } } } } // if an entry has been removed and we cannot find a replacement, have to rebuild the mod db if (rebuildDB) { if (File.Exists(ModMDDBPath)) { File.Delete(ModMDDBPath); } File.Copy(MDDBPath, ModMDDBPath); dbCache = new Dictionary <string, DateTime>(); } // add needed files to db dbText = "Populating Database"; int addCount = 0; yield return(new ProgressReport(0.0f, dbText, "")); using (var metadataDatabase = new MetadataDatabase()) { foreach (var modEntry in modEntries) { if (modEntry.AddToDB && AddModEntryToDB(metadataDatabase, modEntry.Path, modEntry.Type)) { yield return(new ProgressReport((float)addCount / (float)modEntries.Count, dbText, string.Format("Added {0}", modEntry.Path))); Log($"\tAdded/Updated {modEntry.Id} ({modEntry.Type})"); } addCount++; } } // write db/type cache to disk WriteJsonFile(DBCachePath, dbCache); stopwatch.Stop(); Log(""); LogWithDate($"Done. Elapsed running time: {stopwatch.Elapsed.TotalSeconds} seconds\n"); // Cache the completed manifest ModTek.cachedManifest = manifest; try { if (manifest != null && ModTek.modEntries != null) { ModTek.modtekOverrides = manifest.Entries.Where(e => ModTek.modEntries.Any(m => e.Id == m.Id)) // ToDictionary expects distinct keys, so take the last entry of each Id .GroupBy(ks => ks.Id) .Select(v => v.Last()) .ToDictionary(ks => ks.Id); } Logger.Log("Built {0} modtek overrides", ModTek.modtekOverrides.Count()); } catch (Exception e) { Logger.Log("Failed to build overrides {0}", e); } yield break; }
internal static void AddModEntries(VersionManifest manifest) { if (!hasLoadedMods) { LoadMods(); } stopwatch.Start(); // there are no mods loaded, just return if (modLoadOrder == null || modLoadOrder.Count == 0) { return; } if (modEntries != null) { LogWithDate("Loading another manifest with already setup mod manifests."); foreach (var modEntry in modEntries) { AddModEntry(manifest, modEntry); } stopwatch.Stop(); Log(""); LogWithDate($"Done. Elapsed running time: {stopwatch.Elapsed.TotalSeconds} seconds\n"); return; } LogWithDate("Setting up mod manifests..."); var jsonMerges = new Dictionary <string, List <string> >(); modEntries = new List <ModDef.ManifestEntry>(); foreach (var modName in modLoadOrder) { if (!modManifest.ContainsKey(modName)) { continue; } Log($"\t{modName}:"); foreach (var modEntry in modManifest[modName]) { // type being null means we have to figure out the type from the path (StreamingAssets) if (modEntry.Type == null) { // TODO: + 16 is a little bizzare looking, it's the length of the substring + 1 because we want to get rid of it and the \ var relPath = modEntry.Path.Substring(modEntry.Path.LastIndexOf("StreamingAssets", StringComparison.Ordinal) + 16); var fakeStreamingAssetsPath = Path.GetFullPath(Path.Combine(StreamingAssetsDirectory, relPath)); List <string> types; if (typeCache.ContainsKey(fakeStreamingAssetsPath)) { types = typeCache[fakeStreamingAssetsPath]; } else { // get the type from the manifest var matchingEntries = manifest.FindAll(x => Path.GetFullPath(x.FilePath) == fakeStreamingAssetsPath); if (matchingEntries == null || matchingEntries.Count == 0) { Log($"\t\tCould not find an existing VersionManifest entry for {modEntry.Id}. Is this supposed to be a new entry? Don't put new entries in StreamingAssets!"); continue; } types = new List <string>(); foreach (var existingEntry in matchingEntries) { types.Add(existingEntry.Type); } typeCache[fakeStreamingAssetsPath] = types; } if (Path.GetExtension(modEntry.Path).ToLower() == ".json" && modEntry.ShouldMergeJSON) { if (!typeCache.ContainsKey(fakeStreamingAssetsPath)) { Log($"\t\tUnable to determine type of {modEntry.Id}. Is there someone screwy with your this mod.json?"); continue; } if (!jsonMerges.ContainsKey(fakeStreamingAssetsPath)) { jsonMerges[fakeStreamingAssetsPath] = new List <string>(); } if (jsonMerges[fakeStreamingAssetsPath].Contains(modEntry.Path)) { continue; } // this assumes that .json can only have a single type modEntry.Type = typeCache[fakeStreamingAssetsPath][0]; Log($"\t\tMerge => {modEntry.Id} ({modEntry.Type})"); jsonMerges[fakeStreamingAssetsPath].Add(modEntry.Path); continue; } foreach (var type in types) { var subModEntry = new ModDef.ManifestEntry(modEntry, modEntry.Path, modEntry.Id); subModEntry.Type = type; if (AddModEntry(manifest, subModEntry)) { modEntries.Add(subModEntry); } } continue; } // get "fake" entries that don't actually go into the game's VersionManifest // add videos to be loaded from an external path if (modEntry.Type == "Video") { var fileName = Path.GetFileName(modEntry.Path); if (fileName != null && File.Exists(modEntry.Path)) { Log($"\t\tVideo => {fileName}"); ModVideos.Add(fileName, modEntry.Path); } continue; } // non-streamingassets json merges if (Path.GetExtension(modEntry.Path)?.ToLower() == ".json" && modEntry.ShouldMergeJSON) { // have to find the original path for the manifest entry that we're merging onto var matchingEntry = manifest.Find(x => x.Id == modEntry.Id); if (matchingEntry == null) { Log($"\t\tCould not find an existing VersionManifest entry for {modEntry.Id}!"); continue; } if (!jsonMerges.ContainsKey(matchingEntry.FilePath)) { jsonMerges[matchingEntry.FilePath] = new List <string>(); } if (jsonMerges[matchingEntry.FilePath].Contains(modEntry.Path)) { continue; } // this assumes that .json can only have a single type modEntry.Type = matchingEntry.Type; if (!typeCache.ContainsKey(matchingEntry.FilePath)) { typeCache[matchingEntry.FilePath] = new List <string>(); typeCache[matchingEntry.FilePath].Add(modEntry.Type); } Log($"\t\tMerge => {modEntry.Id} ({modEntry.Type})"); jsonMerges[matchingEntry.FilePath].Add(modEntry.Path); continue; } if (AddModEntry(manifest, modEntry)) { modEntries.Add(modEntry); } } } // write type cache to disk WriteJsonFile(TypeCachePath, typeCache); // perform merges into cache LogWithDate("Doing merges..."); foreach (var jsonMerge in jsonMerges) { var cachePath = jsonMergeCache.GetOrCreateCachedEntry(jsonMerge.Key, jsonMerge.Value); // something went wrong (the parent json prob had errors) if (cachePath == null) { continue; } var cacheEntry = new ModDef.ManifestEntry(cachePath); cacheEntry.ShouldMergeJSON = false; cacheEntry.Type = typeCache[jsonMerge.Key][0]; cacheEntry.Id = InferIDFromFile(cachePath); if (AddModEntry(manifest, cacheEntry)) { modEntries.Add(cacheEntry); } } // write merge cache to disk jsonMergeCache.WriteCacheToDisk(Path.Combine(CacheDirectory, MERGE_CACHE_FILE_NAME)); LogWithDate("Adding to DB..."); // check if files removed from DB cache var rebuildDB = false; var replacementEntries = new List <VersionManifestEntry>(); var removeEntries = new List <string>(); foreach (var kvp in dbCache) { var path = kvp.Key; if (File.Exists(path)) { continue; } Log($"\tNeed to remove DB entry from file in path: {path}"); // file is missing, check if another entry exists with same filename in manifest var fileName = Path.GetFileName(path); var existingEntry = manifest.Find(x => Path.GetFileName(x.FilePath) == fileName); if (existingEntry == null) { Log("\t\tHave to rebuild DB, no existing entry in VersionManifest matches removed entry"); rebuildDB = true; break; } replacementEntries.Add(existingEntry); removeEntries.Add(path); } // add removed entries replacements to db if (!rebuildDB) { // remove old entries foreach (var removeEntry in removeEntries) { dbCache.Remove(removeEntry); } using (var metadataDatabase = new MetadataDatabase()) { foreach (var replacementEntry in replacementEntries) { if (AddModEntryToDB(metadataDatabase, Path.GetFullPath(replacementEntry.FilePath), replacementEntry.Type)) { Log($"\t\tReplaced DB entry with an existing entry in path: {Path.GetFullPath(replacementEntry.FilePath)}"); } } } } // if an entry has been removed and we cannot find a replacement, have to rebuild the mod db if (rebuildDB) { if (File.Exists(ModDBPath)) { File.Delete(ModDBPath); } File.Copy(Path.Combine(Path.Combine(StreamingAssetsDirectory, "MDD"), MDD_FILE_NAME), ModDBPath); dbCache = new Dictionary <string, DateTime>(); } // add needed files to db using (var metadataDatabase = new MetadataDatabase()) { foreach (var modEntry in modEntries) { if (modEntry.AddToDB && AddModEntryToDB(metadataDatabase, modEntry.Path, modEntry.Type)) { Log($"\tAdded/Updated {modEntry.Id} ({modEntry.Type})"); } } } // write db/type cache to disk WriteJsonFile(DBCachePath, dbCache); stopwatch.Stop(); Log(""); LogWithDate($"Done. Elapsed running time: {stopwatch.Elapsed.TotalSeconds} seconds\n"); }
internal static IEnumerator <ProgressReport> BuildModManifestEntriesLoop() { stopwatch.Start(); // there are no mods loaded, just return if (modLoadOrder == null || modLoadOrder.Count == 0) { yield break; } Log(""); var jsonMerges = new Dictionary <string, List <string> >(); var manifestMods = modLoadOrder.Where(name => entriesByMod.ContainsKey(name)).ToList(); var entryCount = 0; var numEntries = 0; entriesByMod.Do(entries => numEntries += entries.Value.Count); foreach (var modName in manifestMods) { Log($"{modName}:"); foreach (var modEntry in entriesByMod[modName]) { yield return(new ProgressReport(entryCount++ / ((float)numEntries), $"Loading {modName}", modEntry.Id)); // type being null means we have to figure out the type from the path (StreamingAssets) if (modEntry.Type == null) { // TODO: + 16 is a little bizzare looking, it's the length of the substring + 1 because we want to get rid of it and the \ var relPath = modEntry.Path.Substring(modEntry.Path.LastIndexOf("StreamingAssets", StringComparison.Ordinal) + 16); var fakeStreamingAssetsPath = Path.GetFullPath(Path.Combine(StreamingAssetsDirectory, relPath)); if (!File.Exists(fakeStreamingAssetsPath)) { Log($"\tCould not find a file at {fakeStreamingAssetsPath} for {modName} {modEntry.Id}. NOT LOADING THIS FILE"); continue; } var types = GetTypesFromCacheOrManifest(CachedVersionManifest, modEntry.Id); if (types == null) { Log($"\tCould not find an existing VersionManifest entry for {modEntry.Id}. Is this supposed to be a new entry? Don't put new entries in StreamingAssets!"); continue; } // this is getting merged later and then added to the BTRL entries then if (Path.GetExtension(modEntry.Path).ToLower() == ".json" && modEntry.ShouldMergeJSON) { if (!jsonMerges.ContainsKey(modEntry.Id)) { jsonMerges[modEntry.Id] = new List <string>(); } if (jsonMerges[modEntry.Id].Contains(modEntry.Path)) // TODO: is this necessary? { continue; } // this assumes that .json can only have a single type // typeCache will always contain this path modEntry.Type = GetTypesFromCache(modEntry.Id)[0]; Log($"\tMerge: \"{GetRelativePath(modEntry.Path, ModsDirectory)}\" ({modEntry.Type})"); jsonMerges[modEntry.Id].Add(modEntry.Path); continue; } foreach (var type in types) { var subModEntry = new ModDef.ManifestEntry(modEntry, modEntry.Path, modEntry.Id); subModEntry.Type = type; AddModEntry(CachedVersionManifest, subModEntry); // clear json merges for this entry, mod is overwriting the original file, previous mods merges are tossed if (jsonMerges.ContainsKey(modEntry.Id)) { jsonMerges.Remove(modEntry.Id); Log($"\t\tHad merges for {modEntry.Id} but had to toss, since original file is being replaced"); } } continue; } // get "fake" entries that don't actually go into the game's VersionManifest // add videos to be loaded from an external path switch (modEntry.Type) { case "Video": var fileName = Path.GetFileName(modEntry.Path); if (fileName != null && File.Exists(modEntry.Path)) { Log($"\tVideo: \"{GetRelativePath(modEntry.Path, ModsDirectory)}\""); ModVideos.Add(fileName, modEntry.Path); } continue; case "AdvancedJSONMerge": var id = AdvancedJSONMerger.GetTargetID(modEntry.Path); // need to add the types of the file to the typeCache, so that they can be used later // if merging onto a file added by another mod, the type is already in the cache var types = GetTypesFromCacheOrManifest(CachedVersionManifest, id); if (!jsonMerges.ContainsKey(id)) { jsonMerges[id] = new List <string>(); } if (jsonMerges[id].Contains(modEntry.Path)) // TODO: is this necessary? { continue; } Log($"\tAdvancedJSONMerge: \"{GetRelativePath(modEntry.Path, ModsDirectory)}\" ({types[0]})"); jsonMerges[id].Add(modEntry.Path); continue; } // non-streamingassets json merges if (Path.GetExtension(modEntry.Path)?.ToLower() == ".json" && modEntry.ShouldMergeJSON) { // have to find the original path for the manifest entry that we're merging onto var matchingEntry = GetEntryFromCachedOrBTRLEntries(modEntry.Id); if (matchingEntry == null) { Log($"\tCould not find an existing VersionManifest entry for {modEntry.Id}!"); continue; } var matchingPath = Path.GetFullPath(matchingEntry.FilePath); if (!jsonMerges.ContainsKey(modEntry.Id)) { jsonMerges[modEntry.Id] = new List <string>(); } if (jsonMerges[modEntry.Id].Contains(modEntry.Path)) // TODO: is this necessary? { continue; } Log($"\tMerge: \"{GetRelativePath(modEntry.Path, ModsDirectory)}\" ({modEntry.Type})"); // this assumes that .json can only have a single type modEntry.Type = matchingEntry.Type; TryAddTypeToCache(modEntry.Id, modEntry.Type); jsonMerges[modEntry.Id].Add(modEntry.Path); continue; } AddModEntry(CachedVersionManifest, modEntry); TryAddTypeToCache(modEntry.Id, modEntry.Type); // clear json merges for this entry, mod is overwriting the original file, previous mods merges are tossed if (jsonMerges.ContainsKey(modEntry.Id)) { jsonMerges.Remove(modEntry.Id); Log($"\t\tHad merges for {modEntry.Id} but had to toss, since original file is being replaced"); } } } WriteJsonFile(TypeCachePath, typeCache); // perform merges into cache Log(""); LogWithDate("Doing merges..."); yield return(new ProgressReport(1, "Merging", "")); var mergeCount = 0; foreach (var id in jsonMerges.Keys) { var existingEntry = GetEntryFromCachedOrBTRLEntries(id); if (existingEntry == null) { Log($"\tHave merges for {id} but cannot find an original file! Skipping."); continue; } var originalPath = Path.GetFullPath(existingEntry.FilePath); var mergePaths = jsonMerges[id]; if (!jsonMergeCache.HasCachedEntry(originalPath, mergePaths)) { yield return(new ProgressReport(mergeCount++ / ((float)jsonMerges.Count), "Merging", id)); } var cachePath = jsonMergeCache.GetOrCreateCachedEntry(originalPath, mergePaths); // something went wrong (the parent json prob had errors) if (cachePath == null) { continue; } var cacheEntry = new ModDef.ManifestEntry(cachePath) { ShouldMergeJSON = false, Type = GetTypesFromCache(id)[0], // this assumes only one type for each json file Id = id }; AddModEntry(CachedVersionManifest, cacheEntry); } jsonMergeCache.WriteCacheToDisk(Path.Combine(CacheDirectory, MERGE_CACHE_FILE_NAME)); Log(""); Log("Syncing Database"); yield return(new ProgressReport(1, "Syncing Database", "")); // check if files removed from DB cache var rebuildDB = false; var replacementEntries = new List <VersionManifestEntry>(); var removeEntries = new List <string>(); foreach (var path in dbCache.Keys) { var absolutePath = ResolvePath(path, GameDirectory); // check if the file in the db cache is still used if (BTRLEntries.Exists(x => x.Path == absolutePath)) { continue; } Log($"\tNeed to remove DB entry from file in path: {path}"); // file is missing, check if another entry exists with same filename in manifest or in BTRL entries var fileName = Path.GetFileName(path); var existingEntry = BTRLEntries.FindLast(x => Path.GetFileName(x.Path) == fileName)?.GetVersionManifestEntry() ?? CachedVersionManifest.Find(x => Path.GetFileName(x.FilePath) == fileName); if (existingEntry == null) { Log("\t\tHave to rebuild DB, no existing entry in VersionManifest matches removed entry"); rebuildDB = true; break; } replacementEntries.Add(existingEntry); removeEntries.Add(path); } // add removed entries replacements to db if (!rebuildDB) { // remove old entries foreach (var removeEntry in removeEntries) { dbCache.Remove(removeEntry); } using (var metadataDatabase = new MetadataDatabase()) { foreach (var replacementEntry in replacementEntries) { if (AddModEntryToDB(metadataDatabase, Path.GetFullPath(replacementEntry.FilePath), replacementEntry.Type)) { Log($"\t\tReplaced DB entry with an existing entry in path: {Path.GetFullPath(replacementEntry.FilePath)}"); } } } } // if an entry has been removed and we cannot find a replacement, have to rebuild the mod db if (rebuildDB) { if (File.Exists(ModMDDBPath)) { File.Delete(ModMDDBPath); } File.Copy(MDDBPath, ModMDDBPath); dbCache = new Dictionary <string, DateTime>(); } // add needed files to db var addCount = 0; using (var metadataDatabase = new MetadataDatabase()) { foreach (var modEntry in BTRLEntries) { if (modEntry.AddToDB && AddModEntryToDB(metadataDatabase, modEntry.Path, modEntry.Type)) { yield return(new ProgressReport(addCount / ((float)BTRLEntries.Count), "Populating Database", modEntry.Id)); Log($"\tAdded/Updated {modEntry.Id} ({modEntry.Type})"); } addCount++; } } // write db/type cache to disk WriteJsonFile(DBCachePath, dbCache); stopwatch.Stop(); Log(""); LogWithDate($"Done. Elapsed running time: {stopwatch.Elapsed.TotalSeconds} seconds\n"); CloseLogStream(); yield break; }