Skip to content

Commit 896abb0

Browse files
author
StArray
committed
fix(modload): 修复宿主程序集加载导致mod识别失败的问题
1 parent 5a3bc6e commit 896abb0

6 files changed

Lines changed: 117 additions & 9 deletions

File tree

‎StArray.ModManager.CImGui/imgui/backends/imgui_impl_opengl3.cpp‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,8 +859,12 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
859859
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
860860
"}\n";
861861

862+
// [LOCAL PATCH] 上游此处为 "precision mediump float;"。
863+
// 大图集(4096+,本项目 5 个字体合并全 BMP 就是这种情况)下 mediump(10bit 尾数) 的 UV
864+
// 量化误差可达 1~2 texel,采样会越过字形留白命中相邻格子的墨迹,表现为某些字底下多出一条细线
865+
// (Windows 走 D3D11 fp32 着色器,所以看不到)。ES3 片元着色器强制支持 highp,可直接改。
862866
const GLchar* fragment_shader_glsl_300_es =
863-
"precision mediump float;\n"
867+
"precision highp float;\n"
864868
"uniform sampler2D Texture;\n"
865869
"in vec2 Frag_UV;\n"
866870
"in vec4 Frag_Color;\n"

‎StArray.ModManager.Tests/ModLoadContextTests.cs‎

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ public void TearDown()
2727

2828
/// <summary>生成一个程序集并落盘:含一个公共类 Marker(可被实例化验证)。</summary>
2929
private string WriteAssembly(string fileName, string typeName = "Marker")
30+
=> WriteAssemblyTo(_tempDir, fileName, typeName);
31+
32+
private static string WriteAssemblyTo(string dir, string fileName, string typeName = "Marker")
3033
{
31-
Directory.CreateDirectory(_tempDir);
32-
var path = Path.Combine(_tempDir, fileName);
34+
Directory.CreateDirectory(dir);
35+
var path = Path.Combine(dir, fileName);
3336

3437
var ab = new PersistedAssemblyBuilder(
3538
new AssemblyName(Path.GetFileNameWithoutExtension(fileName)), typeof(object).Assembly);
@@ -159,8 +162,49 @@ public void ResolvesDependencyFromSameDirectory()
159162
finally { alc.Unload(); }
160163
}
161164

