Skip to content

Instantly share code, notes, and snippets.

@gotmachine
Last active May 14, 2022 09:51
Show Gist options
  • Save gotmachine/62d73cccc442532bdeccc0708631314a to your computer and use it in GitHub Desktop.
Save gotmachine/62d73cccc442532bdeccc0708631314a to your computer and use it in GitHub Desktop.
Manual assembly loading for KSP
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