Il2cpp游戏本地化Mod:不修改游戏类实现JSON扩展读取
IL2CPP游戏本地化Mod扩展字段解决方案
问题背景
正在开发一款IL2CPP游戏的本地化Mod,原实现流程为:读取JSON文件 → 反序列化为游戏原生GameClassList → 将数据存入游戏内置私有字典private Dictionary<string, GameClass> __instance._dic。现需在不修改游戏原生GameClass的前提下,为JSON添加自定义字段并让Mod读取使用。
尝试通过继承GameClass/GameClassList创建扩展类(NicknameData/NicknameDataList),但出现以下问题:
- 游戏无法识别自定义类
- 本地编译无错误,但协作端出现
CustomClass无法转换为GameClass的类型转换异常
原有尝试代码
初始实现代码
var jsonString = File.ReadAllText("Myfile.json"); GameClassList Localization = JsonSerializer.Deserialize<GameClassList>(jsonString); foreach (var applyChange in Localization.assets) { __instance._dic[applyChange.name] = applyChange; }
自定义子类代码
[Serializable] public class NicknameData : GameClass { public string? runame; public string? ruNickName; public NicknameData() {} public static GameClass HELP(NicknameData data) { var PLEASE = JsonSerializer.Serialize(data); GameClass assetData = JsonSerializer.Deserialize<GameClass>(PLEASE); return assetData; } } [Serializable] public class NicknameDataList : GameClassList { public new Il2CppSystem.Collections.Generic.List<NicknameData> assetData; public NicknameDataList() { } }
Harmony补丁代码
public static string nicknameJSON = File.ReadAllText("JSON_FILE.json"); public static NicknameDataList nicknameDataList = JsonSerializer.Deserialize<NicknameDataList>(nicknameJSON); [HarmonyPatch(typeof(StoryData), nameof(StoryData.Init))] [HarmonyPostfix] private static void StoryDataInit(StoryData __instance) { GameClassList blyahaMuha = nicknameDataList; foreach (var nicknameData in blyahaMuha.assetData) { __instance._dic[nicknameData.name] = (GameClass)NicknameData.HELP((NicknameData)nicknameData); } }
问题根源
- IL2CPP类型隔离机制:游戏原生
GameClass是IL2CPP编译后的类型,Mod自定义的继承类位于独立Assembly中,跨Assembly的类型转换会因元数据不匹配导致失败。 - 继承结构破坏:
NicknameDataList用new关键字覆盖assetData字段,导致基类GameClassList的对应字段未被填充,类型向上转换时丢失数据。 - 冗余序列化操作:先序列化自定义类再反序列化为
GameClass的操作不仅低效,还会直接丢弃自定义字段,同时引入序列化兼容性问题。
可行解决方案
方案一:自定义数据映射(推荐)
核心思路:不修改原生类结构,通过全局字典将GameClass实例与自定义字段数据关联,避免类型转换问题。
- 定义自定义数据类
[Serializable] public class CustomLocalizationEntry { public string name; // 与GameClass的name字段对应,作为关联键 public string? runame; public string? ruNickName; } [Serializable] public class CustomLocalizationList { public List<CustomLocalizationEntry> assets; }
- 读取并关联数据
// 读取JSON内容 var jsonContent = File.ReadAllText("Myfile.json"); // 反序列化为原生类型,存入游戏字典 var gameDataList = JsonSerializer.Deserialize<GameClassList>(jsonContent); foreach (var asset in gameDataList.assets) { __instance._dic[asset.name] = asset; } // 反序列化为自定义类型,建立全局映射 var customDataList = JsonSerializer.Deserialize<CustomLocalizationList>(jsonContent); var customDataMap = new Dictionary<string, CustomLocalizationEntry>(); foreach (var entry in customDataList.assets) { customDataMap[entry.name] = entry; }
- 使用自定义字段
// 示例:获取目标GameClass对应的自定义昵称 if (customDataMap.TryGetValue(targetGameClass.name, out var customEntry)) { string localizedNick = customEntry.ruNickName ?? targetGameClass.name; // 后续业务逻辑 }
方案二:反射注入字段(适合需修改原生类行为场景)
如果需要让GameClass实例直接持有自定义字段,可通过反射在IL2CPP环境下注入数据(需确保反射工具兼容IL2CPP,如Il2CppInterop)。
- 读取自定义数据
var jsonContent = File.ReadAllText("JSON_FILE.json"); var nicknameEntries = JsonSerializer.Deserialize<List<CustomLocalizationEntry>>(jsonContent);
- Harmony补丁中注入数据
[HarmonyPatch(typeof(StoryData), nameof(StoryData.Init))] [HarmonyPostfix] private static void StoryDataInit(StoryData __instance) { var jsonContent = File.ReadAllText("JSON_FILE.json"); var nicknameEntries = JsonSerializer.Deserialize<List<CustomLocalizationEntry>>(jsonContent); foreach (var entry in nicknameEntries) { if (__instance._dic.TryGetValue(entry.name, out var gameClass)) { // 使用Il2Cpp反射获取或创建字段 var gameClassType = gameClass.GetType(); var targetField = gameClassType.GetField("ruNickName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (targetField != null) { targetField.SetValue(gameClass, entry.ruNickName); } else { // 若原生类无对应字段, fallback到全局映射方案 customDataMap[entry.name] = entry; } } } }
内容的提问来源于stack exchange,提问作者Knightey
相关产品推荐
相关产品推荐

