264 lines
No EOL
8.8 KiB
C#
264 lines
No EOL
8.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32;
|
|
using Decal.Adapter;
|
|
|
|
namespace MosswartMassacre.Loader
|
|
{
|
|
[FriendlyName("MosswartMassacre.Loader")]
|
|
public class LoaderCore : FilterBase
|
|
{
|
|
private Assembly pluginAssembly;
|
|
private Type pluginType;
|
|
private object pluginInstance;
|
|
private FileSystemWatcher pluginWatcher;
|
|
private bool isSubscribedToRenderFrame = false;
|
|
private bool needsReload;
|
|
|
|
public static string PluginAssemblyNamespace => "MosswartMassacre";
|
|
public static string PluginAssemblyName => $"{PluginAssemblyNamespace}.dll";
|
|
public static string PluginAssemblyGuid => "{8C97E839-4D05-4A5F-B0C8-E8E778654322}";
|
|
|
|
public static bool IsPluginLoaded { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Assembly directory (contains both loader and plugin dlls)
|
|
/// </summary>
|
|
public static string AssemblyDirectory => System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(LoaderCore)).Location);
|
|
|
|
public DateTime LastDllChange { get; private set; }
|
|
|
|
#region Event Handlers
|
|
protected override void Startup()
|
|
{
|
|
try
|
|
{
|
|
Core.PluginInitComplete += Core_PluginInitComplete;
|
|
Core.PluginTermComplete += Core_PluginTermComplete;
|
|
Core.FilterInitComplete += Core_FilterInitComplete;
|
|
|
|
// Set up assembly resolution for hot-loaded plugin dependencies
|
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
|
|
// watch the AssemblyDirectory for any .dll file changes
|
|
pluginWatcher = new FileSystemWatcher();
|
|
pluginWatcher.Path = AssemblyDirectory;
|
|
pluginWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
|
pluginWatcher.Filter = "*.dll";
|
|
pluginWatcher.Changed += PluginWatcher_Changed;
|
|
pluginWatcher.EnableRaisingEvents = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
private void Core_FilterInitComplete(object sender, EventArgs e)
|
|
{
|
|
Core.EchoFilter.ClientDispatch += EchoFilter_ClientDispatch;
|
|
}
|
|
|
|
private void EchoFilter_ClientDispatch(object sender, NetworkMessageEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Login_SendEnterWorldRequest
|
|
if (e.Message.Type == 0xF7C8)
|
|
{
|
|
//EnsurePluginIsDisabledInRegistry();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
private void Core_PluginInitComplete(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
LoadPluginAssembly();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
private void Core_PluginTermComplete(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
UnloadPluginAssembly();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
protected override void Shutdown()
|
|
{
|
|
try
|
|
{
|
|
Core.PluginInitComplete -= Core_PluginInitComplete;
|
|
Core.PluginTermComplete -= Core_PluginTermComplete;
|
|
Core.FilterInitComplete -= Core_FilterInitComplete;
|
|
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
|
|
UnloadPluginAssembly();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
private void Core_RenderFrame(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (IsPluginLoaded && needsReload && DateTime.UtcNow - LastDllChange > TimeSpan.FromSeconds(1))
|
|
{
|
|
needsReload = false;
|
|
Core.RenderFrame -= Core_RenderFrame;
|
|
isSubscribedToRenderFrame = false;
|
|
LoadPluginAssembly();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
private void PluginWatcher_Changed(object sender, FileSystemEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Only reload if it's the main plugin DLL
|
|
if (e.Name == PluginAssemblyName)
|
|
{
|
|
LastDllChange = DateTime.UtcNow;
|
|
needsReload = true;
|
|
|
|
if (!isSubscribedToRenderFrame)
|
|
{
|
|
isSubscribedToRenderFrame = true;
|
|
Core.RenderFrame += Core_RenderFrame;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
|
{
|
|
try
|
|
{
|
|
// Extract just the assembly name (without version info)
|
|
string assemblyName = args.Name.Split(',')[0] + ".dll";
|
|
string assemblyPath = System.IO.Path.Combine(AssemblyDirectory, assemblyName);
|
|
|
|
// If the dependency exists in our plugin directory, load it
|
|
if (File.Exists(assemblyPath))
|
|
{
|
|
return Assembly.LoadFrom(assemblyPath);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"AssemblyResolve failed for {args.Name}: {ex.Message}");
|
|
}
|
|
|
|
// Return null to let default resolution continue
|
|
return null;
|
|
}
|
|
#endregion
|
|
|
|
#region Plugin Loading/Unloading
|
|
internal void LoadPluginAssembly()
|
|
{
|
|
try
|
|
{
|
|
if (IsPluginLoaded)
|
|
{
|
|
UnloadPluginAssembly();
|
|
try
|
|
{
|
|
CoreManager.Current.Actions.AddChatText($"[MosswartMassacre] Reloading {PluginAssemblyName}", 5);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
pluginAssembly = Assembly.Load(File.ReadAllBytes(System.IO.Path.Combine(AssemblyDirectory, PluginAssemblyName)));
|
|
pluginType = pluginAssembly.GetType($"{PluginAssemblyNamespace}.PluginCore");
|
|
pluginInstance = Activator.CreateInstance(pluginType);
|
|
|
|
// Set the AssemblyDirectory property if it exists
|
|
var assemblyDirProperty = pluginType.GetProperty("AssemblyDirectory", BindingFlags.Public | BindingFlags.Static);
|
|
assemblyDirProperty?.SetValue(null, AssemblyDirectory);
|
|
|
|
// Set the IsHotReload flag if it exists
|
|
var isHotReloadProperty = pluginType.GetProperty("IsHotReload", BindingFlags.Public | BindingFlags.Static);
|
|
isHotReloadProperty?.SetValue(null, true);
|
|
|
|
// The original template doesn't set up Host - it just calls Startup
|
|
// The plugin should use CoreManager.Current.Actions instead of MyHost for hot reload scenarios
|
|
// We'll set a flag so the plugin knows it's being hot loaded
|
|
|
|
// Call Startup method
|
|
var startupMethod = pluginType.GetMethod("Startup", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
startupMethod.Invoke(pluginInstance, new object[] { });
|
|
|
|
IsPluginLoaded = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
|
|
private void UnloadPluginAssembly()
|
|
{
|
|
try
|
|
{
|
|
if (pluginInstance != null && pluginType != null)
|
|
{
|
|
MethodInfo shutdownMethod = pluginType.GetMethod("Shutdown", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
shutdownMethod.Invoke(pluginInstance, null);
|
|
pluginInstance = null;
|
|
pluginType = null;
|
|
pluginAssembly = null;
|
|
}
|
|
IsPluginLoaded = false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log(ex);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private void Log(Exception ex)
|
|
{
|
|
Log(ex.ToString());
|
|
}
|
|
|
|
private void Log(string message)
|
|
{
|
|
File.AppendAllText(System.IO.Path.Combine(AssemblyDirectory, "loader_log.txt"), $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}\n");
|
|
try
|
|
{
|
|
CoreManager.Current.Actions.AddChatText($"[MosswartMassacre.Loader] {message}", 3);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
} |