提前叠个甲:
我不是专业的 C# 或 Godot 开发人员,只是一个普通玩家。遇到几次闪退后,自己对着日志查了一些关于 C# 底层机制的资料,做了个大概的推测分析。如果接下来说的机理有误导或者不准确的地方,请大佬见谅,权当提供一个查错的参考思路。
- 问题描述
在目前的安卓移植版环境中,尝试加载部分大型内容 Mod(例如 wylder 追踪者角色(B 站名 kashenmir)、NightReign 黑夜君临(B 站名 Rebelwings)等,)时,游戏会在启动阶段直接崩溃闪退。
这些 Mod 的共同点是:它们不仅添加了新卡牌/新角色,通常还包含对游戏底层模式的大幅修改(例如注入新的游戏模式、修改每日挑战或联机大厅参数等)。
2. 报错日志 (Error Logs)
提取了游戏崩溃时的核心报错堆栈(以 NightReign 为例,其他几个大型 Mod 的死法几乎一模一样):
[ERROR] Exception thrown when calling mod initializer of type NightReign.NightReignModInitializer: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> HarmonyLib.HarmonyException: Patching exception in method System.Void MegaCrit.Sts2.Core.Nodes.Screens.DailyRun.NDailyRunScreen::SetupLobbyParams(MegaCrit.Sts2.Core.Multiplayer.Game.Lobby.StartRunLobby lobby)
---> System.TypeInitializationException: The type initializer for 'MegaCrit.Sts2.Core.Nodes.Screens.DailyRun.NDailyRunScreen' threw an exception.
---> System.NullReferenceException: Object reference not set to an instance of an object
at MegaCrit.Sts2.Core.Nodes.Screens.DailyRun.NDailyRunScreen..cctor()
--- End of inner exception stack trace ---
at HarmonyLib.PatchClassProcessor.Patch()
at HarmonyLib.Harmony.PatchAll(Assembly assembly)
...
at STS2Mobile.Patches.ModLoaderPatches.RunAndroidModInitializationSafely(IModManagerFileIo fileIo, ModSettings settings, Object[] args)
...
at MegaCrit.Sts2.Core.Helpers.OneTimeInitialization.ExecuteVeryEarly()
以及后续游戏正式加载主菜单时被彻底带崩的附带报错:
[ERROR] System.TypeInitializationException: The type initializer for 'MegaCrit.Sts2.Core.Assets.AssetSets' threw an exception.
---> System.TypeInitializationException: The type initializer for 'MegaCrit.Sts2.Core.Nodes.Screens.DailyRun.NDailyRunScreen' threw an exception.
- 机理分析推测 (Mechanism Analysis)
结合日志来看,这似乎不是某一个 Mod 作者代码写错了,而是安卓端 ModLoader 的加载时机与原版游戏界面的初始化顺序发生了严重冲突,触发了 C# 的“类中毒”机制。具体推演如下:
- 过早的加载时机:
安卓端的加载环境在引擎极早期(ExecuteVeryEarly())就运行了 Mod 的初始化逻辑。
- Harmony 强制触发静态构造:
这些大型 Mod 为了注入新模式,通常会使用 Harmony.PatchAll(),或者手动去 Patch NDailyRunScreen(每日挑战/大厅屏幕)的相关方法。在 C# 中,Harmony 只要反射碰到了这个类,就会强制触发该类的静态构造函数(.cctor())。
- 前置资源未就绪,导致空指针:
由于此时 Godot 引擎的渲染或 UI 资源大概率还没有加载完毕,NDailyRunScreen 的静态构造函数去拿这些前置数据拿了个空,当场抛出了 NullReferenceException。
- C# 底层“类中毒”机制:
根据 C# 虚拟机的规则,一旦某个类的静态构造函数(.cctor)抛出未处理的异常,这个类就会被永久标记为失效(抛出 TypeInitializationException)。
- 游戏彻底暴毙:
随后,游戏主程序在继续执行 LaunchMainMenu -> LoadLogoAnimation 时,不可避免地需要用到这个已经失效的类,最终导致连环报错,整个游戏启动流程彻底卡死。
- 总结
目前来看,只要是试图修改 NDailyRunScreen(或者使用了无差别 PatchAll 碰到了该类)的 PC 端 Mod,在目前的移动端环境下几乎都会触发必死闪退。
期望的修复方向:不知是否可以考虑调整移动端环境注入 Mod 的时机(比如延后到引擎基本 UI 节点和静态资产准备完毕之后),或者在底层对这种过早触发的反射做一些规避处理?
感谢开发者的辛勤移植与维护!如果需要测试修复方案,我很乐意帮忙。
提前叠个甲:
我不是专业的 C# 或 Godot 开发人员,只是一个普通玩家。遇到几次闪退后,自己对着日志查了一些关于 C# 底层机制的资料,做了个大概的推测分析。如果接下来说的机理有误导或者不准确的地方,请大佬见谅,权当提供一个查错的参考思路。
在目前的安卓移植版环境中,尝试加载部分大型内容 Mod(例如 wylder 追踪者角色(B 站名 kashenmir)、NightReign 黑夜君临(B 站名 Rebelwings)等,)时,游戏会在启动阶段直接崩溃闪退。
这些 Mod 的共同点是:它们不仅添加了新卡牌/新角色,通常还包含对游戏底层模式的大幅修改(例如注入新的游戏模式、修改每日挑战或联机大厅参数等)。
2. 报错日志 (Error Logs)
提取了游戏崩溃时的核心报错堆栈(以 NightReign 为例,其他几个大型 Mod 的死法几乎一模一样):
以及后续游戏正式加载主菜单时被彻底带崩的附带报错:
结合日志来看,这似乎不是某一个 Mod 作者代码写错了,而是安卓端 ModLoader 的加载时机与原版游戏界面的初始化顺序发生了严重冲突,触发了 C# 的“类中毒”机制。具体推演如下:
安卓端的加载环境在引擎极早期(ExecuteVeryEarly())就运行了 Mod 的初始化逻辑。
这些大型 Mod 为了注入新模式,通常会使用 Harmony.PatchAll(),或者手动去 Patch NDailyRunScreen(每日挑战/大厅屏幕)的相关方法。在 C# 中,Harmony 只要反射碰到了这个类,就会强制触发该类的静态构造函数(.cctor())。
由于此时 Godot 引擎的渲染或 UI 资源大概率还没有加载完毕,NDailyRunScreen 的静态构造函数去拿这些前置数据拿了个空,当场抛出了 NullReferenceException。
根据 C# 虚拟机的规则,一旦某个类的静态构造函数(.cctor)抛出未处理的异常,这个类就会被永久标记为失效(抛出 TypeInitializationException)。
随后,游戏主程序在继续执行 LaunchMainMenu -> LoadLogoAnimation 时,不可避免地需要用到这个已经失效的类,最终导致连环报错,整个游戏启动流程彻底卡死。
目前来看,只要是试图修改 NDailyRunScreen(或者使用了无差别 PatchAll 碰到了该类)的 PC 端 Mod,在目前的移动端环境下几乎都会触发必死闪退。
期望的修复方向:不知是否可以考虑调整移动端环境注入 Mod 的时机(比如延后到引擎基本 UI 节点和静态资产准备完毕之后),或者在底层对这种过早触发的反射做一些规避处理?
感谢开发者的辛勤移植与维护!如果需要测试修复方案,我很乐意帮忙。