diff --git a/.github/workflows/aquamai.yaml b/.github/workflows/aquamai.yaml index b0621210..e63a5800 100644 --- a/.github/workflows/aquamai.yaml +++ b/.github/workflows/aquamai.yaml @@ -43,6 +43,7 @@ jobs: cd Output mkdir Upload move AquaMai.dll Upload + move AquaMai.BepInEx.dll Upload move AquaMai.*.toml Upload - uses: actions/upload-artifact@v4 @@ -53,6 +54,7 @@ jobs: - name: Upload CI release if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' shell: cmd + # For MaiChartManager, upload MelonLoader version only. run: build-assets\Releaser\AquaMaiReleaser.exe "Output\Upload\AquaMai.dll" "${{ env.GIT_DESCRIBE }}" - name: Send to Telegram @@ -62,11 +64,13 @@ jobs: $Form = @{ chat_id = "-1002231087502" media = @( - @{ type = "document"; media = "attach://aquamai_main"; caption = "${{ env.GIT_DESCRIBE }}`n${{ github.event.commits[0].message }}" }, + @{ type = "document"; media = "attach://aquamai_melonloader"; caption = "${{ env.GIT_DESCRIBE }}`n${{ github.event.commits[0].message }}" }, + @{ type = "document"; media = "attach://aquamai_bepinex" }, @{ type = "document"; media = "attach://aquamai_zh" } @{ type = "document"; media = "attach://aquamai_en" } ) | ConvertTo-Json - aquamai_main = Get-Item Output\Upload\AquaMai.dll + aquamai_melonloader = Get-Item Output\Upload\AquaMai.dll + aquamai_bepinex = Get-Item Output\Upload\AquaMai.BepInEx.dll aquamai_zh = Get-Item Output\Upload\AquaMai.zh.toml aquamai_en = Get-Item Output\Upload\AquaMai.en.toml } diff --git a/.gitignore b/.gitignore index 86c03662..3935e1ee 100644 --- a/.gitignore +++ b/.gitignore @@ -372,5 +372,5 @@ MigrationBackup/ Output tools -AquaMai/BuildInfo.g.cs +AquaMai.Common/BuildInfo.g.cs .idea \ No newline at end of file diff --git a/AquaMai.BepInEx/AquaMai.BepInEx.csproj b/AquaMai.BepInEx/AquaMai.BepInEx.csproj new file mode 100644 index 00000000..80c17a52 --- /dev/null +++ b/AquaMai.BepInEx/AquaMai.BepInEx.csproj @@ -0,0 +1,60 @@ + + + + Release + AnyCPU + Library + AquaMai.BepInEx + AquaMai.BepInEx + net48 + 512 + true + 12 + 414 + $(ProjectDir)../Libs/;$(AssemblySearchPaths) + $(ProjectDir)../Output/ + false + false + false + + + + false + None + true + prompt + 4 + true + false + + + + DEBUG + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/AquaMai.BepInEx/FodyWeavers.xml b/AquaMai.BepInEx/FodyWeavers.xml new file mode 100644 index 00000000..f254ee21 --- /dev/null +++ b/AquaMai.BepInEx/FodyWeavers.xml @@ -0,0 +1,6 @@ + + + + AquaMai.Common + + diff --git a/AquaMai.BepInEx/FodyWeavers.xsd b/AquaMai.BepInEx/FodyWeavers.xsd new file mode 100644 index 00000000..968f9b5e --- /dev/null +++ b/AquaMai.BepInEx/FodyWeavers.xsd @@ -0,0 +1,111 @@ + + + + + + + + + + + + A regular expression matching the assembly names to include in merging. + + + + + A regular expression matching the assembly names to exclude from merging. + + + + + A regular expression matching the resource names to include in merging. + + + + + A regular expression matching the resource names to exclude from merging. + + + + + A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true' + + + + + A string that is used as prefix for the namespace of the imported types. + + + + + A switch to control whether to import the full assemblies or only the referenced types. Default is 'false' + + + + + A switch to enable compacting of the target assembly by skipping properties, events and unused methods. Default is 'false' + + + + + + A regular expression matching the assembly names to include in merging. + + + + + A regular expression matching the assembly names to exclude from merging. + + + + + A regular expression matching the resource names to include in merging. + + + + + A regular expression matching the resource names to exclude from merging. + + + + + A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true' + + + + + A string that is used as prefix for the namespace of the imported types. + + + + + A switch to control whether to import the full assemblies or only the referenced types. Default is 'false' + + + + + A switch to enable compacting of the target assembly by skipping properties, events and unused methods. Default is 'false' + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/AquaMai.BepInEx/Plugin.cs b/AquaMai.BepInEx/Plugin.cs new file mode 100644 index 00000000..e9afbd2c --- /dev/null +++ b/AquaMai.BepInEx/Plugin.cs @@ -0,0 +1,41 @@ +using System.Reflection; +using AquaMai.Common; +using BepInEx; +using BepInEx.Logging; + +namespace AquaMai.BepInEx; + +[BepInPlugin(PluginName, BuildInfo.Name, BuildInfo.Version)] +public class Plugin : BaseUnityPlugin +{ + public const string PluginName = "net.aquadx.aquamai"; + + public readonly static ManualLogSource LogSource = global::BepInEx.Logging.Logger.CreateLogSource(BuildInfo.Name); + + private static bool _isInitialized = false; + + public void Awake() + { + if (_isInitialized) return; + _isInitialized = true; + + var harmony = new HarmonyLib.Harmony(PluginName); + + Common.AquaMai.Bootstrap(new BootstrapOptions + { + CurrentAssembly = Assembly.GetExecutingAssembly(), + Harmony = harmony, + MsgStringAction = LogSource.LogMessage, + MsgObjectAction = LogSource.LogMessage, + ErrorStringAction = LogSource.LogError, + ErrorObjectAction = LogSource.LogError, + WarningStringAction = LogSource.LogWarning, + WarningObjectAction = LogSource.LogWarning, + }); + } + + public void OnGUI() + { + Common.AquaMai.OnGUI(); + } +} diff --git a/AquaMai.BepInEx/Properties/AssemblyInfo.cs b/AquaMai.BepInEx/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..17aecbae --- /dev/null +++ b/AquaMai.BepInEx/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using AquaMai.Common; + +[assembly: AssemblyTitle(BuildInfo.Description)] +[assembly: AssemblyDescription(BuildInfo.Description)] +[assembly: AssemblyCompany(BuildInfo.Company)] +[assembly: AssemblyProduct(BuildInfo.Name)] +[assembly: AssemblyCopyright("Created by " + BuildInfo.Author)] +[assembly: AssemblyTrademark(BuildInfo.Company)] +[assembly: AssemblyVersion(BuildInfo.Version)] +[assembly: AssemblyFileVersion(BuildInfo.GitVersion)] diff --git a/AquaMai/AquaMai.csproj b/AquaMai.Common/AquaMai.Common.csproj similarity index 85% rename from AquaMai/AquaMai.csproj rename to AquaMai.Common/AquaMai.Common.csproj index f7886f5b..61506d19 100644 --- a/AquaMai/AquaMai.csproj +++ b/AquaMai.Common/AquaMai.Common.csproj @@ -4,9 +4,9 @@ Release AnyCPU Library - AquaMai - AquaMai - net472 + AquaMai.Common + AquaMai.Common + net48 512 true 12 @@ -33,6 +33,11 @@ + + + + + @@ -42,7 +47,6 @@ - @@ -56,6 +60,9 @@ + + MelonLoader.TinyJSON.dll + AquaMai.Config.Interfaces.dll diff --git a/AquaMai.Common/AquaMai.cs b/AquaMai.Common/AquaMai.cs new file mode 100644 index 00000000..65e81525 --- /dev/null +++ b/AquaMai.Common/AquaMai.cs @@ -0,0 +1,99 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using HarmonyLib; + +namespace AquaMai.Common; + +public class BootstrapOptions +{ + public required Assembly CurrentAssembly { get; set; } + public required Harmony Harmony { get; set; } + + public required Action MsgStringAction { get; set; } + public required Action MsgObjectAction { get; set; } + public required Action ErrorStringAction { get; set; } + public required Action ErrorObjectAction { get; set; } + public required Action WarningStringAction { get; set; } + public required Action WarningObjectAction { get; set; } +} + +public class AquaMai +{ + public const string AQUAMAI_SAY = """ + 如果你在 dnSpy / ILSpy 里看到了这行字,请从 resources 中解包 DLLs。 + If you see this line in dnSpy / ILSpy, please unpack the DLLs from resources. + """; + private static BootstrapOptions Options { get; set; } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleOutputCP(uint wCodePageID); + + private static void SetCoreBuildInfo(Assembly coreAssembly) + { + var coreBuildInfo = coreAssembly.GetType("AquaMai.Core.BuildInfo"); + var buildInfo = typeof(BuildInfo); + foreach (var field in buildInfo.GetFields()) + { + coreBuildInfo.GetField(field.Name)?.SetValue(null, field.GetValue(null)); + } + coreBuildInfo.GetField("ModAssembly")?.SetValue(null, Options.CurrentAssembly); + } + + private static void SetCoreLogger(Assembly coreAssembly) + { + var coreLogger = coreAssembly.GetType("AquaMai.Core.Environment.MelonLogger"); + coreLogger.GetField("MsgStringAction").SetValue(null, Options.MsgStringAction); + coreLogger.GetField("MsgObjectAction").SetValue(null, Options.MsgObjectAction); + coreLogger.GetField("ErrorStringAction").SetValue(null, Options.ErrorStringAction); + coreLogger.GetField("ErrorObjectAction").SetValue(null, Options.ErrorObjectAction); + coreLogger.GetField("WarningStringAction").SetValue(null, Options.WarningStringAction); + coreLogger.GetField("WarningObjectAction").SetValue(null, Options.WarningObjectAction); + } + + private static MethodInfo onGUIMethod; + + public static void EarlyStageLog(string message) + { + Options.MsgStringAction?.Invoke(message); + } + + public static void Bootstrap(BootstrapOptions options) + { + Options = options; + try + { + // Prevent Chinese characters from being garbled + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) SetConsoleOutputCP(65001); + + AssemblyLoader.LoadAssemblies(); + + var modsAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Mods); + var coreAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Core); + SetCoreBuildInfo(coreAssembly); + SetCoreLogger(coreAssembly); + var startupMethod = coreAssembly.GetType("AquaMai.Core.Startup") + .GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static); + onGUIMethod = coreAssembly.GetType("AquaMai.Core.Startup") + .GetMethod("OnGUI", BindingFlags.Public | BindingFlags.Static); + + startupMethod.Invoke(null, [modsAssembly, options.Harmony]); + } + catch (Exception ex) + { + Options.ErrorObjectAction?.Invoke(ex); + } + } + + public static void OnGUI() + { + try + { + onGUIMethod?.Invoke(null, []); + } + catch (Exception ex) + { + Options.ErrorObjectAction?.Invoke(ex); + } + } +} diff --git a/AquaMai/AssemblyLoader.cs b/AquaMai.Common/AssemblyLoader.cs similarity index 92% rename from AquaMai/AssemblyLoader.cs rename to AquaMai.Common/AssemblyLoader.cs index 85513061..da7ea33f 100644 --- a/AquaMai/AssemblyLoader.cs +++ b/AquaMai.Common/AssemblyLoader.cs @@ -4,12 +4,13 @@ using System.IO.Compression; using System.Reflection; -namespace AquaMai; +namespace AquaMai.Common; public static class AssemblyLoader { public enum AssemblyName { + TinyJSON, ConfigInterfaces, Config, Core, @@ -18,6 +19,7 @@ public enum AssemblyName private static readonly Dictionary Assemblies = new() { + [AssemblyName.TinyJSON] = "MelonLoader.TinyJSON.dll", [AssemblyName.ConfigInterfaces] = "AquaMai.Config.Interfaces.dll", [AssemblyName.Config] = "AquaMai.Config.dll", [AssemblyName.Core] = "AquaMai.Core.dll", @@ -33,7 +35,7 @@ public static void LoadAssemblies() foreach (var (assemblyName, assemblyFileName) in Assemblies) { # if DEBUG - MelonLoader.MelonLogger.Msg($"Loading assembly \"{assemblyFileName}\"..."); + AquaMai.EarlyStageLog($"Loading assembly \"{assemblyFileName}\"..."); # endif LoadedAssemblies[assemblyName] = LoadAssemblyFromResource(assemblyFileName); } diff --git a/AquaMai/BuildInfo.cs b/AquaMai.Common/BuildInfo.cs similarity index 91% rename from AquaMai/BuildInfo.cs rename to AquaMai.Common/BuildInfo.cs index 96d4d10b..011b89b4 100644 --- a/AquaMai/BuildInfo.cs +++ b/AquaMai.Common/BuildInfo.cs @@ -1,4 +1,4 @@ -namespace AquaMai; +namespace AquaMai.Common; public static partial class BuildInfo { diff --git a/AquaMai/configSort.yaml b/AquaMai.Common/configSort.yaml similarity index 100% rename from AquaMai/configSort.yaml rename to AquaMai.Common/configSort.yaml diff --git a/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj b/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj index ddf5b277..41f30921 100644 --- a/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj +++ b/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj @@ -6,7 +6,7 @@ Library AquaMai.Config.Interfaces AquaMai.Config.Interfaces - net472 + net48 512 true 12 diff --git a/AquaMai.Config/AquaMai.Config.csproj b/AquaMai.Config/AquaMai.Config.csproj index febb28f1..ca65944b 100644 --- a/AquaMai.Config/AquaMai.Config.csproj +++ b/AquaMai.Config/AquaMai.Config.csproj @@ -6,7 +6,7 @@ Library AquaMai.Config AquaMai.Config - net472 + net48 512 true 12 @@ -50,9 +50,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - all - + diff --git a/AquaMai.Core/AquaMai.Core.csproj b/AquaMai.Core/AquaMai.Core.csproj index 25ce5926..44612e4b 100644 --- a/AquaMai.Core/AquaMai.Core.csproj +++ b/AquaMai.Core/AquaMai.Core.csproj @@ -6,7 +6,7 @@ Library AquaMai.Core AquaMai.Core - net472 + net48 512 true 12 @@ -32,6 +32,7 @@ + @@ -41,7 +42,6 @@ - diff --git a/AquaMai.Core/BuildInfo.cs b/AquaMai.Core/BuildInfo.cs index 2132ea96..422fa0f1 100644 --- a/AquaMai.Core/BuildInfo.cs +++ b/AquaMai.Core/BuildInfo.cs @@ -1,4 +1,4 @@ -using MelonLoader; +using System.Reflection; namespace AquaMai.Core; @@ -16,6 +16,6 @@ public static class BuildInfo public static string BuildDate; public static string DownloadLink; - public static MelonAssembly ModAssembly; + public static Assembly ModAssembly; } diff --git a/AquaMai.Core/ConfigLoader.cs b/AquaMai.Core/ConfigLoader.cs index e87088fc..41b41122 100644 --- a/AquaMai.Core/ConfigLoader.cs +++ b/AquaMai.Core/ConfigLoader.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; -using MelonLoader; +using AquaMai.Core.Environment; using AquaMai.Config; using AquaMai.Config.Interfaces; using AquaMai.Config.Migration; diff --git a/AquaMai.Core/Environment/MelonLogger.cs b/AquaMai.Core/Environment/MelonLogger.cs new file mode 100644 index 00000000..f2ffaf6a --- /dev/null +++ b/AquaMai.Core/Environment/MelonLogger.cs @@ -0,0 +1,43 @@ +using System; + +namespace AquaMai.Core.Environment; + +public static class MelonLogger +{ + public static Action MsgStringAction = null; + public static Action MsgObjectAction = null; + public static Action ErrorStringAction = null; + public static Action ErrorObjectAction = null; + public static Action WarningStringAction = null; + public static Action WarningObjectAction = null; + + public static void Msg(string message) + { + MsgStringAction?.Invoke(message); + } + + public static void Msg(object message) + { + MsgObjectAction?.Invoke(message); + } + + public static void Error(string message) + { + ErrorStringAction?.Invoke(message); + } + + public static void Error(object message) + { + ErrorObjectAction?.Invoke(message); + } + + public static void Warning(string message) + { + WarningStringAction?.Invoke(message); + } + + public static void Warning(object message) + { + WarningObjectAction?.Invoke(message); + } +} diff --git a/AquaMai.Core/Helpers/EnableConditionHelper.cs b/AquaMai.Core/Helpers/EnableConditionHelper.cs index 475c24ae..9fead8cb 100644 --- a/AquaMai.Core/Helpers/EnableConditionHelper.cs +++ b/AquaMai.Core/Helpers/EnableConditionHelper.cs @@ -4,7 +4,7 @@ using AquaMai.Core.Attributes; using AquaMai.Core.Resources; using HarmonyLib; -using MelonLoader; +using AquaMai.Core.Environment; namespace AquaMai.Core.Helpers; diff --git a/AquaMai.Core/Helpers/FileSystem.cs b/AquaMai.Core/Helpers/FileSystem.cs index 3eab6f0b..f0608148 100644 --- a/AquaMai.Core/Helpers/FileSystem.cs +++ b/AquaMai.Core/Helpers/FileSystem.cs @@ -1,4 +1,3 @@ -using System; using System.IO; namespace AquaMai.Core.Helpers; @@ -7,9 +6,9 @@ public static class FileSystem { public static string ResolvePath(string path) { - var varExpanded = Environment.ExpandEnvironmentVariables(path); + var varExpanded = System.Environment.ExpandEnvironmentVariables(path); return Path.IsPathRooted(varExpanded) ? varExpanded - : Path.Combine(Environment.CurrentDirectory, varExpanded); + : Path.Combine(System.Environment.CurrentDirectory, varExpanded); } } diff --git a/AquaMai.Core/Helpers/KeyListener.cs b/AquaMai.Core/Helpers/KeyListener.cs index 0215dd4d..2b4c5657 100644 --- a/AquaMai.Core/Helpers/KeyListener.cs +++ b/AquaMai.Core/Helpers/KeyListener.cs @@ -5,7 +5,7 @@ using HarmonyLib; using Main; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; namespace AquaMai.Core.Helpers; diff --git a/AquaMai.Core/Helpers/MessageHelper.cs b/AquaMai.Core/Helpers/MessageHelper.cs index 6de142da..19e62f29 100644 --- a/AquaMai.Core/Helpers/MessageHelper.cs +++ b/AquaMai.Core/Helpers/MessageHelper.cs @@ -1,7 +1,7 @@ using DB; using HarmonyLib; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Process; using UnityEngine; @@ -39,4 +39,4 @@ public static void ShowMessage(string message, WindowSizeID size = WindowSizeID. sprite = sprite, }); } -} \ No newline at end of file +} diff --git a/AquaMai.Core/Helpers/NetPacketHook.cs b/AquaMai.Core/Helpers/NetPacketHook.cs index d20640d4..1643e7f4 100644 --- a/AquaMai.Core/Helpers/NetPacketHook.cs +++ b/AquaMai.Core/Helpers/NetPacketHook.cs @@ -2,7 +2,7 @@ using System.Text; using Net; using Net.Packet; -using MelonLoader; +using AquaMai.Core.Environment; using MelonLoader.TinyJSON; using HarmonyLib; using System.IO; diff --git a/AquaMai.Core/Helpers/Shim.cs b/AquaMai.Core/Helpers/Shim.cs index 17a90f3d..629bd5d7 100644 --- a/AquaMai.Core/Helpers/Shim.cs +++ b/AquaMai.Core/Helpers/Shim.cs @@ -7,7 +7,7 @@ using MAI2.Util; using Manager; using Manager.UserDatas; -using MelonLoader; +using AquaMai.Core.Environment; using Net; using Net.Packet; using Net.Packet.Mai2; diff --git a/AquaMai.Core/Startup.cs b/AquaMai.Core/Startup.cs index b6c5bf85..ba1ddf8b 100644 --- a/AquaMai.Core/Startup.cs +++ b/AquaMai.Core/Startup.cs @@ -6,7 +6,7 @@ using AquaMai.Core.Attributes; using AquaMai.Core.Helpers; using AquaMai.Core.Resources; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; namespace AquaMai.Core; @@ -15,8 +15,8 @@ public class Startup { private static HarmonyLib.Harmony _harmony; - private static bool _hasErrors; - + private static bool _hasErrors; + private static bool _uiInit; private enum ModLifecycleMethod @@ -203,11 +203,11 @@ public static void Initialize(Assembly modsAssembly, HarmonyLib.Harmony harmony) } public static void OnGUI() - { - if (!_uiInit) - { + { + if (!_uiInit) + { _uiInit = true; - GuiSizes.SetupStyles(); + GuiSizes.SetupStyles(); } } } diff --git a/AquaMai.ErrorReport/AquaMai.ErrorReport.csproj b/AquaMai.ErrorReport/AquaMai.ErrorReport.csproj index b2947ce4..51c489ac 100644 --- a/AquaMai.ErrorReport/AquaMai.ErrorReport.csproj +++ b/AquaMai.ErrorReport/AquaMai.ErrorReport.csproj @@ -2,7 +2,7 @@ WinExe - net472 + net48 enable true enable @@ -24,7 +24,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -33,7 +33,4 @@ - - - - \ No newline at end of file + diff --git a/AquaMai.MelonLoader/AquaMai.MelonLoader.csproj b/AquaMai.MelonLoader/AquaMai.MelonLoader.csproj new file mode 100644 index 00000000..2717711b --- /dev/null +++ b/AquaMai.MelonLoader/AquaMai.MelonLoader.csproj @@ -0,0 +1,58 @@ + + + + Release + AnyCPU + Library + AquaMai.MelonLoader + AquaMai.MelonLoader + AquaMai + net48 + 512 + true + 12 + 414 + $(ProjectDir)../Libs/;$(AssemblySearchPaths) + $(ProjectDir)../Output/ + false + false + false + + + + false + None + true + prompt + 4 + true + false + + + + DEBUG + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/AquaMai.MelonLoader/AquaMai.cs b/AquaMai.MelonLoader/AquaMai.cs new file mode 100644 index 00000000..c98623d0 --- /dev/null +++ b/AquaMai.MelonLoader/AquaMai.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using MelonLoader; + +namespace AquaMai.MelonLoader; + +public class AquaMai : MelonMod +{ + public override void OnInitializeMelon() + { + Common.AquaMai.Bootstrap(new Common.BootstrapOptions + { + CurrentAssembly = Assembly.GetExecutingAssembly(), + Harmony = HarmonyInstance, + MsgStringAction = MelonLogger.Msg, + MsgObjectAction = MelonLogger.Msg, + ErrorStringAction = MelonLogger.Error, + ErrorObjectAction = MelonLogger.Error, + WarningStringAction = MelonLogger.Warning, + WarningObjectAction = MelonLogger.Warning, + }); + } + + public override void OnGUI() + { + Common.AquaMai.OnGUI(); + } +} diff --git a/AquaMai.MelonLoader/FodyWeavers.xml b/AquaMai.MelonLoader/FodyWeavers.xml new file mode 100644 index 00000000..f254ee21 --- /dev/null +++ b/AquaMai.MelonLoader/FodyWeavers.xml @@ -0,0 +1,6 @@ + + + + AquaMai.Common + + diff --git a/AquaMai.MelonLoader/FodyWeavers.xsd b/AquaMai.MelonLoader/FodyWeavers.xsd new file mode 100644 index 00000000..968f9b5e --- /dev/null +++ b/AquaMai.MelonLoader/FodyWeavers.xsd @@ -0,0 +1,111 @@ + + + + + + + + + + + + A regular expression matching the assembly names to include in merging. + + + + + A regular expression matching the assembly names to exclude from merging. + + + + + A regular expression matching the resource names to include in merging. + + + + + A regular expression matching the resource names to exclude from merging. + + + + + A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true' + + + + + A string that is used as prefix for the namespace of the imported types. + + + + + A switch to control whether to import the full assemblies or only the referenced types. Default is 'false' + + + + + A switch to enable compacting of the target assembly by skipping properties, events and unused methods. Default is 'false' + + + + + + A regular expression matching the assembly names to include in merging. + + + + + A regular expression matching the assembly names to exclude from merging. + + + + + A regular expression matching the resource names to include in merging. + + + + + A regular expression matching the resource names to exclude from merging. + + + + + A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true' + + + + + A string that is used as prefix for the namespace of the imported types. + + + + + A switch to control whether to import the full assemblies or only the referenced types. Default is 'false' + + + + + A switch to enable compacting of the target assembly by skipping properties, events and unused methods. Default is 'false' + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/AquaMai.MelonLoader/Properties/AssemblyInfo.cs b/AquaMai.MelonLoader/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..95e31fd9 --- /dev/null +++ b/AquaMai.MelonLoader/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using AquaMai.Common; + +[assembly: AssemblyTitle(BuildInfo.Description)] +[assembly: AssemblyDescription(BuildInfo.Description)] +[assembly: AssemblyCompany(BuildInfo.Company)] +[assembly: AssemblyProduct(BuildInfo.Name)] +[assembly: AssemblyCopyright("Created by " + BuildInfo.Author)] +[assembly: AssemblyTrademark(BuildInfo.Company)] +[assembly: AssemblyVersion(BuildInfo.Version)] +[assembly: AssemblyFileVersion(BuildInfo.GitVersion)] +[assembly: MelonLoader.MelonInfo(typeof(AquaMai.MelonLoader.AquaMai), BuildInfo.Name, BuildInfo.GitVersion, BuildInfo.Author, BuildInfo.DownloadLink)] +[assembly: MelonLoader.MelonColor()] +[assembly: MelonLoader.HarmonyDontPatchAll] + +// Create and Setup a MelonGame Attribute to mark a Melon as Universal or Compatible with specific Games. +// If no MelonGame Attribute is found or any of the Values for any MelonGame Attribute on the Melon is null or empty it will be assumed the Melon is Universal. +// Values for MelonGame Attribute can be found in the Game's app.info file or printed at the top of every log directly beneath the Unity version. +[assembly: MelonLoader.MelonGame(null, null)] diff --git a/AquaMai.Mods/AquaMai.Mods.csproj b/AquaMai.Mods/AquaMai.Mods.csproj index 05cc17de..9405b427 100644 --- a/AquaMai.Mods/AquaMai.Mods.csproj +++ b/AquaMai.Mods/AquaMai.Mods.csproj @@ -6,7 +6,7 @@ Library AquaMai.Mods AquaMai.Mods - net472 + net48 512 true 12 @@ -32,6 +32,7 @@ + @@ -42,7 +43,6 @@ - @@ -130,11 +130,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + diff --git a/AquaMai.Mods/DeprecationWarning.cs b/AquaMai.Mods/DeprecationWarning.cs index 4afacde8..6f0c8e93 100644 --- a/AquaMai.Mods/DeprecationWarning.cs +++ b/AquaMai.Mods/DeprecationWarning.cs @@ -1,4 +1,5 @@ using AquaMai.Config.Attributes; +using AquaMai.Core.Environment; namespace AquaMai.Mods; @@ -23,7 +24,7 @@ public static void OnBeforeAllPatch() { if (v1_0_ModKeyMap_TestMode) { - MelonLoader.MelonLogger.Warning("ModKeyMap.TestMode has been deprecated (> v1.0). Please use GameSystem.KeyMap.Test instead."); + MelonLogger.Warning("ModKeyMap.TestMode has been deprecated (> v1.0). Please use GameSystem.KeyMap.Test instead."); } } } diff --git a/AquaMai.Mods/Enhancement/ServerAnnouncement.cs b/AquaMai.Mods/Enhancement/ServerAnnouncement.cs index 20ab399e..5afa16c7 100644 --- a/AquaMai.Mods/Enhancement/ServerAnnouncement.cs +++ b/AquaMai.Mods/Enhancement/ServerAnnouncement.cs @@ -6,7 +6,7 @@ using DB; using HarmonyLib; using JetBrains.Annotations; -using MelonLoader; +using AquaMai.Core.Environment; using MelonLoader.TinyJSON; using Process; using UnityEngine; @@ -134,4 +134,4 @@ private static void EntryProcessOnStart() if (_announcement == null || !_announcement.showOnUserLogin) return; MessageHelper.ShowMessage(_announcement.announcement, title: _announcement.title, sprite: _sprite, size: WindowSizeID.LargeVerticalPostImage); } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Enhancement/ServerNotice.cs b/AquaMai.Mods/Enhancement/ServerNotice.cs index 8c9b0766..59af62c9 100644 --- a/AquaMai.Mods/Enhancement/ServerNotice.cs +++ b/AquaMai.Mods/Enhancement/ServerNotice.cs @@ -3,7 +3,7 @@ using AquaMai.Core.Helpers; using AquaMai.Mods.Types; using JetBrains.Annotations; -using MelonLoader; +using AquaMai.Core.Environment; using MelonLoader.TinyJSON; namespace AquaMai.Mods.Enhancement; @@ -53,4 +53,4 @@ private static Variant OnNetPacketComplete(string api, Variant request, Variant MessageHelper.ShowMessage(entry.content, title: entry.title); return null; } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Enhancement/ServerResources.cs b/AquaMai.Mods/Enhancement/ServerResources.cs index 67bc2854..fd1e1a91 100644 --- a/AquaMai.Mods/Enhancement/ServerResources.cs +++ b/AquaMai.Mods/Enhancement/ServerResources.cs @@ -10,7 +10,7 @@ using AquaMai.Core.Helpers; using AquaMai.Mods.Types; using HarmonyLib; -using MelonLoader; +using AquaMai.Core.Environment; using MelonLoader.TinyJSON; using Monitor; using Process; @@ -239,4 +239,4 @@ private static bool VerifySignature(byte[] data, string signatureBase64) rsa.ImportParameters(param); return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Fancy/CustomButton.cs b/AquaMai.Mods/Fancy/CustomButton.cs index 2ebb25d5..4ad6281d 100644 --- a/AquaMai.Mods/Fancy/CustomButton.cs +++ b/AquaMai.Mods/Fancy/CustomButton.cs @@ -2,7 +2,7 @@ using AquaMai.Core.Helpers; using HarmonyLib; using System.Reflection; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; using System.Collections.Generic; using System.IO; @@ -116,4 +116,4 @@ public static void ButtonManagerInitializePostfix(ButtonManager __instance) } } catch (Exception e) {MelonLogger.Msg($"[CustomButton] {e}");} } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Fancy/CustomSkins.cs b/AquaMai.Mods/Fancy/CustomSkins.cs index 46f6f422..c4a41b05 100644 --- a/AquaMai.Mods/Fancy/CustomSkins.cs +++ b/AquaMai.Mods/Fancy/CustomSkins.cs @@ -3,7 +3,7 @@ using AquaMai.Config.Attributes; using AquaMai.Core.Helpers; using HarmonyLib; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Monitor.Game; using Process; diff --git a/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs b/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs index 0614c970..9d8c6fb5 100644 --- a/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs +++ b/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs @@ -8,7 +8,7 @@ using HarmonyLib; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using UnityEngine; using AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; diff --git a/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs b/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs index a7531e8a..92e1d38f 100644 --- a/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs +++ b/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs @@ -2,7 +2,7 @@ using System.Linq; using DB; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; diff --git a/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs b/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs index 1aaeb7a4..f42cef0d 100644 --- a/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs +++ b/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; -using MelonLoader; +using AquaMai.Core.Environment; namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; diff --git a/AquaMai.Mods/Fancy/RandomBgm.cs b/AquaMai.Mods/Fancy/RandomBgm.cs index bec48456..dda9f018 100644 --- a/AquaMai.Mods/Fancy/RandomBgm.cs +++ b/AquaMai.Mods/Fancy/RandomBgm.cs @@ -7,7 +7,7 @@ using Mai2.Mai2Cue; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; namespace AquaMai.Mods.Fancy; diff --git a/AquaMai.Mods/Fix/DebugFeature.cs b/AquaMai.Mods/Fix/DebugFeature.cs index 0005f805..04152446 100644 --- a/AquaMai.Mods/Fix/DebugFeature.cs +++ b/AquaMai.Mods/Fix/DebugFeature.cs @@ -5,7 +5,7 @@ using HarmonyLib; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Process; using UnityEngine; diff --git a/AquaMai.Mods/Fix/DisableReboot.cs b/AquaMai.Mods/Fix/DisableReboot.cs index a4fe6316..cf6716c4 100644 --- a/AquaMai.Mods/Fix/DisableReboot.cs +++ b/AquaMai.Mods/Fix/DisableReboot.cs @@ -4,7 +4,7 @@ using AquaMai.Core.Attributes; using HarmonyLib; using Manager.Operation; -using MelonLoader; +using AquaMai.Core.Environment; namespace AquaMai.Mods.Fix; diff --git a/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs b/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs index 916fb1d4..80cadb37 100644 --- a/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs +++ b/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs @@ -4,7 +4,7 @@ using HarmonyLib; using Manager; using Manager.MaiStudio; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Process; using Util; diff --git a/AquaMai.Mods/Fix/Stability/SanitizeUserData.cs b/AquaMai.Mods/Fix/Stability/SanitizeUserData.cs index 0ddae205..7056a26d 100644 --- a/AquaMai.Mods/Fix/Stability/SanitizeUserData.cs +++ b/AquaMai.Mods/Fix/Stability/SanitizeUserData.cs @@ -6,7 +6,7 @@ using AquaMai.Config.Attributes; using AquaMai.Core.Helpers; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using MelonLoader.TinyJSON; using Net.VO.Mai2; @@ -371,4 +371,4 @@ private static System.Type ResolveEnumType(string enumName) } return result; } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/GameSettings/TouchSensitivity.cs b/AquaMai.Mods/GameSettings/TouchSensitivity.cs index 181901cf..ce495965 100644 --- a/AquaMai.Mods/GameSettings/TouchSensitivity.cs +++ b/AquaMai.Mods/GameSettings/TouchSensitivity.cs @@ -4,7 +4,7 @@ using HarmonyLib; using IO; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; namespace AquaMai.Mods.GameSettings; diff --git a/AquaMai.Mods/GameSystem/AdxHidInput.cs b/AquaMai.Mods/GameSystem/AdxHidInput.cs index 1ea936cd..585d8465 100644 --- a/AquaMai.Mods/GameSystem/AdxHidInput.cs +++ b/AquaMai.Mods/GameSystem/AdxHidInput.cs @@ -7,7 +7,7 @@ using AquaMai.Config.Types; using HarmonyLib; using HidLibrary; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; namespace AquaMai.Mods.GameSystem; @@ -157,4 +157,4 @@ public static bool Prefix( return false; } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/GameSystem/Assets/Fonts.cs b/AquaMai.Mods/GameSystem/Assets/Fonts.cs index 9204915c..7b737b3f 100644 --- a/AquaMai.Mods/GameSystem/Assets/Fonts.cs +++ b/AquaMai.Mods/GameSystem/Assets/Fonts.cs @@ -3,7 +3,7 @@ using AquaMai.Config.Attributes; using AquaMai.Core.Helpers; using HarmonyLib; -using MelonLoader; +using AquaMai.Core.Environment; using TMPro; using UnityEngine; using UnityEngine.TextCore.LowLevel; diff --git a/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs b/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs index d1585815..f2d0e970 100644 --- a/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs +++ b/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs @@ -7,7 +7,7 @@ using System.Text.RegularExpressions; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using AquaMai.Config.Attributes; using AquaMai.Core.Helpers; diff --git a/AquaMai.Mods/GameSystem/Assets/MovieLoader.cs b/AquaMai.Mods/GameSystem/Assets/MovieLoader.cs index 14cc1267..bbe21e15 100644 --- a/AquaMai.Mods/GameSystem/Assets/MovieLoader.cs +++ b/AquaMai.Mods/GameSystem/Assets/MovieLoader.cs @@ -7,7 +7,7 @@ using HarmonyLib; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Monitor.Game; using UnityEngine; diff --git a/AquaMai.Mods/GameSystem/CustomCameraId.cs b/AquaMai.Mods/GameSystem/CustomCameraId.cs index 2bddfb54..1b5fcf8b 100644 --- a/AquaMai.Mods/GameSystem/CustomCameraId.cs +++ b/AquaMai.Mods/GameSystem/CustomCameraId.cs @@ -4,7 +4,7 @@ using System.Linq; using HarmonyLib; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; using AquaMai.Config.Attributes; using AquaMai.Core.Helpers; @@ -151,4 +151,4 @@ public static void OnBeforePatch() MelonLogger.Msg($"[CustomCameraId] {line}"); } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/GameSystem/DisableTimeout.cs b/AquaMai.Mods/GameSystem/DisableTimeout.cs index 0b628fac..9005bbde 100644 --- a/AquaMai.Mods/GameSystem/DisableTimeout.cs +++ b/AquaMai.Mods/GameSystem/DisableTimeout.cs @@ -4,7 +4,7 @@ using AquaMai.Core.Attributes; using HarmonyLib; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Process; using Process.Entry.State; @@ -128,4 +128,4 @@ public static void ContinueMonitorPlayContinue(PlayableDirector ____director) ____director.extrapolationMode = DirectorWrapMode.Hold; } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/GameSystem/KeyMap.cs b/AquaMai.Mods/GameSystem/KeyMap.cs index 6da8a52d..e8776648 100644 --- a/AquaMai.Mods/GameSystem/KeyMap.cs +++ b/AquaMai.Mods/GameSystem/KeyMap.cs @@ -47,7 +47,7 @@ You may want to configure IO4 emulation key mapping in segatools.ini's [io4] and public static readonly bool disableDebugInput = false; // Implemented in AquaMai.Mods/Fix/Common.cs [EnableIf(nameof(disableIO4))] - [HarmonyPatch("IO.Jvs+JvsSwitch", ".ctor", MethodType.Constructor, [typeof(int), typeof(string), typeof(KeyCode), typeof(bool), typeof(bool)])] + [HarmonyPatch("IO.Jvs+JvsSwitch, Assembly-CSharp", ".ctor", MethodType.Constructor, [typeof(int), typeof(string), typeof(KeyCode), typeof(bool), typeof(bool)])] [HarmonyPrefix] public static void PreJvsSwitchConstructor(ref bool invert) { diff --git a/AquaMai.Mods/GameSystem/SinglePlayer.ExteraMouseInput.cs b/AquaMai.Mods/GameSystem/SinglePlayer.ExteraMouseInput.cs index a1ac9ac2..16318009 100644 --- a/AquaMai.Mods/GameSystem/SinglePlayer.ExteraMouseInput.cs +++ b/AquaMai.Mods/GameSystem/SinglePlayer.ExteraMouseInput.cs @@ -1,4 +1,5 @@ using AquaMai.Config.Attributes; +using AquaMai.Core.Environment; using HarmonyLib; using System; using System.Collections; @@ -33,7 +34,7 @@ public static void MouseTouchPanelStart(MouseTouchPanel __instance) { return; } - MelonLoader.MelonLogger.Msg("ExteraMouseInput: Start"); + MelonLogger.Msg("ExteraMouseInput: Start"); leftCanvas = __instance.transform.parent.GetComponent(); UnityEngine.Object.Destroy(__instance.transform.parent.GetComponent()); MeshButtonRaycaster radiusRaycaster = __instance.transform.parent.gameObject.AddComponent(); diff --git a/AquaMai.Mods/GameSystem/SinglePlayer.cs b/AquaMai.Mods/GameSystem/SinglePlayer.cs index 29d0bcfb..e1120f4a 100644 --- a/AquaMai.Mods/GameSystem/SinglePlayer.cs +++ b/AquaMai.Mods/GameSystem/SinglePlayer.cs @@ -7,7 +7,7 @@ using HarmonyLib; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Monitor.Common; using Monitor.Entry; @@ -126,4 +126,4 @@ public static void OnAfterPatch() { Core.Helpers.GuiSizes.SinglePlayer = true; } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/GameSystem/Unlock.cs b/AquaMai.Mods/GameSystem/Unlock.cs index dfc1f5dc..1c94a9ec 100644 --- a/AquaMai.Mods/GameSystem/Unlock.cs +++ b/AquaMai.Mods/GameSystem/Unlock.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Reflection; using System.Linq; -using MelonLoader; +using AquaMai.Core.Environment; using System.Collections; using Manager.UserDatas; using Net.VO.Mai2; diff --git a/AquaMai.Mods/GameSystem/Window.cs b/AquaMai.Mods/GameSystem/Window.cs index 26b8066b..ffaa3661 100644 --- a/AquaMai.Mods/GameSystem/Window.cs +++ b/AquaMai.Mods/GameSystem/Window.cs @@ -59,6 +59,7 @@ public static void OnBeforePatch() Screen.SetResolution(width, height, FullScreenMode.Windowed); } + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; hwnd = GetWindowHandle(); if (alreadyWindowed) { @@ -84,6 +85,7 @@ public static void OnBeforePatch() public static void SetResizeable() { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; if (hwnd == IntPtr.Zero) return; if (borderless) { diff --git a/AquaMai.Mods/Tweaks/ResetTouch.cs b/AquaMai.Mods/Tweaks/ResetTouch.cs index 64c79d32..b6f751a0 100644 --- a/AquaMai.Mods/Tweaks/ResetTouch.cs +++ b/AquaMai.Mods/Tweaks/ResetTouch.cs @@ -1,5 +1,6 @@ using AquaMai.Config.Attributes; using AquaMai.Config.Types; +using AquaMai.Core.Environment; using AquaMai.Core.Helpers; using AquaMai.Core.Resources; using HarmonyLib; @@ -29,7 +30,7 @@ public static void ResultProcessOnStart() { if (!afterTrack) return; SingletonStateMachine.Instance.StartTouchPanel(); - MelonLoader.MelonLogger.Msg("[TouchResetAfterTrack] Touch panel reset"); + MelonLogger.Msg("[TouchResetAfterTrack] Touch panel reset"); } [HarmonyPostfix] @@ -40,4 +41,4 @@ public static void OnGameMainObjectUpdate() SingletonStateMachine.Instance.StartTouchPanel(); MessageHelper.ShowMessage(Locale.TouchPanelReset); } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/UX/DisableLightOutGame.cs b/AquaMai.Mods/UX/DisableLightOutGame.cs index 1a7b3932..19f27bc8 100644 --- a/AquaMai.Mods/UX/DisableLightOutGame.cs +++ b/AquaMai.Mods/UX/DisableLightOutGame.cs @@ -2,7 +2,7 @@ using HarmonyLib; using IO; using Mecha; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Process; @@ -49,4 +49,4 @@ public static void AdvertiseProcessStart() { MechaManager.SetAllCuOff(); } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/UX/HideSelfMadeCharts.cs b/AquaMai.Mods/UX/HideSelfMadeCharts.cs index 96f9c717..47f9f55e 100644 --- a/AquaMai.Mods/UX/HideSelfMadeCharts.cs +++ b/AquaMai.Mods/UX/HideSelfMadeCharts.cs @@ -10,7 +10,7 @@ using HarmonyLib; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Process; using Util; @@ -138,4 +138,4 @@ public static void EntryProcessOnStart(ref EntryProcess __instance) // reset status on login isShowSelfMadeCharts = !defaultHide; } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/UX/ImmediateSave.cs b/AquaMai.Mods/UX/ImmediateSave.cs index 92b35028..2d3f3a96 100644 --- a/AquaMai.Mods/UX/ImmediateSave.cs +++ b/AquaMai.Mods/UX/ImmediateSave.cs @@ -13,7 +13,7 @@ using Manager; using Manager.MaiStudio; using Manager.UserDatas; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor.Entry.Parts.Screens; using Net.Packet; using Net.Packet.Helper; @@ -249,4 +249,4 @@ public void OnGUI() GUI.Label(rect, Locale.SavingDontExit); } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/UX/OneKeyEntryEnd.cs b/AquaMai.Mods/UX/OneKeyEntryEnd.cs index 5b3e71b9..8c93da27 100644 --- a/AquaMai.Mods/UX/OneKeyEntryEnd.cs +++ b/AquaMai.Mods/UX/OneKeyEntryEnd.cs @@ -8,7 +8,7 @@ using Mai2.Mai2Cue; using Main; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Process; namespace AquaMai.Mods.UX; @@ -85,4 +85,4 @@ public static void DoQuickSkip() new MusicSelectProcess(SharedInstances.ProcessDataContainer))); } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/UX/OneKeyRetrySkip.cs b/AquaMai.Mods/UX/OneKeyRetrySkip.cs index 69ba0fc5..0b1b069c 100644 --- a/AquaMai.Mods/UX/OneKeyRetrySkip.cs +++ b/AquaMai.Mods/UX/OneKeyRetrySkip.cs @@ -4,7 +4,7 @@ using HarmonyLib; using MAI2.Util; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Process; namespace AquaMai.Mods.UX; @@ -66,4 +66,4 @@ public static void PostGameProcessUpdate(GameProcess __instance, Message[] ____m Singleton.Instance.SetQuickRetryFrag(flag: true); } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/UX/SelectionDetail.cs b/AquaMai.Mods/UX/SelectionDetail.cs index ef914c7a..ac045f5b 100644 --- a/AquaMai.Mods/UX/SelectionDetail.cs +++ b/AquaMai.Mods/UX/SelectionDetail.cs @@ -10,7 +10,7 @@ using Manager; using Manager.MaiStudio; using Manager.UserDatas; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Process; using UnityEngine; @@ -163,4 +163,4 @@ public void Close() Destroy(this); } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Utils/AntiLag.cs b/AquaMai.Mods/Utils/AntiLag.cs index 1266b254..1e206928 100644 --- a/AquaMai.Mods/Utils/AntiLag.cs +++ b/AquaMai.Mods/Utils/AntiLag.cs @@ -2,7 +2,7 @@ using AquaMai.Config.Attributes; using HarmonyLib; using Main; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; namespace AquaMai.Mods.Utils; @@ -41,7 +41,8 @@ private void OnTimer() #if DEBUG MelonLogger.Msg("[AntiLag] Trigger"); #endif + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; keybd_event(CTRL, 0, KEYEVENTF_KEYDOWN, 0); keybd_event(CTRL, 0, KEYEVENTF_KEYUP, 0); } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Utils/DisplayTouchInGame.cs b/AquaMai.Mods/Utils/DisplayTouchInGame.cs index 611c126f..45c1ccfa 100644 --- a/AquaMai.Mods/Utils/DisplayTouchInGame.cs +++ b/AquaMai.Mods/Utils/DisplayTouchInGame.cs @@ -7,7 +7,7 @@ using AquaMai.Mods.UX.PracticeMode; using HarmonyLib; using Manager; -using MelonLoader; +using AquaMai.Core.Environment; using Monitor; using Process; using TMPro; @@ -197,4 +197,4 @@ private void OnGUI() } } } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Utils/LogNetworkErrors.cs b/AquaMai.Mods/Utils/LogNetworkErrors.cs index 73f59923..4e66ba4a 100644 --- a/AquaMai.Mods/Utils/LogNetworkErrors.cs +++ b/AquaMai.Mods/Utils/LogNetworkErrors.cs @@ -3,7 +3,7 @@ using HarmonyLib; using Manager; using Manager.Operation; -using MelonLoader; +using AquaMai.Core.Environment; using Net.Packet; namespace AquaMai.Mods.Utils; diff --git a/AquaMai.Mods/Utils/LogNetworkRequests.cs b/AquaMai.Mods/Utils/LogNetworkRequests.cs index dfee58c3..7cb4f3ca 100644 --- a/AquaMai.Mods/Utils/LogNetworkRequests.cs +++ b/AquaMai.Mods/Utils/LogNetworkRequests.cs @@ -6,7 +6,7 @@ using System.Text; using Net; using Net.Packet; -using MelonLoader; +using AquaMai.Core.Environment; using MelonLoader.TinyJSON; using HarmonyLib; using AquaMai.Core.Attributes; diff --git a/AquaMai.Mods/Utils/LogUnity.cs b/AquaMai.Mods/Utils/LogUnity.cs index 3826feba..f1d8e236 100644 --- a/AquaMai.Mods/Utils/LogUnity.cs +++ b/AquaMai.Mods/Utils/LogUnity.cs @@ -1,5 +1,5 @@ using AquaMai.Config.Attributes; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; namespace AquaMai.Mods.Utils; @@ -20,4 +20,4 @@ private static void Log(string msg, string stackTrace, LogType type) MelonLogger.Msg("[Unity] " + msg); MelonLogger.Msg("[Unity] " + stackTrace); } -} \ No newline at end of file +} diff --git a/AquaMai.Mods/Utils/LogUserId.cs b/AquaMai.Mods/Utils/LogUserId.cs index ff1fc73b..674cd37e 100644 --- a/AquaMai.Mods/Utils/LogUserId.cs +++ b/AquaMai.Mods/Utils/LogUserId.cs @@ -1,7 +1,7 @@ using System; using AquaMai.Config.Attributes; using HarmonyLib; -using MelonLoader; +using AquaMai.Core.Environment; using Net.Packet; using Net.Packet.Mai2; using Net.VO.Mai2; diff --git a/AquaMai.Mods/Utils/ShowErrorLog.cs b/AquaMai.Mods/Utils/ShowErrorLog.cs index 2f359248..c42dcb85 100644 --- a/AquaMai.Mods/Utils/ShowErrorLog.cs +++ b/AquaMai.Mods/Utils/ShowErrorLog.cs @@ -9,7 +9,7 @@ using AquaMai.Core.Helpers; using HarmonyLib; using Main; -using MelonLoader; +using AquaMai.Core.Environment; using UnityEngine; using BuildInfo = AquaMai.Core.BuildInfo; @@ -87,13 +87,13 @@ public static void ApplicationOnQuittingNew() private static Stream GetErrorReporterStream() { - var s = BuildInfo.ModAssembly.Assembly.GetManifestResourceStream("AquaMai.ErrorReport.exe"); + var s = BuildInfo.ModAssembly.GetManifestResourceStream("AquaMai.ErrorReport.exe"); if (s != null) { return s; } - s = BuildInfo.ModAssembly.Assembly.GetManifestResourceStream("AquaMai.ErrorReport.exe.compressed"); + s = BuildInfo.ModAssembly.GetManifestResourceStream("AquaMai.ErrorReport.exe.compressed"); return new DeflateStream(s, CompressionMode.Decompress); } @@ -144,4 +144,4 @@ public IEnumerator Show() } } } -} \ No newline at end of file +} diff --git a/AquaMai.slnx b/AquaMai.slnx index 3606fa05..a652793e 100644 --- a/AquaMai.slnx +++ b/AquaMai.slnx @@ -9,6 +9,10 @@ - + + + - \ No newline at end of file + + + diff --git a/AquaMai/Main.cs b/AquaMai/Main.cs deleted file mode 100644 index fe0befc2..00000000 --- a/AquaMai/Main.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using MelonLoader; - -namespace AquaMai; - -public class AquaMai : MelonMod -{ - public const string AQUAMAI_SAY = """ - 如果你在 dnSpy / ILSpy 里看到了这行字,请从 resources 中解包 DLLs。 - If you see this line in dnSpy / ILSpy, please unpack the DLLs from resources. - """; - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleOutputCP(uint wCodePageID); - - private void SetCoreBuildInfo(Assembly coreAssembly) - { - var coreBuildInfo = coreAssembly.GetType("AquaMai.Core.BuildInfo"); - var buildInfo = typeof(BuildInfo); - foreach (var field in buildInfo.GetFields()) - { - coreBuildInfo.GetField(field.Name)?.SetValue(null, field.GetValue(null)); - } - coreBuildInfo.GetField("ModAssembly")?.SetValue(null, MelonAssembly); - } - - private static MethodInfo onGUIMethod; - - public override void OnInitializeMelon() - { - // Prevent Chinese characters from being garbled - SetConsoleOutputCP(65001); - - AssemblyLoader.LoadAssemblies(); - - var modsAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Mods); - var coreAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Core); - SetCoreBuildInfo(coreAssembly); - coreAssembly.GetType("AquaMai.Core.Startup") - .GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static) - .Invoke(null, [modsAssembly, HarmonyInstance]); - onGUIMethod = coreAssembly.GetType("AquaMai.Core.Startup") - .GetMethod("OnGUI", BindingFlags.Public | BindingFlags.Static); - } - - public override void OnGUI() - { - base.OnGUI(); - onGUIMethod?.Invoke(null, []); - } -} diff --git a/AquaMai/Properties/AssemblyInfo.cs b/AquaMai/Properties/AssemblyInfo.cs deleted file mode 100644 index 08009822..00000000 --- a/AquaMai/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using MelonLoader; - -[assembly: AssemblyTitle(AquaMai.BuildInfo.Description)] -[assembly: AssemblyDescription(AquaMai.BuildInfo.Description)] -[assembly: AssemblyCompany(AquaMai.BuildInfo.Company)] -[assembly: AssemblyProduct(AquaMai.BuildInfo.Name)] -[assembly: AssemblyCopyright("Created by " + AquaMai.BuildInfo.Author)] -[assembly: AssemblyTrademark(AquaMai.BuildInfo.Company)] -[assembly: AssemblyVersion(AquaMai.BuildInfo.Version)] -[assembly: AssemblyFileVersion(AquaMai.BuildInfo.GitVersion)] -[assembly: MelonInfo(typeof(AquaMai.AquaMai), AquaMai.BuildInfo.Name, AquaMai.BuildInfo.GitVersion, AquaMai.BuildInfo.Author, AquaMai.BuildInfo.DownloadLink)] -[assembly: MelonColor()] -[assembly: HarmonyDontPatchAll] - -// Create and Setup a MelonGame Attribute to mark a Melon as Universal or Compatible with specific Games. -// If no MelonGame Attribute is found or any of the Values for any MelonGame Attribute on the Melon is null or empty it will be assumed the Melon is Universal. -// Values for MelonGame Attribute can be found in the Game's app.info file or printed at the top of every log directly beneath the Unity version. -[assembly: MelonGame(null, null)] diff --git a/Libs/0Harmony.dll b/Libs/0Harmony.dll index 0b9bdf1d..99daef72 100644 Binary files a/Libs/0Harmony.dll and b/Libs/0Harmony.dll differ diff --git a/Libs/BepInEx.Harmony.dll b/Libs/BepInEx.Harmony.dll new file mode 100644 index 00000000..5ee21c93 Binary files /dev/null and b/Libs/BepInEx.Harmony.dll differ diff --git a/Libs/BepInEx.dll b/Libs/BepInEx.dll new file mode 100644 index 00000000..2fad6328 Binary files /dev/null and b/Libs/BepInEx.dll differ diff --git a/MelonLoader.TinyJSON/Decoder.cs b/MelonLoader.TinyJSON/Decoder.cs new file mode 100644 index 00000000..5f433c2e --- /dev/null +++ b/MelonLoader.TinyJSON/Decoder.cs @@ -0,0 +1,392 @@ +using System.IO; +using System.Text; +using System; + +namespace MelonLoader.TinyJSON +{ + public sealed class Decoder : IDisposable + { + const string whiteSpace = " \t\n\r"; + const string wordBreak = " \t\n\r{}[],:\""; + + enum Token + { + None, + OpenBrace, + CloseBrace, + OpenBracket, + CloseBracket, + Colon, + Comma, + String, + Number, + True, + False, + Null + } + + StringReader json; + + + Decoder( string jsonString ) + { + json = new StringReader( jsonString ); + } + + + public static Variant Decode( string jsonString ) + { + using (var instance = new Decoder( jsonString )) + { + return instance.DecodeValue(); + } + } + + + public void Dispose() + { + json.Dispose(); + json = null; + } + + + ProxyObject DecodeObject() + { + var proxy = new ProxyObject(); + + // Ditch opening brace. + json.Read(); + + // { + while (true) + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (NextToken) + { + case Token.None: + return null; + + case Token.Comma: + continue; + + case Token.CloseBrace: + return proxy; + + default: + // Key + string key = DecodeString(); + if (key == null) + { + return null; + } + + // : + if (NextToken != Token.Colon) + { + return null; + } + + json.Read(); + + // Value + proxy.Add( key, DecodeValue() ); + break; + } + } + } + + + ProxyArray DecodeArray() + { + var proxy = new ProxyArray(); + + // Ditch opening bracket. + json.Read(); + + // [ + var parsing = true; + while (parsing) + { + var nextToken = NextToken; + + // ReSharper disable once SwitchStatementMissingSomeCases + switch (nextToken) + { + case Token.None: + return null; + + case Token.Comma: + continue; + + case Token.CloseBracket: + parsing = false; + break; + + default: + proxy.Add( DecodeByToken( nextToken ) ); + break; + } + } + + return proxy; + } + + + Variant DecodeValue() + { + var nextToken = NextToken; + return DecodeByToken( nextToken ); + } + + + Variant DecodeByToken( Token token ) + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (token) + { + case Token.String: + return DecodeString(); + + case Token.Number: + return DecodeNumber(); + + case Token.OpenBrace: + return DecodeObject(); + + case Token.OpenBracket: + return DecodeArray(); + + case Token.True: + return new ProxyBoolean( true ); + + case Token.False: + return new ProxyBoolean( false ); + + case Token.Null: + return null; + + default: + return null; + } + } + + + Variant DecodeString() + { + var stringBuilder = new StringBuilder(); + + // ditch opening quote + json.Read(); + + var parsing = true; + while (parsing) + { + if (json.Peek() == -1) + { + // ReSharper disable once RedundantAssignment + parsing = false; + break; + } + + var c = NextChar; + switch (c) + { + case '"': + parsing = false; + break; + + case '\\': + if (json.Peek() == -1) + { + parsing = false; + break; + } + + c = NextChar; + + // ReSharper disable once SwitchStatementMissingSomeCases + switch (c) + { + case '"': + case '\\': + case '/': + stringBuilder.Append( c ); + break; + + case 'b': + stringBuilder.Append( '\b' ); + break; + + case 'f': + stringBuilder.Append( '\f' ); + break; + + case 'n': + stringBuilder.Append( '\n' ); + break; + + case 'r': + stringBuilder.Append( '\r' ); + break; + + case 't': + stringBuilder.Append( '\t' ); + break; + + case 'u': + var hex = new StringBuilder(); + + for (var i = 0; i < 4; i++) + { + hex.Append( NextChar ); + } + + stringBuilder.Append( (char) Convert.ToInt32( hex.ToString(), 16 ) ); + break; + + //default: + // throw new DecodeException( @"Illegal character following escape character: " + c ); + } + + break; + + default: + stringBuilder.Append( c ); + break; + } + } + + return new ProxyString( stringBuilder.ToString() ); + } + + + Variant DecodeNumber() + { + return new ProxyNumber( NextWord ); + } + + + void ConsumeWhiteSpace() + { + while (whiteSpace.IndexOf( PeekChar ) != -1) + { + json.Read(); + + if (json.Peek() == -1) + { + break; + } + } + } + + + char PeekChar + { + get + { + var peek = json.Peek(); + return peek == -1 ? '\0' : Convert.ToChar( peek ); + } + } + + + char NextChar + { + get + { + return Convert.ToChar( json.Read() ); + } + } + + + string NextWord + { + get + { + var word = new StringBuilder(); + + while (wordBreak.IndexOf( PeekChar ) == -1) + { + word.Append( NextChar ); + + if (json.Peek() == -1) + { + break; + } + } + + return word.ToString(); + } + } + + + Token NextToken + { + get + { + ConsumeWhiteSpace(); + + if (json.Peek() == -1) + { + return Token.None; + } + + // ReSharper disable once SwitchStatementMissingSomeCases + switch (PeekChar) + { + case '{': + return Token.OpenBrace; + + case '}': + json.Read(); + return Token.CloseBrace; + + case '[': + return Token.OpenBracket; + + case ']': + json.Read(); + return Token.CloseBracket; + + case ',': + json.Read(); + return Token.Comma; + + case '"': + return Token.String; + + case ':': + return Token.Colon; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + } + + // ReSharper disable once SwitchStatementMissingSomeCases + switch (NextWord) + { + case "false": + return Token.False; + + case "true": + return Token.True; + + case "null": + return Token.Null; + } + + return Token.None; + } + } + } +} diff --git a/MelonLoader.TinyJSON/EncodeOptions.cs b/MelonLoader.TinyJSON/EncodeOptions.cs new file mode 100644 index 00000000..b2fbaad2 --- /dev/null +++ b/MelonLoader.TinyJSON/EncodeOptions.cs @@ -0,0 +1,17 @@ +using System; + +namespace MelonLoader.TinyJSON +{ + [Flags] + public enum EncodeOptions + { + None = 0, + PrettyPrint = 1, + NoTypeHints = 2, + IncludePublicProperties = 4, + EnforceHierarchyOrder = 8, + + [Obsolete( "Use EncodeOptions.EnforceHierarchyOrder instead." )] + EnforceHeirarchyOrder = EnforceHierarchyOrder + } +} diff --git a/MelonLoader.TinyJSON/Encoder.cs b/MelonLoader.TinyJSON/Encoder.cs new file mode 100644 index 00000000..ed13201f --- /dev/null +++ b/MelonLoader.TinyJSON/Encoder.cs @@ -0,0 +1,611 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; + +namespace MelonLoader.TinyJSON +{ + public sealed class Encoder + { + static readonly Type includeAttrType = typeof(Include); + static readonly Type excludeAttrType = typeof(Exclude); + static readonly Type typeHintAttrType = typeof(TypeHint); + + readonly StringBuilder builder; + readonly EncodeOptions options; + int indent; + + + Encoder( EncodeOptions options ) + { + this.options = options; + builder = new StringBuilder(); + indent = 0; + } + + + // ReSharper disable once UnusedMember.Global + public static string Encode( object obj ) + { + return Encode( obj, EncodeOptions.None ); + } + + + public static string Encode( object obj, EncodeOptions options ) + { + var instance = new Encoder( options ); + instance.EncodeValue( obj, false ); + return instance.builder.ToString(); + } + + + bool PrettyPrintEnabled + { + get + { + return (options & EncodeOptions.PrettyPrint) == EncodeOptions.PrettyPrint; + } + } + + + bool TypeHintsEnabled + { + get + { + return (options & EncodeOptions.NoTypeHints) != EncodeOptions.NoTypeHints; + } + } + + + bool IncludePublicPropertiesEnabled + { + get + { + return (options & EncodeOptions.IncludePublicProperties) == EncodeOptions.IncludePublicProperties; + } + } + + + bool EnforceHierarchyOrderEnabled + { + get + { + return (options & EncodeOptions.EnforceHierarchyOrder) == EncodeOptions.EnforceHierarchyOrder; + } + } + + + void EncodeValue( object value, bool forceTypeHint ) + { + if (value == null) + { + builder.Append( "null" ); + return; + } + + if (value is string) + { + EncodeString( (string) value ); + return; + } + + if (value is ProxyString) + { + EncodeString( ((ProxyString) value).ToString( CultureInfo.InvariantCulture ) ); + return; + } + + if (value is char) + { + EncodeString( value.ToString() ); + return; + } + + if (value is bool) + { + builder.Append( (bool) value ? "true" : "false" ); + return; + } + + if (value is Enum) + { + EncodeString( value.ToString() ); + return; + } + + if (value is Array) + { + EncodeArray( (Array) value, forceTypeHint ); + return; + } + + if (value is IList) + { + EncodeList( (IList) value, forceTypeHint ); + return; + } + + if (value is IDictionary) + { + EncodeDictionary( (IDictionary) value, forceTypeHint ); + return; + } + + if (value is Guid) + { + EncodeString( value.ToString() ); + return; + } + + if (value is ProxyArray) + { + EncodeProxyArray( (ProxyArray) value ); + return; + } + + if (value is ProxyObject) + { + EncodeProxyObject( (ProxyObject) value ); + return; + } + + if (value is float || + value is double || + value is int || + value is uint || + value is long || + value is sbyte || + value is byte || + value is short || + value is ushort || + value is ulong || + value is decimal || + value is ProxyBoolean || + value is ProxyNumber) + { + builder.Append( Convert.ToString( value, CultureInfo.InvariantCulture ) ); + return; + } + + EncodeObject( value, forceTypeHint ); + } + + + IEnumerable GetFieldsForType( Type type ) + { + if (EnforceHierarchyOrderEnabled) + { + var types = new Stack(); + while (type != null) + { + types.Push( type ); + type = type.BaseType; + } + + var fields = new List(); + while (types.Count > 0) + { + fields.AddRange( types.Pop().GetFields( BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) ); + } + + return fields; + } + + return type.GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); + } + + + IEnumerable GetPropertiesForType( Type type ) + { + if (EnforceHierarchyOrderEnabled) + { + var types = new Stack(); + while (type != null) + { + types.Push( type ); + type = type.BaseType; + } + + var properties = new List(); + while (types.Count > 0) + { + properties.AddRange( types.Pop().GetProperties( BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) ); + } + + return properties; + } + + return type.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); + } + + + void EncodeObject( object value, bool forceTypeHint ) + { + var type = value.GetType(); + + AppendOpenBrace(); + + forceTypeHint = forceTypeHint || TypeHintsEnabled; + + var includePublicProperties = IncludePublicPropertiesEnabled; + + var firstItem = !forceTypeHint; + if (forceTypeHint) + { + if (PrettyPrintEnabled) + { + AppendIndent(); + } + + EncodeString( ProxyObject.TypeHintKey ); + AppendColon(); + EncodeString( type.FullName ); + + // ReSharper disable once RedundantAssignment + firstItem = false; + } + + foreach (var field in GetFieldsForType( type )) + { + var shouldTypeHint = false; + var shouldEncode = field.IsPublic; + foreach (var attribute in field.GetCustomAttributes( true )) + { + if (excludeAttrType.IsInstanceOfType( attribute )) + { + shouldEncode = false; + } + + if (includeAttrType.IsInstanceOfType( attribute )) + { + shouldEncode = true; + } + + if (typeHintAttrType.IsInstanceOfType( attribute )) + { + shouldTypeHint = true; + } + } + + if (shouldEncode) + { + AppendComma( firstItem ); + EncodeString( field.Name ); + AppendColon(); + EncodeValue( field.GetValue( value ), shouldTypeHint ); + firstItem = false; + } + } + + foreach (var property in GetPropertiesForType( type )) + { + if (property.CanRead) + { + var shouldTypeHint = false; + var shouldEncode = includePublicProperties; + + foreach (var attribute in property.GetCustomAttributes( true )) + { + if (excludeAttrType.IsInstanceOfType( attribute )) + { + shouldEncode = false; + } + + if (includeAttrType.IsInstanceOfType( attribute )) + { + shouldEncode = true; + } + + if (typeHintAttrType.IsInstanceOfType( attribute )) + { + shouldTypeHint = true; + } + } + + if (shouldEncode) + { + AppendComma( firstItem ); + EncodeString( property.Name ); + AppendColon(); + EncodeValue( property.GetValue( value, null ), shouldTypeHint ); + firstItem = false; + } + } + } + + AppendCloseBrace(); + } + + + void EncodeProxyArray( ProxyArray value ) + { + if (value.Count == 0) + { + builder.Append( "[]" ); + } + else + { + AppendOpenBracket(); + + var firstItem = true; + foreach (var obj in value) + { + AppendComma( firstItem ); + EncodeValue( obj, false ); + firstItem = false; + } + + AppendCloseBracket(); + } + } + + + void EncodeProxyObject( ProxyObject value ) + { + if (value.Count == 0) + { + builder.Append( "{}" ); + } + else + { + AppendOpenBrace(); + + var firstItem = true; + foreach (var e in value.Keys) + { + AppendComma( firstItem ); + EncodeString( e ); + AppendColon(); + EncodeValue( value[e], false ); + firstItem = false; + } + + AppendCloseBrace(); + } + } + + + void EncodeDictionary( IDictionary value, bool forceTypeHint ) + { + if (value.Count == 0) + { + builder.Append( "{}" ); + } + else + { + AppendOpenBrace(); + + var firstItem = true; + foreach (var e in value.Keys) + { + AppendComma( firstItem ); + EncodeString( e.ToString() ); + AppendColon(); + EncodeValue( value[e], forceTypeHint ); + firstItem = false; + } + + AppendCloseBrace(); + } + } + + + // ReSharper disable once SuggestBaseTypeForParameter + void EncodeList( IList value, bool forceTypeHint ) + { + if (value.Count == 0) + { + builder.Append( "[]" ); + } + else + { + AppendOpenBracket(); + + var firstItem = true; + foreach (var obj in value) + { + AppendComma( firstItem ); + EncodeValue( obj, forceTypeHint ); + firstItem = false; + } + + AppendCloseBracket(); + } + } + + + void EncodeArray( Array value, bool forceTypeHint ) + { + if (value.Rank == 1) + { + EncodeList( value, forceTypeHint ); + } + else + { + var indices = new int[value.Rank]; + EncodeArrayRank( value, 0, indices, forceTypeHint ); + } + } + + + void EncodeArrayRank( Array value, int rank, int[] indices, bool forceTypeHint ) + { + AppendOpenBracket(); + + var min = value.GetLowerBound( rank ); + var max = value.GetUpperBound( rank ); + + if (rank == value.Rank - 1) + { + for (var i = min; i <= max; i++) + { + indices[rank] = i; + AppendComma( i == min ); + EncodeValue( value.GetValue( indices ), forceTypeHint ); + } + } + else + { + for (var i = min; i <= max; i++) + { + indices[rank] = i; + AppendComma( i == min ); + EncodeArrayRank( value, rank + 1, indices, forceTypeHint ); + } + } + + AppendCloseBracket(); + } + + + void EncodeString( string value ) + { + builder.Append( '\"' ); + + var charArray = value.ToCharArray(); + foreach (var c in charArray) + { + switch (c) + { + case '"': + builder.Append( "\\\"" ); + break; + + case '\\': + builder.Append( "\\\\" ); + break; + + case '\b': + builder.Append( "\\b" ); + break; + + case '\f': + builder.Append( "\\f" ); + break; + + case '\n': + builder.Append( "\\n" ); + break; + + case '\r': + builder.Append( "\\r" ); + break; + + case '\t': + builder.Append( "\\t" ); + break; + + default: + var codepoint = Convert.ToInt32( c ); + if ((codepoint >= 32) && (codepoint <= 126)) + { + builder.Append( c ); + } + else + { + builder.Append( "\\u" + Convert.ToString( codepoint, 16 ).PadLeft( 4, '0' ) ); + } + + break; + } + } + + builder.Append( '\"' ); + } + + + #region Helpers + + void AppendIndent() + { + for (var i = 0; i < indent; i++) + { + builder.Append( '\t' ); + } + } + + + void AppendOpenBrace() + { + builder.Append( '{' ); + + if (PrettyPrintEnabled) + { + builder.Append( '\n' ); + indent++; + } + } + + + void AppendCloseBrace() + { + if (PrettyPrintEnabled) + { + builder.Append( '\n' ); + indent--; + AppendIndent(); + } + + builder.Append( '}' ); + } + + + void AppendOpenBracket() + { + builder.Append( '[' ); + + if (PrettyPrintEnabled) + { + builder.Append( '\n' ); + indent++; + } + } + + + void AppendCloseBracket() + { + if (PrettyPrintEnabled) + { + builder.Append( '\n' ); + indent--; + AppendIndent(); + } + + builder.Append( ']' ); + } + + + void AppendComma( bool firstItem ) + { + if (!firstItem) + { + builder.Append( ',' ); + + if (PrettyPrintEnabled) + { + builder.Append( '\n' ); + } + } + + if (PrettyPrintEnabled) + { + AppendIndent(); + } + } + + + void AppendColon() + { + builder.Append( ':' ); + + if (PrettyPrintEnabled) + { + builder.Append( ' ' ); + } + } + + #endregion + } +} diff --git a/MelonLoader.TinyJSON/Extensions.cs b/MelonLoader.TinyJSON/Extensions.cs new file mode 100644 index 00000000..de57daeb --- /dev/null +++ b/MelonLoader.TinyJSON/Extensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace MelonLoader.TinyJSON +{ + public static class Extensions + { + public static bool AnyOfType( this IEnumerable source, Type expectedType ) + { + if (source == null) + { + throw new ArgumentNullException( "source" ); + } + + if (expectedType == null) + { + throw new ArgumentNullException( "expectedType" ); + } + + foreach (var item in source) + { + if (expectedType.IsInstanceOfType( item )) + { + return true; + } + } + + return false; + } + } +} diff --git a/MelonLoader.TinyJSON/JSON.cs b/MelonLoader.TinyJSON/JSON.cs new file mode 100644 index 00000000..05f27fc1 --- /dev/null +++ b/MelonLoader.TinyJSON/JSON.cs @@ -0,0 +1,661 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; + +namespace MelonLoader.TinyJSON +{ + /// + /// Mark members that should be included. + /// Public fields are included by default. + /// + [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )] + public sealed class Include : Attribute {} + + + /// + /// Mark members that should be excluded. + /// Private fields and all properties are excluded by default. + /// + [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )] + public class Exclude : Attribute {} + + + /// + /// Mark methods to be called after an object is decoded. + /// + [AttributeUsage( AttributeTargets.Method )] + public class AfterDecode : Attribute {} + + + /// + /// Mark methods to be called before an object is encoded. + /// + [AttributeUsage( AttributeTargets.Method )] + public class BeforeEncode : Attribute {} + + + /// + /// Mark members to force type hinting even when EncodeOptions.NoTypeHints is set. + /// + [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )] + public class TypeHint : Attribute {} + + + /// + /// Provide field and property aliases when an object is decoded. + /// If a field or property is not found while decoding, this list will be searched for a matching alias. + /// + [AttributeUsage( AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true )] + public class DecodeAlias : Attribute + { + public string[] Names { get; private set; } + + + public DecodeAlias( params string[] names ) + { + Names = names; + } + + + public bool Contains( string name ) + { + return Array.IndexOf( Names, name ) > -1; + } + } + + + [Obsolete( "Use the Exclude attribute instead." )] + // ReSharper disable once UnusedMember.Global + public sealed class Skip : Exclude {} + + + [Obsolete( "Use the AfterDecode attribute instead." )] + // ReSharper disable once UnusedMember.Global + public sealed class Load : AfterDecode {} + + + public sealed class DecodeException : Exception + { + public DecodeException( string message ) + : base( message ) {} + + + public DecodeException( string message, Exception innerException ) + : base( message, innerException ) {} + } + + + // ReSharper disable once InconsistentNaming + public static class JSON + { + static readonly Type includeAttrType = typeof(Include); + static readonly Type excludeAttrType = typeof(Exclude); + static readonly Type decodeAliasAttrType = typeof(DecodeAlias); + + + public static Variant Load( string json ) + { + if (string.IsNullOrEmpty(json)) + { + throw new ArgumentNullException( "json" ); + } + + return Decoder.Decode( json ); + } + + + public static string Dump( object data ) + { + return Dump( data, EncodeOptions.None ); + } + + + public static string Dump( object data, EncodeOptions options ) + { + // Invoke methods tagged with [BeforeEncode] attribute. + if (data != null) + { + var type = data.GetType(); + if (!(type.IsEnum || type.IsPrimitive || type.IsArray)) + { + foreach (var method in type.GetMethods( instanceBindingFlags )) + { + if (method.GetCustomAttributes( false ).AnyOfType( typeof(BeforeEncode) )) + { + if (method.GetParameters().Length == 0) + { + method.Invoke( data, null ); + } + } + } + } + } + + return Encoder.Encode( data, options ); + } + + + public static void MakeInto( Variant data, out T item ) + { + item = DecodeType( data ); + } + + + public static void Populate( Variant data, T item ) where T : class + { + if (item == null) + { + throw new ArgumentNullException( nameof(item) ); + } + DecodeFields( data, ref item ); + } + + + static readonly Dictionary typeCache = new Dictionary(); + + static Type FindType( string fullName ) + { + if (fullName == null) + { + return null; + } + + Type type; + if (typeCache.TryGetValue( fullName, out type )) + { + return type; + } + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + type = assembly.GetType( fullName ); + if (type != null) + { + typeCache.Add( fullName, type ); + return type; + } + } + + return null; + } + + static T DecodeType( Variant data ) + { + if (data == null) + { + return default(T); + } + + var type = typeof(T); + + Type nulledType = Nullable.GetUnderlyingType(type); + if (nulledType != null) + { + var makeFunc = decodeTypeMethod.MakeGenericMethod( nulledType ); + var v = makeFunc.Invoke( null, new object[] { data } ); + return (T) v; + } + + if (type.IsEnum) + { + return (T) Enum.Parse( type, data.ToString( CultureInfo.InvariantCulture ) ); + } + + if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal)) + { + return (T) Convert.ChangeType( data, type ); + } + + if (type == typeof(Guid)) + { + return (T) (object) new Guid( data.ToString( CultureInfo.InvariantCulture ) ); + } + + if (type.IsArray) + { + if (type.GetArrayRank() == 1) + { + var makeFunc = decodeArrayMethod.MakeGenericMethod( type.GetElementType() ); + return (T) makeFunc.Invoke( null, new object[] { data } ); + } + + var arrayData = data as ProxyArray; + if (arrayData == null) + { + throw new DecodeException( "Variant is expected to be a ProxyArray here, but it is not." ); + } + + var arrayRank = type.GetArrayRank(); + var rankLengths = new int[arrayRank]; + if (arrayData.CanBeMultiRankArray( rankLengths )) + { + var elementType = type.GetElementType(); + if (elementType == null) + { + throw new DecodeException( "Array element type is expected to be not null, but it is." ); + } + + var array = Array.CreateInstance( elementType, rankLengths ); + var makeFunc = decodeMultiRankArrayMethod.MakeGenericMethod( elementType ); + try + { + makeFunc.Invoke( null, new object[] { arrayData, array, 1, rankLengths } ); + } + catch (Exception e) + { + throw new DecodeException( "Error decoding multidimensional array. Did you try to decode into an array of incompatible rank or element type?", e ); + } + + return (T) Convert.ChangeType( array, typeof(T) ); + } + + throw new DecodeException( "Error decoding multidimensional array; JSON data doesn't seem fit this structure." ); + } + + if (typeof(IList).IsAssignableFrom( type )) + { + var makeFunc = decodeListMethod.MakeGenericMethod( type.GetGenericArguments() ); + return (T) makeFunc.Invoke( null, new object[] { data } ); + } + + if (typeof(IDictionary).IsAssignableFrom( type )) + { + var makeFunc = decodeDictionaryMethod.MakeGenericMethod( type.GetGenericArguments() ); + return (T) makeFunc.Invoke( null, new object[] { data } ); + } + + // At this point we should be dealing with a class or struct. + T instance; + var proxyObject = data as ProxyObject; + if (proxyObject == null) + { + throw new InvalidCastException( "ProxyObject expected when decoding into '" + type.FullName + "'." ); + } + + // If there's a type hint, use it to create the instance. + var typeHint = proxyObject.TypeHint; + if (typeHint != null && typeHint != type.FullName) + { + var makeType = FindType( typeHint ); + if (makeType == null) + { + throw new TypeLoadException( "Could not load type '" + typeHint + "'." ); + } + + if (type.IsAssignableFrom( makeType )) + { + instance = (T) Activator.CreateInstance( makeType ); + type = makeType; + } + else + { + throw new InvalidCastException( "Cannot assign type '" + typeHint + "' to type '" + type.FullName + "'." ); + } + } + else + { + // We don't have a type hint, so just instantiate the type we have. + instance = Activator.CreateInstance(); + } + + foreach (var pair in proxyObject) + { + var field = type.GetField( pair.Key, instanceBindingFlags ); + + // If the field doesn't exist, search through any [DecodeAlias] attributes. + if (field == null) + { + var fields = type.GetFields( instanceBindingFlags ); + foreach (var fieldInfo in fields) + { + foreach (var attribute in fieldInfo.GetCustomAttributes( true )) + { + if (decodeAliasAttrType.IsInstanceOfType( attribute )) + { + if (((DecodeAlias) attribute).Contains( pair.Key )) + { + field = fieldInfo; + break; + } + } + } + } + } + + if (field != null) + { + var shouldDecode = field.IsPublic; + foreach (var attribute in field.GetCustomAttributes( true )) + { + if (excludeAttrType.IsInstanceOfType( attribute )) + { + shouldDecode = false; + } + + if (includeAttrType.IsInstanceOfType( attribute )) + { + shouldDecode = true; + } + } + + if (shouldDecode) + { + var makeFunc = decodeTypeMethod.MakeGenericMethod( field.FieldType ); + if (type.IsValueType) + { + // Type is a struct. + var instanceRef = (object) instance; + field.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ) ); + instance = (T) instanceRef; + } + else + { + // Type is a class. + field.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ) ); + } + } + } + + var property = type.GetProperty( pair.Key, instanceBindingFlags ); + + // If the property doesn't exist, search through any [DecodeAlias] attributes. + if (property == null) + { + var properties = type.GetProperties( instanceBindingFlags ); + foreach (var propertyInfo in properties) + { + foreach (var attribute in propertyInfo.GetCustomAttributes( false )) + { + if (decodeAliasAttrType.IsInstanceOfType( attribute )) + { + if (((DecodeAlias) attribute).Contains( pair.Key )) + { + property = propertyInfo; + break; + } + } + } + } + } + + if (property != null) + { + if (property.CanWrite && property.GetCustomAttributes( false ).AnyOfType( includeAttrType )) + { + var makeFunc = decodeTypeMethod.MakeGenericMethod( new Type[] { property.PropertyType } ); + if (type.IsValueType) + { + // Type is a struct. + var instanceRef = (object) instance; + property.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ), null ); + instance = (T) instanceRef; + } + else + { + // Type is a class. + property.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ), null ); + } + } + } + } + + // Invoke methods tagged with [AfterDecode] attribute. + foreach (var method in type.GetMethods( instanceBindingFlags )) + { + if (method.GetCustomAttributes( false ).AnyOfType( typeof(AfterDecode) )) + { + method.Invoke( instance, method.GetParameters().Length == 0 ? null : new object[] { data } ); + } + } + + return instance; + } + + static void DecodeFields( Variant data, ref T instance ) + { + var type = typeof(T); + var proxyObject = data as ProxyObject; + if (proxyObject == null) + { + throw new InvalidCastException( "ProxyObject expected when decoding into '" + type.FullName + "'." ); + } + + foreach (var pair in proxyObject) + { + var field = type.GetField( pair.Key, instanceBindingFlags ); + + // If the field doesn't exist, search through any [DecodeAlias] attributes. + if (field == null) + { + var fields = type.GetFields( instanceBindingFlags ); + foreach (var fieldInfo in fields) + { + foreach (var attribute in fieldInfo.GetCustomAttributes( true )) + { + if (decodeAliasAttrType.IsInstanceOfType( attribute )) + { + if (((DecodeAlias) attribute).Contains( pair.Key )) + { + field = fieldInfo; + break; + } + } + } + } + } + + if (field != null) + { + var shouldDecode = field.IsPublic; + foreach (var attribute in field.GetCustomAttributes( true )) + { + if (excludeAttrType.IsInstanceOfType( attribute )) + { + shouldDecode = false; + } + + if (includeAttrType.IsInstanceOfType( attribute )) + { + shouldDecode = true; + } + } + + if (shouldDecode) + { + var makeFunc = decodeTypeMethod.MakeGenericMethod( field.FieldType ); + if (type.IsValueType) + { + // Type is a struct. + var instanceRef = (object) instance; + field.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ) ); + instance = (T) instanceRef; + } + else + { + // Type is a class. + field.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ) ); + } + } + } + + var property = type.GetProperty( pair.Key, instanceBindingFlags ); + + // If the property doesn't exist, search through any [DecodeAlias] attributes. + if (property == null) + { + var properties = type.GetProperties( instanceBindingFlags ); + foreach (var propertyInfo in properties) + { + foreach (var attribute in propertyInfo.GetCustomAttributes( false )) + { + if (decodeAliasAttrType.IsInstanceOfType( attribute )) + { + if (((DecodeAlias) attribute).Contains( pair.Key )) + { + property = propertyInfo; + break; + } + } + } + } + } + + if (property != null) + { + if (property.CanWrite && property.GetCustomAttributes( false ).AnyOfType( includeAttrType )) + { + var makeFunc = decodeTypeMethod.MakeGenericMethod( new Type[] { property.PropertyType } ); + if (type.IsValueType) + { + // Type is a struct. + var instanceRef = (object) instance; + property.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ), null ); + instance = (T) instanceRef; + } + else + { + // Type is a class. + property.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ), null ); + } + } + } + } + + // Invoke methods tagged with [AfterDecode] attribute. + foreach (var method in type.GetMethods( instanceBindingFlags )) + { + if (method.GetCustomAttributes( false ).AnyOfType( typeof(AfterDecode) )) + { + method.Invoke( instance, method.GetParameters().Length == 0 ? null : new object[] { data } ); + } + } + } + + // ReSharper disable once UnusedMethodReturnValue.Local + static List DecodeList( Variant data ) + { + var list = new List(); + + var proxyArray = data as ProxyArray; + if (proxyArray == null) + { + throw new DecodeException( "Variant is expected to be a ProxyArray here, but it is not." ); + } + + foreach (var item in proxyArray) + { + list.Add( DecodeType( item ) ); + } + + return list; + } + + // ReSharper disable once UnusedMethodReturnValue.Local + static Dictionary DecodeDictionary( Variant data ) + { + var dict = new Dictionary(); + var type = typeof(TKey); + + var proxyObject = data as ProxyObject; + if (proxyObject == null) + { + throw new DecodeException( "Variant is expected to be a ProxyObject here, but it is not." ); + } + + foreach (var pair in proxyObject) + { + var k = (TKey) (type.IsEnum ? Enum.Parse( type, pair.Key ) : Convert.ChangeType( pair.Key, type )); + var v = DecodeType( pair.Value ); + dict.Add( k, v ); + } + + return dict; + } + + // ReSharper disable once UnusedMethodReturnValue.Local + static T[] DecodeArray( Variant data ) + { + var arrayData = data as ProxyArray; + if (arrayData == null) + { + throw new DecodeException( "Variant is expected to be a ProxyArray here, but it is not." ); + } + + var arraySize = arrayData.Count; + var array = new T[arraySize]; + + var i = 0; + foreach (var item in arrayData) + { + array[i++] = DecodeType( item ); + } + + return array; + } + + // ReSharper disable once UnusedMember.Local + static void DecodeMultiRankArray( ProxyArray arrayData, Array array, int arrayRank, int[] indices ) + { + var count = arrayData.Count; + for (var i = 0; i < count; i++) + { + indices[arrayRank - 1] = i; + if (arrayRank < array.Rank) + { + DecodeMultiRankArray( arrayData[i] as ProxyArray, array, arrayRank + 1, indices ); + } + else + { + array.SetValue( DecodeType( arrayData[i] ), indices ); + } + } + } + + + const BindingFlags instanceBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + const BindingFlags staticBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + static readonly MethodInfo decodeTypeMethod = typeof(JSON).GetMethod( "DecodeType", staticBindingFlags ); + static readonly MethodInfo decodeListMethod = typeof(JSON).GetMethod( "DecodeList", staticBindingFlags ); + static readonly MethodInfo decodeDictionaryMethod = typeof(JSON).GetMethod( "DecodeDictionary", staticBindingFlags ); + static readonly MethodInfo decodeArrayMethod = typeof(JSON).GetMethod( "DecodeArray", staticBindingFlags ); + static readonly MethodInfo decodeMultiRankArrayMethod = typeof(JSON).GetMethod( "DecodeMultiRankArray", staticBindingFlags ); + + // ReSharper disable once InconsistentNaming + public static void SupportTypeForAOT() + { + DecodeType( null ); + DecodeList( null ); + DecodeArray( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + DecodeDictionary( null ); + } + + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedMember.Local + static void SupportValueTypesForAOT() + { + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + SupportTypeForAOT(); + } + } +} diff --git a/MelonLoader.TinyJSON/LICENSE.md b/MelonLoader.TinyJSON/LICENSE.md new file mode 100644 index 00000000..5fa84f8d --- /dev/null +++ b/MelonLoader.TinyJSON/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2013 Patrick Hogan + +Based on MiniJSON by Calvin Rien +https://gist.github.com/darktable/1411710 + +Based on the JSON parser by Patrick van Bergen +http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/MelonLoader.TinyJSON/MelonLoader.TinyJSON.csproj b/MelonLoader.TinyJSON/MelonLoader.TinyJSON.csproj new file mode 100644 index 00000000..a97d26e9 --- /dev/null +++ b/MelonLoader.TinyJSON/MelonLoader.TinyJSON.csproj @@ -0,0 +1,39 @@ + + + + Release + AnyCPU + Library + MelonLoader.TinyJSON + MelonLoader.TinyJSON + net48 + 512 + true + 12 + 414 + $(ProjectDir)../Libs/;$(AssemblySearchPaths) + $(ProjectDir)../Output/ + false + false + + + + false + None + true + prompt + 4 + true + false + + + + DEBUG + + + + + + + + diff --git a/MelonLoader.TinyJSON/README.md b/MelonLoader.TinyJSON/README.md new file mode 100644 index 00000000..997981de --- /dev/null +++ b/MelonLoader.TinyJSON/README.md @@ -0,0 +1,218 @@ +## Description + +TinyJSON is a simple JSON library for C# that strives for ease of use. + +## Features + +* Transmogrify objects into JSON and back again. +* Uses reflection to dump and load object graphs automagically. +* Supports primitives, classes, structs, enums, lists, dictionaries and arrays. +* Supports single dimensional arrays, multidimensional arrays and jagged arrays. +* Parsed data uses proxy variants that can be implicitly cast to primitive types for cleaner code, or directly encoded back to JSON. +* Numeric types are handled without fuss. +* Polymorphic classes supported with a type hint encoded into the JSON. +* Supports optionally pretty printing JSON output. +* Supports optionally encode properties and private fields. +* Supports decoding fields and properties from aliased names. +* Unit tested. + +## Usage + +The API is namespaced under `TinyJSON` and the primary class is `JSON`. There are really only a few methods you need to know: + +```csharp +namespace TinyJSON +{ + public static class JSON + { + public static Variant Load( string json ); + public static string Dump( object data, EncodeOptions = EncodeOptions.None ); + public static void MakeInto( Variant data, out T item ); + } +} +``` + +`Load()` will load a string of JSON, returns `null` if invalid or a `Variant` proxy object if successful. The proxy allows for implicit casts and can convert between various C# numeric value types. + +```csharp +var data = JSON.Load( "{\"foo\": 1, \"bar\": 2.34}" ); +int i = data["foo"]; +float f = data["bar"]; +``` + +`Dump()` will take a C# object, list, dictionary or primitive value type and turn it into JSON. + +```csharp +var data = new List() { { 0 }, { 1 }, { 2 } }; +Console.WriteLine( JSON.Dump( data ) ); // output: [1,2,3] +``` + +TinyJSON can also handle classes, structs, enums and nested objects. Given these definitions: + +```csharp +enum TestEnum +{ + Thing1, + Thing2, + Thing3 +} + + +struct TestStruct +{ + public int x; + public int y; +} + + +class TestClass +{ + public string name; + public TestEnum type; + public List data = new List(); + + [Exclude] + public int _ignored; + + [BeforeEncode] + public void BeforeEncode() + { + Console.WriteLine( "BeforeEncode callback fired!" ); + } + + [AfterDecode] + public void AfterDecode() + { + Console.WriteLine( "AfterDecode callback fired!" ); + } +} +``` + +The following code: + +```csharp +var testClass = new TestClass(); +testClass.name = "Rumpelstiltskin Jones"; +testClass.type = TestEnum.Thing2; +testClass.data.Add( new TestStruct() { x = 1, y = 2 } ); +testClass.data.Add( new TestStruct() { x = 3, y = 4 } ); +testClass.data.Add( new TestStruct() { x = 5, y = 6 } ); + +var testClassJson = JSON.Dump( testClass, true ); +Console.WriteLine( testClassJson ); +``` + +Will output: + +```json +{ + "name": "Rumpelstiltskin Jones", + "type": "Thing2", + "data": [ + { + "x": 1, + "y": 2 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 5, + "y": 6 + } + ] +} +``` + +You can use, `MakeInto()` can be used to reconstruct JSON data back into an object: + +```csharp +TestClass testClass; +JSON.MakeInto( JSON.Load( testClassJson ), out testClass ); +``` + +There are also `Make()` methods on `Variant` which provide options for slightly more natural syntax: + +```csharp +TestClass testClass; + +JSON.Load( json ).Make( out testClass ); +// or +testClass = JSON.Load( json ).Make(); +``` + +Finally, you'll notice that `TestClass` has the methods `BeforeEncode()` and `AfterDecode()` which have the `TinyJSON.BeforeEncode` and `TinyJSON.AfterDecode` attributes. These methods will be called *before* the object starts being serialized and *after* the object has been fully deserialized. This is useful when some further preparation or initialization logic is required. + +By default, only public fields are encoded, not properties or private fields. You can tag any field or property to be included with the `TinyJSON.Include` attribute, or force a public field to be excluded with the `TinyJSON.Exclude` attribute. + + +## Decode Aliases + +Fields and properties can be decoded from aliases using the `TinyJSON.DecodeAlias` attribute. While decoding, if no matching data is found in the JSON for a given field or property, its aliases will also be searched for. + +```csharp +class TestClass +{ + [DecodeAlias("anotherName")] + public string name; // decode from "name" or "anotherName" + + [DecodeAlias("anotherNumber", "yetAnotherNumber")] + public int number; // decode from "number", "anotherNumber", or "yetAnotherNumber" +} +``` + + +## Type Hinting + +When decoding polymorphic types, TinyJSON has no way of knowing which subclass to instantiate unless a type hint is included. So, by default, TinyJSON will add a key named `@type` to each encoded object with the fully qualified type of the object. + +## Encode Options + +Several options are currently available for JSON encoding, and can be passed in as a second parameter to `JSON.Dump()`. + +* `EncodeOptions.PrettyPrint` will output nicely formatted JSON to make it more readable. +* `EncodeOptions.NoTypeHints` will disable the outputting of type hints into the JSON output. This may be desirable if you plan to read the JSON into another application that might choke on the type information. You can override this on a per-member basis with the `TinyJSON.TypeHint` attribute. +* `EncodeOptions.IncludePublicProperties` will include public properties in the output. +* `EncodeOptions.EnforceHeirarchyOrder` will ensure fields and properties are encoded in class heirarchy order, from the root base class on down, but comes at a slight performance cost. + +## Using Variants + +For most use cases you can just assign, cast or make your object graph using the API outlined above, but at times you may need to work with the intermediate proxy objects to, say, dig through and iterate over a collection. To do this, cast the `Variant` to the appropriate subclass (likely either `ProxyArray` or `ProxyObject`) and you're good to go: + +```csharp +var list = JSON.Load( "[1,2,3]" ); +foreach (var item in list as ProxyArray) +{ + int number = item; + Console.WriteLine( number ); +} + +var dict = JSON.Load( "{\"x\":1,\"y\":2}" ); +foreach (var pair in dict as ProxyObject) +{ + float value = pair.Value; + Console.WriteLine( pair.Key + " = " + value ); +} +``` + +The non-collection `Variant` subclasses are `ProxyBoolean`, `ProxyNumber` and `ProxyString`. A variant can also be `null`. + +Any `Variant` object can be directly encoded to JSON by calling its `ToJSON()` method or passing it to `JSON.Dump()`. + +## Notes + +This project was developed with pain elimination and lightweight size in mind. It should be able to handle reasonable amounts of reasonable data at reasonable speeds, but it's not meant for massive data sets. + +The primary use case for this library is with Unity3D, so compatibility is focused there, though it should work with most modern C# environments. + +It has been used in several published games. It's good for preferences, level and progress data, etc. + +## Meta + +Handcrafted by Patrick Hogan [[twitter](http://twitter.com/pbhogan) • [github](http://github.com/pbhogan) • [website](http://www.gallantgames.com)] + +Based on [MiniJSON](https://gist.github.com/darktable/1411710) by Calvin Rien + +Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php). + diff --git a/MelonLoader.TinyJSON/Types/ProxyArray.cs b/MelonLoader.TinyJSON/Types/ProxyArray.cs new file mode 100644 index 00000000..3a053eec --- /dev/null +++ b/MelonLoader.TinyJSON/Types/ProxyArray.cs @@ -0,0 +1,104 @@ +using System.Collections; +using System.Collections.Generic; + +namespace MelonLoader.TinyJSON +{ + public sealed class ProxyArray : Variant, IEnumerable + { + readonly List list; + + + public ProxyArray() + { + list = new List(); + } + + + IEnumerator IEnumerable.GetEnumerator() + { + return list.GetEnumerator(); + } + + + IEnumerator IEnumerable.GetEnumerator() + { + return list.GetEnumerator(); + } + + + public void Add( Variant item ) + { + list.Add( item ); + } + + + public override Variant this[ int index ] + { + get + { + return list[index]; + } + set + { + list[index] = value; + } + } + + + public int Count + { + get + { + return list.Count; + } + } + + + internal bool CanBeMultiRankArray( int[] rankLengths ) + { + return CanBeMultiRankArray( 0, rankLengths ); + } + + + bool CanBeMultiRankArray( int rank, int[] rankLengths ) + { + var count = list.Count; + rankLengths[rank] = count; + + if (rank == rankLengths.Length - 1) + { + return true; + } + + var firstItem = list[0] as ProxyArray; + if (firstItem == null) + { + return false; + } + + var firstItemCount = firstItem.Count; + + for (var i = 1; i < count; i++) + { + var item = list[i] as ProxyArray; + + if (item == null) + { + return false; + } + + if (item.Count != firstItemCount) + { + return false; + } + + if (!item.CanBeMultiRankArray( rank + 1, rankLengths )) + { + return false; + } + } + + return true; + } + } +} diff --git a/MelonLoader.TinyJSON/Types/ProxyBoolean.cs b/MelonLoader.TinyJSON/Types/ProxyBoolean.cs new file mode 100644 index 00000000..b4d07abc --- /dev/null +++ b/MelonLoader.TinyJSON/Types/ProxyBoolean.cs @@ -0,0 +1,27 @@ +using System; + +namespace MelonLoader.TinyJSON +{ + public sealed class ProxyBoolean : Variant + { + readonly bool value; + + + public ProxyBoolean( bool value ) + { + this.value = value; + } + + + public override bool ToBoolean( IFormatProvider provider ) + { + return value; + } + + + public override string ToString( IFormatProvider provider ) + { + return value ? "true" : "false"; + } + } +} diff --git a/MelonLoader.TinyJSON/Types/ProxyNumber.cs b/MelonLoader.TinyJSON/Types/ProxyNumber.cs new file mode 100644 index 00000000..af80ad3d --- /dev/null +++ b/MelonLoader.TinyJSON/Types/ProxyNumber.cs @@ -0,0 +1,153 @@ +using System; +using System.Globalization; + +namespace MelonLoader.TinyJSON +{ + public sealed class ProxyNumber : Variant + { + static readonly char[] floatingPointCharacters = { '.', 'e' }; + readonly IConvertible value; + + + public ProxyNumber( IConvertible value ) + { + var stringValue = value as string; + this.value = stringValue != null ? Parse( stringValue ) : value; + } + + + static IConvertible Parse( string value ) + { + if (value.IndexOfAny( floatingPointCharacters ) == -1) + { + if (value[0] == '-') + { + Int64 parsedValue; + if (Int64.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue )) + { + return parsedValue; + } + } + else + { + UInt64 parsedValue; + if (UInt64.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue )) + { + return parsedValue; + } + } + } + + Decimal decimalValue; + if (Decimal.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out decimalValue )) + { + // Check for decimal underflow. + if (decimalValue == Decimal.Zero) + { + Double parsedValue; + if (Double.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue )) + { + if (Math.Abs( parsedValue ) > Double.Epsilon) + { + return parsedValue; + } + } + } + + return decimalValue; + } + + Double doubleValue; + if (Double.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out doubleValue )) + { + return doubleValue; + } + + return 0; + } + + + public override bool ToBoolean( IFormatProvider provider ) + { + return value.ToBoolean( provider ); + } + + + public override byte ToByte( IFormatProvider provider ) + { + return value.ToByte( provider ); + } + + + public override char ToChar( IFormatProvider provider ) + { + return value.ToChar( provider ); + } + + + public override decimal ToDecimal( IFormatProvider provider ) + { + return value.ToDecimal( provider ); + } + + + public override double ToDouble( IFormatProvider provider ) + { + return value.ToDouble( provider ); + } + + + public override short ToInt16( IFormatProvider provider ) + { + return value.ToInt16( provider ); + } + + + public override int ToInt32( IFormatProvider provider ) + { + return value.ToInt32( provider ); + } + + + public override long ToInt64( IFormatProvider provider ) + { + return value.ToInt64( provider ); + } + + + public override sbyte ToSByte( IFormatProvider provider ) + { + return value.ToSByte( provider ); + } + + + public override float ToSingle( IFormatProvider provider ) + { + return value.ToSingle( provider ); + } + + + public override string ToString( IFormatProvider provider ) + { + return value.ToString( provider ); + } + + + public override ushort ToUInt16( IFormatProvider provider ) + { + return value.ToUInt16( provider ); + } + + + public override uint ToUInt32( IFormatProvider provider ) + { + return value.ToUInt32( provider ); + } + + + public override ulong ToUInt64( IFormatProvider provider ) + { + return value.ToUInt64( provider ); + } + } +} diff --git a/MelonLoader.TinyJSON/Types/ProxyObject.cs b/MelonLoader.TinyJSON/Types/ProxyObject.cs new file mode 100644 index 00000000..b94c6645 --- /dev/null +++ b/MelonLoader.TinyJSON/Types/ProxyObject.cs @@ -0,0 +1,98 @@ +using System.Collections; +using System.Collections.Generic; +using System.Globalization; + +namespace MelonLoader.TinyJSON +{ + public sealed class ProxyObject : Variant, IEnumerable> + { + public const string TypeHintKey = "@type"; + readonly Dictionary dict; + + + public ProxyObject() + { + dict = new Dictionary(); + } + + + IEnumerator> IEnumerable>.GetEnumerator() + { + return dict.GetEnumerator(); + } + + + IEnumerator IEnumerable.GetEnumerator() + { + return dict.GetEnumerator(); + } + + + public void Add( string key, Variant item ) + { + dict.Add( key, item ); + } + + + public bool TryGetValue( string key, out Variant item ) + { + return dict.TryGetValue( key, out item ); + } + + + public string TypeHint + { + get + { + Variant item; + if (TryGetValue( TypeHintKey, out item )) + { + return item.ToString( CultureInfo.InvariantCulture ); + } + + return null; + } + } + + + public override Variant this[ string key ] + { + get + { + return dict[key]; + } + set + { + dict[key] = value; + } + } + + + public int Count + { + get + { + return dict.Count; + } + } + + + public Dictionary.KeyCollection Keys + { + get + { + return dict.Keys; + } + } + + + // ReSharper disable once UnusedMember.Global + public Dictionary.ValueCollection Values + { + get + { + return dict.Values; + } + } + } +} diff --git a/MelonLoader.TinyJSON/Types/ProxyString.cs b/MelonLoader.TinyJSON/Types/ProxyString.cs new file mode 100644 index 00000000..48003cd3 --- /dev/null +++ b/MelonLoader.TinyJSON/Types/ProxyString.cs @@ -0,0 +1,21 @@ +using System; + +namespace MelonLoader.TinyJSON +{ + public sealed class ProxyString : Variant + { + readonly string value; + + + public ProxyString( string value ) + { + this.value = value; + } + + + public override string ToString( IFormatProvider provider ) + { + return value; + } + } +} diff --git a/MelonLoader.TinyJSON/Types/Variant.cs b/MelonLoader.TinyJSON/Types/Variant.cs new file mode 100644 index 00000000..17738a1e --- /dev/null +++ b/MelonLoader.TinyJSON/Types/Variant.cs @@ -0,0 +1,250 @@ +using System; +using System.Globalization; + +namespace MelonLoader.TinyJSON +{ + public abstract class Variant : IConvertible + { + protected static readonly IFormatProvider FormatProvider = new NumberFormatInfo(); + + + // ReSharper disable once UnusedMember.Global + public void Make( out T item ) + { + JSON.MakeInto( this, out item ); + } + + + public T Make() + { + T item; + JSON.MakeInto( this, out item ); + return item; + } + + + public void Populate( T item ) where T : class + { + JSON.Populate( this, item ); + } + + + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedMember.Global + public string ToJSON() + { + return JSON.Dump( this ); + } + + + public virtual TypeCode GetTypeCode() + { + return TypeCode.Object; + } + + + public virtual object ToType( Type conversionType, IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to " + conversionType.Name ); + } + + + public virtual DateTime ToDateTime( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to DateTime" ); + } + + + public virtual bool ToBoolean( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Boolean" ); + } + + public virtual byte ToByte( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Byte" ); + } + + + public virtual char ToChar( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Char" ); + } + + + public virtual decimal ToDecimal( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Decimal" ); + } + + + public virtual double ToDouble( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Double" ); + } + + + public virtual short ToInt16( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Int16" ); + } + + + public virtual int ToInt32( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Int32" ); + } + + + public virtual long ToInt64( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Int64" ); + } + + + public virtual sbyte ToSByte( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to SByte" ); + } + + + public virtual float ToSingle( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to Single" ); + } + + + public virtual string ToString( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to String" ); + } + + + public virtual ushort ToUInt16( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to UInt16" ); + } + + + public virtual uint ToUInt32( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to UInt32" ); + } + + + public virtual ulong ToUInt64( IFormatProvider provider ) + { + throw new InvalidCastException( "Cannot convert " + GetType() + " to UInt64" ); + } + + + public override string ToString() + { + return ToString( FormatProvider ); + } + + + // ReSharper disable once UnusedMemberInSuper.Global + public virtual Variant this[ string key ] + { + get + { + throw new NotSupportedException(); + } + + // ReSharper disable once UnusedMember.Global + set + { + throw new NotSupportedException(); + } + } + + + // ReSharper disable once UnusedMemberInSuper.Global + public virtual Variant this[ int index ] + { + get + { + throw new NotSupportedException(); + } + + // ReSharper disable once UnusedMember.Global + set + { + throw new NotSupportedException(); + } + } + + + public static implicit operator Boolean( Variant variant ) + { + return variant.ToBoolean( FormatProvider ); + } + + + public static implicit operator Single( Variant variant ) + { + return variant.ToSingle( FormatProvider ); + } + + + public static implicit operator Double( Variant variant ) + { + return variant.ToDouble( FormatProvider ); + } + + + public static implicit operator UInt16( Variant variant ) + { + return variant.ToUInt16( FormatProvider ); + } + + + public static implicit operator Int16( Variant variant ) + { + return variant.ToInt16( FormatProvider ); + } + + + public static implicit operator UInt32( Variant variant ) + { + return variant.ToUInt32( FormatProvider ); + } + + + public static implicit operator Int32( Variant variant ) + { + return variant.ToInt32( FormatProvider ); + } + + + public static implicit operator UInt64( Variant variant ) + { + return variant.ToUInt64( FormatProvider ); + } + + + public static implicit operator Int64( Variant variant ) + { + return variant.ToInt64( FormatProvider ); + } + + + public static implicit operator Decimal( Variant variant ) + { + return variant.ToDecimal( FormatProvider ); + } + + + public static implicit operator String( Variant variant ) + { + return variant.ToString( FormatProvider ); + } + + + public static implicit operator Guid( Variant variant ) + { + return new Guid( variant.ToString( FormatProvider ) ); + } + } +} diff --git a/README.md b/README.md index 7de34635..5d2b1497 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ ## Development 1. Copy `Assembly-CSharp.dll` and `AMDaemon.NET.dll` to `Libs` folder. -1. Install [.NET Framework 4.7.2 Developer Pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer) +1. Install [.NET Framework 4.8 Developer Pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/thank-you/net48-developer-pack-offline-installer) 1. Run `build.ps1`. 1. Copy `Output/AquaMai.dll` to `Mods` folder. 1. Configure and copy `AquaMai.toml` to the same folder as your game executable: `Sinmai.exe` diff --git a/build.cake b/build.cake index ca1c7c59..bad29589 100644 --- a/build.cake +++ b/build.cake @@ -30,7 +30,7 @@ Task("PreBuild") var versionContent = $@" // Auto-generated file. Do not modify manually. - namespace AquaMai; + namespace AquaMai.Common; public static partial class BuildInfo {{ @@ -39,7 +39,7 @@ Task("PreBuild") public const string BuildDate = ""{buildDate}""; }} "; - FileWriteText("./AquaMai/BuildInfo.g.cs", versionContent); + FileWriteText("./AquaMai.Common/BuildInfo.g.cs", versionContent); }); Task("Build")