Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 87 additions & 27 deletions ModTek/ModTek.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand All @@ -17,11 +18,13 @@
// ReSharper disable FieldCanBeMadeReadOnly.Local

namespace ModTek
{
{
using static Logger;

public static class ModTek
{
public static VersionManifest cachedManifest = null;

private static bool hasLoadedMods; //defaults to false

// file/directory names
Expand Down Expand Up @@ -123,9 +126,21 @@ public static void Init()
jsonMergeCache = LoadOrCreateMergeCache(MergeCachePath);
typeCache = LoadOrCreateTypeCache(TypeCachePath);

// 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());
// First step in setting up the progress panel
if (ProgressPanel.Initialize(ModDirectory, $"ModTek v{Assembly.GetExecutingAssembly().GetName().Version}"))
{
// 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());

LoadMods();

BuildCachedManifest();
}
else
{
Log("Failed to load progress bar. Skipping mod loading completely.");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we will control the filename, I would be more explicit about like "you need modtekassetbundle in your Mods directory"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the result after the change I made:

Attempting to load asset bundle: C:\Program Files (x86)\Steam\steamapps\common\BATTLETECH\Mods\modtekassetbundle
8:09:55 PM - Error loading asset bundle modtekassetbundle
Exception caught: System.IO.IOException: Error loading asset bundle modtekassetbundle
at ModTek.ProgressPanel.InitializeAssets (System.String assetDirectory) [0x00000] in :0
at ModTek.ProgressPanel.Initialize (System.String directory, System.String panelTitle) [0x00000] in :0
Failed to load progress bar. Skipping mod loading completely.

}

stopwatch.Stop();
}
Expand Down Expand Up @@ -329,13 +344,24 @@ private static void LoadMod(ModDef modDef)

internal static void LoadMods()
{
ProgressPanel.SubmitWork(ModTek.LoadMoadsLoop);
}

internal static IEnumerator<ProgressReport> LoadMoadsLoop()
{
// Only want to run this function once -- it could get submitted a few times
if (hasLoadedMods)
return;
{
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(ModDirectory)
Expand All @@ -345,7 +371,7 @@ internal static void LoadMods()
{
hasLoadedMods = true;
Log("No ModTek-compatable mods found.");
return;
yield break;
}

// create ModDef objects for each mod.json file
Expand Down Expand Up @@ -384,10 +410,12 @@ internal static void LoadMods()
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}", modDef.Name));

try
{
Expand Down Expand Up @@ -417,6 +445,8 @@ internal static void LoadMods()
File.WriteAllText(LoadOrderPath, JsonConvert.SerializeObject(modLoadOrder, Formatting.Indented));

hasLoadedMods = true;

yield break;
}

private static string InferIDFromFile(string path)
Expand Down Expand Up @@ -686,41 +716,47 @@ private static bool AddModEntryToDB(MetadataDatabase db, string path, string typ
return false;
}

internal static void AddModEntries(VersionManifest manifest)
internal static void BuildCachedManifest()
{
if (!hasLoadedMods)
LoadMods();
// First load the default battletech manifest, then it'll get appended to
VersionManifest vanillaManifest = VersionManifestUtilities.LoadDefaultManifest();

// Wrapper to be able to submit a parameterless work function
IEnumerator<ProgressReport> NestedFunc()
{
IEnumerator<ProgressReport> reports = BuildCachedManifestLoop(vanillaManifest);
while (reports.MoveNext())
{
yield return reports.Current;
}
}

ProgressPanel.SubmitWork(NestedFunc);
}


internal static IEnumerator<ProgressReport> BuildCachedManifestLoop(VersionManifest manifest) {

stopwatch.Start();

// there are no mods loaded, just return
if (modLoadOrder == null || modLoadOrder.Count == 0)
return;
yield break;

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;
}
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>();
foreach (var modName in modLoadOrder)
{
if (!modManifest.ContainsKey(modName))
continue;
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)
Expand Down Expand Up @@ -840,13 +876,18 @@ internal static void AddModEntries(VersionManifest manifest)
}
}

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)
Expand All @@ -863,6 +904,8 @@ internal static void AddModEntries(VersionManifest manifest)
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));

Expand All @@ -872,6 +915,9 @@ internal static void AddModEntries(VersionManifest manifest)
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;
Expand All @@ -897,6 +943,8 @@ internal static void AddModEntries(VersionManifest manifest)
}

// add removed entries replacements to db
dbText = "Cleaning Database";
yield return new ProgressReport(100.0f, dbText, "");
if (!rebuildDB)
{
// remove old entries
Expand Down Expand Up @@ -924,12 +972,19 @@ internal static void AddModEntries(VersionManifest manifest)
}

// 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++;
}
}

Expand All @@ -939,6 +994,11 @@ internal static void AddModEntries(VersionManifest manifest)
stopwatch.Stop();
Log("");
LogWithDate($"Done. Elapsed running time: {stopwatch.Elapsed.TotalSeconds} seconds\n");

// Cache the completed manifest
ModTek.cachedManifest = manifest;

yield break;
}
}
}
4 changes: 4 additions & 0 deletions ModTek/ModTek.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
<HintPath>$(BattleTechGame)\BattleTech_Data\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>$(BattleTechGame)\BattleTech_Data\Managed\UnityEngine.UI.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="IManifestEntry.cs" />
Expand All @@ -75,6 +78,7 @@
<Compile Include="ModDef.cs" />
<Compile Include="ModTek.cs" />
<Compile Include="Patches.cs" />
<Compile Include="ProgressPanel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
38 changes: 32 additions & 6 deletions ModTek/Patches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,18 +138,44 @@ public static void Prefix(ref string fileID)
[HarmonyPatch(typeof(VersionManifestUtilities), "LoadDefaultManifest")]
public static class VersionManifestUtilities_LoadDefaultManifest_Patch
{
public static void Postfix(VersionManifest __result)
public static bool Prefix(ref VersionManifest __result)
{
ModTek.AddModEntries(__result);
// Return the cached manifest if it exists -- otherwise call the method as normal
if (ModTek.cachedManifest != null)
{
__result = ModTek.cachedManifest;
return false;
}
else
{
return true;
}
}
}

[HarmonyPatch(typeof(DataManager), new[] { typeof(MessageCenter) })]
public static class DataManager_CTOR_Patch
// This will disable activateAfterInit from functioning for the Start() on the "Main" game object which activates the BattleTechGame object
// This stops the main game object from loading immediately -- so work can be done beforehand
[HarmonyPatch(typeof(ActivateAfterInit), "Start")]
public static class ActivateAfterInit_Start_Patch
{
public static void Prefix()
public static bool Prefix(ActivateAfterInit __instance)
{
ModTek.LoadMods();
Traverse trav = Traverse.Create(__instance);
if (ActivateAfterInit.ActivateAfter.Start.Equals(trav.Field("activateAfter").GetValue<ActivateAfterInit.ActivateAfter>()))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this code gets called more than once per time the game is started (via save game loads or whatever), should cached the field infos potentially.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK -- the Main object activates BattleTechGame once during the initial startup, this is here just to 'pause' the loading so we can animate. I don't believe that BattleTechGame gets activated another time through this mechanism (it can be activated in other ways though)

{
GameObject[] gameObjects = trav.Field("activationSet").GetValue<GameObject[]>();
foreach (var gameObject in gameObjects)
{
if ("BattleTechGame".Equals(gameObject.name))
{
// Don't activate through this call!
return false;
}
}
}
// Call the method
return true;
}
}
}

Loading