162-
// ── 文件锁行为(路径加载的已知权衡) ──
165+
/// <summary>
166+
/// 管理器目录里也存在宿主程序集的副本时,依赖解析必须复用宿主已加载的实例。
167+
/// 回归用例:SMM 自身就在 manager/ 目录里,若 mod ALC 从该目录再加载一份,
168+
/// mod 看到的 IModPlugin 是另一个 Type,插件识别全失败(表现为“发现 0 个 Mod”)。
169+
/// </summary>
170+
[Test]
171+
public void HostLoadedAssembly_WinsOverManagerDirCopy()
172+
{
173+
var sharedName = "HostShared_" + Guid.NewGuid().ToString("N");
174+
var hostDir = Path.Combine(_tempDir, "host");
175+
var managerDir = Path.Combine(_tempDir, "manager");
176+
var modDir = Path.Combine(_tempDir, "mod");
177+
178+
// 两份同名程序集:一份已在宿主上下文,一份躺在管理器目录
179+
var hostPath = WriteAssemblyTo(hostDir, sharedName + ".dll", "SharedMarker");
180+
WriteAssemblyTo(managerDir, sharedName + ".dll", "SharedMarker");
181+
182+
var hostAsm = AssemblyLoadContext.Default.LoadFromAssemblyPath(hostPath);
183+
var hostMarker = hostAsm.GetType("SharedMarker")!;
184+
185+
// mod 的依赖 dll 引用 SharedMarker → 触发 mod ALC 的依赖解析
186+
var ab = new PersistedAssemblyBuilder(new AssemblyName("DepOfShared"), typeof(object).Assembly);
187+
var mb = ab.DefineDynamicModule("MainModule");
188+
var tb = mb.DefineType("DepMarker", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class);
189+
tb.DefineField("Ref", hostMarker, FieldAttributes.Public | FieldAttributes.Static);
190+
tb.CreateTypeInfo();
191+
var depPath = Path.Combine(modDir, "DepOfShared.dll");
192+
Directory.CreateDirectory(modDir);
193+
ab.Save(depPath);
163194

195+
var alc = new ModLoadContext("T:HostFirst", modDir, managerDir);
196+
try
197+
{
198+
var resolved = alc.LoadFromFilePath(depPath)
199+
.GetType("DepMarker")!.GetField("Ref")!.FieldType;
200+
201+
Assert.That(resolved, Is.SameAs(hostMarker),
202+
"宿主已加载的程序集必须复用同一实例,不能从管理器目录加载副本");
203+
}
204+
finally { alc.Unload(); }
205+
}
206+
207+
// ── 文件锁行为(路径加载的已知权衡) ──
164208
[Test]
165209
public void PathLoad_HoldsFileUntilUnloadCollected()
166210
{

‎StArray.ModManager/Runtime/ModLoadContext.cs‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ public sealed class ModLoadContext : AssemblyLoadContext
2323
private readonly string _baseDir;
2424
private readonly string? _managerDir;
2525

26+
/// <summary>宿主上下文(SMM 本体所在的 ALC)——其中的程序集在 mod 侧必须保持同一身份。</summary>
27+
private static readonly AssemblyLoadContext HostContext =
28+
AssemblyLoadContext.GetLoadContext(typeof(ModLoadContext).Assembly) ?? AssemblyLoadContext.Default;
29+
2630
/// <summary>mod 依赖解析失败时触发(诊断用)。</summary>
2731
public event Action<ModLoadContext, AssemblyName>? OnResolveFailure;
2832

@@ -35,7 +39,14 @@ public ModLoadContext(string name, string baseDir, string? managerDir = null)
3539

3640
protected override Assembly? Load(AssemblyName assemblyName)
3741
{
38-
// 只处理本 mod 目录内的程序集;系统/框架程序集返回 null 交给默认上下文,
42+
// 1) 宿主已加载的同名程序集必须复用同一实例。
43+
// 管理器目录里同样有 StArray.ModManager.dll,若直接从这里加载一份副本,
44+
// mod 看到的 IModPlugin / ModEntryPointAttribute 就变成另一份类型,
45+
// IsPluginType 恒为 false → 一个 Mod 都发现不了(Android/Windows 皆然)。
46+
if (TryFindHostAssembly(assemblyName.Name, out var hostAssembly))
47+
return hostAssembly;
48+
49+
// 2) 只处理本 mod 目录内的程序集;系统/框架程序集返回 null 交给默认上下文,
3950
// 保证 mscorlib/System.* 与 SMM 本体的类型一致性(IModPlugin 接口可赋值)。
4051
var local = ProbeDir(_baseDir, assemblyName);
4152
if (local != null) return local;
@@ -66,6 +77,43 @@ public ModLoadContext(string name, string baseDir, string? managerDir = null)
6677
}
6778
}
6879

