using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using AquaMai.Core.Resources; using AquaMai.Core.Types; using DB; using HarmonyLib; using Manager.UserDatas; using Monitor; using Monitor.Game; using Process.SubSequence; using UnityEngine; using Util; namespace AquaMai.Core.Helpers; public static class GameSettingsManager { /// /// <#ffffff> /// public static readonly string DefaultTag = OptionRootID.DefaultColorTag.GetName(); /// /// <#02f6ff> /// public static readonly string NormalTag = OptionRootID.NormalColorTag.GetName(); private static bool isPatched = false; private static readonly List settings = []; public static void RegisterSetting(IPlayerSettingsItem setting) { settings.Add(setting); settings.Sort((a, b) => a.Sort.CompareTo(b.Sort)); if (!isPatched) { isPatched = true; var applyPatchMethod = typeof(Core.Startup).GetMethod("ApplyPatch", BindingFlags.Static | BindingFlags.NonPublic); applyPatchMethod.Invoke(null, [typeof(GameSettingsManager)]); applyPatchMethod.Invoke(null, [typeof(PatchOptionCategoryIDExtension)]); } } public static void UnregisterSetting(IPlayerSettingsItem setting) { settings.Remove(setting); } public static TabDataBase tab; [HarmonyPostfix] [HarmonyPatch(typeof(MusicSelectMonitor), nameof(MusicSelectMonitor.Initialize))] private static void Init(Sprite ____otherSprite) { if (tab != null) return; tab = new TabDataBase(new Color(172 / 255f, 181 / 255f, 250 / 255f), ____otherSprite, Locale.GameSettingsMod); } /// /// 由于在进入设置界面时 UserOption 会被复制一份,所以我们在这边存储对应关系,方便后面查询 /// private static Dictionary userOptionToPlayer = new(); [HarmonyPostfix] [HarmonyPatch(typeof(OptionSelectSequence), nameof(OptionSelectSequence.OnStartSequence))] private static void OnStartSequence(UserOption ____userOption, int ___PlayerIndex) { userOptionToPlayer[____userOption] = ___PlayerIndex; } [HarmonyPostfix] [HarmonyPatch(typeof(OptionSelectSequence), nameof(OptionSelectSequence.OnGameStart))] private static void OnGameStart(UserOption ____userOption) { userOptionToPlayer.Remove(____userOption); } private const int ModCategoryId = 5; private const int CategoryIdMax = ModCategoryId + 1; [HarmonyPostfix] [HarmonyPatch(typeof(MusicSelectMonitor), nameof(MusicSelectMonitor.SetOptionCard))] private static void SetOptionCard(GenreSelectController ____genreTabController, Dictionary ____optionSprite) { List list = new List(); for (OptionCategoryID optionCategoryID = OptionCategoryID.SpeedSetting; optionCategoryID < OptionCategoryID.End; optionCategoryID++) { Sprite sprite = ____optionSprite[(int)optionCategoryID]; string title = optionCategoryID.GetName(); Color baseColor = Utility.ConvertColor(optionCategoryID.GetMainColor()); list.Add(new TabDataBase(baseColor, sprite, title)); } list.Add(tab); ____genreTabController.SortType2Genre(list, 0); ____genreTabController.Change(0); } // 改字节码,有意思 [HarmonyTranspiler] [HarmonyPatch(typeof(OptionSelectSequence), "Update")] private static IEnumerable OptionSelectSequenceUpdate(IEnumerable instructions) { var insts = instructions.ToArray(); var load5s = insts.Where(inst => inst.opcode == OpCodes.Ldc_I4_5).ToArray(); if (load5s.Length < 9) { throw new Exception("无法找到 OptionSelectSequence.Update 的 ldc.i4.5 指令"); } // TODO 应该有更好的寻找方式 load5s[1].opcode = OpCodes.Ldc_I4_6; load5s[load5s.Length - 1].opcode = OpCodes.Ldc_I4_6; // 最后一个 var load4s = insts.Where(inst => inst.opcode == OpCodes.Ldc_I4_4).ToArray(); if (load4s.Length < 2) { throw new Exception("无法找到 OptionSelectSequence.Update 的 ldc.i4.4 指令"); } load4s[0].opcode = OpCodes.Ldc_I4_5; load4s[1].opcode = OpCodes.Ldc_I4_5; return insts; } [HarmonyTranspiler] [HarmonyPatch(typeof(OptionSelectSequence), nameof(OptionSelectSequence.IsOptionBoundary))] private static IEnumerable IsOptionBoundary(IEnumerable instructions) { var insts = instructions.ToArray(); insts.First(inst => inst.opcode == OpCodes.Ldc_I4_5).opcode = OpCodes.Ldc_I4_6; insts.First(inst => inst.opcode == OpCodes.Ldc_I4_4).opcode = OpCodes.Ldc_I4_5; return insts; } [HarmonyTranspiler] [HarmonyPatch(typeof(OptionSelectSequence), nameof(OptionSelectSequence.GetCategory))] private static IEnumerable GetCategory(IEnumerable instructions) { var insts = instructions.ToArray(); insts.First(inst => inst.opcode == OpCodes.Ldc_I4_5).opcode = OpCodes.Ldc_I4_6; insts.First(inst => inst.opcode == OpCodes.Ldc_I4_4).opcode = OpCodes.Ldc_I4_5; return insts; } [HarmonyPrefix] [HarmonyPatch(typeof(OptionSelectSequence), nameof(OptionSelectSequence.GetCategoryName))] private static bool GetCategoryName(int diffIndex, ref string __result, OptionSelectSequence __instance) { int optionCategoryID = (int)__instance.CurrentOptionCategory + diffIndex; if (optionCategoryID >= CategoryIdMax) { optionCategoryID -= CategoryIdMax; } if (optionCategoryID < 0) { optionCategoryID = CategoryIdMax + optionCategoryID; } if (optionCategoryID == ModCategoryId) { __result = Locale.GameSettingsMod; } __result = ((OptionCategoryID)optionCategoryID).GetName(); return false; } [HarmonyPostfix] [HarmonyPatch(typeof(OptionSelectSequence), nameof(OptionSelectSequence.GetCategory))] private static void GetCategory(ref bool isLeftButtonActive, ref bool isRightButtonActive, string __result) { var option = settings.FirstOrDefault(it => it.Name == __result); if (option == null) return; isLeftButtonActive = option.IsLeftButtonActive; isRightButtonActive = option.IsRightButtonActive; } /// /// mai 的代码会传入无效的 index(GetCategory 处) /// 所以接下来的访问都必须判断 index /// /// /// private static bool IsValidIndex(int index) { return index >= 0 && index < settings.Count; } [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "GetOptionName")] private static bool GetOptionName(OptionCategoryID category, int optionIndex, ref string __result) { if ((int)category != ModCategoryId) return true; if (!IsValidIndex(optionIndex)) return false; __result = settings[optionIndex].Name; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "GetOptionDetail")] private static bool GetOptionDetail(OptionCategoryID category, int optionIndex, ref string __result) { if ((int)category != ModCategoryId) return true; if (!IsValidIndex(optionIndex)) return false; __result = settings[optionIndex].Detail; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "GetFilePath")] private static bool GetFilePath(OptionCategoryID category, int currentOptionIndex, ref string __result) { if ((int)category != ModCategoryId) return true; if (!IsValidIndex(currentOptionIndex)) return false; __result = settings[currentOptionIndex].SpriteFile; return false; } // 由于加减按钮由我们自己控制,所以这个没有意义,只要让游戏觉得在范围内就行 // 这两个关系到点击屏幕的事件是否被响应 [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "GetOptionValueIndex")] private static bool GetOptionValueIndex(OptionCategoryID category, int currentOptionIndex, ref int __result) { if ((int)category != ModCategoryId) return true; __result = 114514; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "GetOptionMax")] private static bool GetOptionMax(OptionCategoryID category, int currentOptionIndex, ref int __result) { if ((int)category != ModCategoryId) return true; __result = 1919810; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "GetOptionValue")] private static bool GetOptionValue(OptionCategoryID category, int optionIndex, ref string __result, string ___DefaultTag, string ___NormalTag, UserOption __instance) { if ((int)category != ModCategoryId) return true; if (!IsValidIndex(optionIndex)) return false; if (!userOptionToPlayer.TryGetValue(__instance, out var player)) { __result = "错误的玩家 ID"; return false; } __result = settings[optionIndex].GetOptionValue(player); return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "AddOption")] private static bool AddOption(OptionCategoryID category, int currentOptionIndex, UserOption __instance) { if ((int)category != ModCategoryId) return true; if (!IsValidIndex(currentOptionIndex)) return false; if (!userOptionToPlayer.TryGetValue(__instance, out var player)) { return false; } settings[currentOptionIndex].AddOption(player); return false; } [HarmonyPrefix] [HarmonyPatch(typeof(UserOption), "SubOption")] private static bool SubOption(OptionCategoryID category, int currentOptionIndex, UserOption __instance) { if ((int)category != ModCategoryId) return true; if (!IsValidIndex(currentOptionIndex)) return false; if (!userOptionToPlayer.TryGetValue(__instance, out var player)) { return false; } settings[currentOptionIndex].SubOption(player); return false; } [HarmonyPatch] private class PatchOptionCategoryIDExtension { public static IEnumerable TargetMethods() { // Origin typo var clas = Assembly.GetAssembly(typeof(GameCtrl)).GetType("Manager.UserDatas.OptionCategoryIDExcenstion"); yield return clas.GetMethod("GetCategoryMax"); } public static bool Prefix(ref int __result, OptionCategoryID category) { if ((int)category != ModCategoryId) return true; __result = settings.Count; return false; } } }