// ran by BTML public static void Init() { ModDirectory = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(VersionManifestUtilities.MANIFEST_FILEPATH), @"..\..\..\Mods\")); logPath = Path.Combine(ModDirectory, "ModTek.log"); // create log file, overwritting if it's already there using (var logWriter = File.CreateText(logPath)) logWriter.WriteLine("ModTek -- {0}", DateTime.Now); // init harmony and patch the stuff that comes with ModTek (contained in Patches.cs) var harmony = HarmonyInstance.Create("io.github.mpstark.ModTek"); harmony.PatchAll(Assembly.GetExecutingAssembly()); // find all sub-directories that have a mod.json file var modDirectories = Directory.GetDirectories(ModDirectory).Where(x => File.Exists(Path.Combine(x, MOD_JSON_NAME))); if (modDirectories.Count() == 0) { Log("No ModTek-compatable mods found."); } // create ModDef objects for each mod.json file, building a dependancy graph var modDefs = new List <ModDef>(); foreach (var modDirectory in modDirectories) { ModDef modDef = null; var modDefPath = Path.Combine(modDirectory, MOD_JSON_NAME); try { modDef = JsonConvert.DeserializeObject <ModDef>(File.ReadAllText(modDefPath)); } catch (Exception e) { Log("Caught exception while parsing {1} at path {0}", modDefPath, MOD_JSON_NAME); Log("Exception: {0}", e.ToString()); continue; } if (modDef == null) { Log("ModDef found in {0} not valid.", modDefPath); continue; } if (!modDef.Enabled) { Log("{0} disabled, skipping load.", modDef.Name); continue; } modDef.Directory = Path.GetDirectoryName(modDefPath); modDefs.Add(modDef); Log("Loaded ModDef for {0}.", modDef.Name); // TODO: build some sort of dependacy graph } // TODO: load mods in the correct order foreach (var modDef in modDefs) { LoadMod(modDef); } }
public static void LoadMod(ModDef modDef) { var potentialAdditions = new Dictionary <string, ModDef.ManifestEntry>(); LogWithDate($"Loading {modDef.Name}"); // load out of the manifest // TODO: actually ignore the modDef specified files/directories if (modDef.Manifest != null && modDef.Manifest.Count > 0) { foreach (var entry in modDef.Manifest) { var entryPath = Path.Combine(modDef.Directory, entry.Path); if (string.IsNullOrEmpty(entry.Path) || string.IsNullOrEmpty(entry.Type)) { LogWithDate($"{modDef.Name} has a manifest entry that is missing its type or path! Aborting load."); return; } if (Directory.Exists(entryPath)) { // path is a directory, add all the files there var files = Directory.GetFiles(entryPath); foreach (var filePath in files) { var id = InferIDFromFileAndType(filePath, entry.Type); if (potentialAdditions.ContainsKey(id)) { LogWithDate($"{modDef.Name}'s manifest has a file at {filePath}, but it's inferred ID is already used by this mod! Aborting load."); return; } potentialAdditions.Add(id, new ModDef.ManifestEntry(entry.Type, filePath)); } } else if (File.Exists(entryPath)) { // path is a file, add the single entry var id = entry.Id ?? InferIDFromFileAndType(entryPath, entry.Type); if (potentialAdditions.ContainsKey(id)) { LogWithDate($"{modDef.Name}'s manifest has a file at {entryPath}, but it's inferred ID is already used by this mod! Aborting load."); return; } potentialAdditions.Add(id, new ModDef.ManifestEntry(entry.Type, entryPath)); } else { // wasn't a file and wasn't a path, must not exist LogWithDate($"{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)) { LogWithDate($"{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 - 1); methodName = modDef.DLLEntryPoint.Substring(pos + 1); } } LogWithDate($"Using 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) { // TODO, this kvp is the weirdest thing foreach (var additionKvp in potentialAdditions) { Log($"\tWill load manifest entry -- id: {additionKvp.Key} type: {additionKvp.Value.Type} path: {additionKvp.Value.Path}"); NewManifestEntries[additionKvp.Key] = additionKvp.Value; } } LogWithDate($"Loaded {modDef.Name}"); }
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> LoadMoadsLoop() { // Only want to run this function once -- it could get submitted a few times if (hasLoadedMods) { yield break; } stopwatch.Start(); string sliderText = "Loading Mods"; Log(""); LogWithDate("Pre-loading mods..."); yield return(new ProgressReport(0, sliderText, "Pre-loading mods...")); // find all sub-directories that have a mod.json file var modDirectories = Directory.GetDirectories(ModsDirectory) .Where(x => File.Exists(Path.Combine(x, MOD_JSON_NAME))).ToArray(); if (modDirectories.Length == 0) { hasLoadedMods = true; Log("No ModTek-compatable mods found."); yield break; } // create ModDef objects for each mod.json file var modDefs = new Dictionary <string, ModDef>(); foreach (var modDirectory in modDirectories) { ModDef modDef; var modDefPath = Path.Combine(modDirectory, MOD_JSON_NAME); try { modDef = ModDef.CreateFromPath(modDefPath); } catch (Exception e) { Log($"Caught exception while parsing {MOD_JSON_NAME} at path {modDefPath}"); Log($"\t{e.Message}"); continue; } if (!modDef.Enabled) { Log($"Will not load {modDef.Name} because it's disabled."); continue; } if (modDefs.ContainsKey(modDef.Name)) { Log($"Already loaded a mod named {modDef.Name}. Skipping load from {modDef.Directory}."); continue; } modDefs.Add(modDef.Name, modDef); } PropagateConflictsForward(modDefs); modLoadOrder = GetLoadOrder(modDefs, out var willNotLoad); int modLoaded = 0; // lists guarentee order foreach (var modName in modLoadOrder) { var modDef = modDefs[modName]; yield return(new ProgressReport( (float)modLoaded++ / (float)modLoadOrder.Count, sliderText, string.Format("Loading Mod: {0} {1}", modDef.Name, modDef.Version) )); try { LoadMod(modDef); } catch (Exception e) { Log($"Tried to load mod: {modDef.Name}, but something went wrong. Make sure all of your JSON is correct!"); Log($"\t{e.Message}"); } } foreach (var modDef in willNotLoad) { Log($"Will not load {modDef}. It's lacking a dependancy or a conflict loaded before it."); } stopwatch.Stop(); Log(""); LogWithDate($"Done pre-load mods. Elapsed running time: {stopwatch.Elapsed.TotalSeconds} seconds\n"); Log("----------\n"); // write out harmony summary PrintHarmonySummary(HarmonySummaryPath); // write out load order File.WriteAllText(LoadOrderPath, JsonConvert.SerializeObject(modLoadOrder, Formatting.Indented)); hasLoadedMods = true; yield break; }
private static bool AreDependanciesResolved(ModDef modDef, HashSet <string> loaded) { return(!(modDef.DependsOn.Count != 0 && modDef.DependsOn.Intersect(loaded).Count() != modDef.DependsOn.Count || modDef.ConflictsWith.Count != 0 && modDef.ConflictsWith.Intersect(loaded).Any())); }
internal static IEnumerator <ProgressReport> LoadMoadsLoop() { stopwatch.Start(); Log(""); yield return(new ProgressReport(1, "Initializing Mods", "")); // find all sub-directories that have a mod.json file var modDirectories = Directory.GetDirectories(ModsDirectory) .Where(x => File.Exists(Path.Combine(x, MOD_JSON_NAME))).ToArray(); if (modDirectories.Length == 0) { Log("No ModTek-compatable mods found."); yield break; } // create ModDef objects for each mod.json file var modDefs = new Dictionary <string, ModDef>(); foreach (var modDirectory in modDirectories) { ModDef modDef; var modDefPath = Path.Combine(modDirectory, MOD_JSON_NAME); try { modDef = ModDef.CreateFromPath(modDefPath); } catch (Exception e) { Log($"Caught exception while parsing {MOD_JSON_NAME} at path {modDefPath}"); Log($"\t{e.Message}"); continue; } if (!modDef.Enabled) { Log($"Will not load {modDef.Name} because it's disabled."); continue; } if (modDefs.ContainsKey(modDef.Name)) { Log($"Already loaded a mod named {modDef.Name}. Skipping load from {modDef.Directory}."); continue; } modDefs.Add(modDef.Name, modDef); } Log(""); modLoadOrder = GetLoadOrder(modDefs, out var willNotLoad); foreach (var modName in willNotLoad) { Log($"Will not load {modName} because it's lacking a dependancy or a conflict loaded before it."); } Log(""); // lists guarentee order var modLoaded = 0; foreach (var modName in modLoadOrder) { var modDef = modDefs[modName]; yield return(new ProgressReport(modLoaded++ / ((float)modLoadOrder.Count), "Initializing Mods", $"{modDef.Name} {modDef.Version}")); try { LoadMod(modDef); } catch (Exception e) { Log($"Tried to load mod: {modDef.Name}, but something went wrong. Make sure all of your JSON is correct!"); Log($"\t{e.Message}"); } } PrintHarmonySummary(HarmonySummaryPath); WriteJsonFile(LoadOrderPath, modLoadOrder); stopwatch.Stop(); yield break; }