mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-05-22 05:34:36 +00:00
Mod Atlas (ModAtlas.cpp/h): - Build merged terrain.png and items.png from mod assets (blocks/*.png, items/*.png) - Scan vanilla atlas for empty (fully transparent) cells; place mod textures only there - Install merged atlases over game files before Minecraft::init; restore originals after - Hook loadUVs to create SimpleIcon objects for mod textures - Hook registerIcon to return mod icons when requested by name - FixupModIcons: copy field_0x48 (source-image ptr) from vanilla icons after init Mod Strings (ModStrings.cpp/h): - Store mod display names by description ID - Hook GetString to serve mod names for blocks/items API changes: - BlockProperties/ItemProperties: .Name(displayName), namespaced .Icon() - NativeInterop: displayName params, native_allocate_description_id, native_register_string - Registry.Assets for string registration - Output: mods/LegacyForge.API/, mods/ExampleMod/ (per-mod folders) Mod discovery: - Scan mods/*/ for mod folders; load DLLs from each - LegacyForge.API as mod in mods/LegacyForge.API/ ExampleMod: - Ruby ore block and ruby item with custom textures and names - Assets: blocks/ruby_ore.png, items/ruby.png, lang files - Furnace recipe: ruby_ore -> ruby Runtime: loadUVs, registerIcon, getResourceAsStream, GetString hooks; stb_image for PNG
113 lines
4.1 KiB
C#
113 lines
4.1 KiB
C#
using System.Reflection;
|
|
using System.Runtime.Loader;
|
|
using LegacyForge.API;
|
|
|
|
namespace LegacyForge.Core;
|
|
|
|
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;
|
|
}
|
|
|
|
// Count LegacyForge.API as a mod when its folder exists (mods/LegacyForge.API/)
|
|
var apiFolder = Path.Combine(modsPath, "LegacyForge.API");
|
|
if (Directory.Exists(apiFolder))
|
|
{
|
|
var apiMod = new LegacyForgeApiMod();
|
|
var attr = typeof(LegacyForgeApiMod).GetCustomAttribute<ModAttribute>()!;
|
|
mods.Add(new DiscoveredMod(apiMod, attr, typeof(ModDiscovery).Assembly));
|
|
Logger.Info($"Discovered mod: {attr.Name} v{attr.Version} by {attr.Author} (mods/LegacyForge.API/)");
|
|
}
|
|
|
|
// Scan each mod folder: mods/ExampleMod/, mods/SomeMod/, etc.
|
|
// Each subfolder may contain one or more mod DLLs (we skip LegacyForge.API.dll)
|
|
var modFolders = Directory.GetDirectories(modsPath);
|
|
foreach (var folder in modFolders)
|
|
{
|
|
var folderName = Path.GetFileName(folder);
|
|
var dllFiles = Directory.GetFiles(folder, "*.dll", SearchOption.TopDirectoryOnly);
|
|
|
|
foreach (var dllPath in dllFiles)
|
|
{
|
|
string fileName = Path.GetFileName(dllPath);
|
|
|
|
// Skip the API assembly -- it's in its own folder and counted above
|
|
if (fileName.Equals("LegacyForge.API.dll", StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
var discovered = LoadModAssembly(dllPath);
|
|
mods.AddRange(discovered);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error($"Failed to load mod from {fileName}: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
Logger.Info($"Scanning {modsPath} -- found {mods.Count} mod(s) total");
|
|
return mods;
|
|
}
|
|
|
|
private static List<DiscoveredMod> LoadModAssembly(string dllPath)
|
|
{
|
|
var results = new List<DiscoveredMod>();
|
|
var fileName = Path.GetFileName(dllPath);
|
|
var fullPath = Path.GetFullPath(dllPath);
|
|
|
|
// Load into the SAME ALC that LegacyForge.Core lives in (the hostfxr component context).
|
|
// This ensures LegacyForge.API types (IMod, ModAttribute, etc.) have the same identity.
|
|
var coreContext = AssemblyLoadContext.GetLoadContext(typeof(ModDiscovery).Assembly)
|
|
?? AssemblyLoadContext.Default;
|
|
var assembly = coreContext.LoadFromAssemblyPath(fullPath);
|
|
|
|
var allTypes = assembly.GetTypes();
|
|
Logger.Debug($"{fileName}: {allTypes.Length} type(s), checking for IMod implementations...");
|
|
|
|
var modTypes = allTypes
|
|
.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 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;
|
|
}
|
|
}
|