From 2c6b3eba4625ce185b02f4b21e6cc3267031b12b Mon Sep 17 00:00:00 2001 From: Tyler Young Date: Sun, 5 Apr 2020 12:56:31 -0400 Subject: [PATCH] added support for delayed loading of other mods delayed loading allows mods to refer to other modules' assemblies --- SubModule.xml | 44 +++--- src/CommunityPatch/CommunityPatch.csproj | 40 ++--- src/CommunityPatch/CommunityPatch.sln | 24 +-- .../CommunityPatchSubModule.Initialization.cs | 137 ++++++++++++++++++ src/CommunityPatch/CommunityPatchSubModule.cs | 17 +++ src/CommunityPatch/FodyWeavers.xml | 3 - src/CommunityPatch/FodyWeavers.xsd | 26 ---- src/CommunityPatch/ModuleInitializer.cs | 17 --- 8 files changed, 194 insertions(+), 114 deletions(-) create mode 100644 src/CommunityPatch/CommunityPatchSubModule.Initialization.cs delete mode 100644 src/CommunityPatch/FodyWeavers.xml delete mode 100644 src/CommunityPatch/FodyWeavers.xsd delete mode 100644 src/CommunityPatch/ModuleInitializer.cs diff --git a/SubModule.xml b/SubModule.xml index adeecc2..812ea5d 100644 --- a/SubModule.xml +++ b/SubModule.xml @@ -1,28 +1,22 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CommunityPatch/CommunityPatch.csproj b/src/CommunityPatch/CommunityPatch.csproj index 2e831f1..79ce517 100644 --- a/src/CommunityPatch/CommunityPatch.csproj +++ b/src/CommunityPatch/CommunityPatch.csproj @@ -1,11 +1,10 @@ - netstandard2.0 True MSB3277 Release;Debug - X64 + x64 0.0.1 Community Patch @@ -14,7 +13,6 @@ ../../bin/Win64_Shipping_Client 8 - %(Identity) @@ -28,36 +26,16 @@ %(Identity) False - - %(Identity) - False - - - %(Identity) - False - - - %(Identity) - False - - - %(Identity) - False - - - - SubModule.xml - + + SubModule.xml + - - - - - - - + + + + - + \ No newline at end of file diff --git a/src/CommunityPatch/CommunityPatch.sln b/src/CommunityPatch/CommunityPatch.sln index 151fec0..9fb3f37 100644 --- a/src/CommunityPatch/CommunityPatch.sln +++ b/src/CommunityPatch/CommunityPatch.sln @@ -6,19 +6,19 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageRelease", "..\..\too EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|X64 = Debug|X64 - Release|X64 = Release|X64 + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Debug|X64.Build.0 = Debug|X64 - {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Debug|X64.Deploy.0 = Debug|X64 - {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Debug|X64.ActiveCfg = Debug|X64 - {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Release|X64.Build.0 = Release|X64 - {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Release|X64.Deploy.0 = Release|X64 - {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Release|X64.ActiveCfg = Release|X64 - {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Debug|X64.ActiveCfg = Debug|Any CPU - {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Debug|X64.Build.0 = Debug|Any CPU - {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Release|X64.ActiveCfg = Release|Any CPU - {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Release|X64.Build.0 = Release|Any CPU + {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Debug|x64.Build.0 = Debug|x64 + {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Debug|x64.Deploy.0 = Debug|x64 + {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Debug|x64.ActiveCfg = Debug|x64 + {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Release|x64.Build.0 = Release|x64 + {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Release|x64.Deploy.0 = Release|x64 + {D32B20F4-AB8C-4FDD-9C02-D71AEB37700B}.Release|x64.ActiveCfg = Release|x64 + {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Debug|x64.ActiveCfg = Debug|Any CPU + {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Debug|x64.Build.0 = Debug|Any CPU + {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Release|x64.ActiveCfg = Release|Any CPU + {E4C55A69-67F6-4F62-8336-B5665C5B55FF}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/CommunityPatch/CommunityPatchSubModule.Initialization.cs b/src/CommunityPatch/CommunityPatchSubModule.Initialization.cs new file mode 100644 index 0000000..d9c6776 --- /dev/null +++ b/src/CommunityPatch/CommunityPatchSubModule.Initialization.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using TaleWorlds.Library; +using TaleWorlds.MountAndBlade; +using Module = TaleWorlds.MountAndBlade.Module; + +namespace CommunityPatch { + + public partial class CommunityPatchSubModule { + + static CommunityPatchSubModule() { + // catch and record exceptions + AppDomain.CurrentDomain.FirstChanceException += (sender, args) => { + if (RecordFirstChanceExceptions) + RecordedFirstChanceExceptions.AddLast(args.Exception); + }; + AppDomain.CurrentDomain.UnhandledException += (sender, args) => { + RecordedUnhandledExceptions.AddLast((Exception) args.ExceptionObject); + CopyDiagnosticsToClipboard(); + }; + + // TODO: + /* + AppDomain.CurrentDomain.TypeResolve += (sender, args) => { + return null; + }; + */ + + // help delay loaded libs refer to mods they depend on + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { + MBSubModuleBase reqSm = null; + foreach (var sm in Module.CurrentModule.SubModules) { + var smAsm = sm.GetType().Assembly; + if (smAsm == args.RequestingAssembly) + reqSm = sm; + } + + if (reqSm == null) + return null; + + var resolvable = new LinkedList<(ModuleInfo Mod, SubModuleInfo SubMod)>(); + ModuleInfo reqMi = null; + SubModuleInfo reqSmi = null; + var modules = ModuleInfo.GetModules(); + foreach (var mi in modules) { + foreach (var smi in mi.SubModules) { + if (smi.Assemblies.Contains(args.Name)) + resolvable.AddLast((mi, smi)); + + if (smi.SubModuleClassType != reqSm.GetType().FullName) + continue; + + reqMi = mi; + reqSmi = smi; + } + } + + if (reqSmi == null) + return null; + + foreach (var modId in reqMi.DependedModuleIds) { + foreach (var resolution in resolvable) { + if (modId != resolution.Mod.Id) + continue; + + var modDir = Path.GetDirectoryName(ModuleInfo.GetPath(modId)); + if (modDir == null) + continue; + + var modPath = Path.Combine(modDir, "bin", Common.ConfigName, args.Name + ".dll"); + if (File.Exists(modPath)) + return Assembly.LoadFile(modPath); + } + } + + return null; + }; + } + + private static void LoadDelayedSubModules() { + foreach (var mod in ModuleInfo.GetModules()) { + var id = mod.Id; + var subModsXmlPath = ModuleInfo.GetPath(id); + var modDir = Path.GetDirectoryName(subModsXmlPath); + if (modDir == null) + continue; + + var subModsXml = new XmlDocument(); + subModsXml.Load(subModsXmlPath); + var delayedSubMods = subModsXml.SelectNodes("/Module/DelayedSubModules/SubModule")?.OfType(); + if (delayedSubMods == null) + continue; + + var main = Module.CurrentModule; + var typeMain = typeof(Module); + + foreach (var elem in delayedSubMods) { + var delayedSubModInfo = new SubModuleInfo(); + delayedSubModInfo.LoadFrom(elem); + + var dllPath = Path.Combine(modDir, "bin", Common.ConfigName, delayedSubModInfo.DLLName); + + var subModAsm = AssemblyLoader.LoadFrom(dllPath); + var subModType = subModAsm.GetType(delayedSubModInfo.SubModuleClassType); + if (!typeof(MBSubModuleBase).IsAssignableFrom(subModType)) + continue; + + var delayLoadedSubMod = (MBSubModuleBase) subModType.InvokeMember(".ctor", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance, + null, null, new object[0]); + + typeMain.InvokeMember("AddSubModule", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, + null, main, new object[] {subModAsm, delayedSubModInfo.SubModuleClassType}); + + var subMods = (ICollection) typeMain.InvokeMember("_submodules", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, + null, main, new object[0]); + + subMods.Add(delayLoadedSubMod); + + subModType.InvokeMember(nameof(OnSubModuleLoad), + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, + null, delayLoadedSubMod, new object[0]); + } + } + } + + } + +} \ No newline at end of file diff --git a/src/CommunityPatch/CommunityPatchSubModule.cs b/src/CommunityPatch/CommunityPatchSubModule.cs index 3a0b596..8b0985e 100644 --- a/src/CommunityPatch/CommunityPatchSubModule.cs +++ b/src/CommunityPatch/CommunityPatchSubModule.cs @@ -2,9 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; using JetBrains.Annotations; using TaleWorlds.CampaignSystem; using TaleWorlds.Core; +using TaleWorlds.Library; using TaleWorlds.Localization; using TaleWorlds.MountAndBlade; using Module = TaleWorlds.MountAndBlade.Module; @@ -81,6 +84,20 @@ protected override void OnSubModuleLoad() { base.OnSubModuleLoad(); } + private bool _ticked = false; + + protected override void OnApplicationTick(float dt) { + if (!_ticked) { + _ticked = true; + SynchronizationContext.Current.Post(_ => { + LoadDelayedSubModules(); + }, null); + base.OnApplicationTick(dt); + } + + // other stuff? + } + private static void ShowMessage(string msg) => InformationManager.DisplayMessage(new InformationMessage(msg)); diff --git a/src/CommunityPatch/FodyWeavers.xml b/src/CommunityPatch/FodyWeavers.xml deleted file mode 100644 index cb9dc50..0000000 --- a/src/CommunityPatch/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/CommunityPatch/FodyWeavers.xsd b/src/CommunityPatch/FodyWeavers.xsd deleted file mode 100644 index e24ec01..0000000 --- a/src/CommunityPatch/FodyWeavers.xsd +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/src/CommunityPatch/ModuleInitializer.cs b/src/CommunityPatch/ModuleInitializer.cs deleted file mode 100644 index 447c371..0000000 --- a/src/CommunityPatch/ModuleInitializer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using static CommunityPatch.CommunityPatchSubModule; - -internal static class ModuleInitializer { - - public static void Initialize() { - AppDomain.CurrentDomain.FirstChanceException += (sender, args) => { - if (RecordFirstChanceExceptions) - RecordedFirstChanceExceptions.AddLast(args.Exception); - }; - AppDomain.CurrentDomain.UnhandledException += (sender, args) => { - RecordedUnhandledExceptions.AddLast((Exception) args.ExceptionObject); - CopyDiagnosticsToClipboard(); - }; - } - -} \ No newline at end of file