mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-15 23:27:26 +08:00
[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:
132
AquaMai/AquaMai.Core/AquaMai.Core.csproj
Normal file
132
AquaMai/AquaMai.Core/AquaMai.Core.csproj
Normal file
@@ -0,0 +1,132 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{33C0D4ED-6A84-4659-9A05-12D43D75D0B3}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>AquaMai.Core</RootNamespace>
|
||||
<AssemblyName>AquaMai.Core</AssemblyName>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<LangVersion>12</LangVersion>
|
||||
<NoWarn>414</NoWarn>
|
||||
<AssemblySearchPaths>$(ProjectDir)../Libs/;$(AssemblySearchPaths)</AssemblySearchPaths>
|
||||
<OutputPath>$(ProjectDir)../Output/</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>None</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../AquaMai.Config/AquaMai.Config.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="0Harmony" />
|
||||
<Reference Include="AMDaemon.NET" />
|
||||
<Reference Include="Assembly-CSharp" />
|
||||
<Reference Include="Assembly-CSharp-firstpass" />
|
||||
<Reference Include="MelonLoader" />
|
||||
<Reference Include="Mono.Cecil" />
|
||||
<Reference Include="Mono.Posix" />
|
||||
<Reference Include="Mono.Security" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Unity.Analytics.DataPrivacy" />
|
||||
<Reference Include="Unity.TextMeshPro" />
|
||||
<Reference Include="UnityEngine" />
|
||||
<Reference Include="UnityEngine.AccessibilityModule" />
|
||||
<Reference Include="UnityEngine.AIModule" />
|
||||
<Reference Include="UnityEngine.AnimationModule" />
|
||||
<Reference Include="UnityEngine.ARModule" />
|
||||
<Reference Include="UnityEngine.AssetBundleModule" />
|
||||
<Reference Include="UnityEngine.AudioModule" />
|
||||
<Reference Include="UnityEngine.BaselibModule" />
|
||||
<Reference Include="UnityEngine.ClothModule" />
|
||||
<Reference Include="UnityEngine.ClusterInputModule" />
|
||||
<Reference Include="UnityEngine.ClusterRendererModule" />
|
||||
<Reference Include="UnityEngine.CoreModule" />
|
||||
<Reference Include="UnityEngine.CrashReportingModule" />
|
||||
<Reference Include="UnityEngine.DirectorModule" />
|
||||
<Reference Include="UnityEngine.FileSystemHttpModule" />
|
||||
<Reference Include="UnityEngine.GameCenterModule" />
|
||||
<Reference Include="UnityEngine.GridModule" />
|
||||
<Reference Include="UnityEngine.HotReloadModule" />
|
||||
<Reference Include="UnityEngine.ImageConversionModule" />
|
||||
<Reference Include="UnityEngine.IMGUIModule" />
|
||||
<Reference Include="UnityEngine.InputModule" />
|
||||
<Reference Include="UnityEngine.JSONSerializeModule" />
|
||||
<Reference Include="UnityEngine.LocalizationModule" />
|
||||
<Reference Include="UnityEngine.Networking" />
|
||||
<Reference Include="UnityEngine.ParticleSystemModule" />
|
||||
<Reference Include="UnityEngine.PerformanceReportingModule" />
|
||||
<Reference Include="UnityEngine.Physics2DModule" />
|
||||
<Reference Include="UnityEngine.PhysicsModule" />
|
||||
<Reference Include="UnityEngine.ProfilerModule" />
|
||||
<Reference Include="UnityEngine.ScreenCaptureModule" />
|
||||
<Reference Include="UnityEngine.SharedInternalsModule" />
|
||||
<Reference Include="UnityEngine.SpatialTracking" />
|
||||
<Reference Include="UnityEngine.SpriteMaskModule" />
|
||||
<Reference Include="UnityEngine.SpriteShapeModule" />
|
||||
<Reference Include="UnityEngine.StreamingModule" />
|
||||
<Reference Include="UnityEngine.StyleSheetsModule" />
|
||||
<Reference Include="UnityEngine.SubstanceModule" />
|
||||
<Reference Include="UnityEngine.TerrainModule" />
|
||||
<Reference Include="UnityEngine.TerrainPhysicsModule" />
|
||||
<Reference Include="UnityEngine.TextCoreModule" />
|
||||
<Reference Include="UnityEngine.TextRenderingModule" />
|
||||
<Reference Include="UnityEngine.TilemapModule" />
|
||||
<Reference Include="UnityEngine.Timeline" />
|
||||
<Reference Include="UnityEngine.TimelineModule" />
|
||||
<Reference Include="UnityEngine.TLSModule" />
|
||||
<Reference Include="UnityEngine.UI" />
|
||||
<Reference Include="UnityEngine.UIElementsModule" />
|
||||
<Reference Include="UnityEngine.UIModule" />
|
||||
<Reference Include="UnityEngine.UmbraModule" />
|
||||
<Reference Include="UnityEngine.UNETModule" />
|
||||
<Reference Include="UnityEngine.UnityAnalyticsModule" />
|
||||
<Reference Include="UnityEngine.UnityConnectModule" />
|
||||
<Reference Include="UnityEngine.UnityTestProtocolModule" />
|
||||
<Reference Include="UnityEngine.UnityWebRequestAssetBundleModule" />
|
||||
<Reference Include="UnityEngine.UnityWebRequestAudioModule" />
|
||||
<Reference Include="UnityEngine.UnityWebRequestModule" />
|
||||
<Reference Include="UnityEngine.UnityWebRequestTextureModule" />
|
||||
<Reference Include="UnityEngine.UnityWebRequestWWWModule" />
|
||||
<Reference Include="UnityEngine.VehiclesModule" />
|
||||
<Reference Include="UnityEngine.VFXModule" />
|
||||
<Reference Include="UnityEngine.VideoModule" />
|
||||
<Reference Include="UnityEngine.VRModule" />
|
||||
<Reference Include="UnityEngine.WindModule" />
|
||||
<Reference Include="UnityEngine.XRModule" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources/Locale.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Locale.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources/Locale.zh.resx" WithCulture="false">
|
||||
<DependentUpon>Locale.resx</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace AquaMai.Core.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
public class EnableGameVersionAttribute(uint minVersion = 0, uint maxVersion = 0, bool noWarn = false) : Attribute
|
||||
{
|
||||
public uint MinVersion { get; } = minVersion;
|
||||
public uint MaxVersion { get; } = maxVersion;
|
||||
public bool NoWarn { get; } = noWarn;
|
||||
|
||||
public bool ShouldEnable(uint gameVersion)
|
||||
{
|
||||
if (MinVersion > 0 && MinVersion > gameVersion) return false;
|
||||
if (MaxVersion > 0 && MaxVersion < gameVersion) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
79
AquaMai/AquaMai.Core/Attributes/EnableIfAttribute.cs
Normal file
79
AquaMai/AquaMai.Core/Attributes/EnableIfAttribute.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
|
||||
namespace AquaMai.Core.Attributes;
|
||||
|
||||
public enum EnableConditionOperator
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThanOrEqual
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
public class EnableIfAttribute(
|
||||
Type referenceType,
|
||||
string referenceMember,
|
||||
EnableConditionOperator @operator,
|
||||
object rightSideValue) : Attribute
|
||||
{
|
||||
public Type ReferenceType { get; } = referenceType;
|
||||
public string ReferenceMember { get; } = referenceMember;
|
||||
public EnableConditionOperator Operator { get; } = @operator;
|
||||
public object RightSideValue { get; } = rightSideValue;
|
||||
|
||||
// Referencing a field in another class and checking if it's true.
|
||||
public EnableIfAttribute(Type referenceType, string referenceMember)
|
||||
: this(referenceType, referenceMember, EnableConditionOperator.Equal, true)
|
||||
{ }
|
||||
|
||||
// Referencing a field in the same class and comparing it with a value.
|
||||
public EnableIfAttribute(string referenceMember, EnableConditionOperator condition, object value)
|
||||
: this(null, referenceMember, condition, value)
|
||||
{ }
|
||||
|
||||
// Referencing a field in the same class and checking if it's true.
|
||||
public EnableIfAttribute(string referenceMember)
|
||||
: this(referenceMember, EnableConditionOperator.Equal, true)
|
||||
{ }
|
||||
|
||||
public bool ShouldEnable(Type selfType)
|
||||
{
|
||||
var referenceType = ReferenceType ?? selfType;
|
||||
var referenceField = referenceType.GetField(
|
||||
ReferenceMember,
|
||||
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
|
||||
var referenceProperty = referenceType.GetProperty(
|
||||
ReferenceMember,
|
||||
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
|
||||
if (referenceField == null && referenceProperty == null)
|
||||
{
|
||||
throw new ArgumentException($"Field or property {ReferenceMember} not found in {referenceType.FullName}");
|
||||
}
|
||||
var referenceMemberValue = referenceField != null ? referenceField.GetValue(null) : referenceProperty.GetValue(null);
|
||||
switch (Operator)
|
||||
{
|
||||
case EnableConditionOperator.Equal:
|
||||
return referenceMemberValue.Equals(RightSideValue);
|
||||
case EnableConditionOperator.NotEqual:
|
||||
return !referenceMemberValue.Equals(RightSideValue);
|
||||
case EnableConditionOperator.GreaterThan:
|
||||
case EnableConditionOperator.LessThan:
|
||||
case EnableConditionOperator.GreaterThanOrEqual:
|
||||
case EnableConditionOperator.LessThanOrEqual:
|
||||
var comparison = (IComparable)referenceMemberValue;
|
||||
return Operator switch
|
||||
{
|
||||
EnableConditionOperator.GreaterThan => comparison.CompareTo(RightSideValue) > 0,
|
||||
EnableConditionOperator.LessThan => comparison.CompareTo(RightSideValue) < 0,
|
||||
EnableConditionOperator.GreaterThanOrEqual => comparison.CompareTo(RightSideValue) >= 0,
|
||||
EnableConditionOperator.LessThanOrEqual => comparison.CompareTo(RightSideValue) <= 0,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace AquaMai.Core.Attributes;
|
||||
|
||||
// If the field or property with this name is true, the patch will be implicitly enabled, regardless of the config state.
|
||||
// This is handled outside the config module, while The config state won't be actually set to enabled by it.
|
||||
// Won't bypass the restriction of [EnableIf()] and [EnableGameVersion()].
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class EnableImplicitlyIf(string memberName) : Attribute
|
||||
{
|
||||
public string MemberName { get; } = memberName;
|
||||
}
|
||||
83
AquaMai/AquaMai.Core/ConfigLoader.cs
Normal file
83
AquaMai/AquaMai.Core/ConfigLoader.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using MelonLoader;
|
||||
using AquaMai.Config;
|
||||
using AquaMai.Config.Interfaces;
|
||||
using AquaMai.Config.Migration;
|
||||
|
||||
namespace AquaMai.Core;
|
||||
|
||||
public static class ConfigLoader
|
||||
{
|
||||
private static string ConfigFile => "AquaMai.toml";
|
||||
private static string ConfigExampleFile(string lang) => $"AquaMai.{lang}.toml";
|
||||
private static string OldConfigFile(string version) => $"AquaMai.toml.old-v{version}.";
|
||||
|
||||
private static Config.Config config;
|
||||
|
||||
public static Config.Config Config => config;
|
||||
|
||||
public static bool LoadConfig(Assembly modsAssembly)
|
||||
{
|
||||
Utility.LogFunction = MelonLogger.Msg;
|
||||
|
||||
config = new(
|
||||
new Config.Reflection.ReflectionManager(
|
||||
new Config.Reflection.SystemReflectionProvider(modsAssembly)));
|
||||
|
||||
if (!File.Exists(ConfigFile))
|
||||
{
|
||||
var examples = GenerateExamples();
|
||||
foreach (var (lang, example) in examples)
|
||||
{
|
||||
var filename = ConfigExampleFile(lang);
|
||||
File.WriteAllText(filename, example);
|
||||
}
|
||||
MelonLogger.Error("======================================!!!");
|
||||
MelonLogger.Error("AquaMai.toml not found! Please create it.");
|
||||
MelonLogger.Error("找不到配置文件 AquaMai.toml!请创建。");
|
||||
MelonLogger.Error("Example copied to AquaMai.en.toml");
|
||||
MelonLogger.Error("示例已复制到 AquaMai.zh.toml");
|
||||
MelonLogger.Error("=========================================");
|
||||
return false;
|
||||
}
|
||||
|
||||
var configText = File.ReadAllText(ConfigFile);
|
||||
var configView = new ConfigView(configText);
|
||||
var configVersion = ConfigMigrationManager.Instance.GetVersion(configView);
|
||||
if (configVersion != ConfigMigrationManager.Instance.latestVersion)
|
||||
{
|
||||
File.WriteAllText(OldConfigFile(configVersion), configText);
|
||||
configView = (ConfigView)ConfigMigrationManager.Instance.Migrate(configView);
|
||||
}
|
||||
|
||||
// Read AquaMai.toml to load settings
|
||||
ConfigParser.Instance.Parse(config, configView);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void SaveConfig(string lang)
|
||||
{
|
||||
File.WriteAllText(ConfigFile, SerailizeCurrentConfig(lang));
|
||||
}
|
||||
|
||||
private static string SerailizeCurrentConfig(string lang) =>
|
||||
new ConfigSerializer(new IConfigSerializer.Options()
|
||||
{
|
||||
Lang = lang,
|
||||
IncludeBanner = true,
|
||||
OverrideLocaleValue = true
|
||||
}).Serialize(config);
|
||||
|
||||
private static IDictionary<string, string> GenerateExamples()
|
||||
{
|
||||
var examples = new Dictionary<string, string>();
|
||||
foreach (var lang in (string[]) ["en", "zh"])
|
||||
{
|
||||
examples[lang] = SerailizeCurrentConfig(lang);
|
||||
}
|
||||
return examples;
|
||||
}
|
||||
}
|
||||
72
AquaMai/AquaMai.Core/Helpers/EnableConditionHelper.cs
Normal file
72
AquaMai/AquaMai.Core/Helpers/EnableConditionHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
15
AquaMai/AquaMai.Core/Helpers/FileSystem.cs
Normal file
15
AquaMai/AquaMai.Core/Helpers/FileSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
21
AquaMai/AquaMai.Core/Helpers/GameInfo.cs
Normal file
21
AquaMai/AquaMai.Core/Helpers/GameInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
57
AquaMai/AquaMai.Core/Helpers/GuiSizes.cs
Normal file
57
AquaMai/AquaMai.Core/Helpers/GuiSizes.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
146
AquaMai/AquaMai.Core/Helpers/KeyListener.cs
Normal file
146
AquaMai/AquaMai.Core/Helpers/KeyListener.cs
Normal 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, "游戏功能键需要单独处理")
|
||||
};
|
||||
}
|
||||
39
AquaMai/AquaMai.Core/Helpers/MessageHelper.cs
Normal file
39
AquaMai/AquaMai.Core/Helpers/MessageHelper.cs
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
31
AquaMai/AquaMai.Core/Helpers/MusicDirHelper.cs
Normal file
31
AquaMai/AquaMai.Core/Helpers/MusicDirHelper.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
25
AquaMai/AquaMai.Core/Helpers/SharedInstances.cs
Normal file
25
AquaMai/AquaMai.Core/Helpers/SharedInstances.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
90
AquaMai/AquaMai.Core/Helpers/Shim.cs
Normal file
90
AquaMai/AquaMai.Core/Helpers/Shim.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
32
AquaMai/AquaMai.Core/Resources/I18nSingleAssemblyHook.cs
Normal file
32
AquaMai/AquaMai.Core/Resources/I18nSingleAssemblyHook.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace AquaMai.Core.Resources;
|
||||
|
||||
public class I18nSingleAssemblyHook
|
||||
{
|
||||
[HarmonyPatch(typeof(ResourceManager), "InternalGetResourceSet", typeof(CultureInfo), typeof(bool), typeof(bool))]
|
||||
[HarmonyPrefix]
|
||||
public static bool GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents, ref ResourceSet __result, ResourceManager __instance)
|
||||
{
|
||||
var GetResourceFileName = __instance.GetType().GetMethod("GetResourceFileName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var resourceFileName = (string)GetResourceFileName.Invoke(__instance, [culture]);
|
||||
var ResourcesAssembly = typeof(I18nSingleAssemblyHook).Assembly;
|
||||
var manifestResourceStream = ResourcesAssembly.GetManifestResourceStream(resourceFileName);
|
||||
if (manifestResourceStream == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var resourceGroveler = __instance.GetType().GetField("resourceGroveler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance);
|
||||
var CreateResourceSet = resourceGroveler.GetType().GetMethod("CreateResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var resourceSet = CreateResourceSet.Invoke(resourceGroveler, [manifestResourceStream, ResourcesAssembly]);
|
||||
var AddResourceSet = __instance.GetType().GetMethod("AddResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||
var localResourceSets = __instance.GetType().GetField("_resourceSets", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance);
|
||||
object[] args = [localResourceSets, culture.Name, resourceSet];
|
||||
AddResourceSet.Invoke(null, args);
|
||||
__result = (ResourceSet)args[2];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
316
AquaMai/AquaMai.Core/Resources/Locale.Designer.cs
generated
Normal file
316
AquaMai/AquaMai.Core/Resources/Locale.Designer.cs
generated
Normal file
@@ -0,0 +1,316 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace AquaMai.Core.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Locale {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public Locale() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AquaMai.Core.Resources.Locale", typeof(Locale).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You are using AquaMai CI build version. This version is built from the latest mainline code and may contain undocumented configuration changes or potential issues..
|
||||
/// </summary>
|
||||
public static string CiBuildAlertContent {
|
||||
get {
|
||||
return ResourceManager.GetString("CiBuildAlertContent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Important Notice: Test Version.
|
||||
/// </summary>
|
||||
public static string CiBuildAlertTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("CiBuildAlertTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Loaded!.
|
||||
/// </summary>
|
||||
public static string Loaded {
|
||||
get {
|
||||
return ResourceManager.GetString("Loaded", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Errors detected while loading!
|
||||
///- Are you using a modified Assembly-CSharp.dll, which will cause inconsistent functions and cannot find the functions that need to be modified
|
||||
///- Check for conflicting mods, or enabled incompatible options.
|
||||
/// </summary>
|
||||
public static string LoadError {
|
||||
get {
|
||||
return ResourceManager.GetString("LoadError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to End.
|
||||
/// </summary>
|
||||
public static string MarkRepeatEnd {
|
||||
get {
|
||||
return ResourceManager.GetString("MarkRepeatEnd", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Start.
|
||||
/// </summary>
|
||||
public static string MarkRepeatStart {
|
||||
get {
|
||||
return ResourceManager.GetString("MarkRepeatStart", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Aime reader error.
|
||||
/// </summary>
|
||||
public static string NetErrIsAliveAimeReader {
|
||||
get {
|
||||
return ResourceManager.GetString("NetErrIsAliveAimeReader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Aime server error.
|
||||
/// </summary>
|
||||
public static string NetErrIsAliveAimeServer {
|
||||
get {
|
||||
return ResourceManager.GetString("NetErrIsAliveAimeServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Server communication error.
|
||||
/// </summary>
|
||||
public static string NetErrIsAliveServer {
|
||||
get {
|
||||
return ResourceManager.GetString("NetErrIsAliveServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Data download not success.
|
||||
/// </summary>
|
||||
public static string NetErrWasDownloadSuccessOnce {
|
||||
get {
|
||||
return ResourceManager.GetString("NetErrWasDownloadSuccessOnce", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pause.
|
||||
/// </summary>
|
||||
public static string Pause {
|
||||
get {
|
||||
return ResourceManager.GetString("Pause", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 游玩次数:{0}.
|
||||
/// </summary>
|
||||
public static string PlayCount {
|
||||
get {
|
||||
return ResourceManager.GetString("PlayCount", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to SSS+ => DXRating += {0}.
|
||||
/// </summary>
|
||||
public static string RatingUpWhenSSSp {
|
||||
get {
|
||||
return ResourceManager.GetString("RatingUpWhenSSSp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Repeat end time cannot be less than repeat start time.
|
||||
/// </summary>
|
||||
public static string RepeatEndTimeLessThenStartTime {
|
||||
get {
|
||||
return ResourceManager.GetString("RepeatEndTimeLessThenStartTime", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Loop Not Set.
|
||||
/// </summary>
|
||||
public static string RepeatNotSet {
|
||||
get {
|
||||
return ResourceManager.GetString("RepeatNotSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reset.
|
||||
/// </summary>
|
||||
public static string RepeatReset {
|
||||
get {
|
||||
return ResourceManager.GetString("RepeatReset", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Loop Set.
|
||||
/// </summary>
|
||||
public static string RepeatStartEndSet {
|
||||
get {
|
||||
return ResourceManager.GetString("RepeatStartEndSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Loop Start Set.
|
||||
/// </summary>
|
||||
public static string RepeatStartSet {
|
||||
get {
|
||||
return ResourceManager.GetString("RepeatStartSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please set repeat start time first.
|
||||
/// </summary>
|
||||
public static string RepeatStartTimeNotSet {
|
||||
get {
|
||||
return ResourceManager.GetString("RepeatStartTimeNotSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Saving... Do not exit the game.
|
||||
/// </summary>
|
||||
public static string SavingDontExit {
|
||||
get {
|
||||
return ResourceManager.GetString("SavingDontExit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Seek <<.
|
||||
/// </summary>
|
||||
public static string SeekBackward {
|
||||
get {
|
||||
return ResourceManager.GetString("SeekBackward", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Seek >>.
|
||||
/// </summary>
|
||||
public static string SeekForward {
|
||||
get {
|
||||
return ResourceManager.GetString("SeekForward", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Skip.
|
||||
/// </summary>
|
||||
public static string Skip {
|
||||
get {
|
||||
return ResourceManager.GetString("Skip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to > Skipping incompatible patch: {0}.
|
||||
/// </summary>
|
||||
public static string SkipIncompatiblePatch {
|
||||
get {
|
||||
return ResourceManager.GetString("SkipIncompatiblePatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Speed.
|
||||
/// </summary>
|
||||
public static string Speed {
|
||||
get {
|
||||
return ResourceManager.GetString("Speed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Speed -.
|
||||
/// </summary>
|
||||
public static string SpeedDown {
|
||||
get {
|
||||
return ResourceManager.GetString("SpeedDown", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Speed Reset.
|
||||
/// </summary>
|
||||
public static string SpeedReset {
|
||||
get {
|
||||
return ResourceManager.GetString("SpeedReset", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Speed +.
|
||||
/// </summary>
|
||||
public static string SpeedUp {
|
||||
get {
|
||||
return ResourceManager.GetString("SpeedUp", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
107
AquaMai/AquaMai.Core/Resources/Locale.resx
Normal file
107
AquaMai/AquaMai.Core/Resources/Locale.resx
Normal file
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SeekBackward" xml:space="preserve">
|
||||
<value>Seek <<</value>
|
||||
</data>
|
||||
<data name="SeekForward" xml:space="preserve">
|
||||
<value>Seek >></value>
|
||||
</data>
|
||||
<data name="Pause" xml:space="preserve">
|
||||
<value>Pause</value>
|
||||
</data>
|
||||
<data name="MarkRepeatStart" xml:space="preserve">
|
||||
<value>Start</value>
|
||||
</data>
|
||||
<data name="MarkRepeatEnd" xml:space="preserve">
|
||||
<value>End</value>
|
||||
</data>
|
||||
<data name="RepeatReset" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<data name="RepeatNotSet" xml:space="preserve">
|
||||
<value>Loop Not Set</value>
|
||||
</data>
|
||||
<data name="RepeatStartSet" xml:space="preserve">
|
||||
<value>Loop Start Set</value>
|
||||
</data>
|
||||
<data name="RepeatStartEndSet" xml:space="preserve">
|
||||
<value>Loop Set</value>
|
||||
</data>
|
||||
<data name="SpeedDown" xml:space="preserve">
|
||||
<value>Speed -</value>
|
||||
</data>
|
||||
<data name="SpeedUp" xml:space="preserve">
|
||||
<value>Speed +</value>
|
||||
</data>
|
||||
<data name="Speed" xml:space="preserve">
|
||||
<value>Speed</value>
|
||||
</data>
|
||||
<data name="SpeedReset" xml:space="preserve">
|
||||
<value>Speed Reset</value>
|
||||
</data>
|
||||
<data name="LoadError" xml:space="preserve">
|
||||
<value>Errors detected while loading!
|
||||
- Are you using a modified Assembly-CSharp.dll, which will cause inconsistent functions and cannot find the functions that need to be modified
|
||||
- Check for conflicting mods, or enabled incompatible options</value>
|
||||
</data>
|
||||
<data name="SavingDontExit" xml:space="preserve">
|
||||
<value>Saving... Do not exit the game</value>
|
||||
</data>
|
||||
<data name="Loaded" xml:space="preserve">
|
||||
<value>Loaded!</value>
|
||||
</data>
|
||||
<data name="NetErrIsAliveServer" xml:space="preserve">
|
||||
<value>Server communication error</value>
|
||||
</data>
|
||||
<data name="NetErrIsAliveAimeReader" xml:space="preserve">
|
||||
<value>Aime reader error</value>
|
||||
</data>
|
||||
<data name="NetErrIsAliveAimeServer" xml:space="preserve">
|
||||
<value>Aime server error</value>
|
||||
</data>
|
||||
<data name="NetErrWasDownloadSuccessOnce" xml:space="preserve">
|
||||
<value>Data download not success</value>
|
||||
</data>
|
||||
<data name="RatingUpWhenSSSp" xml:space="preserve">
|
||||
<value>SSS+ => DXRating += {0}</value>
|
||||
</data>
|
||||
<data name="Skip" xml:space="preserve">
|
||||
<value>Skip</value>
|
||||
</data>
|
||||
<data name="SkipIncompatiblePatch" xml:space="preserve">
|
||||
<value>> Skipping incompatible patch: {0}</value>
|
||||
</data>
|
||||
<data name="RepeatStartTimeNotSet" xml:space="preserve">
|
||||
<value>Please set repeat start time first</value>
|
||||
</data>
|
||||
<data name="RepeatEndTimeLessThenStartTime" xml:space="preserve">
|
||||
<value>Repeat end time cannot be less than repeat start time</value>
|
||||
</data>
|
||||
<data name="CiBuildAlertTitle" xml:space="preserve">
|
||||
<value>Important Notice: Test Version</value>
|
||||
</data>
|
||||
<data name="CiBuildAlertContent" xml:space="preserve">
|
||||
<value>You are using AquaMai CI build version. This version is built from the latest mainline code and may contain undocumented configuration changes or potential issues.</value>
|
||||
</data>
|
||||
<data name="PlayCount" xml:space="preserve">
|
||||
<value>游玩次数:{0}</value>
|
||||
</data>
|
||||
</root>
|
||||
100
AquaMai/AquaMai.Core/Resources/Locale.zh.resx
Normal file
100
AquaMai/AquaMai.Core/Resources/Locale.zh.resx
Normal file
@@ -0,0 +1,100 @@
|
||||
<root>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SeekBackward" xml:space="preserve">
|
||||
<value>倒退 <<</value>
|
||||
</data>
|
||||
<data name="SeekForward" xml:space="preserve">
|
||||
<value>快进 >></value>
|
||||
</data>
|
||||
<data name="Pause" xml:space="preserve">
|
||||
<value>暂停</value>
|
||||
</data>
|
||||
<data name="MarkRepeatEnd" xml:space="preserve">
|
||||
<value>标记结尾</value>
|
||||
</data>
|
||||
<data name="MarkRepeatStart" xml:space="preserve">
|
||||
<value>标记开头</value>
|
||||
</data>
|
||||
<data name="RepeatNotSet" xml:space="preserve">
|
||||
<value>循环未设定</value>
|
||||
</data>
|
||||
<data name="RepeatReset" xml:space="preserve">
|
||||
<value>循环解除</value>
|
||||
</data>
|
||||
<data name="RepeatStartEndSet" xml:space="preserve">
|
||||
<value>循环已设定</value>
|
||||
</data>
|
||||
<data name="RepeatStartSet" xml:space="preserve">
|
||||
<value>循环开头已设定</value>
|
||||
</data>
|
||||
<data name="Speed" xml:space="preserve">
|
||||
<value>速度</value>
|
||||
</data>
|
||||
<data name="SpeedDown" xml:space="preserve">
|
||||
<value>速度 -</value>
|
||||
</data>
|
||||
<data name="SpeedReset" xml:space="preserve">
|
||||
<value>速度重置</value>
|
||||
</data>
|
||||
<data name="SpeedUp" xml:space="preserve">
|
||||
<value>速度 +</value>
|
||||
</data>
|
||||
<data name="LoadError" xml:space="preserve">
|
||||
<value>加载过程中检测到错误!
|
||||
- 你是否正在使用魔改的 Assembly-CSharp.dll,这会导致函数不一致而无法找到需要修改的函数
|
||||
- 请检查是否有冲突的 Mod,或者开启了不兼容的选项</value>
|
||||
</data>
|
||||
<data name="SavingDontExit" xml:space="preserve">
|
||||
<value>正在保存… 请不要关闭游戏</value>
|
||||
</data>
|
||||
<data name="Loaded" xml:space="preserve">
|
||||
<value>加载完成!</value>
|
||||
</data>
|
||||
<data name="NetErrIsAliveServer" xml:space="preserve">
|
||||
<value>主服务器通信错误</value>
|
||||
</data>
|
||||
<data name="NetErrIsAliveAimeReader" xml:space="preserve">
|
||||
<value>Aime 读卡器错误</value>
|
||||
</data>
|
||||
<data name="NetErrIsAliveAimeServer" xml:space="preserve">
|
||||
<value>AimeDB 通信错误</value>
|
||||
</data>
|
||||
<data name="NetErrWasDownloadSuccessOnce" xml:space="preserve">
|
||||
<value>数据下载不成功</value>
|
||||
</data>
|
||||
<data name="RatingUpWhenSSSp" xml:space="preserve">
|
||||
<value>推到鸟加可上 {0} 分</value>
|
||||
</data>
|
||||
<data name="Skip" xml:space="preserve">
|
||||
<value>跳过</value>
|
||||
</data>
|
||||
<data name="SkipIncompatiblePatch" xml:space="preserve">
|
||||
<value>> 已跳过加载不兼容的功能: {0}</value>
|
||||
</data>
|
||||
<data name="RepeatEndTimeLessThenStartTime" xml:space="preserve">
|
||||
<value>循环结束时间不能早于开始时间</value>
|
||||
</data>
|
||||
<data name="RepeatStartTimeNotSet" xml:space="preserve">
|
||||
<value>请先设置循环开始时间</value>
|
||||
</data>
|
||||
<data name="CiBuildAlertTitle" xml:space="preserve">
|
||||
<value>重要提示:测试版本</value>
|
||||
</data>
|
||||
<data name="CiBuildAlertContent" xml:space="preserve">
|
||||
<value>您正在使用的是 AquaMai CI 构建版本。由于该版本基于最新的主线代码构建,可能包含未通知的配置文件变更或潜在问题。</value>
|
||||
</data>
|
||||
<data name="PlayCount" xml:space="preserve">
|
||||
<value>Play Count: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
199
AquaMai/AquaMai.Core/Startup.cs
Normal file
199
AquaMai/AquaMai.Core/Startup.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AquaMai.Core.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Core.Resources;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Core;
|
||||
|
||||
public class Startup
|
||||
{
|
||||
private static HarmonyLib.Harmony _harmony;
|
||||
|
||||
private static bool _hasErrors;
|
||||
|
||||
private enum ModLifecycleMethod
|
||||
{
|
||||
// Invoked before all patches are applied, including core patches
|
||||
OnBeforeAllPatch,
|
||||
// Invoked after all patches are applied
|
||||
OnAfterAllPatch,
|
||||
// Invoked before the current patch is applied
|
||||
OnBeforePatch,
|
||||
// Invoked after the current patch is applied
|
||||
// Subclasses are treated as separate patches
|
||||
OnAfterPatch,
|
||||
// Invoked when an error occurs applying the current patch
|
||||
// Lifecycle methods' excpetions not included
|
||||
// Subclasses' error not included
|
||||
OnPatchError
|
||||
}
|
||||
|
||||
private static bool ShouldEnableImplicitly(Type type)
|
||||
{
|
||||
var implicitEnableAttribute = type.GetCustomAttribute<EnableImplicitlyIf>();
|
||||
if (implicitEnableAttribute == null) return false;
|
||||
var referenceField = type.GetField(implicitEnableAttribute.MemberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var referenceProperty = type.GetProperty(implicitEnableAttribute.MemberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (referenceField == null && referenceProperty == null)
|
||||
{
|
||||
throw new ArgumentException($"Field or property {implicitEnableAttribute.MemberName} not found in {type.FullName}");
|
||||
}
|
||||
var referenceMemberValue = referenceField != null ? referenceField.GetValue(null) : referenceProperty.GetValue(null);
|
||||
if ((bool)referenceMemberValue)
|
||||
{
|
||||
MelonLogger.Msg($"Enabled {type.FullName} implicitly");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void InvokeLifecycleMethod(Type type, ModLifecycleMethod methodName)
|
||||
{
|
||||
var method = type.GetMethod(methodName.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (method == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var parameters = method.GetParameters();
|
||||
var arguments = parameters.Select(p =>
|
||||
{
|
||||
if (p.ParameterType == typeof(HarmonyLib.Harmony)) return _harmony;
|
||||
throw new InvalidOperationException($"Unsupported parameter type {p.ParameterType} in lifecycle method {type.FullName}.{methodName}");
|
||||
}).ToArray();
|
||||
try
|
||||
{
|
||||
method.Invoke(null, arguments);
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
MelonLogger.Error($"Failed to invoke lifecycle method {type.FullName}.{methodName}: {e.InnerException}");
|
||||
_hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CollectWantedPatches(List<Type> wantedPatches, Type type)
|
||||
{
|
||||
if (EnableConditionHelper.ShouldSkipClass(type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
wantedPatches.Add(type);
|
||||
foreach (var nested in type.GetNestedTypes())
|
||||
{
|
||||
CollectWantedPatches(wantedPatches, nested);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyPatch(Type type)
|
||||
{
|
||||
MelonLogger.Msg($"> Applying {type}");
|
||||
try
|
||||
{
|
||||
InvokeLifecycleMethod(type, ModLifecycleMethod.OnBeforePatch);
|
||||
_harmony.PatchAll(type);
|
||||
InvokeLifecycleMethod(type, ModLifecycleMethod.OnAfterPatch);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Error($"Failed to patch {type}: {e}");
|
||||
InvokeLifecycleMethod(type, ModLifecycleMethod.OnPatchError);
|
||||
_hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveLocale()
|
||||
{
|
||||
var localeConfigEntry = ConfigLoader.Config.ReflectionManager.GetEntry("General.Locale");
|
||||
var localeValue = (string)ConfigLoader.Config.GetEntryState(localeConfigEntry).Value;
|
||||
return localeValue switch
|
||||
{
|
||||
"en" => localeValue,
|
||||
"zh" => localeValue,
|
||||
_ => Application.systemLanguage switch
|
||||
{
|
||||
SystemLanguage.Chinese or SystemLanguage.ChineseSimplified or SystemLanguage.ChineseTraditional => "zh",
|
||||
SystemLanguage.English => "en",
|
||||
_ => "en"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void Initialize(Assembly modsAssembly, HarmonyLib.Harmony harmony)
|
||||
{
|
||||
MelonLogger.Msg("Loading mod settings...");
|
||||
|
||||
var configLoaded = ConfigLoader.LoadConfig(modsAssembly);
|
||||
var lang = ResolveLocale();
|
||||
if (configLoaded)
|
||||
{
|
||||
ConfigLoader.SaveConfig(lang); // Re-save the config as soon as possible
|
||||
}
|
||||
|
||||
_harmony = harmony;
|
||||
|
||||
// Init locale with patching C# runtime
|
||||
// https://stackoverflow.com/questions/1952638/single-assembly-multi-language-windows-forms-deployment-ilmerge-and-satellite-a
|
||||
ApplyPatch(typeof(I18nSingleAssemblyHook));
|
||||
Locale.Culture = CultureInfo.GetCultureInfo(lang); // Must be called after I18nSingleAssemblyHook patched
|
||||
|
||||
// The patch list is ordered
|
||||
List<Type> wantedPatches = [];
|
||||
|
||||
// Must be patched first to support [EnableIf(...)] and [EnableGameVersion(...)]
|
||||
CollectWantedPatches(wantedPatches, typeof(EnableConditionHelper));
|
||||
// Core helpers patched first
|
||||
CollectWantedPatches(wantedPatches, typeof(MessageHelper));
|
||||
CollectWantedPatches(wantedPatches, typeof(MusicDirHelper));
|
||||
CollectWantedPatches(wantedPatches, typeof(SharedInstances));
|
||||
CollectWantedPatches(wantedPatches, typeof(GuiSizes));
|
||||
CollectWantedPatches(wantedPatches, typeof(KeyListener));
|
||||
|
||||
// Collect patches based on the config
|
||||
var config = ConfigLoader.Config;
|
||||
foreach (var section in config.ReflectionManager.Sections)
|
||||
{
|
||||
var reflectionType = (Config.Reflection.SystemReflectionProvider.ReflectionType)section.Type;
|
||||
var type = reflectionType.UnderlyingType;
|
||||
if (!config.GetSectionState(section).Enabled && !ShouldEnableImplicitly(type)) continue;
|
||||
CollectWantedPatches(wantedPatches, type);
|
||||
}
|
||||
|
||||
foreach (var type in wantedPatches)
|
||||
{
|
||||
InvokeLifecycleMethod(type, ModLifecycleMethod.OnBeforeAllPatch);
|
||||
}
|
||||
foreach (var type in wantedPatches)
|
||||
{
|
||||
ApplyPatch(type);
|
||||
}
|
||||
foreach (var type in wantedPatches)
|
||||
{
|
||||
InvokeLifecycleMethod(type, ModLifecycleMethod.OnAfterAllPatch);
|
||||
}
|
||||
|
||||
if (_hasErrors)
|
||||
{
|
||||
MelonLogger.Warning("========================================================================!!!\n" + Locale.LoadError);
|
||||
MelonLogger.Warning("===========================================================================");
|
||||
}
|
||||
|
||||
# if CI
|
||||
MelonLogger.Warning(Locale.CiBuildAlertTitle);
|
||||
MelonLogger.Warning(Locale.CiBuildAlertContent);
|
||||
# endif
|
||||
|
||||
MelonLogger.Msg(Locale.Loaded);
|
||||
}
|
||||
|
||||
public static void OnGUI()
|
||||
{
|
||||
GuiSizes.SetupStyles();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user