[RF] AquaMai configuration refactor (#82)

更新了配置文件格式,原有的配置文件将被自动无缝迁移,详情请见新的配置文件中的注释(例外:`SlideJudgeTweak` 不再默认启用)
旧配置文件将被重命名备份,如果更新到此版本遇到 Bug 请联系我们

Updated configuration file schema. The old config file will be migrated automatically and seamlessly. See the comments in the new configuration file for details. (Except for `SlideJudgeTweak` is no longer enabled by default)
Your old configuration file will be renamed as a backup. If you encounter any bug with this version, please contact us.
This commit is contained in:
Menci
2024-11-25 01:25:19 +08:00
committed by GitHub
parent e9ee31b22a
commit 37044dae01
217 changed files with 6051 additions and 3040 deletions

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections;
using System.Reflection;
using AquaMai.Core.Attributes;
using AquaMai.Core.Resources;
using HarmonyLib;
using MelonLoader;
namespace AquaMai.Core.Helpers;
public class EnableConditionHelper
{
[HarmonyPostfix]
[HarmonyPatch("HarmonyLib.PatchTools", "GetPatchMethod")]
public static void PostGetPatchMethod(ref MethodInfo __result)
{
if (__result != null)
{
if (ShouldSkipMethodOrClass(__result.GetCustomAttribute, __result.ReflectedType, __result.Name))
{
__result = null;
}
}
}
[HarmonyPostfix]
[HarmonyPatch("HarmonyLib.PatchTools", "GetPatchMethods")]
public static void PostGetPatchMethods(ref IList __result)
{
for (int i = 0; i < __result.Count; i++)
{
var harmonyMethod = Traverse.Create(__result[i]).Field("info").GetValue() as HarmonyMethod;
var method = harmonyMethod.method;
if (ShouldSkipMethodOrClass(method.GetCustomAttribute, method.ReflectedType, method.Name))
{
__result.RemoveAt(i);
i--;
}
}
}
public static bool ShouldSkipClass(Type type)
{
return ShouldSkipMethodOrClass(type.GetCustomAttribute, type);
}
private static bool ShouldSkipMethodOrClass(Func<Type, object> getCustomAttribute, Type type, string methodName = "")
{
var displayName = type.FullName + (string.IsNullOrEmpty(methodName) ? "" : $".{methodName}");
var enableIf = (EnableIfAttribute)getCustomAttribute(typeof(EnableIfAttribute));
if (enableIf != null && !enableIf.ShouldEnable(type))
{
# if DEBUG
MelonLogger.Msg($"Skipping {displayName} due to EnableIf condition");
# endif
return true;
}
var enableGameVersion = (EnableGameVersionAttribute)getCustomAttribute(typeof(EnableGameVersionAttribute));
if (enableGameVersion != null && !enableGameVersion.ShouldEnable(GameInfo.GameVersion))
{
# if DEBUG
MelonLogger.Msg($"Skipping {displayName} due to EnableGameVersion condition");
# endif
if (!enableGameVersion.NoWarn)
{
MelonLogger.Warning(string.Format(Locale.SkipIncompatiblePatch, type));
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.IO;
namespace AquaMai.Core.Helpers;
public static class FileSystem
{
public static string ResolvePath(string path)
{
var varExpanded = Environment.ExpandEnvironmentVariables(path);
return Path.IsPathRooted(varExpanded)
? varExpanded
: Path.Combine(Environment.CurrentDirectory, varExpanded);
}
}

View File

@@ -0,0 +1,21 @@
using System.Reflection;
using MAI2System;
namespace AquaMai.Core.Helpers;
public class GameInfo
{
public static uint GameVersion { get; } = GetGameVersion();
private static uint GetGameVersion()
{
return (uint)typeof(ConstParameter).GetField("NowGameVersion", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null);
}
public static string GameId { get; } = GetGameId();
private static string GetGameId()
{
return typeof(ConstParameter).GetField("GameIDStr", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null) as string;
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using UnityEngine;
namespace AquaMai.Core.Helpers;
public static class GuiSizes
{
public static bool SinglePlayer { get; set; } = false;
public static float PlayerWidth => Screen.height / 1920f * 1080;
public static float PlayerCenter => SinglePlayer ? Screen.width / 2f : Screen.width / 2f - PlayerWidth / 2;
public static int FontSize => (int)(PlayerWidth * .015f);
public static float LabelHeight => FontSize * 1.5f;
public static float Margin => PlayerWidth * .005f;
private static Color backgroundColor = new(147 / 256f, 160 / 256f, 173 / 256f, .8f);
public static void SetupStyles()
{
var buttonStyle = GUI.skin.button;
buttonStyle.normal.textColor = Color.white;
buttonStyle.normal.background = Texture2D.whiteTexture;
buttonStyle.hover.background = Texture2D.whiteTexture;
buttonStyle.active.background = Texture2D.whiteTexture;
buttonStyle.border = new RectOffset(0, 0, 0, 0);
buttonStyle.margin = new RectOffset(0, 0, 0, 0);
buttonStyle.padding = new RectOffset(10, 10, 10, 10);
buttonStyle.overflow = new RectOffset(0, 0, 0, 0);
var boxStyle = GUI.skin.box;
boxStyle.border = new RectOffset(0, 0, 0, 0);
boxStyle.normal.background = Texture2D.whiteTexture;
GUI.backgroundColor = backgroundColor;
}
[HarmonyPatch]
public class BoxBackground
{
public static IEnumerable<MethodBase> TargetMethods()
{
return typeof(GUI).GetMethods().Where(x => x.Name == "Box");
}
public static void Prefix()
{
GUI.backgroundColor = new Color(62 / 256f, 62 / 256f, 66 / 256f, .6f);
}
public static void Postfix()
{
GUI.backgroundColor = backgroundColor;
}
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using AquaMai.Config.Types;
using HarmonyLib;
using Main;
using Manager;
using MelonLoader;
using UnityEngine;
namespace AquaMai.Core.Helpers;
public static class KeyListener
{
private static readonly Dictionary<KeyCodeOrName, int> _keyPressFrames = [];
private static readonly Dictionary<KeyCodeOrName, int> _keyPressFramesPrev = [];
static KeyListener()
{
foreach (KeyCodeOrName key in Enum.GetValues(typeof(KeyCodeOrName)))
{
_keyPressFrames[key] = 0;
_keyPressFramesPrev[key] = 0;
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameMainObject), "Update")]
public static void CheckLongPush()
{
foreach (KeyCodeOrName key in Enum.GetValues(typeof(KeyCodeOrName)))
{
_keyPressFramesPrev[key] = _keyPressFrames[key];
if (GetKeyPush(key))
{
# if DEBUG
MelonLogger.Msg($"CheckLongPush {key} is push {_keyPressFrames[key]}");
# endif
_keyPressFrames[key]++;
}
else
{
_keyPressFrames[key] = 0;
}
}
}
public static bool GetKeyPush(KeyCodeOrName key) =>
key switch
{
KeyCodeOrName.None => false,
< KeyCodeOrName.Select1P => Input.GetKey(key.GetKeyCode()),
KeyCodeOrName.Test => InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonTest),
KeyCodeOrName.Service => InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService),
KeyCodeOrName.Select1P => InputManager.GetButtonPush(0, InputManager.ButtonSetting.Select),
KeyCodeOrName.Select2P => InputManager.GetButtonPush(1, InputManager.ButtonSetting.Select),
_ => throw new ArgumentOutOfRangeException(nameof(key), key, "我也不知道这是什么键")
};
public static bool GetKeyDown(KeyCodeOrName key)
{
// return key switch
// {
// KeyCodeOrName.None => false,
// < KeyCodeOrName.Select1P => Input.GetKeyDown(key.GetKeyCode()),
// KeyCodeOrName.Test => InputManager.GetSystemInputDown(InputManager.SystemButtonSetting.ButtonTest),
// KeyCodeOrName.Service => InputManager.GetSystemInputDown(InputManager.SystemButtonSetting.ButtonService),
// KeyCodeOrName.Select1P => InputManager.GetButtonDown(0, InputManager.ButtonSetting.Select),
// KeyCodeOrName.Select2P => InputManager.GetButtonDown(1, InputManager.ButtonSetting.Select),
// _ => throw new ArgumentOutOfRangeException(nameof(key), key, "我也不知道这是什么键")
// };
// 不用这个,我们检测按键是否弹起以及弹起之前按下的时间是否小于 30这样可以防止要长按时按下的时候就触发
return _keyPressFrames[key] == 0 && 0 < _keyPressFramesPrev[key] && _keyPressFramesPrev[key] < 30;
}
public static bool GetKeyDownOrLongPress(KeyCodeOrName key, bool isLongPress)
{
bool ret;
if (isLongPress)
{
ret = _keyPressFrames[key] == 60;
}
else
{
ret = GetKeyDown(key);
}
# if DEBUG
if (ret)
{
MelonLogger.Msg($"Key {key} is pressed, long press: {isLongPress}");
MelonLogger.Msg(new StackTrace());
}
# endif
return ret;
}
private static KeyCode GetKeyCode(this KeyCodeOrName keyCodeOrName) =>
keyCodeOrName switch
{
KeyCodeOrName.Alpha0 => KeyCode.Alpha0,
KeyCodeOrName.Alpha1 => KeyCode.Alpha1,
KeyCodeOrName.Alpha2 => KeyCode.Alpha2,
KeyCodeOrName.Alpha3 => KeyCode.Alpha3,
KeyCodeOrName.Alpha4 => KeyCode.Alpha4,
KeyCodeOrName.Alpha5 => KeyCode.Alpha5,
KeyCodeOrName.Alpha6 => KeyCode.Alpha6,
KeyCodeOrName.Alpha7 => KeyCode.Alpha7,
KeyCodeOrName.Alpha8 => KeyCode.Alpha8,
KeyCodeOrName.Alpha9 => KeyCode.Alpha9,
KeyCodeOrName.Keypad0 => KeyCode.Keypad0,
KeyCodeOrName.Keypad1 => KeyCode.Keypad1,
KeyCodeOrName.Keypad2 => KeyCode.Keypad2,
KeyCodeOrName.Keypad3 => KeyCode.Keypad3,
KeyCodeOrName.Keypad4 => KeyCode.Keypad4,
KeyCodeOrName.Keypad5 => KeyCode.Keypad5,
KeyCodeOrName.Keypad6 => KeyCode.Keypad6,
KeyCodeOrName.Keypad7 => KeyCode.Keypad7,
KeyCodeOrName.Keypad8 => KeyCode.Keypad8,
KeyCodeOrName.Keypad9 => KeyCode.Keypad9,
KeyCodeOrName.F1 => KeyCode.F1,
KeyCodeOrName.F2 => KeyCode.F2,
KeyCodeOrName.F3 => KeyCode.F3,
KeyCodeOrName.F4 => KeyCode.F4,
KeyCodeOrName.F5 => KeyCode.F5,
KeyCodeOrName.F6 => KeyCode.F6,
KeyCodeOrName.F7 => KeyCode.F7,
KeyCodeOrName.F8 => KeyCode.F8,
KeyCodeOrName.F9 => KeyCode.F9,
KeyCodeOrName.F10 => KeyCode.F10,
KeyCodeOrName.F11 => KeyCode.F11,
KeyCodeOrName.F12 => KeyCode.F12,
KeyCodeOrName.Insert => KeyCode.Insert,
KeyCodeOrName.Delete => KeyCode.Delete,
KeyCodeOrName.Home => KeyCode.Home,
KeyCodeOrName.End => KeyCode.End,
KeyCodeOrName.PageUp => KeyCode.PageUp,
KeyCodeOrName.PageDown => KeyCode.PageDown,
KeyCodeOrName.UpArrow => KeyCode.UpArrow,
KeyCodeOrName.DownArrow => KeyCode.DownArrow,
KeyCodeOrName.LeftArrow => KeyCode.LeftArrow,
KeyCodeOrName.RightArrow => KeyCode.RightArrow,
_ => throw new ArgumentOutOfRangeException(nameof(keyCodeOrName), keyCodeOrName, "游戏功能键需要单独处理")
};
}

View File

@@ -0,0 +1,39 @@
using DB;
using HarmonyLib;
using Manager;
using MelonLoader;
using Process;
namespace AquaMai.Core.Helpers;
public class MessageHelper
{
private static IGenericManager _genericManager = null;
[HarmonyPostfix]
[HarmonyPatch(typeof(ProcessManager), "SetMessageManager")]
private static void OnSetMessageManager(IGenericManager genericManager)
{
_genericManager = genericManager;
}
public static void ShowMessage(string message, WindowSizeID size = WindowSizeID.Middle, string title = null)
{
if (_genericManager is null)
{
MelonLogger.Error($"[MessageHelper] Unable to show message: `{message}` GenericManager is null");
return;
}
_genericManager.Enqueue(0, WindowMessageID.CollectionAttentionEmptyFavorite, new WindowParam()
{
hideTitle = title is null,
replaceTitle = true,
title = title,
replaceText = true,
text = message,
changeSize = true,
sizeID = size,
});
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using HarmonyLib;
namespace AquaMai.Core.Helpers;
public class MusicDirHelper
{
private static Dictionary<int, string> _map = new();
[HarmonyPostfix]
[HarmonyPatch(typeof(Manager.MaiStudio.Serialize.MusicData), "AddPath")]
private static void AddPath(Manager.MaiStudio.Serialize.MusicData __instance, string parentPath)
{
_map[__instance.GetID()] = parentPath;
}
public static string LookupPath(int id)
{
return _map.GetValueOrDefault(id);
}
public static string LookupPath(Manager.MaiStudio.Serialize.MusicData musicData)
{
return LookupPath(musicData.GetID());
}
public static string LookupPath(Manager.MaiStudio.MusicData musicData)
{
return LookupPath(musicData.GetID());
}
}

View File

@@ -0,0 +1,25 @@
using HarmonyLib;
using Main;
using Process;
namespace AquaMai.Core.Helpers;
public class SharedInstances
{
public static ProcessDataContainer ProcessDataContainer { get; private set; }
public static GameMainObject GameMainObject { get; private set; }
[HarmonyPrefix]
[HarmonyPatch(typeof(ProcessDataContainer), MethodType.Constructor)]
public static void OnCreateProcessDataContainer(ProcessDataContainer __instance)
{
ProcessDataContainer = __instance;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GameMainObject), "Awake")]
public static void OnCreateGameMainObject(GameMainObject __instance)
{
GameMainObject = __instance;
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using MAI2.Util;
using Manager;
using Manager.UserDatas;
using Net.Packet;
using Net.Packet.Mai2;
namespace AquaMai.Core.Helpers;
public static class Shim
{
public delegate string GetAccessTokenMethod(int index);
public static readonly GetAccessTokenMethod GetAccessToken = new Func<GetAccessTokenMethod>(() => {
var tOperationManager = Traverse.Create(Singleton<OperationManager>.Instance);
var tGetAccessToken = tOperationManager.Method("GetAccessToken", [typeof(int)]);
if (!tGetAccessToken.MethodExists())
{
return (index) => throw new MissingMethodException("No matching OperationManager.GetAccessToken() method found");
}
return (index) => tGetAccessToken.GetValue<string>(index);
})();
public delegate PacketUploadUserPlaylog PacketUploadUserPlaylogCreator(int index, UserData src, int trackNo, Action<int> onDone, Action<PacketStatus> onError = null);
public static readonly PacketUploadUserPlaylogCreator CreatePacketUploadUserPlaylog = new Func<PacketUploadUserPlaylogCreator>(() => {
var type = typeof(PacketUploadUserPlaylog);
if (type.GetConstructor([typeof(int), typeof(UserData), typeof(int), typeof(Action<int>), typeof(Action<PacketStatus>)]) is ConstructorInfo ctor1) {
return (index, src, trackNo, onDone, onError) => {
var args = new object[] {index, src, trackNo, onDone, onError};
return (PacketUploadUserPlaylog)ctor1.Invoke(args);
};
}
else if (type.GetConstructor([typeof(int), typeof(UserData), typeof(int), typeof(string), typeof(Action<int>), typeof(Action<PacketStatus>)]) is ConstructorInfo ctor2) {
return (index, src, trackNo, onDone, onError) => {
var accessToken = GetAccessToken(index);
var args = new object[] {index, src, trackNo, accessToken, onDone, onError};
return (PacketUploadUserPlaylog)ctor2.Invoke(args);
};
}
else
{
throw new MissingMethodException("No matching PacketUploadUserPlaylog constructor found");
}
})();
public delegate PacketUpsertUserAll PacketUpsertUserAllCreator(int index, UserData src, Action<int> onDone, Action<PacketStatus> onError = null);
public static readonly PacketUpsertUserAllCreator CreatePacketUpsertUserAll = new Func<PacketUpsertUserAllCreator>(() => {
var type = typeof(PacketUpsertUserAll);
if (type.GetConstructor([typeof(int), typeof(UserData), typeof(Action<int>), typeof(Action<PacketStatus>)]) is ConstructorInfo ctor1) {
return (index, src, onDone, onError) => {
var args = new object[] {index, src, onDone, onError};
return (PacketUpsertUserAll)ctor1.Invoke(args);
};
}
else if (type.GetConstructor([typeof(int), typeof(UserData), typeof(string), typeof(Action<int>), typeof(Action<PacketStatus>)]) is ConstructorInfo ctor2) {
return (index, src, onDone, onError) => {
var accessToken = GetAccessToken(index);
var args = new object[] {index, src, accessToken, onDone, onError};
return (PacketUpsertUserAll)ctor2.Invoke(args);
};
}
else
{
throw new MissingMethodException("No matching PacketUpsertUserAll constructor found");
}
})();
public static IEnumerable<UserScore>[] GetUserScoreList(UserData userData)
{
var tUserData = Traverse.Create(userData);
var tScoreList = tUserData.Property("ScoreList");
if (tScoreList.PropertyExists())
{
return tScoreList.GetValue<List<UserScore>[]>();
}
var tScoreDic = tUserData.Property("ScoreDic");
if (tScoreDic.PropertyExists())
{
var scoreDic = tScoreDic.GetValue<Dictionary<int, UserScore>[]>();
return scoreDic.Select(dic => dic.Values).ToArray();
}
throw new MissingFieldException("No matching UserData.ScoreList/ScoreDic found");
}
}