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 Assembly OnResolve(object sender, ResolveEventArgs args)
        {
            try {
                var name = new AssemblyName(args.Name);

                // handle ambiguous resolve requests by checking all mod bin dirs
                // TODO: recursive scan module dirs
                if (args.RequestingAssembly == null)
                {
                    foreach (var modInfo in ModuleInfo.GetModules())
                    {
                        if (!modInfo.IsSelected)
                        {
                            continue;
                        }

                        var modDir = Path.GetDirectoryName(ModuleInfo.GetPath(modInfo.Id));
                        if (modDir == null)
                        {
                            continue;
                        }

                        var modPath = Path.Combine(modDir, "bin", Common.ConfigName, name.Name + ".dll");
                        if (!File.Exists(modPath))
                        {
                            continue;
                        }

                        var absPath = new Uri(Path.Combine(Environment.CurrentDirectory, modPath)).LocalPath;

                        if (SecurityHelpers.UnblockFile(absPath))
                        {
                            Console.WriteLine("Unblocked: " + absPath);
                        }

                        Console.WriteLine("Resolved: " + absPath);

                        return(Assembly.LoadFrom(absPath));
                    }

                    return(null);
                }

                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)
                {
                    if (!mi.IsSelected)
                    {
                        continue;
                    }

                    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, name.Name + ".dll");

                        if (!File.Exists(modPath))
                        {
                            continue;
                        }

                        var absPath = new Uri(Path.Combine(Environment.CurrentDirectory, modPath)).LocalPath;

                        if (SecurityHelpers.UnblockFile(absPath))
                        {
                            Console.WriteLine("Unblocked: " + absPath);
                        }

                        Console.WriteLine("Resolved: " + absPath);

                        return(Assembly.LoadFrom(absPath));
                    }
                }
            }
            catch {
                // TODO: log?
            }

            return(null);
        }
        private static void LoadDelayedSubModules()
        {
            foreach (var mod in ModuleInfo.GetModules())
            {
                if (!mod.IsSelected)
                {
                    continue;
                }

                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 <XmlElement>();
                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 <MBSubModuleBase>)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]);
                }
            }
        }
 public MCMSubModuleInfo(MCMModuleInfo owner, SubModuleInfo subModuleInfo, Type subModuleType)
 {
     Owner         = owner;
     SubModuleInfo = subModuleInfo;
     SubModuleType = subModuleType;
 }