private static void LoadConfig()
        {
            try
            {
                ConfigNode settings = ConfigNode.Load(KSPUtil.ApplicationRootPath + "GameData/KramaxPluginReload/Settings.cfg");

                foreach (ConfigNode node in settings.GetNodes("PluginSetting"))
                {
                    PluginSetting pluginSetting = new PluginSetting()
                    {
                        Name                 = node.GetValue("name"),
                        Path                 = node.GetValue("path"),
                        LoadOnce             = bool.Parse(node.GetValue("loadOnce")),
                        MethodsAllowedToFail = bool.Parse(node.GetValue("methodsAllowedToFail"))
                    };

                    PluginSettings.Add(pluginSetting);
                }
            }
            catch (Exception ex)
            {
                Deb.Log("KramaxPluginReload: Failed to load settings.cfg. Error:\n{0}", ex);
                return;
            }
        }
        public PluginReloadModule()
        {
            Deb.Log("KramaxPluginReload loaded, Version: {0}.", Assembly.GetExecutingAssembly().GetName().Version);

            LoadConfig();
            LoadPlugins();
            PluginReloadWindow.OpenWindow();
        }
        static Type CreateUniqueSubClass(ModuleBuilder moduleBldr, Type originalType, int versionUid)
        {
            String newClassName = String.Format("{0}_{1}_", originalType.Name, versionUid);

            Deb.Log("CreateUniqueSubClass: new class name is {0}", newClassName);


            TypeBuilder typeBldr =
                moduleBldr.DefineType(newClassName, TypeAttributes.Public | TypeAttributes.Class, originalType);

            ConstructorBuilder ctor = typeBldr.DefineDefaultConstructor(MethodAttributes.Public);

            return(typeBldr.CreateType());
        }
Beispiel #4
0
 private static Assembly LoadAssembly(string location)
 {
     try
     {
         byte[]   assemblyBytes = System.IO.File.ReadAllBytes(location);
         Assembly a             = Assembly.Load(assemblyBytes);
         Deb.Log("KramaxPluginReload: Reloaded assembly: {0} version: {1}.", a.GetName().Name, a.GetName().Version);
         return(a);
     }
     catch (Exception ex)
     {
         Deb.Log("KramaxPluginReload: Failed to load plugin from file {0}. Error:\n\n{1}", location, ex);
     }
     return(null);
 }
        static void RunProcess(String execPath, String execName, params String[] arguments)
        {
            Deb.Log("RunProcess, execPath: " + execPath + ", execName: " + execName);
            ProcessStartInfo startInfo = new ProcessStartInfo();
            Process          p         = new Process();

            startInfo.CreateNoWindow         = true;
            startInfo.RedirectStandardOutput = true;
            startInfo.RedirectStandardInput  = true;

            startInfo.UseShellExecute = false;
            startInfo.Arguments       = string.Join(" ", arguments.Select(e => "\"" + e + "\""));;
            startInfo.FileName        = Path.Combine(execPath, execName);
            Deb.Log("RunProcess, filename: " + startInfo.FileName + ", arguments: " + startInfo.Arguments);

            p.StartInfo = startInfo;
            p.Start();
            p.StandardOutput.ReadToEnd();
            p.WaitForExit();
        }
        private static void LoadPlugins()
        {
            versionCount = versionCount + 1;

            Deb.Log("KramaxPluginReload: (Re)loading plugins with version {0}.", versionCount);

            Type type = typeof(KramaxReloadExtensions.ReloadableMonoBehaviour);

            foreach (PluginSetting setting in PluginSettings)
            {
                //Skip reloading of loadonce assemblies
                if (setting.LoadOnce == true)
                {
                    continue;
                }

                //Call ondestroy on alive classes
                List <PluginClass> toRemove = new List <PluginClass>();

                foreach (PluginClass pluginClass in PluginClasses.Where(pc => pc.pluginSetting == setting))
                {
                    if (pluginClass.alive)
                    {
                        pluginClass.DeleteInstance();
                    }
                    toRemove.Add(pluginClass);
                }

                //Remove old class references
                foreach (PluginClass r in toRemove)
                {
                    PluginClasses.Remove(r);
                }

                var assembly = LoadAssembly(setting.Path);

                //Remove assembly if reloading failed
                if (assembly == null)
                {
                    foreach (PluginClass pluginClass in PluginClasses.Where(pc => pc.pluginSetting == setting).ToList())
                    {
                        PluginClasses.Remove(pluginClass);
                    }
                    continue;
                }

                String tmpAssemblyName = String.Format("KramaxPIRLAsmb_{0}", versionCount);
                String tmpModuleName   = String.Format("KramaxPIRLMod_{0}", versionCount);

                AssemblyBuilder assemblyBldr =
                    Thread.GetDomain().DefineDynamicAssembly(new AssemblyName(tmpAssemblyName),
                                                             AssemblyBuilderAccess.Run);

                ModuleBuilder moduleBldr = assemblyBldr.DefineDynamicModule(tmpModuleName);

                IList <Type> derivedClassess =
                    (from t in assembly.GetTypes()
                     where t.IsSubclassOf(typeof(MonoBehaviour))
                     select t).ToList();

                List <PluginClass>      plugins     = new List <PluginClass>();
                Dictionary <Type, Type> typeMapping = new Dictionary <Type, Type>();

                foreach (var derivedClass in derivedClassess)
                {
                    Deb.Log("KramaxPluginReload.LoadPlugins: got type {0}.", derivedClass.Name);
                    Deb.Log("KramaxPluginReload.LoadPlugins: from assembly {0}, v{1}.",
                            derivedClass.Assembly.GetName().Name,
                            derivedClass.Assembly.GetName().Version);

                    System.Attribute[] attrs = System.Attribute.GetCustomAttributes(derivedClass);

                    KSPAddon kspAddon = null;

                    foreach (var att in attrs)
                    {
                        if (att is KSPAddon)
                        {
                            kspAddon = att as KSPAddon;
                        }
                    }

                    if (!derivedClass.IsSubclassOf(typeof(KramaxReloadExtensions.ReloadableMonoBehaviour)))
                    {
                        Deb.Log("KramaxPluginReload.LoadPlugins: ERROR type {0} is not ReloadableMonoBehaviour subclass.",
                                derivedClass.Name);
                        continue;
                    }

                    if (kspAddon != null)
                    {
                        Deb.Log("KramaxPluginReload.LoadPlugins: type {0} will be top-level component.", derivedClass.Name);

                        PluginClass pluginClass = new PluginClass();

                        pluginClass.pluginSetting = setting;
                        pluginClass.originalType  = derivedClass;
                        pluginClass.kspAddon      = kspAddon;
                        pluginClass.type          = CreateUniqueSubClass(moduleBldr, derivedClass, versionCount);

                        plugins.Add(pluginClass);
                    }
                    else
                    {
                        Deb.Log("KramaxPluginReload.LoadPlugins: type {0} will be sub-level component.", derivedClass.Name);

                        var newType = CreateUniqueSubClass(moduleBldr, derivedClass, versionCount);
                        typeMapping[derivedClass] = newType;
                    }
                }

                foreach (var plugin in plugins)
                {
                    plugin.typeMapping = typeMapping;
                    PluginClasses.Add(plugin);
                }
            }

            foreach (var pluginClass in PluginClasses)
            {
                if (pluginClass.kspAddon.once == false || pluginClass.fired == false)
                {
                    bool awake = false;
                    switch (pluginClass.kspAddon.startup)
                    {
                    case KSPAddon.Startup.Instantly:
                    case KSPAddon.Startup.EveryScene:
                    //TODO: Check wether PSystem should even respawn.
                    case KSPAddon.Startup.PSystemSpawn:
                        awake = true;
                        break;

                    case KSPAddon.Startup.Credits:
                        awake = (HighLogic.LoadedScene == GameScenes.CREDITS);
                        break;

                    case KSPAddon.Startup.EditorAny:
                        awake = (HighLogic.LoadedScene == GameScenes.EDITOR);
                        break;

                    case KSPAddon.Startup.Flight:
                        awake = (HighLogic.LoadedScene == GameScenes.FLIGHT);
                        break;

                    case KSPAddon.Startup.MainMenu:
                        awake = (HighLogic.LoadedScene == GameScenes.MAINMENU);
                        break;

                    case KSPAddon.Startup.Settings:
                        awake = (HighLogic.LoadedScene == GameScenes.SETTINGS);
                        break;

                    case KSPAddon.Startup.SpaceCentre:
                        awake = (HighLogic.LoadedScene == GameScenes.SPACECENTER);
                        break;

                    case KSPAddon.Startup.TrackingStation:
                        awake = (HighLogic.LoadedScene == GameScenes.TRACKSTATION);
                        break;
                    }

                    if (awake)
                    {
                        Deb.Log("KramaxPluginReload.LoadPlugins: plugin should be awake: {0}.", pluginClass.Name);
                        pluginClass.CreateInstance();
                    }
                }
            }
            Deb.Log("KramaxPluginReload.LoadPlugins: Plugins (re)loaded.");
        }
        private static Assembly LoadAssembly(string location)
        {
            try
            {
                //Therere is a bug in Mono that prevents loading assembly with the same name more than once
                //(if such assembly is loaded then old version is returned). The bug report can be
                //found here: https://xamarin.github.io/bugzilla-archives/11/11199/bug.html.
                //The bug is corrected in mono-6.6.0.161 and mono-5.16.0.179
                //(commit https://github.com/mono/mono/commit/40c13f7b0ff71bfff8e58f8bd66bca0734d7d284 )
                //but mono used in KSP 1.8.1 is reported as "5.11.0 (Visual Studio built mono)".
                //To hack around this we do the following:
                // - Decompile .dll using ildasm
                // - Change assembly name inside decomiled file (fragment ".assembly <assembly-name>")
                // - Compile file again to .dll using ilasm
                // - Load changed .dll
                //example location of ildasm is C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\
                //example location of ilasm is C:\Windows\Microsoft.NET\Framework\v4.0.30319\.
                //
                //I do not know why this was working correctly in previous versions of KSP. Either the bug manifests
                //itself only on Windows (previously I was developing on OsX) or there was some change in Mono in Unity3d.
                //
                //
                if (!System.IO.File.Exists(location))
                {
                    Deb.LogError("File does not exist: {0}", location);
                    return(null);
                }

                List <string> filesToRemoveAtEnd = new List <string>();
                String        locationToRead     = location;
                if (windowsSdkBinPath != null && windowsSdkBinPath.Length > 0 && dotFrameworkBinPath != null && dotFrameworkBinPath.Length > 0)
                {
#if DEBUG
                    Deb.Log("Paths to ildasm and ilasm are provided. Will change the assembly name");
#endif
                    String oldName = Path.GetFileNameWithoutExtension(location);
                    String newName = oldName + "v" + versionCount;
                    String directoryForIntermediateOutput = Path.GetDirectoryName(location);
                    String decompiledPath = Path.Combine(directoryForIntermediateOutput, newName + ".decompiled");
                    filesToRemoveAtEnd.Add(decompiledPath);
                    filesToRemoveAtEnd.Add(Path.Combine(directoryForIntermediateOutput, newName + ".res"));
#if DEBUG
                    Deb.Log("Running ildasm, decompiled file: " + decompiledPath);
#endif
                    RunProcess(windowsSdkBinPath, "ildasm", location, "/output=" + decompiledPath, "/nobar");
#if DEBUG
                    Deb.Log("Substituting assembly name");
#endif
                    string[] lines = System.IO.File.ReadAllLines(decompiledPath);
                    for (int i = 0; i < lines.Length; ++i)
                    {
                        String line = lines[i];
                        if (line.Contains(".assembly " + oldName))
                        {
                            line = line.Replace(".assembly " + oldName, ".assembly " + newName);
                        }

                        // following line to work around a bug in ilasm
                        // details here: https://developercommunity.visualstudio.com/content/problem/742329/doublenan-field-ilasm-compilation-error.html
                        if (line.Contains("-nan(ind)"))
                        {
                            line = line.Replace("-nan(ind)", "0xfff8000000000000");
                        }

                        // Following line to work around a bug in ilasm,
                        // details here: https://developercommunity.visualstudio.com/content/problem/545431/ildasmexe-regression-with-infinum-floating-point-v.html
                        if (line.Contains("ldc.r8     inf"))
                        {
                            line = line.Replace("ldc.r8     inf", "ldc.r8(00 00 00 00 00 00 F0 7F)");
                        }

                        lines[i] = line;
                    }
#if DEBUG
                    Deb.Log("Writing decompiled file: " + decompiledPath);
#endif
                    System.IO.File.WriteAllLines(decompiledPath, lines);

#if DEBUG
                    Deb.Log("Running ilasm");
                    Deb.Log("dotFrameworkBinPath: " + dotFrameworkBinPath);
#endif
                    RunProcess(dotFrameworkBinPath, "ilasm", decompiledPath, "/dll");
                    locationToRead = Path.Combine(directoryForIntermediateOutput, newName + ".dll");
                    filesToRemoveAtEnd.Add(locationToRead);
                }

                byte[]   assemblyBytes = System.IO.File.ReadAllBytes(locationToRead);
                Assembly a             = Assembly.Load(assemblyBytes);

                Deb.Log("Reloaded assembly: {0} version: {1}.", a.GetName().Name, a.GetName().Version);

                foreach (String fileToRemove in filesToRemoveAtEnd)
                {
                    System.IO.File.Delete(fileToRemove);
                }

                return(a);
            }
            catch (Exception ex)
            {
                Deb.LogError("KramaxPluginReload: Failed to load plugin from file {0}. Error:\n\n{1}", location, ex);
            }

            return(null);
        }