80+
/// <summary>
81+
/// 在宿主上下文(及默认上下文)中查找已加载的同名程序集。
82+
/// 命中则返回该实例,<b>绝不再从磁盘加载第二份</b> —— 类型身份一致是 mod 能被识别的
83+
/// 前提(<c>IModPlugin</c> 与宿主必须是同一个 Type)。
84+
/// </summary>
85+
private static bool TryFindHostAssembly(string? simpleName, out Assembly? assembly)
86+
{
87+
assembly = null;
88+
if (string.IsNullOrEmpty(simpleName)) return false;
89+
90+
if (TryFind(HostContext, simpleName, out assembly)) return true;
91+
return !ReferenceEquals(HostContext, AssemblyLoadContext.Default)
92+
&& TryFind(AssemblyLoadContext.Default, simpleName, out assembly);
93+
}
94+
95+
private static bool TryFind(AssemblyLoadContext context, string simpleName, out Assembly? assembly)
96+
{
97+
try
98+
{
99+
foreach (var loaded in context.Assemblies)
100+
{
101+
if (string.Equals(loaded.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase))
102+
{
103+
assembly = loaded;
104+
return true;
105+
}
106+
}
107+
}
108+
catch (InvalidOperationException)
109+
{
110+
// 上下文正在卸载,忽略
111+
}
112+
113+
assembly = null;
114+
return false;
115+
}
116+
69117
protected override nint LoadUnmanagedDll(string unmanagedDllName)
70118
{
71119
// 原生库不经 ALC:交由 NativeLibraryResolver / OS 搜索路径处理

‎StArray.ModManager/Runtime/ModLoader.cs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,13 @@ private static bool IsPluginType(Type t) =>
129129
var assembly = probe.LoadMetadataFromPath(entryDll);
130130

131131
var pluginType = ResolvePluginType(assembly);
132-
if (pluginType == null) return null;
132+
if (pluginType == null)
133+
{
134+
// 静默跳过会让“共发现 0 个 Mod”难以定位,这里留下原因
135+
Logger.Warn(nameof(ModLoader),
136+
$"No IModPlugin type in {Path.GetFileName(entryDll)} ({dirName}); skipped");
137+
return null;
138+
}
133139

134140
// 实例化以读取元数据(Dependencies 允许实现返回 null —— 接口不变,这里宽容处理)
135141
var plugin = (IModPlugin)Activator.CreateInstance(pluginType)!;

‎StArray.ModManager/UI/IImGuiRenderer.cs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Reflection;
22
using System.Runtime.InteropServices;
33
using ImGuiNET;
4+
using StArray.ModManager.Manager;
45

56
namespace StArray.ModManager.UI;
67

@@ -85,6 +86,11 @@ void InitImGui()
8586

8687
// AddFontFromMemoryTTF 只保存指针,Build() 时才真正读取数据。
8788
io.Fonts.Build();
89+
90+
// 图集尺寸是 UV 精度的关键:越大的图集,GLES 上同样的相对误差对应的 texel 越多
91+
// (mediump 在 4096 宽图集上约 1 texel、8192 高上约 2 texel)——记录以便核实。
92+
Logger.Info("FontAtlas",
93+
$"Built {io.Fonts.TexWidth}x{io.Fonts.TexHeight}, {io.Fonts.Fonts.Size} fonts");
8894
}
8995
finally
9096
{

‎version.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"version": "1.0.6",
3-
"versionCode": 13,
3+
"versionCode": 14,
44
"manager": "https://github.com/StArraySharp/StArray.ModManager/releases/download/1.0.6/modmanager.zip",
5-
"sha256": "2eb3066ddfed555d13643e3b9c81af51f79eafc54a1ced28d20cafb418c36928",
5+
"sha256": "4381976c1807e8bc58bd9e5ff6033a22c0915b246dd35f40daf3b5bae2f3b6df",
66
"entryAssembly": "StArray.ModManager.Android.dll",
77
"entryMethod": "StArray.ModManager.Android.Managed::Entry",
88
"platforms": [
@@ -16,7 +16,7 @@
1616
{
1717
"name": "android",
1818
"manager": "https://github.com/StArraySharp/StArray.ModManager/releases/download/1.0.6/modmanager-android.zip",
19-
"sha256": "79209ef2bcea49049448f9eff11815b005d221fb3f44b0d7bf80fe5b7b0b99f3",
19+
"sha256": "4381976c1807e8bc58bd9e5ff6033a22c0915b246dd35f40daf3b5bae2f3b6df",
2020
"entryAssembly": "StArray.ModManager.Android.dll",
2121
"entryMethod": "StArray.ModManager.Android.Managed::Entry"
2222
}

0 commit comments

Comments
 (0)