Last active
May 14, 2022 09:51
-
-
Save gotmachine/62d73cccc442532bdeccc0708631314a to your computer and use it in GitHub Desktop.
Manual assembly loading for KSP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Reflection; | |
using System.Linq; | |
using UnityEngine; | |
namespace KSPCommunityFixes | |
{ | |
[KSPAddon(KSPAddon.Startup.Instantly, true)] | |
public class MyAssemblyLoader : MonoBehaviour | |
{ | |
private void Awake() | |
{ | |
string myCustomAssemblyPath; | |
// Example : get an absolute path in GameData | |
myCustomAssemblyPath = Path.Combine(Path.GetFullPath(KSPUtil.ApplicationRootPath), "GameData", "MyModPath", "Plugins", "mySupportAssembly.supportdll"); | |
// Example : load an assembly from the same folder as the current plugin | |
myCustomAssemblyPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mySupportAssembly.supportdll"); | |
// Example : load a non-renamed assembly from a PluginData subfolder | |
myCustomAssemblyPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "PluginData", "mySupportAssembly.dll"); | |
// Example : load the assembly only if the "anotherAssembly" plugin is already loaded | |
// Since we are running from a "Startup.Instantly" KSPAddon, also use that startup state to ensure any KSPAddon in the loaded assembly also gets started. | |
CustomAssemblyLoader.LoadAssembly(myCustomAssemblyPath, KSPAddon.Startup.Instantly, new string[] {"anotherAssembly"}); | |
Destroy(this); | |
} | |
} | |
static class CustomAssemblyLoader | |
{ | |
/// <summary> | |
/// Manually load a dll assembly instead of relying on KSP built-in plugin loader. | |
/// Allow to perform extra checks like conditionally loading a support assembly based on the KSP version or the presence of another assembly. | |
/// To prevent KSP from automatically loading your dll, either change the file extension from *.dll to something else, or place your *.dll in a "PluginData" subfolder. | |
/// </summary> | |
/// <param name="path">Full path to the dll file you want to load</param> | |
/// <param name="kspAddonStartup">If your assembly has KSPAddon classes that you want instantiated immediately, set this to match what is defined in your class attribute. Set to 0 to disable KSPAddon instantiation.</param> | |
/// <param name="assemblyDependencies">If non-null, the assembly will be loaded only if those assemblies (by internal AssemblyTitle) are already loaded</param> | |
/// <param name="gameDataDependencies">If non-null, the assembly will be loaded only if those GameData subfolders exists (case-sensitive)</param> | |
/// <param name="kspVersionMin">If non-null, the assembly will be loaded only if the current KSP version is greater than the provided Version</param> | |
/// <param name="kspVersionMax">If non-null, the assembly will be loaded only if the current KSP version is lower than the provided Version</param> | |
/// <returns>true if the assembly was loaded, false otherwise</returns> | |
public static bool LoadAssembly(string path, KSPAddon.Startup kspAddonStartup = 0, string[] assemblyDependencies = null, string[] gameDataDependencies = null, Version kspVersionMin = null, Version kspVersionMax = null) | |
{ | |
AssemblyName assemblyName; | |
try | |
{ | |
assemblyName = AssemblyName.GetAssemblyName(path); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to load assembly from \"{path}\":\n{e}"); | |
return false; | |
} | |
if (kspVersionMin != null || kspVersionMax != null) | |
{ | |
Version kspVersion = new Version(Versioning.version_major, Versioning.version_minor, Versioning.Revision); | |
if (kspVersionMin != null && kspVersion < kspVersionMin) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to load assembly \"{assemblyName.Name}\" from \"{path}\": min compatible KSP version is {kspVersionMin}, KSP version is {kspVersion}"); | |
return false; | |
} | |
if (kspVersionMax != null && kspVersion > kspVersionMax) | |
{ | |
Debug.LogError($"[LoadAssembly] Failed to load assembly \"{assemblyName.Name}\" from \"{path}\": max compatible KSP version is {kspVersionMax}, KSP version is {kspVersion}"); | |
return false; | |
} | |
} | |
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); | |
string[] loadedAssemblyNames = new string[loadedAssemblies.Length]; | |
for (int i = 0; i < loadedAssemblies.Length; i++) | |
{ | |
loadedAssemblyNames[i] = loadedAssemblies[i].GetName().Name; | |
if (loadedAssemblyNames[i] == assemblyName.Name) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to load assembly \"{assemblyName.Name}\" from \"{path}\": this assembly is already loaded"); | |
return false; | |
} | |
} | |
if (assemblyDependencies != null) | |
{ | |
foreach (string assemblyDependency in assemblyDependencies) | |
{ | |
if (!loadedAssemblyNames.Contains(assemblyDependency)) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to load assembly \"{assemblyName.Name}\" from \"{path}\": the required assembly dependency \"{assemblyDependency}\" isn't loaded."); | |
return false; | |
} | |
} | |
} | |
if (gameDataDependencies != null) | |
{ | |
string gameDataPath = Path.Combine(Path.GetFullPath(KSPUtil.ApplicationRootPath), "GameData"); | |
foreach (string gameDataDependency in gameDataDependencies) | |
{ | |
if (!Directory.Exists(Path.Combine(gameDataPath, gameDataDependency))) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to load assembly \"{assemblyName.Name}\" from \"{path}\": the required gamedata folder \"{gameDataDependency}\" doesn't exists."); | |
return false; | |
} | |
} | |
} | |
AssemblyLoader.LoadedAssembly kspLoadedAssembly; | |
try | |
{ | |
// note : we don't care about the url. nothing is using it in stock, and hopefully nobody in their right mind would ever use it. | |
kspLoadedAssembly = new AssemblyLoader.LoadedAssembly(null, path, string.Empty, null); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to create LoadedAssembly while loading \"{assemblyName.Name}\" from \"{path}\":\n{e}"); | |
return false; | |
} | |
kspLoadedAssembly.CheckDependencies(AssemblyLoader.loadedAssemblies.ToList()); | |
if (!kspLoadedAssembly.dependenciesMet) | |
{ | |
Debug.LogWarning($"[CustomAssemblyLoader] Failed to load \"{assemblyName.Name}\" from \"{path}\": assembly has unmet KSPAssembly dependencies"); | |
return false; | |
} | |
Type[] loadedAssemblyTypes; | |
try | |
{ | |
kspLoadedAssembly.Load(); | |
if (kspLoadedAssembly.assembly == null) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to load \"{assemblyName.Name}\" from \"{path}\": unknown error"); | |
return false; | |
} | |
AssemblyLoader.LoadedTypes loadedTypes = new AssemblyLoader.LoadedTypes(); | |
loadedAssemblyTypes = kspLoadedAssembly.assembly.GetTypes(); | |
foreach (Type type in loadedAssemblyTypes) | |
{ | |
foreach (Type loadedType in AssemblyLoader.loadedTypes) | |
{ | |
if (type.IsSubclassOf(loadedType) || type == loadedType) | |
{ | |
loadedTypes.Add(loadedType, type); | |
} | |
} | |
} | |
foreach (Type key in loadedTypes.Keys) | |
{ | |
foreach (Type item in loadedTypes[key]) | |
{ | |
kspLoadedAssembly.types.Add(key, item); | |
kspLoadedAssembly.typesDictionary.Add(key, item); | |
} | |
} | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to load \"{assemblyName.Name}\" from \"{path}\":\n{e}"); | |
return false; | |
} | |
AssemblyLoader.loadedAssemblies.Add(kspLoadedAssembly); | |
Debug.Log($"[CustomAssemblyLoader] Loaded assembly \"{assemblyName.Name}\" from \"{path}\""); | |
foreach (Type t in loadedAssemblyTypes) | |
{ | |
if (t.IsSubclassOf(typeof(VesselModule)) && !(t == typeof(VesselModule))) | |
{ | |
VesselModuleManager.VesselModuleWrapper vesselModuleWrapper = new VesselModuleManager.VesselModuleWrapper(t); | |
try | |
{ | |
GameObject gameObject = new GameObject("Temp"); | |
VesselModule vesselModule = gameObject.AddComponent(t) as VesselModule; | |
if (vesselModule != null) | |
{ | |
vesselModuleWrapper.order = vesselModule.GetOrder(); | |
Debug.Log("VesselModules: Found VesselModule of type " + t.Name + " with order " + vesselModuleWrapper.order); | |
UnityEngine.Object.DestroyImmediate(vesselModule); | |
} | |
UnityEngine.Object.DestroyImmediate(gameObject); | |
VesselModuleManager.Modules.Add(vesselModuleWrapper); | |
} | |
catch (Exception ex) | |
{ | |
Debug.LogError("VesselModules: Error getting order of VesselModule of type " + t.Name + " so it was not added. Exception: " + ex); | |
} | |
} | |
} | |
if (kspAddonStartup != 0) | |
{ | |
foreach (Type type in loadedAssemblyTypes) | |
{ | |
if (type.IsSubclassOf(typeof(MonoBehaviour)) || !(type != typeof(MonoBehaviour))) | |
{ | |
KSPAddon[] array = (KSPAddon[])type.GetCustomAttributes(typeof(KSPAddon), inherit: true); | |
if (array.Length != 0) | |
{ | |
try | |
{ | |
MethodInfo startAddon = typeof(AddonLoader).GetMethod("StartAddon", BindingFlags.Instance | BindingFlags.NonPublic); | |
startAddon.Invoke(AddonLoader.Instance, new object[] { kspLoadedAssembly, type, array[0], kspAddonStartup }); ; | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError($"[CustomAssemblyLoader] Failed to start KSPAddon \"{type}\" from assembly \"{assemblyName.Name}\": {e}"); | |
} | |
} | |
} | |
} | |
} | |
return true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment