mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-05-25 23:24:31 +00:00
SKSE-style external mod loader with zero game source modifications. - LegacyForge.Launcher: C# console app that injects runtime DLL into game process - LegacyForgeRuntime: C++ DLL with PDB symbol resolution, MinHook function hooking, and .NET CoreCLR hosting - LegacyForge.Core: C# mod discovery and lifecycle management - LegacyForge.API: Fabric-style mod API with namespaced string IDs, fluent property builders, and event system - ExampleMod: Sample mod demonstrating block/item registration
87 lines
2.7 KiB
C#
87 lines
2.7 KiB
C#
using System.Reflection;
|
|
using System.Runtime.Loader;
|
|
using LegacyForge.API;
|
|
|
|
namespace LegacyForge.Core;
|
|
|
|
/// <summary>
|
|
/// Discovers and loads mod assemblies from the mods/ directory.
|
|
/// Each mod is loaded into its own AssemblyLoadContext for isolation.
|
|
/// </summary>
|
|
internal static class ModDiscovery
|
|
{
|
|
internal record DiscoveredMod(
|
|
IMod Instance,
|
|
ModAttribute Metadata,
|
|
Assembly Assembly);
|
|
|
|
internal static List<DiscoveredMod> DiscoverMods(string modsPath)
|
|
{
|
|
var mods = new List<DiscoveredMod>();
|
|
|
|
if (!Directory.Exists(modsPath))
|
|
{
|
|
Logger.Warning($"Mods directory not found: {modsPath}");
|
|
return mods;
|
|
}
|
|
|
|
var dllFiles = Directory.GetFiles(modsPath, "*.dll");
|
|
Logger.Info($"Scanning {modsPath} -- found {dllFiles.Length} DLL(s)");
|
|
|
|
foreach (var dllPath in dllFiles)
|
|
{
|
|
try
|
|
{
|
|
var discovered = LoadModAssembly(dllPath);
|
|
mods.AddRange(discovered);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error($"Failed to load mod from {Path.GetFileName(dllPath)}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
return mods;
|
|
}
|
|
|
|
private static List<DiscoveredMod> LoadModAssembly(string dllPath)
|
|
{
|
|
var results = new List<DiscoveredMod>();
|
|
var fileName = Path.GetFileName(dllPath);
|
|
|
|
var loadContext = new AssemblyLoadContext(fileName, isCollectible: false);
|
|
var assembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath(dllPath));
|
|
|
|
var modTypes = assembly.GetTypes()
|
|
.Where(t => t.IsClass && !t.IsAbstract && typeof(IMod).IsAssignableFrom(t));
|
|
|
|
foreach (var type in modTypes)
|
|
{
|
|
var attr = type.GetCustomAttribute<ModAttribute>();
|
|
if (attr == null)
|
|
{
|
|
Logger.Warning($"Class {type.FullName} in {fileName} implements IMod but is missing [Mod] attribute -- skipping");
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
var instance = (IMod)Activator.CreateInstance(type)!;
|
|
results.Add(new ModDiscovery.DiscoveredMod(instance, attr, assembly));
|
|
|
|
string name = string.IsNullOrEmpty(attr.Name) ? attr.Id : attr.Name;
|
|
Logger.Info($"Discovered mod: {name} v{attr.Version} by {attr.Author} ({fileName})");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error($"Failed to instantiate mod {type.FullName}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
if (results.Count == 0)
|
|
Logger.Debug($"No IMod implementations found in {fileName}");
|
|
|
|
return results;
|
|
}
|
|
}
|