├── .config └── dotnet-tools.json ├── .github └── workflows │ └── aquamai.yaml ├── .gitignore ├── AquaMai.Build ├── AquaMai.Build.csproj ├── GenerateExampleConfig.cs └── PostBuildPatch.cs ├── AquaMai.Config.HeadlessLoader ├── AquaMai.Config.HeadlessLoader.csproj ├── ConfigAssemblyLoader.cs ├── CustomAssemblyResolver.cs ├── HeadlessConfigInterface.cs ├── HeadlessConfigLoader.cs ├── Polyfills.cs └── ResourceLoader.cs ├── AquaMai.Config.Interfaces ├── AquaMai.Config.Interfaces.csproj ├── IConfig.cs ├── IConfigComment.cs ├── IConfigEntryAttribute.cs ├── IConfigMigrationManager.cs ├── IConfigParser.cs ├── IConfigSectionAttribute.cs ├── IConfigSerializer.cs ├── IConfigView.cs ├── IReflectionManager.cs ├── IReflectionProvider.cs └── Polyfills.cs ├── AquaMai.Config ├── ApiVersion.cs ├── AquaMai.Config.csproj ├── Attributes │ ├── ConfigCollapseNamespaceAttribute.cs │ ├── ConfigComment.cs │ ├── ConfigEntryAttribute.cs │ ├── ConfigSectionAttribute.cs │ └── EnableCondition.cs ├── Config.cs ├── ConfigParser.cs ├── ConfigSerializer.cs ├── ConfigView.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Migration │ ├── ConfigMigrationManager.cs │ ├── ConfigMigration_V1_0_V2_0.cs │ ├── ConfigMigration_V2_0_V2_1.cs │ ├── ConfigMigration_V2_1_V2_2.cs │ └── IConfigMigration.cs ├── Polyfills.cs ├── Reflection │ ├── MonoCecilAssemblyReflectionProvider.cs │ ├── ReflectionManager.cs │ └── SystemReflectionProvider.cs ├── Types │ ├── AdxKeyMap.cs │ ├── KeyCodeID.cs │ └── KeyCodeOrName.cs └── Utility.cs ├── AquaMai.Core ├── AquaMai.Core.csproj ├── Attributes │ ├── EnableGameVersionAttribute.cs │ ├── EnableIfAttribute.cs │ └── EnableImplicitlyIfAttribute.cs ├── BuildInfo.cs ├── ConfigLoader.cs ├── Helpers │ ├── EnableConditionHelper.cs │ ├── FileSystem.cs │ ├── GameInfo.cs │ ├── GuiSizes.cs │ ├── JsonHelper.cs │ ├── KeyListener.cs │ ├── MessageHelper.cs │ ├── MusicDirHelper.cs │ ├── NetPacketHook.cs │ ├── SharedInstances.cs │ └── Shim.cs ├── Resources │ ├── I18nSingleAssemblyHook.cs │ ├── Locale.Designer.cs │ ├── Locale.resx │ └── Locale.zh.resx └── Startup.cs ├── AquaMai.ErrorReport ├── AquaMai.ErrorReport.csproj ├── ChecksumCalculator.cs ├── CrashForm.Designer.cs ├── CrashForm.cs ├── CrashForm.resx ├── FileHeaderMeta.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Polyfills.cs └── Program.cs ├── AquaMai.Mods ├── AquaMai.Mods.csproj ├── DeprecationWarning.cs ├── Enhancement │ ├── ServerAnnouncement.cs │ ├── ServerNotice.cs │ └── ServerResources.cs ├── Fancy │ ├── CustomButton.cs │ ├── CustomCreditsString.cs │ ├── CustomLogo.cs │ ├── CustomPlaceName.cs │ ├── CustomSkins.cs │ ├── CustomTrackStartDiff.cs │ ├── CustomVersionString.cs │ ├── DemoMaster.cs │ ├── GamePlay │ │ ├── AlignCircleSlideJudgeDisplay.cs │ │ ├── BreakSlideJudgeBlink.cs │ │ ├── CustomNoteTypes │ │ │ ├── CustomNoteTypes.cs │ │ │ └── Libs │ │ │ │ ├── CustomSlideNoteData.cs │ │ │ │ ├── MaiGeometry.cs │ │ │ │ ├── ParametricSlidePath.cs │ │ │ │ ├── SlideCodeParser.cs │ │ │ │ ├── SlideDataBuilder.cs │ │ │ │ └── SlidePathGenerator.cs │ │ ├── DisableTrackStartTabs.cs │ │ ├── ExtendNotesPool.cs │ │ ├── FanJudgeFlip.cs │ │ ├── HideHanabi.cs │ │ ├── JudgeDisplay4B.cs │ │ ├── RealisticRandomJudge.cs │ │ ├── SlideArrowAnimation.cs │ │ ├── SlideFadeInTweak.cs │ │ ├── SlideLayerReverse.cs │ │ └── TrackStartProcessTweak.cs │ ├── HideMask.cs │ ├── README.md │ ├── RandomBgm.cs │ └── Triggers.cs ├── Fix │ ├── Common.cs │ ├── DebugFeature.cs │ ├── DisableReboot.cs │ ├── FixCheckAuth.cs │ ├── FixConnSlide.cs │ ├── FixLevelDisplay.cs │ ├── FixSlideAutoPlay.cs │ ├── Legacy │ │ └── FixQuickRetry130.cs │ ├── README.md │ └── Stability │ │ ├── FixMissingCharaCrash.cs │ │ └── SanitizeUserData.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── GameSettings │ ├── CreditConfig.cs │ ├── ForceAsServer.cs │ ├── JudgeAdjust.cs │ ├── README.md │ └── TouchSensitivity.cs ├── GameSystem │ ├── AdxHidInput.cs │ ├── Assets │ │ ├── Fonts.cs │ │ ├── LoadAssetBundleWithoutManifest.cs │ │ ├── LoadLocalImages.cs │ │ └── MovieLoader.cs │ ├── CustomCameraId.cs │ ├── DisableTimeout.cs │ ├── KeyMap.cs │ ├── OptionLoadFix.cs │ ├── QuickRetry.cs │ ├── README.md │ ├── RemoveEncryption.cs │ ├── SinglePlayer.ExteraMouseInput.cs │ ├── SinglePlayer.cs │ ├── Sound.cs │ ├── TestProof.cs │ ├── TouchPanelBaudRate.cs │ ├── TouchToButtonInput.cs │ ├── Unlock.cs │ └── Window.cs ├── General.cs ├── Polyfills.cs ├── Tweaks │ ├── IgnoreAimeServerError.cs │ ├── LockFrameRate.cs │ ├── README.md │ ├── ResetTouch.cs │ ├── SkipUserVersionCheck.cs │ └── TimeSaving │ │ ├── EntryToMusicSelection.cs │ │ ├── ExitToSave.cs │ │ ├── IWontTapOrSlideVigorously.cs │ │ ├── SkipEventInfo.cs │ │ ├── SkipGoodbyeScreen.cs │ │ ├── SkipStartupDelays.cs │ │ ├── SkipStartupWarning.cs │ │ └── SkipTrackStart.cs ├── Types │ └── ConditionalMessage.cs ├── UX │ ├── DisableLightOutGame.cs │ ├── HideSelfMadeCharts.cs │ ├── ImmediateSave.cs │ ├── JudgeAccuracyInfo.cs │ ├── NoAmDaemonAlert.cs │ ├── OneKeyEntryEnd.cs │ ├── OneKeyRetrySkip.cs │ ├── PracticeMode │ │ ├── Libs │ │ │ └── PractiseModeUI.cs │ │ └── PracticeMode.cs │ ├── QuickEndPlay.cs │ ├── README.md │ ├── SelectionDetail.cs │ └── TestModeHook.cs └── Utils │ ├── AntiLag.cs │ ├── DisplayFrameRate.cs │ ├── LogNetworkErrors.cs │ ├── LogNetworkRequests.cs │ ├── LogUnity.cs │ ├── LogUserId.cs │ ├── README.md │ ├── ShowErrorLog.cs │ └── ShowNetErrorDetail.cs ├── AquaMai.sln ├── AquaMai ├── AquaMai.csproj ├── AssemblyLoader.cs ├── BuildInfo.cs ├── Main.cs ├── Properties │ └── AssemblyInfo.cs └── configSort.yaml ├── Libs ├── .gitignore ├── 0Harmony.dll ├── Assembly-CSharp-firstpass.dll ├── MelonLoader.dll ├── Mono.Cecil.dll ├── Mono.Posix.dll ├── Mono.Security.dll ├── System.Configuration.dll ├── System.Core.dll ├── System.Numerics.dll ├── System.Security.dll ├── System.Xml.dll ├── System.dll ├── Unity.Analytics.DataPrivacy.dll ├── Unity.TextMeshPro.dll ├── UnityEngine.AIModule.dll ├── UnityEngine.ARModule.dll ├── UnityEngine.AccessibilityModule.dll ├── UnityEngine.AnimationModule.dll ├── UnityEngine.AssetBundleModule.dll ├── UnityEngine.AudioModule.dll ├── UnityEngine.BaselibModule.dll ├── UnityEngine.ClothModule.dll ├── UnityEngine.ClusterInputModule.dll ├── UnityEngine.ClusterRendererModule.dll ├── UnityEngine.CoreModule.dll ├── UnityEngine.CrashReportingModule.dll ├── UnityEngine.DirectorModule.dll ├── UnityEngine.FileSystemHttpModule.dll ├── UnityEngine.GameCenterModule.dll ├── UnityEngine.GridModule.dll ├── UnityEngine.HotReloadModule.dll ├── UnityEngine.IMGUIModule.dll ├── UnityEngine.ImageConversionModule.dll ├── UnityEngine.InputModule.dll ├── UnityEngine.JSONSerializeModule.dll ├── UnityEngine.LocalizationModule.dll ├── UnityEngine.Networking.dll ├── UnityEngine.ParticleSystemModule.dll ├── UnityEngine.PerformanceReportingModule.dll ├── UnityEngine.Physics2DModule.dll ├── UnityEngine.PhysicsModule.dll ├── UnityEngine.ProfilerModule.dll ├── UnityEngine.ScreenCaptureModule.dll ├── UnityEngine.SharedInternalsModule.dll ├── UnityEngine.SpatialTracking.dll ├── UnityEngine.SpriteMaskModule.dll ├── UnityEngine.SpriteShapeModule.dll ├── UnityEngine.StreamingModule.dll ├── UnityEngine.StyleSheetsModule.dll ├── UnityEngine.SubstanceModule.dll ├── UnityEngine.TLSModule.dll ├── UnityEngine.TerrainModule.dll ├── UnityEngine.TerrainPhysicsModule.dll ├── UnityEngine.TextCoreModule.dll ├── UnityEngine.TextRenderingModule.dll ├── UnityEngine.TilemapModule.dll ├── UnityEngine.Timeline.dll ├── UnityEngine.TimelineModule.dll ├── UnityEngine.UI.dll ├── UnityEngine.UIElementsModule.dll ├── UnityEngine.UIModule.dll ├── UnityEngine.UNETModule.dll ├── UnityEngine.UmbraModule.dll ├── UnityEngine.UnityAnalyticsModule.dll ├── UnityEngine.UnityConnectModule.dll ├── UnityEngine.UnityTestProtocolModule.dll ├── UnityEngine.UnityWebRequestAssetBundleModule.dll ├── UnityEngine.UnityWebRequestAudioModule.dll ├── UnityEngine.UnityWebRequestModule.dll ├── UnityEngine.UnityWebRequestTextureModule.dll ├── UnityEngine.UnityWebRequestWWWModule.dll ├── UnityEngine.VFXModule.dll ├── UnityEngine.VRModule.dll ├── UnityEngine.VehiclesModule.dll ├── UnityEngine.VideoModule.dll ├── UnityEngine.WindModule.dll ├── UnityEngine.XRModule.dll ├── UnityEngine.dll └── mscorlib.dll ├── README.md ├── build.cake └── build.ps1 /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "cake.tool": { 6 | "version": "5.0.0", 7 | "commands": [ 8 | "dotnet-cake" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.github/workflows/aquamai.yaml: -------------------------------------------------------------------------------- 1 | name: AquaMai Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | pull_request_target: 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | steps: 12 | - name: Fix Git line encoding bug 13 | run: | 14 | git config --global core.autocrlf false 15 | git config --global core.eol lf 16 | 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Get Git Describe 22 | run: echo "GIT_DESCRIBE=$(git describe --tags --always)" >> $env:GITHUB_ENV 23 | 24 | - name: Checkout Assets 25 | uses: clansty/checkout@main 26 | with: 27 | repository: MewoLab/AquaMai-Build-Assets 28 | ssh-key: ${{ secrets.BUILD_ASSETS_KEY }} 29 | path: build-assets 30 | max-attempts: 50 31 | min-retry-interval: 1 32 | max-retry-interval: 5 33 | 34 | - name: Build AquaMai 35 | shell: cmd 36 | run: | 37 | copy /y build-assets\SDEZ\* Libs 38 | powershell ./build.ps1 39 | 40 | - name: Prepare artifact 41 | shell: cmd 42 | run: | 43 | cd Output 44 | mkdir Upload 45 | move AquaMai.dll Upload 46 | move AquaMai.*.toml Upload 47 | 48 | - uses: actions/upload-artifact@v4 49 | with: 50 | name: AquaMai 51 | path: Output\Upload 52 | 53 | - name: Upload CI release 54 | if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' 55 | shell: cmd 56 | run: build-assets\Releaser\AquaMaiReleaser.exe "Output\Upload\AquaMai.dll" "${{ env.GIT_DESCRIBE }}" 57 | 58 | - name: Send to Telegram 59 | if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' 60 | run: | 61 | $Uri = "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMediaGroup" 62 | $Form = @{ 63 | chat_id = "-1002231087502" 64 | media = @( 65 | @{ type = "document"; media = "attach://aquamai_main"; caption = "${{ env.GIT_DESCRIBE }}`n${{ github.event.commits[0].message }}" }, 66 | @{ type = "document"; media = "attach://aquamai_zh" } 67 | @{ type = "document"; media = "attach://aquamai_en" } 68 | ) | ConvertTo-Json 69 | aquamai_main = Get-Item Output\Upload\AquaMai.dll 70 | aquamai_zh = Get-Item Output\Upload\AquaMai.zh.toml 71 | aquamai_en = Get-Item Output\Upload\AquaMai.en.toml 72 | } 73 | Invoke-RestMethod -Uri $uri -Form $Form -Method Post 74 | -------------------------------------------------------------------------------- /AquaMai.Build/AquaMai.Build.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Release 5 | AnyCPU 6 | {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5} 7 | Library 8 | AquaMai.Build 9 | AquaMai.Build 10 | netstandard2.0 11 | 512 12 | true 13 | 12 14 | 414;NU1702 15 | $(ProjectDir)../Libs/ 16 | $(ProjectDir)../Output/ 17 | false 18 | false 19 | 20 | 21 | 22 | false 23 | None 24 | true 25 | prompt 26 | 4 27 | true 28 | false 29 | 30 | 31 | 32 | DEBUG 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | $(LibsPath)Mono.Cecil.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /AquaMai.Build/GenerateExampleConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using AquaMai.Config.Interfaces; 4 | using AquaMai.Config.HeadlessLoader; 5 | using Microsoft.Build.Framework; 6 | using Microsoft.Build.Utilities; 7 | 8 | public class GenerateExampleConfig : Task 9 | { 10 | [Required] 11 | public string DllPath { get; set; } 12 | 13 | [Required] 14 | public string OutputPath { get; set; } 15 | 16 | public override bool Execute() 17 | { 18 | try 19 | { 20 | var configInterface = HeadlessConfigLoader.LoadFromPacked(DllPath); 21 | var config = configInterface.CreateConfig(); 22 | foreach (var lang in (string[]) ["en", "zh"]) 23 | { 24 | var configSerializer = configInterface.CreateConfigSerializer(new IConfigSerializer.Options() 25 | { 26 | Lang = lang, 27 | IncludeBanner = true, 28 | OverrideLocaleValue = true 29 | }); 30 | var example = configSerializer.Serialize(config); 31 | File.WriteAllText(Path.Combine(OutputPath, $"AquaMai.{lang}.toml"), example); 32 | } 33 | 34 | return true; 35 | } 36 | catch (Exception e) 37 | { 38 | Log.LogErrorFromException(e, true); 39 | return false; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AquaMai.Build/PostBuildPatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using Microsoft.Build.Framework; 6 | using Microsoft.Build.Utilities; 7 | using Mono.Cecil; 8 | 9 | public class PostBuildPatch : Task 10 | { 11 | [Required] 12 | public string DllPath { get; set; } 13 | 14 | public override bool Execute() 15 | { 16 | try 17 | { 18 | var assembly = AssemblyDefinition.ReadAssembly(new MemoryStream(File.ReadAllBytes(DllPath))); 19 | CompressEmbeddedAssemblies(assembly); 20 | var outputStream = new MemoryStream(); 21 | assembly.Write(outputStream); 22 | File.WriteAllBytes(DllPath, outputStream.ToArray()); 23 | return true; 24 | } 25 | catch (Exception e) 26 | { 27 | Log.LogErrorFromException(e, true); 28 | return false; 29 | } 30 | } 31 | 32 | private void CompressEmbeddedAssemblies(AssemblyDefinition assembly) 33 | { 34 | foreach (var resource in assembly.MainModule.Resources.ToList()) 35 | { 36 | if ((resource.Name.EndsWith(".dll") || resource.Name.EndsWith(".exe") || resource.Name.EndsWith(".yaml")) && resource is EmbeddedResource embeddedResource) 37 | { 38 | using var compressedStream = new MemoryStream(); 39 | using (var deflateStream = new DeflateStream(compressedStream, CompressionLevel.Optimal)) 40 | { 41 | embeddedResource.GetResourceStream().CopyTo(deflateStream); 42 | } 43 | var compressedBytes = compressedStream.ToArray(); 44 | 45 | Log.LogMessage($"Compressed {resource.Name} from {embeddedResource.GetResourceStream().Length} to {compressedBytes.Length} bytes"); 46 | 47 | assembly.MainModule.Resources.Remove(resource); 48 | assembly.MainModule.Resources.Add(new EmbeddedResource(resource.Name + ".compressed", resource.Attributes, compressedBytes)); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /AquaMai.Config.HeadlessLoader/AquaMai.Config.HeadlessLoader.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Release 5 | AnyCPU 6 | {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66} 7 | Library 8 | AquaMai.Config.HeadlessLoader 9 | AquaMai.Config.HeadlessLoader 10 | netstandard2.0 11 | 512 12 | true 13 | 12 14 | 414;NU1702 15 | $(ProjectDir)../Libs/ 16 | $(ProjectDir)../Output/ 17 | false 18 | false 19 | 20 | 21 | 22 | false 23 | None 24 | true 25 | prompt 26 | 4 27 | true 28 | false 29 | 30 | 31 | 32 | DEBUG 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | $(LibsPath)Mono.Cecil.dll 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AquaMai.Config.HeadlessLoader/ConfigAssemblyLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Mono.Cecil; 7 | 8 | namespace AquaMai.Config.HeadlessLoader; 9 | 10 | class ConfigAssemblyLoader 11 | { 12 | public static Assembly LoadConfigAssembly(AssemblyDefinition assembly) 13 | { 14 | var references = assembly.MainModule.AssemblyReferences; 15 | foreach (var reference in references) 16 | { 17 | if (reference.Name == "mscorlib" || reference.Name == "System" || reference.Name.StartsWith("System.")) 18 | { 19 | reference.Name = "netstandard"; 20 | reference.Version = new Version(2, 0, 0, 0); 21 | reference.PublicKeyToken = null; 22 | } 23 | } 24 | 25 | var targetFrameworkAttribute = assembly.CustomAttributes.FirstOrDefault(attr => attr.AttributeType.Name == "TargetFrameworkAttribute"); 26 | if (targetFrameworkAttribute != null) 27 | { 28 | targetFrameworkAttribute.ConstructorArguments.Clear(); 29 | targetFrameworkAttribute.ConstructorArguments.Add(new CustomAttributeArgument( 30 | assembly.MainModule.TypeSystem.String, ".NETStandard,Version=v2.0")); 31 | targetFrameworkAttribute.Properties.Clear(); 32 | targetFrameworkAttribute.Properties.Add(new Mono.Cecil.CustomAttributeNamedArgument( 33 | "FrameworkDisplayName", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, ".NET Standard 2.0"))); 34 | } 35 | 36 | var stream = new MemoryStream(); 37 | assembly.Write(stream); 38 | FixLoadedAssemblyResolution(); 39 | return AppDomain.CurrentDomain.Load(stream.ToArray()); 40 | } 41 | 42 | private static bool FixedLoadedAssemblyResolution = false; 43 | 44 | // XXX: Why, without this, the already loaded assemblies are not resolved? 45 | public static void FixLoadedAssemblyResolution() 46 | { 47 | if (FixedLoadedAssemblyResolution) 48 | { 49 | return; 50 | } 51 | FixedLoadedAssemblyResolution = true; 52 | 53 | var loadedAssemblies = new Dictionary(); 54 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 55 | { 56 | loadedAssemblies[assembly.FullName] = assembly; 57 | } 58 | 59 | AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => 60 | { 61 | if (loadedAssemblies.TryGetValue(args.Name, out var assembly)) 62 | { 63 | return assembly; 64 | } 65 | return null; 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AquaMai.Config.HeadlessLoader/CustomAssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | namespace AquaMai.Config.HeadlessLoader; 4 | 5 | public class CustomAssemblyResolver : DefaultAssemblyResolver 6 | { 7 | public new void RegisterAssembly(AssemblyDefinition assembly) 8 | { 9 | base.RegisterAssembly(assembly); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AquaMai.Config.HeadlessLoader/HeadlessConfigInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using AquaMai.Config.Interfaces; 4 | using Mono.Cecil; 5 | 6 | namespace AquaMai.Config.HeadlessLoader; 7 | 8 | public class HeadlessConfigInterface 9 | { 10 | private readonly Assembly loadedConfigAssembly; 11 | 12 | public IReflectionProvider ReflectionProvider { get; init; } 13 | public IReflectionManager ReflectionManager { get; init; } 14 | 15 | public string ApiVersion { get; init; } 16 | 17 | public HeadlessConfigInterface(Assembly loadedConfigAssembly, AssemblyDefinition modsAssembly) 18 | { 19 | this.loadedConfigAssembly = loadedConfigAssembly; 20 | 21 | ReflectionProvider = Activator.CreateInstance( 22 | loadedConfigAssembly.GetType("AquaMai.Config.Reflection.MonoCecilReflectionProvider"), [modsAssembly]) as IReflectionProvider; 23 | ReflectionManager = Activator.CreateInstance( 24 | loadedConfigAssembly.GetType("AquaMai.Config.Reflection.ReflectionManager"), [ReflectionProvider]) as IReflectionManager; 25 | ApiVersion = loadedConfigAssembly 26 | .GetType("AquaMai.Config.ApiVersion") 27 | .GetField("Version", BindingFlags.Public | BindingFlags.Static) 28 | .GetRawConstantValue() as string; 29 | } 30 | 31 | public IConfigView CreateConfigView(string tomlString = null) 32 | { 33 | return Activator.CreateInstance( 34 | loadedConfigAssembly.GetType("AquaMai.Config.ConfigView"), 35 | tomlString == null ? [] : [tomlString]) as IConfigView; 36 | } 37 | 38 | public IConfig CreateConfig() 39 | { 40 | return Activator.CreateInstance( 41 | loadedConfigAssembly.GetType("AquaMai.Config.Config"), [ReflectionManager]) as IConfig; 42 | } 43 | 44 | public IConfigParser GetConfigParser() 45 | { 46 | return loadedConfigAssembly 47 | .GetType("AquaMai.Config.ConfigParser") 48 | .GetField("Instance", BindingFlags.Public | BindingFlags.Static) 49 | .GetValue(null) as IConfigParser; 50 | } 51 | 52 | public IConfigSerializer CreateConfigSerializer(IConfigSerializer.Options options) 53 | { 54 | return Activator.CreateInstance( 55 | loadedConfigAssembly.GetType("AquaMai.Config.ConfigSerializer"), [options]) as IConfigSerializer; 56 | } 57 | 58 | public IConfigMigrationManager GetConfigMigrationManager() 59 | { 60 | return loadedConfigAssembly 61 | .GetType("AquaMai.Config.Migration.ConfigMigrationManager") 62 | .GetField("Instance", BindingFlags.Public | BindingFlags.Static) 63 | .GetValue(null) as IConfigMigrationManager; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /AquaMai.Config.HeadlessLoader/HeadlessConfigLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | 7 | namespace AquaMai.Config.HeadlessLoader; 8 | 9 | public class HeadlessConfigLoader 10 | { 11 | public static HeadlessConfigInterface LoadFromPacked(string fileName) 12 | { 13 | using var file = new FileStream(fileName, FileMode.Open); 14 | return LoadFromPacked(file); 15 | } 16 | 17 | public static HeadlessConfigInterface LoadFromPacked(byte[] assemblyBinary) 18 | => LoadFromPacked(new MemoryStream(assemblyBinary)); 19 | 20 | public static HeadlessConfigInterface LoadFromPacked(Stream assemblyStream) 21 | => LoadFromPacked(AssemblyDefinition.ReadAssembly(assemblyStream)); 22 | 23 | public static HeadlessConfigInterface LoadFromPacked(AssemblyDefinition assembly) 24 | { 25 | return LoadFromUnpacked( 26 | ResourceLoader.LoadEmbeddedAssemblies(assembly).Values); 27 | } 28 | 29 | public static HeadlessConfigInterface LoadFromUnpacked(IEnumerable assemblyBinariess) => 30 | LoadFromUnpacked(assemblyBinariess.Select(binary => new MemoryStream(binary))); 31 | 32 | public static HeadlessConfigInterface LoadFromUnpacked(IEnumerable assemblyStreams) 33 | { 34 | var resolver = new CustomAssemblyResolver(); 35 | var assemblies = assemblyStreams 36 | .Select( 37 | assemblyStream => 38 | AssemblyDefinition.ReadAssembly( 39 | assemblyStream, 40 | new ReaderParameters() { 41 | AssemblyResolver = resolver 42 | })) 43 | .ToArray(); 44 | foreach (var assembly in assemblies) 45 | { 46 | resolver.RegisterAssembly(assembly); 47 | } 48 | 49 | var configAssembly = assemblies.First(assembly => assembly.Name.Name == "AquaMai.Config"); 50 | if (configAssembly == null) 51 | { 52 | throw new InvalidOperationException("AquaMai.Config assembly not found"); 53 | } 54 | var loadedConfigAssembly = ConfigAssemblyLoader.LoadConfigAssembly(configAssembly); 55 | var modsAssembly = assemblies.First(assembly => assembly.Name.Name == "AquaMai.Mods"); 56 | if (modsAssembly == null) 57 | { 58 | throw new InvalidOperationException("AquaMai.Mods assembly not found"); 59 | } 60 | return new(loadedConfigAssembly, modsAssembly); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AquaMai.Config.HeadlessLoader/Polyfills.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices 2 | { 3 | internal static class IsExternalInit {} 4 | } 5 | -------------------------------------------------------------------------------- /AquaMai.Config.HeadlessLoader/ResourceLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | 7 | namespace AquaMai.Config.HeadlessLoader; 8 | 9 | public class ResourceLoader 10 | { 11 | private const string DLL_SUFFIX = ".dll"; 12 | private const string COMPRESSED_SUFFIX = ".compressed"; 13 | private const string DLL_COMPRESSED_SUFFIX = $"{DLL_SUFFIX}{COMPRESSED_SUFFIX}"; 14 | 15 | public static Dictionary LoadEmbeddedAssemblies(AssemblyDefinition assembly) 16 | { 17 | return assembly.MainModule.Resources 18 | .Where(resource => resource.Name.ToLower().EndsWith(DLL_SUFFIX) || resource.Name.ToLower().EndsWith(DLL_COMPRESSED_SUFFIX)) 19 | .Select(LoadResource) 20 | .Where(data => data.Name != null) 21 | .ToDictionary(data => data.Name, data => data.Stream); 22 | } 23 | 24 | public static (string Name, Stream Stream) LoadResource(Resource resource) 25 | { 26 | if (resource is EmbeddedResource embeddedResource) 27 | { 28 | if (resource.Name.ToLower().EndsWith(COMPRESSED_SUFFIX)) 29 | { 30 | var decompressedStream = new MemoryStream(); 31 | using (var deflateStream = new DeflateStream(embeddedResource.GetResourceStream(), CompressionMode.Decompress)) 32 | { 33 | deflateStream.CopyTo(decompressedStream); 34 | } 35 | decompressedStream.Position = 0; 36 | return (resource.Name.Substring(0, resource.Name.Length - COMPRESSED_SUFFIX.Length), decompressedStream); 37 | } 38 | return (resource.Name, embeddedResource.GetResourceStream()); 39 | } 40 | return (null, null); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Release 5 | AnyCPU 6 | {DF1536F9-3B06-4463-B654-4CC3E708B610} 7 | Library 8 | AquaMai.Config.Interfaces 9 | AquaMai.Config.Interfaces 10 | net472 11 | 512 12 | true 13 | 12 14 | 414 15 | $(ProjectDir)../Libs/;$(AssemblySearchPaths) 16 | $(ProjectDir)../Output/ 17 | false 18 | false 19 | 20 | 21 | 22 | false 23 | None 24 | true 25 | prompt 26 | 4 27 | true 28 | false 29 | 30 | 31 | 32 | DEBUG 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AquaMai.Config.Interfaces; 4 | 5 | public interface IConfig 6 | { 7 | public interface IEntryState 8 | { 9 | public bool IsDefault { get; } 10 | public object DefaultValue { get; } 11 | public object Value { get; set; } 12 | } 13 | 14 | public interface ISectionState 15 | { 16 | public bool IsDefault { get; set; } 17 | public bool DefaultEnabled { get; } 18 | public bool Enabled { get; set; } 19 | } 20 | 21 | public IReflectionManager ReflectionManager { get; } 22 | 23 | public ISectionState GetSectionState(IReflectionManager.ISection section); 24 | public ISectionState GetSectionState(Type type); 25 | public void SetSectionEnabled(IReflectionManager.ISection section, bool enabled); 26 | public IEntryState GetEntryState(IReflectionManager.IEntry entry); 27 | public void SetEntryValue(IReflectionManager.IEntry entry, object value); 28 | } 29 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfigComment.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Interfaces; 2 | 3 | public interface IConfigComment 4 | { 5 | string CommentEn { get; init; } 6 | string CommentZh { get; init; } 7 | public string GetLocalized(string lang); 8 | } 9 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfigEntryAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Interfaces; 2 | 3 | public interface IConfigEntryAttribute 4 | { 5 | IConfigComment Comment { get; } 6 | bool HideWhenDefault { get; } 7 | } -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfigMigrationManager.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Interfaces; 2 | 3 | public interface IConfigMigrationManager 4 | { 5 | public IConfigView Migrate(IConfigView config); 6 | public string GetVersion(IConfigView config); 7 | public string LatestVersion { get; } 8 | } 9 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfigParser.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Interfaces; 2 | 3 | public interface IConfigParser 4 | { 5 | public void Parse(IConfig config, string tomlString); 6 | public void Parse(IConfig config, IConfigView configView); 7 | } 8 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfigSectionAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Interfaces; 2 | 3 | public interface IConfigSectionAttribute 4 | { 5 | IConfigComment Comment { get; } 6 | bool ExampleHidden { get; } 7 | bool DefaultOn { get; } 8 | bool AlwaysEnabled { get; } 9 | } -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfigSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Interfaces; 2 | 3 | public interface IConfigSerializer 4 | { 5 | public record Options 6 | { 7 | public string Lang { get; init; } 8 | public bool IncludeBanner { get; init; } 9 | public bool OverrideLocaleValue { get; init; } 10 | } 11 | 12 | public string Serialize(IConfig config); 13 | } 14 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IConfigView.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Interfaces; 2 | 3 | public interface IConfigView 4 | { 5 | public void SetValue(string path, object value); 6 | public T GetValueOrDefault(string path, T defaultValue = default); 7 | public bool TryGetValue(string path, out T resultValue); 8 | public bool Remove(string path); 9 | public bool IsSectionEnabled(string path); 10 | public string ToToml(); 11 | public IConfigView Clone(); 12 | } 13 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IReflectionManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System; 3 | 4 | namespace AquaMai.Config.Interfaces; 5 | 6 | public interface IReflectionManager 7 | { 8 | public interface IEntry 9 | { 10 | public string Path { get; } 11 | public string Name { get; } 12 | public IReflectionField Field { get; } 13 | public IConfigEntryAttribute Attribute { get; init; } 14 | } 15 | 16 | public interface ISection 17 | { 18 | public string Path { get; } 19 | public IReflectionType Type { get; } 20 | public List Entries { get; } 21 | public IConfigSectionAttribute Attribute { get; init; } 22 | } 23 | 24 | public IEnumerable Sections { get; } 25 | 26 | public IEnumerable Entries { get; } 27 | 28 | public bool ContainsSection(string path); 29 | 30 | public bool TryGetSection(string path, out ISection section); 31 | 32 | public bool TryGetSection(Type type, out ISection section); 33 | 34 | public ISection GetSection(string path); 35 | 36 | public ISection GetSection(Type type); 37 | 38 | public bool ContainsEntry(string path); 39 | 40 | public bool TryGetEntry(string path, out IEntry entry); 41 | 42 | public IEntry GetEntry(string path); 43 | } 44 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/IReflectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace AquaMai.Config.Interfaces; 6 | 7 | public interface IReflectionField 8 | { 9 | public string Name { get; } 10 | public Type FieldType { get; } 11 | 12 | public T GetCustomAttribute() where T : Attribute; 13 | public object GetValue(object objIsNull); 14 | public void SetValue(object objIsNull, object value); 15 | } 16 | 17 | public interface IReflectionType 18 | { 19 | public string FullName { get; } 20 | public string Namespace { get; } 21 | 22 | public T GetCustomAttribute() where T : Attribute; 23 | public IReflectionField[] GetFields(BindingFlags bindingAttr); 24 | } 25 | 26 | public interface IReflectionProvider 27 | { 28 | public IReflectionType[] GetTypes(); 29 | public Dictionary GetEnum(string enumName); 30 | } 31 | -------------------------------------------------------------------------------- /AquaMai.Config.Interfaces/Polyfills.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices 2 | { 3 | internal static class IsExternalInit {} 4 | } 5 | -------------------------------------------------------------------------------- /AquaMai.Config/ApiVersion.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config; 2 | 3 | public static class ApiVersion 4 | { 5 | // Using a raw string for API version instead of a constant for maximum compatibility. 6 | // When breaking changes are made, increment the major version. 7 | // When new APIs are added in a backwards-compatible but non-forward-compatible manner, increment the minor version. 8 | public const string Version = "1.0"; 9 | } 10 | -------------------------------------------------------------------------------- /AquaMai.Config/AquaMai.Config.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Release 5 | AnyCPU 6 | {DF1536F9-3B06-4463-B654-4CC3E708B610} 7 | Library 8 | AquaMai.Config 9 | AquaMai.Config 10 | net472 11 | 512 12 | true 13 | 12 14 | 414 15 | $(ProjectDir)../Libs/;$(AssemblySearchPaths) 16 | $(ProjectDir)../Output/ 17 | false 18 | false 19 | 20 | 21 | 22 | false 23 | None 24 | true 25 | prompt 26 | 4 27 | true 28 | false 29 | 30 | 31 | 32 | DEBUG 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | runtime; build; native; contentfiles; analyzers; buildtransitive 52 | all 53 | 54 | 55 | all 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /AquaMai.Config/Attributes/ConfigCollapseNamespaceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AquaMai.Config.Attributes; 4 | 5 | // When The most inner namespace is the same name of the class, it should be collapsed. 6 | // The class must be the only class in the namespace with a [ConfigSection] attribute. 7 | [AttributeUsage(AttributeTargets.Class)] 8 | public class ConfigCollapseNamespaceAttribute : Attribute 9 | {} 10 | -------------------------------------------------------------------------------- /AquaMai.Config/Attributes/ConfigComment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AquaMai.Config.Interfaces; 3 | 4 | namespace AquaMai.Config.Attributes; 5 | 6 | public record ConfigComment(string CommentEn, string CommentZh) : IConfigComment 7 | { 8 | public string GetLocalized(string lang) => lang switch 9 | { 10 | "en" => CommentEn ?? "", 11 | "zh" => CommentZh ?? "", 12 | _ => throw new ArgumentException($"Unsupported language: {lang}") 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /AquaMai.Config/Attributes/ConfigEntryAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AquaMai.Config.Interfaces; 3 | 4 | namespace AquaMai.Config.Attributes; 5 | 6 | public enum SpecialConfigEntry 7 | { 8 | None, 9 | Locale 10 | } 11 | 12 | [AttributeUsage(AttributeTargets.Field)] 13 | public class ConfigEntryAttribute( 14 | string en = null, 15 | string zh = null, 16 | // NOTE: Don't use this argument to hide any useful options. 17 | // Only use it to hide options that really won't be used. 18 | bool hideWhenDefault = false, 19 | // NOTE: Use this argument to mark special config entries that need special handling. 20 | SpecialConfigEntry specialConfigEntry = SpecialConfigEntry.None) : Attribute, IConfigEntryAttribute 21 | { 22 | public IConfigComment Comment { get; } = new ConfigComment(en, zh); 23 | public bool HideWhenDefault { get; } = hideWhenDefault; 24 | public SpecialConfigEntry SpecialConfigEntry { get; } = specialConfigEntry; 25 | } 26 | -------------------------------------------------------------------------------- /AquaMai.Config/Attributes/ConfigSectionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AquaMai.Config.Interfaces; 3 | 4 | namespace AquaMai.Config.Attributes; 5 | 6 | [AttributeUsage(AttributeTargets.Class)] 7 | public class ConfigSectionAttribute( 8 | string en = null, 9 | string zh = null, 10 | // It will be hidden if the default value is preserved. 11 | bool exampleHidden = false, 12 | // A "Disabled = true" entry is required to disable the section. 13 | bool defaultOn = false, 14 | // NOTE: You probably shouldn't use this. Only the "General" section is using this. 15 | // Implies defaultOn = true. 16 | bool alwaysEnabled = false) : Attribute, IConfigSectionAttribute 17 | { 18 | public IConfigComment Comment { get; } = new ConfigComment(en, zh); 19 | public bool ExampleHidden { get; } = exampleHidden; 20 | public bool DefaultOn { get; } = defaultOn || alwaysEnabled; 21 | public bool AlwaysEnabled { get; } = alwaysEnabled; 22 | } 23 | -------------------------------------------------------------------------------- /AquaMai.Config/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | tomlet 5 | $AquaMai.Config$_ 6 | 7 | 8 | -------------------------------------------------------------------------------- /AquaMai.Config/Migration/ConfigMigrationManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using AquaMai.Config.Interfaces; 5 | 6 | namespace AquaMai.Config.Migration; 7 | 8 | public class ConfigMigrationManager : IConfigMigrationManager 9 | { 10 | public static readonly ConfigMigrationManager Instance = new(); 11 | 12 | private readonly Dictionary migrationMap = 13 | new List 14 | { 15 | new ConfigMigration_V1_0_V2_0(), 16 | new ConfigMigration_V2_0_V2_1(), 17 | new ConfigMigration_V2_1_V2_2(), 18 | }.ToDictionary(m => m.FromVersion); 19 | 20 | public string LatestVersion { get; } 21 | 22 | private ConfigMigrationManager() 23 | { 24 | LatestVersion = migrationMap.Values 25 | .Select(m => m.ToVersion) 26 | .OrderByDescending(version => 27 | { 28 | var versionParts = version.Split('.').Select(int.Parse).ToArray(); 29 | return versionParts[0] * 100000 + versionParts[1]; 30 | }) 31 | .First(); 32 | } 33 | 34 | public IConfigView Migrate(IConfigView config) 35 | { 36 | var currentVersion = GetVersion(config); 37 | while (migrationMap.ContainsKey(currentVersion)) 38 | { 39 | var migration = migrationMap[currentVersion]; 40 | Utility.Log($"Migrating config from v{migration.FromVersion} to v{migration.ToVersion}"); 41 | config = migration.Migrate((ConfigView)config); 42 | currentVersion = migration.ToVersion; 43 | } 44 | 45 | if (currentVersion != LatestVersion) 46 | { 47 | throw new ArgumentException($"Could not migrate the config from v{currentVersion} to v{LatestVersion}"); 48 | } 49 | 50 | return config; 51 | } 52 | 53 | public string GetVersion(IConfigView config) 54 | { 55 | if (config.TryGetValue("Version", out var version)) 56 | { 57 | return version; 58 | } 59 | 60 | // Assume v1.0 if not found 61 | return "1.0"; 62 | } 63 | } -------------------------------------------------------------------------------- /AquaMai.Config/Migration/ConfigMigration_V2_0_V2_1.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Interfaces; 2 | using Tomlet.Models; 3 | 4 | namespace AquaMai.Config.Migration; 5 | 6 | public class ConfigMigration_V2_0_V2_1 : IConfigMigration 7 | { 8 | public string FromVersion => "2.0"; 9 | public string ToVersion => "2.1"; 10 | 11 | public ConfigView Migrate(ConfigView src) 12 | { 13 | var dst = (ConfigView)src.Clone(); 14 | dst.SetValue("Version", ToVersion); 15 | 16 | if (src.IsSectionEnabled("Tweaks.ResetTouchAfterTrack")) 17 | { 18 | dst.Remove("Tweaks.ResetTouchAfterTrack"); 19 | dst.SetValue("Tweaks.ResetTouch.AfterTrack", true); 20 | } 21 | 22 | return dst; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AquaMai.Config/Migration/ConfigMigration_V2_1_V2_2.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Interfaces; 2 | using Tomlet.Models; 3 | 4 | namespace AquaMai.Config.Migration; 5 | 6 | public class ConfigMigration_V2_1_V2_2 : IConfigMigration 7 | { 8 | public string FromVersion => "2.1"; 9 | public string ToVersion => "2.2"; 10 | 11 | public ConfigView Migrate(ConfigView src) 12 | { 13 | var dst = (ConfigView)src.Clone(); 14 | dst.SetValue("Version", ToVersion); 15 | 16 | if (src.IsSectionEnabled("GameSystem.Assets.UseJacketAsDummyMovie")) 17 | { 18 | dst.Remove("GameSystem.Assets.UseJacketAsDummyMovie"); 19 | dst.SetValue("GameSystem.Assets.MovieLoader.JacketAsMovie", true); 20 | } 21 | 22 | if (src.TryGetValue("GameSystem.Assets.LoadLocalImages.LocalAssetsDir", out var localAssetsDir)) 23 | { 24 | dst.SetValue("GameSystem.Assets.LoadLocalImages.ImageAssetsDir", localAssetsDir); 25 | dst.Remove("GameSystem.Assets.LoadLocalImages.LocalAssetsDir"); 26 | } 27 | 28 | return dst; 29 | } 30 | } -------------------------------------------------------------------------------- /AquaMai.Config/Migration/IConfigMigration.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Interfaces; 2 | 3 | namespace AquaMai.Config.Migration; 4 | 5 | public interface IConfigMigration 6 | { 7 | public string FromVersion { get; } 8 | public string ToVersion { get; } 9 | public ConfigView Migrate(ConfigView config); 10 | } 11 | -------------------------------------------------------------------------------- /AquaMai.Config/Polyfills.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices 2 | { 3 | internal static class IsExternalInit {} 4 | } 5 | -------------------------------------------------------------------------------- /AquaMai.Config/Reflection/SystemReflectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using AquaMai.Config.Interfaces; 5 | 6 | namespace AquaMai.Config.Reflection; 7 | 8 | public class SystemReflectionProvider(Assembly assembly) : IReflectionProvider 9 | { 10 | public class ReflectionField(FieldInfo field) : IReflectionField 11 | { 12 | public FieldInfo UnderlyingField { get; } = field; 13 | 14 | public string Name => UnderlyingField.Name; 15 | public Type FieldType => UnderlyingField.FieldType; 16 | public T GetCustomAttribute() where T : Attribute => UnderlyingField.GetCustomAttribute(); 17 | public object GetValue(object obj) => UnderlyingField.GetValue(obj); 18 | public void SetValue(object obj, object value) => UnderlyingField.SetValue(obj, value); 19 | } 20 | 21 | public class ReflectionType(Type type) : IReflectionType 22 | { 23 | public Type UnderlyingType { get; } = type; 24 | 25 | public string FullName => UnderlyingType.FullName; 26 | public string Namespace => UnderlyingType.Namespace; 27 | public T GetCustomAttribute() where T : Attribute => UnderlyingType.GetCustomAttribute(); 28 | public IReflectionField[] GetFields(BindingFlags bindingAttr) => Array.ConvertAll(UnderlyingType.GetFields(bindingAttr), f => new ReflectionField(f)); 29 | } 30 | 31 | public Assembly UnderlyingAssembly { get; } = assembly; 32 | 33 | public IReflectionType[] GetTypes() => Array.ConvertAll(UnderlyingAssembly.GetTypes(), t => new ReflectionType(t)); 34 | 35 | public Dictionary GetEnum(string enumName) 36 | { 37 | var enumType = UnderlyingAssembly.GetType(enumName); 38 | if (enumType == null) return null; 39 | var enumValues = Enum.GetValues(enumType); 40 | var enumDict = new Dictionary(); 41 | foreach (var enumValue in enumValues) 42 | { 43 | enumDict.Add(enumValue.ToString(), enumValue); 44 | } 45 | return enumDict; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /AquaMai.Config/Types/AdxKeyMap.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Types; 2 | 3 | public enum AdxKeyMap 4 | { 5 | None = 0, 6 | Select1P, 7 | Select2P, 8 | Service, 9 | Test, 10 | } -------------------------------------------------------------------------------- /AquaMai.Config/Types/KeyCodeID.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Types; 2 | 3 | public enum KeyCodeID 4 | { 5 | None, 6 | Backspace, 7 | Tab, 8 | Clear, 9 | Return, 10 | Pause, 11 | Escape, 12 | Space, 13 | Exclaim, 14 | DoubleQuote, 15 | Hash, 16 | Dollar, 17 | Ampersand, 18 | Quote, 19 | LeftParen, 20 | RightParen, 21 | Asterisk, 22 | Plus, 23 | Comma, 24 | Minus, 25 | Period, 26 | Slash, 27 | Alpha0, 28 | Alpha1, 29 | Alpha2, 30 | Alpha3, 31 | Alpha4, 32 | Alpha5, 33 | Alpha6, 34 | Alpha7, 35 | Alpha8, 36 | Alpha9, 37 | Colon, 38 | Semicolon, 39 | Less, 40 | Equals, 41 | Greater, 42 | Question, 43 | At, 44 | LeftBracket, 45 | Backslash, 46 | RightBracket, 47 | Caret, 48 | Underscore, 49 | BackQuote, 50 | A, 51 | B, 52 | C, 53 | D, 54 | E, 55 | F, 56 | G, 57 | H, 58 | I, 59 | J, 60 | K, 61 | L, 62 | M, 63 | N, 64 | O, 65 | P, 66 | Q, 67 | R, 68 | S, 69 | T, 70 | U, 71 | V, 72 | W, 73 | X, 74 | Y, 75 | Z, 76 | Delete, 77 | Keypad0, 78 | Keypad1, 79 | Keypad2, 80 | Keypad3, 81 | Keypad4, 82 | Keypad5, 83 | Keypad6, 84 | Keypad7, 85 | Keypad8, 86 | Keypad9, 87 | KeypadPeriod, 88 | KeypadDivide, 89 | KeypadMultiply, 90 | KeypadMinus, 91 | KeypadPlus, 92 | KeypadEnter, 93 | KeypadEquals, 94 | UpArrow, 95 | DownArrow, 96 | RightArrow, 97 | LeftArrow, 98 | Insert, 99 | Home, 100 | End, 101 | PageUp, 102 | PageDown, 103 | F1, 104 | F2, 105 | F3, 106 | F4, 107 | F5, 108 | F6, 109 | F7, 110 | F8, 111 | F9, 112 | F10, 113 | F11, 114 | F12, 115 | F13, 116 | F14, 117 | F15, 118 | Numlock, 119 | CapsLock, 120 | ScrollLock, 121 | RightShift, 122 | LeftShift, 123 | RightControl, 124 | LeftControl, 125 | RightAlt, 126 | LeftAlt, 127 | RightCommand, 128 | RightApple, 129 | LeftCommand, 130 | LeftApple, 131 | LeftWindows, 132 | RightWindows, 133 | AltGr, 134 | Help, 135 | Print, 136 | SysReq, 137 | Break, 138 | Menu, 139 | Mouse0, 140 | Mouse1, 141 | Mouse2, 142 | Mouse3, 143 | Mouse4, 144 | Mouse5, 145 | Mouse6, 146 | } 147 | -------------------------------------------------------------------------------- /AquaMai.Config/Types/KeyCodeOrName.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.Config.Types; 2 | 3 | public enum KeyCodeOrName 4 | { 5 | None, 6 | Alpha0, 7 | Alpha1, 8 | Alpha2, 9 | Alpha3, 10 | Alpha4, 11 | Alpha5, 12 | Alpha6, 13 | Alpha7, 14 | Alpha8, 15 | Alpha9, 16 | Keypad0, 17 | Keypad1, 18 | Keypad2, 19 | Keypad3, 20 | Keypad4, 21 | Keypad5, 22 | Keypad6, 23 | Keypad7, 24 | Keypad8, 25 | Keypad9, 26 | F1, 27 | F2, 28 | F3, 29 | F4, 30 | F5, 31 | F6, 32 | F7, 33 | F8, 34 | F9, 35 | F10, 36 | F11, 37 | F12, 38 | Insert, 39 | Delete, 40 | Home, 41 | End, 42 | PageUp, 43 | PageDown, 44 | UpArrow, 45 | DownArrow, 46 | LeftArrow, 47 | RightArrow, 48 | 49 | Select1P, 50 | Select2P, 51 | Service, 52 | Test, 53 | } 54 | -------------------------------------------------------------------------------- /AquaMai.Core/Attributes/EnableGameVersionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AquaMai.Core.Attributes; 4 | 5 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] 6 | public class EnableGameVersionAttribute(uint minVersion = 0, uint maxVersion = 0, bool noWarn = false) : Attribute 7 | { 8 | public uint MinVersion { get; } = minVersion; 9 | public uint MaxVersion { get; } = maxVersion; 10 | public bool NoWarn { get; } = noWarn; 11 | 12 | public bool ShouldEnable(uint gameVersion) 13 | { 14 | if (MinVersion > 0 && MinVersion > gameVersion) return false; 15 | if (MaxVersion > 0 && MaxVersion < gameVersion) return false; 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AquaMai.Core/Attributes/EnableImplicitlyIfAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AquaMai.Core.Attributes; 4 | 5 | // If the field or property with this name is true, the patch will be implicitly enabled, regardless of the config state. 6 | // This is handled outside the config module, while The config state won't be actually set to enabled by it. 7 | // Won't bypass the restriction of [EnableIf()] and [EnableGameVersion()]. 8 | [AttributeUsage(AttributeTargets.Class)] 9 | public class EnableImplicitlyIf(string memberName) : Attribute 10 | { 11 | public string MemberName { get; } = memberName; 12 | } 13 | -------------------------------------------------------------------------------- /AquaMai.Core/BuildInfo.cs: -------------------------------------------------------------------------------- 1 | using MelonLoader; 2 | 3 | namespace AquaMai.Core; 4 | 5 | public static class BuildInfo 6 | { 7 | // The fields are set in `AquaMai/Main.cs` with reflection. 8 | // The values are from `AquaMai/BuildInfo.cs`. 9 | 10 | public static string Name; 11 | public static string Description; 12 | public static string Author; 13 | public static string Company; 14 | public static string Version; 15 | public static string GitVersion; 16 | public static string BuildDate; 17 | public static string DownloadLink; 18 | 19 | public static MelonAssembly ModAssembly; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /AquaMai.Core/ConfigLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Reflection; 4 | using MelonLoader; 5 | using AquaMai.Config; 6 | using AquaMai.Config.Interfaces; 7 | using AquaMai.Config.Migration; 8 | 9 | namespace AquaMai.Core; 10 | 11 | public static class ConfigLoader 12 | { 13 | private static string ConfigFile => "AquaMai.toml"; 14 | private static string ConfigExampleFile(string lang) => $"AquaMai.{lang}.toml"; 15 | private static string OldConfigFile(string version) => $"AquaMai.toml.old-v{version}."; 16 | 17 | private static Config.Config config; 18 | 19 | public static Config.Config Config => config; 20 | 21 | public static bool LoadConfig(Assembly modsAssembly) 22 | { 23 | Utility.LogFunction = MelonLogger.Msg; 24 | 25 | config = new( 26 | new Config.Reflection.ReflectionManager( 27 | new Config.Reflection.SystemReflectionProvider(modsAssembly))); 28 | 29 | if (!File.Exists(ConfigFile)) 30 | { 31 | var examples = GenerateExamples(); 32 | foreach (var (lang, example) in examples) 33 | { 34 | var filename = ConfigExampleFile(lang); 35 | File.WriteAllText(filename, example); 36 | } 37 | MelonLogger.Error("======================================!!!"); 38 | MelonLogger.Error("AquaMai.toml not found! Please create it."); 39 | MelonLogger.Error("找不到配置文件 AquaMai.toml!请创建。"); 40 | MelonLogger.Error("Example copied to AquaMai.en.toml"); 41 | MelonLogger.Error("示例已复制到 AquaMai.zh.toml"); 42 | MelonLogger.Error("========================================="); 43 | return false; 44 | } 45 | 46 | var configText = File.ReadAllText(ConfigFile); 47 | var configView = new ConfigView(configText); 48 | var configVersion = ConfigMigrationManager.Instance.GetVersion(configView); 49 | if (configVersion != ConfigMigrationManager.Instance.LatestVersion) 50 | { 51 | File.WriteAllText(OldConfigFile(configVersion), configText); 52 | configView = (ConfigView)ConfigMigrationManager.Instance.Migrate(configView); 53 | } 54 | 55 | // Read AquaMai.toml to load settings 56 | ConfigParser.Instance.Parse(config, configView); 57 | 58 | return true; 59 | } 60 | 61 | public static void SaveConfig(string lang) 62 | { 63 | File.WriteAllText(ConfigFile, SerailizeCurrentConfig(lang)); 64 | } 65 | 66 | private static string SerailizeCurrentConfig(string lang) => 67 | new ConfigSerializer(new IConfigSerializer.Options() 68 | { 69 | Lang = lang, 70 | IncludeBanner = true, 71 | OverrideLocaleValue = true 72 | }).Serialize(config); 73 | 74 | private static IDictionary GenerateExamples() 75 | { 76 | var examples = new Dictionary(); 77 | foreach (var lang in (string[]) ["en", "zh"]) 78 | { 79 | examples[lang] = SerailizeCurrentConfig(lang); 80 | } 81 | return examples; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/EnableConditionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Reflection; 4 | using AquaMai.Core.Attributes; 5 | using AquaMai.Core.Resources; 6 | using HarmonyLib; 7 | using MelonLoader; 8 | 9 | namespace AquaMai.Core.Helpers; 10 | 11 | public class EnableConditionHelper 12 | { 13 | [HarmonyPostfix] 14 | [HarmonyPatch("HarmonyLib.PatchTools", "GetPatchMethod")] 15 | public static void PostGetPatchMethod(ref MethodInfo __result) 16 | { 17 | if (__result != null) 18 | { 19 | if (ShouldSkipMethodOrClass(__result.GetCustomAttribute, __result.ReflectedType, __result.Name)) 20 | { 21 | __result = null; 22 | } 23 | } 24 | } 25 | 26 | [HarmonyPostfix] 27 | [HarmonyPatch("HarmonyLib.PatchTools", "GetPatchMethods")] 28 | public static void PostGetPatchMethods(ref IList __result) 29 | { 30 | for (int i = 0; i < __result.Count; i++) 31 | { 32 | var harmonyMethod = Traverse.Create(__result[i]).Field("info").GetValue() as HarmonyMethod; 33 | var method = harmonyMethod.method; 34 | if (ShouldSkipMethodOrClass(method.GetCustomAttribute, method.ReflectedType, method.Name)) 35 | { 36 | __result.RemoveAt(i); 37 | i--; 38 | } 39 | } 40 | } 41 | 42 | public static bool ShouldSkipClass(Type type) 43 | { 44 | return ShouldSkipMethodOrClass(type.GetCustomAttribute, type); 45 | } 46 | 47 | private static bool ShouldSkipMethodOrClass(Func getCustomAttribute, Type type, string methodName = "") 48 | { 49 | var displayName = type.FullName + (string.IsNullOrEmpty(methodName) ? "" : $".{methodName}"); 50 | var enableIf = (EnableIfAttribute)getCustomAttribute(typeof(EnableIfAttribute)); 51 | if (enableIf != null && !enableIf.ShouldEnable(type)) 52 | { 53 | # if DEBUG 54 | MelonLogger.Msg($"Skipping {displayName} due to EnableIf condition"); 55 | # endif 56 | return true; 57 | } 58 | var enableGameVersion = (EnableGameVersionAttribute)getCustomAttribute(typeof(EnableGameVersionAttribute)); 59 | if (enableGameVersion != null && !enableGameVersion.ShouldEnable(GameInfo.GameVersion)) 60 | { 61 | # if DEBUG 62 | MelonLogger.Msg($"Skipping {displayName} due to EnableGameVersion condition"); 63 | # endif 64 | if (!enableGameVersion.NoWarn) 65 | { 66 | MelonLogger.Warning(string.Format(Locale.SkipIncompatiblePatch, type)); 67 | } 68 | return true; 69 | } 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/FileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace AquaMai.Core.Helpers; 5 | 6 | public static class FileSystem 7 | { 8 | public static string ResolvePath(string path) 9 | { 10 | var varExpanded = Environment.ExpandEnvironmentVariables(path); 11 | return Path.IsPathRooted(varExpanded) 12 | ? varExpanded 13 | : Path.Combine(Environment.CurrentDirectory, varExpanded); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/GameInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using MAI2System; 3 | 4 | namespace AquaMai.Core.Helpers; 5 | 6 | public class GameInfo 7 | { 8 | public static uint GameVersion { get; } = GetGameVersion(); 9 | 10 | private static uint GetGameVersion() 11 | { 12 | return (uint)typeof(ConstParameter).GetField("NowGameVersion", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null); 13 | } 14 | 15 | public static string GameId { get; } = GetGameId(); 16 | 17 | private static string GetGameId() 18 | { 19 | return typeof(ConstParameter).GetField("GameIDStr", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null) as string; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/GuiSizes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using HarmonyLib; 5 | using UnityEngine; 6 | 7 | namespace AquaMai.Core.Helpers; 8 | 9 | public static class GuiSizes 10 | { 11 | public static bool SinglePlayer { get; set; } = false; 12 | public static float PlayerWidth => Screen.height / 1920f * 1080; 13 | public static float PlayerCenter => SinglePlayer ? Screen.width / 2f : Screen.width / 2f - PlayerWidth / 2; 14 | public static int FontSize => (int)(PlayerWidth * .015f); 15 | public static float LabelHeight => FontSize * 1.5f; 16 | public static float Margin => PlayerWidth * .005f; 17 | 18 | private static Color backgroundColor = new(147 / 256f, 160 / 256f, 173 / 256f, .8f); 19 | 20 | public static void SetupStyles() 21 | { 22 | var buttonStyle = GUI.skin.button; 23 | buttonStyle.normal.textColor = Color.white; 24 | buttonStyle.normal.background = Texture2D.whiteTexture; 25 | buttonStyle.hover.background = Texture2D.whiteTexture; 26 | buttonStyle.active.background = Texture2D.whiteTexture; 27 | buttonStyle.border = new RectOffset(0, 0, 0, 0); 28 | buttonStyle.margin = new RectOffset(0, 0, 0, 0); 29 | buttonStyle.padding = new RectOffset(10, 10, 10, 10); 30 | buttonStyle.overflow = new RectOffset(0, 0, 0, 0); 31 | 32 | var boxStyle = GUI.skin.box; 33 | boxStyle.border = new RectOffset(0, 0, 0, 0); 34 | boxStyle.normal.background = Texture2D.whiteTexture; 35 | 36 | GUI.backgroundColor = backgroundColor; 37 | } 38 | 39 | [HarmonyPatch] 40 | public class BoxBackground 41 | { 42 | public static IEnumerable TargetMethods() 43 | { 44 | return typeof(GUI).GetMethods().Where(x => x.Name == "Box"); 45 | } 46 | 47 | public static void Prefix() 48 | { 49 | GUI.backgroundColor = new Color(62 / 256f, 62 / 256f, 66 / 256f, .6f); 50 | } 51 | 52 | public static void Postfix() 53 | { 54 | GUI.backgroundColor = backgroundColor; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/JsonHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using System.Linq; 4 | using MelonLoader.TinyJSON; 5 | 6 | namespace AquaMai.Core.Helpers; 7 | 8 | public static class JsonHelper 9 | { 10 | public static bool TryToInt32(Variant variant, out int result) 11 | { 12 | if (variant is ProxyNumber proxyNumber) 13 | { 14 | try 15 | { 16 | result = proxyNumber.ToInt32(CultureInfo.InvariantCulture); 17 | return true; 18 | } 19 | catch 20 | {} 21 | } 22 | else if (variant is ProxyString proxyString) 23 | { 24 | return int.TryParse(proxyString.ToString(), out result); 25 | } 26 | result = 0; 27 | return false; 28 | } 29 | 30 | public static bool TryToInt64(Variant variant, out long result) 31 | { 32 | if (variant is ProxyNumber proxyNumber) 33 | { 34 | try 35 | { 36 | result = proxyNumber.ToInt64(CultureInfo.InvariantCulture); 37 | return true; 38 | } 39 | catch 40 | {} 41 | } 42 | else if (variant is ProxyString proxyString) 43 | { 44 | return long.TryParse(proxyString.ToString(), out result); 45 | } 46 | result = 0; 47 | return false; 48 | } 49 | 50 | public class DeepEqualityComparer : IEqualityComparer 51 | { 52 | public bool Equals(Variant a, Variant b) => DeepEqual(a, b); 53 | public int GetHashCode(Variant a) => a.ToJSON().GetHashCode(); 54 | } 55 | 56 | 57 | public static bool DeepEqual(Variant a, Variant b) => 58 | (a, b) switch { 59 | (ProxyArray arrayA, ProxyArray arrayB) => Enumerable.SequenceEqual(arrayA, arrayB, new DeepEqualityComparer()), 60 | (ProxyObject objectA, ProxyObject objectB) => 61 | objectA.Keys.Count == objectB.Keys.Count && 62 | objectA.All(pair => objectB.TryGetValue(pair.Key, out var valueB) && DeepEqual(pair.Value, valueB)), 63 | (ProxyBoolean booleanA, ProxyBoolean booleanB) => booleanA.ToBoolean(null) == booleanB.ToBoolean(null), 64 | (ProxyNumber numberA, ProxyNumber numberB) => numberA.ToString() == numberB.ToString(), 65 | (ProxyString stringA, ProxyString stringB) => stringA.ToString() == stringB.ToString(), 66 | (null, null) => true, 67 | _ => false 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/MessageHelper.cs: -------------------------------------------------------------------------------- 1 | using DB; 2 | using HarmonyLib; 3 | using Manager; 4 | using MelonLoader; 5 | using Process; 6 | using UnityEngine; 7 | 8 | namespace AquaMai.Core.Helpers; 9 | 10 | public class MessageHelper 11 | { 12 | private static IGenericManager _genericManager = null; 13 | 14 | [HarmonyPostfix] 15 | [HarmonyPatch(typeof(ProcessManager), "SetMessageManager")] 16 | private static void OnSetMessageManager(IGenericManager genericManager) 17 | { 18 | _genericManager = genericManager; 19 | } 20 | 21 | public static void ShowMessage(string message, WindowSizeID size = WindowSizeID.Middle, string title = null, Sprite sprite = null, bool retain = false) 22 | { 23 | if (_genericManager is null) 24 | { 25 | MelonLogger.Error($"[MessageHelper] Unable to show message: `{message}` GenericManager is null"); 26 | return; 27 | } 28 | 29 | _genericManager.Enqueue(0, retain ? WindowMessageID.CollectionCategorySelectAnnounce : WindowMessageID.CollectionAttentionEmptyFavorite, new WindowParam() 30 | { 31 | hideTitle = title is null, 32 | replaceTitle = true, 33 | title = title, 34 | replaceText = true, 35 | text = message, 36 | changeSize = true, 37 | sizeID = size, 38 | directSprite = sprite is not null, 39 | sprite = sprite, 40 | }); 41 | } 42 | } -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/MusicDirHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using HarmonyLib; 3 | 4 | namespace AquaMai.Core.Helpers; 5 | 6 | public class MusicDirHelper 7 | { 8 | private static Dictionary _map = new(); 9 | 10 | [HarmonyPostfix] 11 | [HarmonyPatch(typeof(Manager.MaiStudio.Serialize.MusicData), "AddPath")] 12 | private static void AddPath(Manager.MaiStudio.Serialize.MusicData __instance, string parentPath) 13 | { 14 | _map[__instance.GetID()] = parentPath; 15 | } 16 | 17 | public static string LookupPath(int id) 18 | { 19 | return _map.GetValueOrDefault(id); 20 | } 21 | 22 | public static string LookupPath(Manager.MaiStudio.Serialize.MusicData musicData) 23 | { 24 | return LookupPath(musicData.GetID()); 25 | } 26 | 27 | public static string LookupPath(Manager.MaiStudio.MusicData musicData) 28 | { 29 | return LookupPath(musicData.GetID()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/NetPacketHook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Net; 4 | using Net.Packet; 5 | using MelonLoader; 6 | using MelonLoader.TinyJSON; 7 | using HarmonyLib; 8 | using System.IO; 9 | 10 | namespace AquaMai.Core.Helpers; 11 | 12 | public class NetPacketHook 13 | { 14 | // Returns true if the packet was modified 15 | public delegate Variant NetPacketCompleteHook(string api, Variant request, Variant response); 16 | 17 | public static event NetPacketCompleteHook OnNetPacketComplete; 18 | 19 | [HarmonyPrefix] 20 | [HarmonyPatch(typeof(Packet), "ProcImpl")] 21 | public static void PreProcImpl(Packet __instance) 22 | { 23 | try 24 | { 25 | if ( 26 | __instance.State == PacketState.Process && 27 | Traverse.Create(__instance).Field("Client").GetValue() is NetHttpClient client && 28 | client.State == NetHttpClient.StateDone) 29 | { 30 | var netQuery = __instance.Query; 31 | var api = Shim.RemoveApiSuffix(netQuery.Api); 32 | var responseBytes = client.GetResponse().ToArray(); 33 | var decryptedResponse = Shim.NetHttpClientDecryptsResponse ? responseBytes : Shim.DecryptNetPacketBody(responseBytes); 34 | var decodedResponse = Encoding.UTF8.GetString(decryptedResponse); 35 | var responseJson = JSON.Load(decodedResponse); 36 | var requestJson = JSON.Load(netQuery.GetRequest()); 37 | var modified = false; 38 | foreach (var handler in OnNetPacketComplete?.GetInvocationList()) 39 | { 40 | try 41 | { 42 | if (handler.DynamicInvoke(api, requestJson, responseJson) is Variant result) 43 | { 44 | responseJson = result; 45 | modified = true; 46 | } 47 | } 48 | catch (Exception e) 49 | { 50 | MelonLogger.Error($"[NetPacketExtension] Error in handler: {e}"); 51 | } 52 | } 53 | if ( 54 | modified && 55 | Traverse.Create(client).Field("_memoryStream").GetValue() is MemoryStream memoryStream && 56 | !JsonHelper.DeepEqual(responseJson, JSON.Load(decodedResponse))) 57 | { 58 | var modifiedResponse = Encoding.UTF8.GetBytes(responseJson.ToJSON()); 59 | if (!Shim.NetHttpClientDecryptsResponse) 60 | { 61 | modifiedResponse = Shim.EncryptNetPacketBody(modifiedResponse); 62 | } 63 | memoryStream.SetLength(0); 64 | memoryStream.Write(modifiedResponse, 0, modifiedResponse.Length); 65 | memoryStream.Seek(0, SeekOrigin.Begin); 66 | MelonLogger.Msg($"[NetPacketExtension] Modified response for {api} ({decryptedResponse.Length} bytes -> {modifiedResponse.Length} bytes)"); 67 | } 68 | } 69 | } 70 | catch (Exception e) 71 | { 72 | MelonLogger.Error($"[NetPacketExtension] Failed to process NetPacket: {e}"); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /AquaMai.Core/Helpers/SharedInstances.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using Main; 3 | using Process; 4 | 5 | namespace AquaMai.Core.Helpers; 6 | 7 | public class SharedInstances 8 | { 9 | public static ProcessDataContainer ProcessDataContainer { get; private set; } 10 | public static GameMainObject GameMainObject { get; private set; } 11 | 12 | [HarmonyPrefix] 13 | [HarmonyPatch(typeof(ProcessDataContainer), MethodType.Constructor)] 14 | public static void OnCreateProcessDataContainer(ProcessDataContainer __instance) 15 | { 16 | ProcessDataContainer = __instance; 17 | } 18 | 19 | [HarmonyPrefix] 20 | [HarmonyPatch(typeof(GameMainObject), "Awake")] 21 | public static void OnCreateGameMainObject(GameMainObject __instance) 22 | { 23 | GameMainObject = __instance; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /AquaMai.Core/Resources/I18nSingleAssemblyHook.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Resources; 3 | using HarmonyLib; 4 | 5 | namespace AquaMai.Core.Resources; 6 | 7 | public class I18nSingleAssemblyHook 8 | { 9 | [HarmonyPatch(typeof(ResourceManager), "InternalGetResourceSet", typeof(CultureInfo), typeof(bool), typeof(bool))] 10 | [HarmonyPrefix] 11 | public static bool GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents, ref ResourceSet __result, ResourceManager __instance) 12 | { 13 | var GetResourceFileName = __instance.GetType().GetMethod("GetResourceFileName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); 14 | var resourceFileName = (string)GetResourceFileName.Invoke(__instance, [culture]); 15 | var ResourcesAssembly = typeof(I18nSingleAssemblyHook).Assembly; 16 | var manifestResourceStream = ResourcesAssembly.GetManifestResourceStream(resourceFileName); 17 | if (manifestResourceStream == null) 18 | { 19 | return true; 20 | } 21 | 22 | var resourceGroveler = __instance.GetType().GetField("resourceGroveler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance); 23 | var CreateResourceSet = resourceGroveler.GetType().GetMethod("CreateResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); 24 | var resourceSet = CreateResourceSet.Invoke(resourceGroveler, [manifestResourceStream, ResourcesAssembly]); 25 | var AddResourceSet = __instance.GetType().GetMethod("AddResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); 26 | var localResourceSets = __instance.GetType().GetField("_resourceSets", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance); 27 | object[] args = [localResourceSets, culture.Name, resourceSet]; 28 | AddResourceSet.Invoke(null, args); 29 | __result = (ResourceSet)args[2]; 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AquaMai.ErrorReport/AquaMai.ErrorReport.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net472 6 | enable 7 | true 8 | enable 9 | 512 10 | true 11 | 12 12 | $(ProjectDir)../Libs/;$(AssemblySearchPaths) 13 | $(ProjectDir)../Output/ 14 | false 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /AquaMai.ErrorReport/ChecksumCalculator.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.ErrorReport; 2 | 3 | public class ChecksumCalculator 4 | { 5 | private static readonly uint[] CrcTable = new uint[256]; 6 | 7 | static ChecksumCalculator() 8 | { 9 | for (uint i = 0; i < 256; i++) 10 | { 11 | uint crc = i; 12 | for (int j = 0; j < 8; j++) 13 | { 14 | crc = (crc & 1) != 0 ? (0xEDB88320 ^ (crc >> 1)) : (crc >> 1); 15 | } 16 | CrcTable[i] = crc; 17 | } 18 | } 19 | 20 | public static uint GetChecksum(byte[] data, uint k) 21 | { 22 | uint crc = 0xFFFFFFFF; // Initial value 23 | foreach (byte b in data) 24 | { 25 | crc = (crc >> 8) ^ CrcTable[(crc ^ b) & 0xFF]; 26 | } 27 | return (crc ^ k ^ 0xFFFFFFFF); // Final XOR value and ensure 32-bit unsigned 28 | } 29 | } -------------------------------------------------------------------------------- /AquaMai.ErrorReport/FileHeaderMeta.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai.ErrorReport; 2 | 3 | public class FileHeaderMeta 4 | { 5 | public string? Filename { get; set; } 6 | public string? ContentType { get; set; } 7 | } -------------------------------------------------------------------------------- /AquaMai.ErrorReport/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $AquaMai.ErrorReport$_ 5 | 6 | 7 | -------------------------------------------------------------------------------- /AquaMai.ErrorReport/Polyfills.cs: -------------------------------------------------------------------------------- 1 | // 一些线索: 2 | // AdjustWndProcFlagsFromMetadata 3 | // DebuggableAttribute 4 | 5 | namespace MelonLoader; 6 | 7 | [AttributeUsage(AttributeTargets.Assembly)] 8 | public class MelonInfoAttribute : Attribute 9 | { 10 | public MelonInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) 11 | { 12 | } 13 | 14 | public MelonInfoAttribute(Type type, string name, int versionMajor, int versionMinor, int versionRevision, string versionIdentifier, string author, string downloadLink = null) 15 | : this(type, name, $"{versionMajor}.{versionMinor}.{versionRevision}{(string.IsNullOrEmpty(versionIdentifier) ? "" : versionIdentifier)}", author, downloadLink) 16 | { 17 | } 18 | 19 | public MelonInfoAttribute(Type type, string name, int versionMajor, int versionMinor, int versionRevision, string author, string downloadLink = null) 20 | : this(type, name, versionMajor, versionMinor, versionRevision, null, author, downloadLink) 21 | { 22 | } 23 | } 24 | 25 | [AttributeUsage(AttributeTargets.Assembly)] 26 | public class MelonColorAttribute : Attribute 27 | { 28 | public MelonColorAttribute() 29 | { 30 | } 31 | 32 | public MelonColorAttribute(ConsoleColor color) 33 | { 34 | } 35 | 36 | public MelonColorAttribute(int alpha, int red, int green, int blue) 37 | { 38 | } 39 | } 40 | 41 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 42 | public class MelonGameAttribute : Attribute 43 | { 44 | public MelonGameAttribute(string developer = null, string name = null) 45 | { 46 | } 47 | } 48 | 49 | [AttributeUsage(AttributeTargets.Assembly)] 50 | public class HarmonyDontPatchAllAttribute : Attribute 51 | { 52 | public HarmonyDontPatchAllAttribute() 53 | { 54 | } 55 | } 56 | 57 | public class MelonMod 58 | { 59 | } -------------------------------------------------------------------------------- /AquaMai.ErrorReport/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace AquaMai.ErrorReport; 4 | 5 | public class Program 6 | { 7 | [DllImport("SHCore.dll")] 8 | [return: MarshalAs(UnmanagedType.Bool)] 9 | public static extern bool SetProcessDpiAwareness(DPI_AWARENESS awareness); 10 | 11 | // 定义 DPI_AWARENESS 枚举 12 | public enum DPI_AWARENESS 13 | { 14 | DPI_AWARENESS_INVALID = -1, 15 | DPI_UNAWARE = 0, 16 | SYSTEM_AWARE = 1, 17 | PER_MONITOR_AWARE = 2 18 | } 19 | 20 | [STAThread] 21 | public static void Main() 22 | { 23 | SetProcessDpiAwareness(DPI_AWARENESS.SYSTEM_AWARE); 24 | Application.EnableVisualStyles(); 25 | Application.SetCompatibleTextRenderingDefault(false); 26 | Application.Run(new CrashForm()); 27 | } 28 | } -------------------------------------------------------------------------------- /AquaMai.Mods/DeprecationWarning.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | 3 | namespace AquaMai.Mods; 4 | 5 | [ConfigSection( 6 | en: """ 7 | These options have been deprecated and no longer work in the current version. 8 | Remove them to get rid of the warning message at startup. 9 | """, 10 | zh: """ 11 | 这些配置项已经被废弃,在当前版本不再生效 12 | 删除它们以去除启动时的警告信息 13 | """, 14 | exampleHidden: true)] 15 | public class DeprecationWarning 16 | { 17 | [ConfigEntry(hideWhenDefault: true)] 18 | public static readonly bool v1_0_ModKeyMap_TestMode; 19 | 20 | // Print friendly warning messages here. 21 | // Please keep them up-to-date while refactoring the config. 22 | public static void OnBeforeAllPatch() 23 | { 24 | if (v1_0_ModKeyMap_TestMode) 25 | { 26 | MelonLoader.MelonLogger.Warning("ModKeyMap.TestMode has been deprecated (> v1.0). Please use GameSystem.KeyMap.Test instead."); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AquaMai.Mods/Enhancement/ServerNotice.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using AquaMai.Config.Attributes; 3 | using AquaMai.Core.Helpers; 4 | using AquaMai.Mods.Types; 5 | using JetBrains.Annotations; 6 | using MelonLoader; 7 | using MelonLoader.TinyJSON; 8 | 9 | namespace AquaMai.Mods.Enhancement; 10 | 11 | [ConfigSection( 12 | defaultOn: true, 13 | exampleHidden: true, 14 | en: """ 15 | Show extra information from compatible server 16 | (no side effects for other servers, no extra requests made) 17 | """, 18 | zh: """ 19 | 在用户登录之类的时候,显示来自兼容服务器的额外信息 20 | (对其他服务器无副作用,不会发出额外请求) 21 | """)] 22 | public class ServerNotice 23 | { 24 | private const string FieldName = "_aquaMaiServerNotice"; 25 | 26 | private class ServerNoticeEntry : ConditionalMessage 27 | { 28 | [CanBeNull] public string title = null; 29 | [CanBeNull] public string content = null; 30 | } 31 | 32 | private class ServerNoticeData 33 | { 34 | public ServerNoticeEntry[] entries = []; 35 | } 36 | 37 | public static void OnBeforePatch() 38 | { 39 | NetPacketHook.OnNetPacketComplete += OnNetPacketComplete; 40 | } 41 | 42 | private static Variant OnNetPacketComplete(string api, Variant request, Variant response) 43 | { 44 | if (response is not ProxyObject obj) return null; 45 | var serverNoticeJson = obj.Keys.Contains(FieldName) ? obj[FieldName] : null; 46 | if (serverNoticeJson == null) return null; 47 | 48 | var data = serverNoticeJson.Make(); 49 | var entry = data?.entries.FirstOrDefault(it => it.ShouldShow()); 50 | if (entry == null) return null; 51 | 52 | MelonLogger.Msg($"[ServerNotice] {entry.title}: {entry.content}"); 53 | MessageHelper.ShowMessage(entry.content, title: entry.title); 54 | return null; 55 | } 56 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/CustomCreditsString.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using AquaMai.Core.Attributes; 3 | using HarmonyLib; 4 | using Monitor; 5 | 6 | namespace AquaMai.Mods.Fancy; 7 | 8 | [ConfigSection( 9 | en: "Set the \"CREDIT(S)\" string displayed at the middle and top-right corner of the screen.", 10 | zh: "自定义中间和右上角的 \"CREDIT(S)\"(可用点数)文本")] 11 | public class CustomCreditsString 12 | { 13 | [ConfigEntry( 14 | en: "Custom string to replace the \"CREDIT(S)\". Empty for a blank string.", 15 | zh: "用于替换 \"CREDIT(S)\" 的自定义文本,留空则为空白" 16 | )] 17 | private static readonly string creditsString = ""; 18 | 19 | [ConfigEntry( 20 | en: "Hide the number of credits after the \"CREDITS(S)\" string.", 21 | zh: "隐藏 \"CREDIT(S)\" 后的可用点数数量" 22 | )] 23 | private static readonly bool hideCreditsNumber = true; 24 | 25 | [HarmonyPrefix] 26 | [HarmonyPatch(typeof(CreditController), "Initialize")] 27 | public static void Initialize(ref string ____freePlayText, ref string ___CreditText) 28 | { 29 | ____freePlayText = creditsString; 30 | ___CreditText = hideCreditsNumber 31 | ? creditsString 32 | : creditsString.TrimEnd() + " "; // Original: "CREDIT(S) " 33 | } 34 | 35 | [EnableIf(nameof(hideCreditsNumber))] 36 | [HarmonyPostfix] 37 | [HarmonyPatch(typeof(CreditController), "SetCredits")] 38 | public static void PostSetCredits(TMPro.TextMeshProUGUI ____creditText, string ___CreditText) 39 | { 40 | ____creditText.text = ___CreditText.TrimEnd(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/CustomLogo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using AquaMai.Config.Attributes; 4 | using AquaMai.Core.Helpers; 5 | using HarmonyLib; 6 | using Monitor; 7 | using Process; 8 | using UnityEngine; 9 | using UnityEngine.UI; 10 | 11 | namespace AquaMai.Mods.Fancy; 12 | 13 | [ConfigSection( 14 | en: "Replace the \"SEGA\" and \"ALL.Net\" logos with custom ones.", 15 | zh: "用自定义的图片替换「SEGA」和「ALL.Net」的标志")] 16 | public class CustomLogo 17 | { 18 | [ConfigEntry( 19 | en: "Replace the \"SEGA\" logo with a random PNG image from this directory.", 20 | zh: "从此目录中随机选择一张 PNG 图片用于「SEGA」标志")] 21 | private static readonly string segaLogoDir = "LocalAssets/SegaLogo"; 22 | 23 | [ConfigEntry( 24 | en: "Replace the \"ALL.Net\" logo with a random PNG image from this directory.", 25 | zh: "从此目录中随机选择一张 PNG 图片用于「ALL.Net」标志")] 26 | private static readonly string allNetLogoDir = "LocalAssets/AllNetLogo"; 27 | 28 | private readonly static List segaLogo = []; 29 | private readonly static List allNetLogo = []; 30 | 31 | public static void OnBeforePatch() 32 | { 33 | EnumSprite(segaLogo, FileSystem.ResolvePath(segaLogoDir)); 34 | EnumSprite(allNetLogo, FileSystem.ResolvePath(allNetLogoDir)); 35 | } 36 | 37 | private static void EnumSprite(List collection, string path) 38 | { 39 | if (!Directory.Exists(path)) return; 40 | foreach (var file in Directory.EnumerateFiles(path, "*.png")) 41 | { 42 | var data = File.ReadAllBytes(file); 43 | var texture2D = new Texture2D(1, 1, TextureFormat.RGBA32, false); 44 | if (texture2D.LoadImage(data)) 45 | { 46 | collection.Add(Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f))); 47 | } 48 | } 49 | } 50 | 51 | [HarmonyPatch(typeof(AdvertiseProcess), "OnStart")] 52 | [HarmonyPostfix] 53 | private static void AdvProcessPostFix(AdvertiseMonitor[] ____monitors) 54 | { 55 | if (segaLogo.Count > 0) 56 | { 57 | var logo = segaLogo[UnityEngine.Random.Range(0, segaLogo.Count)]; 58 | foreach (var monitor in ____monitors) 59 | { 60 | monitor.transform.Find("Canvas/Main/SegaAllNet_LOGO/NUL_ADT_SegaAllNet_LOGO/SegaLogo").GetComponent().sprite = logo; 61 | } 62 | } 63 | 64 | if (allNetLogo.Count > 0) 65 | { 66 | var logo = allNetLogo[UnityEngine.Random.Range(0, allNetLogo.Count)]; 67 | foreach (var monitor in ____monitors) 68 | { 69 | monitor.transform.Find("Canvas/Main/SegaAllNet_LOGO/NUL_ADT_SegaAllNet_LOGO/AllNetLogo").GetComponent().sprite = logo; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/CustomPlaceName.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Manager; 4 | 5 | namespace AquaMai.Mods.Fancy; 6 | 7 | [ConfigSection( 8 | en: """ 9 | Custom shop name in photo. 10 | Also enable shop name display in SDGA. 11 | """, 12 | zh: """ 13 | 自定义拍照的店铺名称 14 | 同时在 SDGA 中会启用店铺名称的显示(但是不会在游戏里有设置) 15 | """)] 16 | public class CustomPlaceName 17 | { 18 | [ConfigEntry] 19 | private static readonly string placeName = ""; 20 | 21 | [HarmonyPostfix] 22 | [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] 23 | public static void CheckAuth_Proc(OperationManager __instance) 24 | { 25 | if (string.IsNullOrEmpty(placeName)) 26 | { 27 | return; 28 | } 29 | 30 | __instance.ShopData.ShopName = placeName; 31 | __instance.ShopData.ShopNickName = placeName; 32 | } 33 | 34 | [HarmonyPostfix] 35 | [HarmonyPatch(typeof(ResultCardBaseController), "Initialize")] 36 | public static void Initialize(ResultCardBaseController __instance) 37 | { 38 | if (string.IsNullOrEmpty(placeName)) 39 | { 40 | return; 41 | } 42 | 43 | __instance.SetVisibleStoreName(true); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/CustomTrackStartDiff.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Monitor; 5 | using UI; 6 | using UnityEngine; 7 | using UnityEngine.UI; 8 | 9 | namespace AquaMai.Mods.Fancy; 10 | 11 | [ConfigSection( 12 | en: """ 13 | Custom track start difficulty image (not really custom difficulty). 14 | Requires CustomSkins to be enabled. 15 | Will load four image resources through custom skins: musicBase, musicTab, musicLvBase, musicLvText. 16 | """, 17 | zh: """ 18 | 自定义在歌曲开始界面上显示的难度贴图 (并不是真的自定义难度) 19 | 需要启用自定义皮肤功能 20 | 会通过自定义皮肤加载四个图片资源: musicBase, musicTab, musicLvBase, musicLvText 21 | """)] 22 | public class CustomTrackStartDiff 23 | { 24 | // 自定义在歌曲开始界面上显示的难度 (并不是真的自定义难度) 25 | // 需要启用自定义皮肤功能 26 | // 会加载四个图片资源: musicBase, musicTab, musicLvBase, musicLvText 27 | 28 | [HarmonyPostfix] 29 | [HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")] 30 | private static void DisableTabs( 31 | MultipleImage ____musicBaseImage, 32 | MultipleImage ____musicTabImage, 33 | SpriteCounter ____difficultySingle, 34 | SpriteCounter ____difficultyDouble, 35 | Image ____levelTextImage, 36 | List ____musicLevelSpriteSheets, 37 | TimelineRoot ____musicDetail 38 | ) 39 | { 40 | var texture = CustomSkins.CustomTrackStart[0]; 41 | if (texture != null) 42 | { 43 | ____musicBaseImage.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f); 44 | ____musicBaseImage.ChangeSprite(6); 45 | } 46 | 47 | texture = CustomSkins.CustomTrackStart[1]; 48 | if (texture != null) 49 | { 50 | ____musicTabImage.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f); 51 | ____musicTabImage.ChangeSprite(6); 52 | } 53 | 54 | texture = CustomSkins.CustomTrackStart[2]; 55 | if (texture != null) 56 | { 57 | var lvBase = Traverse.Create(____musicDetail).Field("_lv_Base").Value; 58 | lvBase.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f); 59 | lvBase.ChangeSprite(6); 60 | } 61 | 62 | texture = CustomSkins.CustomTrackStart[3]; 63 | if (texture != null) 64 | { 65 | var original = ____musicLevelSpriteSheets[0].Sheet; 66 | var sheet = new Sprite[original.Length]; 67 | for (var i = 0; i < original.Length; i++) 68 | { 69 | var sprite = original[i]; 70 | sheet[i] = Sprite.Create(texture, sprite.textureRect, new Vector2(0.5f, 0.5f), 100f); 71 | } 72 | 73 | ____difficultySingle.SetSpriteSheet(sheet); 74 | ____difficultyDouble.SetSpriteSheet(sheet); 75 | ____levelTextImage.sprite = sheet[14]; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/CustomVersionString.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | 4 | namespace AquaMai.Mods.Fancy; 5 | 6 | [ConfigSection( 7 | en: "Set the version string displayed at the top-right corner of the screen.", 8 | zh: "把右上角的版本更改为自定义文本")] 9 | public class CustomVersionString 10 | { 11 | [ConfigEntry] 12 | private static readonly string versionString = ""; 13 | 14 | /* 15 | * Patch displayVersionString Property Getter 16 | */ 17 | [HarmonyPrefix] 18 | [HarmonyPatch(typeof(MAI2System.Config), "displayVersionString", MethodType.Getter)] 19 | public static bool GetDisplayVersionString(ref string __result) 20 | { 21 | if (string.IsNullOrEmpty(versionString)) 22 | { 23 | return true; 24 | } 25 | 26 | __result = versionString; 27 | // Return false to block the original method 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/DemoMaster.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using DB; 3 | using HarmonyLib; 4 | using MAI2.Util; 5 | using Manager; 6 | using Process; 7 | 8 | namespace AquaMai.Mods.Fancy; 9 | 10 | [ConfigSection( 11 | en: "Play \"Master\" difficulty on Demo screen.", 12 | zh: "在闲置时的演示画面上播放紫谱而不是绿谱")] 13 | public class DemoMaster 14 | { 15 | [HarmonyPostfix] 16 | [HarmonyPatch(typeof(AdvDemoProcess), "OnStart")] 17 | public static void AdvDemoProcessPostStart() 18 | { 19 | for (int i = 0; i < 2; i++) 20 | { 21 | var userOption = Singleton.Instance.GetGameScore(i).UserOption; 22 | userOption.NoteSpeed = OptionNotespeedID.Speed6_5; 23 | userOption.TouchSpeed = OptionTouchspeedID.Speed7_0; 24 | } 25 | } 26 | 27 | [HarmonyPrefix] 28 | [HarmonyPatch(typeof(GamePlayManager), "InitializeAdvertise")] 29 | public static void PreInitializeAdvertise() 30 | { 31 | GameManager.SelectDifficultyID[0] = 3; 32 | GameManager.SelectDifficultyID[1] = 3; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/AlignCircleSlideJudgeDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Manager; 5 | using Monitor; 6 | using UnityEngine; 7 | 8 | namespace AquaMai.Mods.Fancy.GamePlay; 9 | 10 | [ConfigSection( 11 | en: """ 12 | Make the judgment display of circular Slides align precisely with the judgment line (originally a bit off). 13 | Just like in majdata. 14 | """, 15 | zh: """ 16 | 让圆弧形的 Slide 的判定显示与判定线精确对齐 (原本会有一点歪) 17 | 就像 majdata 里那样 18 | """)] 19 | public class AlignCircleSlideJudgeDisplay 20 | { 21 | /* 22 | * 这个 Patch 让圆弧形的 Slide 的判定显示与判定线精确对齐 (原本会有一点歪), 就像 majdata 里那样 23 | */ 24 | [HarmonyPostfix] 25 | [HarmonyPatch(typeof(SlideRoot), "Initialize")] 26 | private static void FixJudgePosition( 27 | SlideRoot __instance, SlideType ___EndSlideType, SlideJudge ___JudgeObj 28 | ) 29 | { 30 | if (null != ___JudgeObj) 31 | { 32 | float z = ___JudgeObj.transform.localPosition.z; 33 | if (___EndSlideType == SlideType.Slide_Circle_L) 34 | { 35 | float angle = -45.0f - 45.0f * __instance.EndButtonId; 36 | double angleRad = Math.PI / 180.0 * (angle + 90 + 22.5 + 2.6415); 37 | ___JudgeObj.transform.localPosition = new Vector3(480f * (float)Math.Cos(angleRad), 480f * (float)Math.Sin(angleRad), z); 38 | ___JudgeObj.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, angle); 39 | } 40 | else if (___EndSlideType == SlideType.Slide_Circle_R) 41 | { 42 | float angle = -45.0f * __instance.EndButtonId; 43 | double angleRad = Math.PI / 180.0 * (angle + 90 - 22.5 - 2.6415); 44 | ___JudgeObj.transform.localPosition = new Vector3(480f * (float)Math.Cos(angleRad), 480f * (float)Math.Sin(angleRad), z); 45 | ___JudgeObj.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, angle); 46 | } 47 | } 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/BreakSlideJudgeBlink.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | using UnityEngine; 5 | 6 | namespace AquaMai.Mods.Fancy.GamePlay; 7 | 8 | [ConfigSection( 9 | en: """ 10 | This Patch makes the Critical judgment of BreakSlide also flicker like BreakTap. 11 | Recommended to use with custom skins (otherwise the visual effect may not be good). 12 | """, 13 | zh: """ 14 | 这个 Patch 让 BreakSlide 的 Critical 判定也可以像 BreakTap 一样闪烁 15 | 推荐与自定义皮肤一起使用 (否则视觉效果可能并不好) 16 | """)] 17 | public class BreakSlideJudgeBlink 18 | { 19 | /* 20 | * 这个 Patch 让 BreakSlide 的 Critical 判定也可以像 BreakTap 一样闪烁 21 | * 推荐与自定义皮肤一起使用 (否则视觉效果可能并不好) 22 | */ 23 | [HarmonyPostfix] 24 | [HarmonyPatch(typeof(SlideJudge), "UpdateBreakEffectAdd")] 25 | private static void FixBreakSlideJudgeBlink( 26 | SpriteRenderer ___SpriteRenderAdd, int ____addEffectCount 27 | ) 28 | { 29 | if (!___SpriteRenderAdd.gameObject.activeSelf) return; 30 | float num = (____addEffectCount & 0b10) >> 1; 31 | ___SpriteRenderAdd.color = new Color(num, num, num, 1f); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using DB; 4 | using Manager; 5 | using MelonLoader; 6 | using UnityEngine; 7 | 8 | namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; 9 | 10 | public class CustomSlideNoteData: NoteData 11 | { 12 | public string SlideCode; 13 | public List> SlidePathList = new List>(); 14 | public List> SlideHitAreasList = new List>(); 15 | public float SlidePathLength; 16 | 17 | public bool ParseSlideCode(string slideCode, OptionMirrorID mirrorMode) 18 | { 19 | if (string.IsNullOrEmpty(slideCode)) 20 | { 21 | return false; 22 | } 23 | 24 | SlidePathList.Clear(); 25 | SlideHitAreasList.Clear(); 26 | 27 | this.SlideCode = slideCode; 28 | var path = SlideCodeParser.Parse(slideCode); 29 | if (path == null) 30 | { 31 | return false; 32 | } 33 | 34 | var arrowData = SlideDataBuilder.BuildArrowData(path); 35 | SlidePathLength = (float)path.GetPathLength(); 36 | var hitAreaData = SlideDataBuilder.BuildHitAreas(path); 37 | for (var i = 0; i < 8; i++) 38 | { 39 | SlidePathList.Add(SlideDataBuilder.ConvertAndRotateArrowData(arrowData, i, mirrorMode)); 40 | SlideHitAreasList.Add(SlideDataBuilder.ConvertAndRotateHitAreas(hitAreaData, i, mirrorMode)); 41 | } 42 | 43 | var msg = string.Join(", ", 44 | hitAreaData.Select(x => x.PanelAreas).Select(x => string.Join("/", x.Cast()))); 45 | MelonLogger.Msg(msg); 46 | 47 | this.slideData.type = path.GetEndType(mirrorMode); 48 | 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/DisableTrackStartTabs.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | using TMPro; 5 | using UI; 6 | using UnityEngine; 7 | 8 | namespace AquaMai.Mods.Fancy.GamePlay; 9 | 10 | [ConfigSection( 11 | en: """ 12 | Disable the TRACK X text, DX/Standard display box, and the derakkuma at the bottom of the screen in the song start screen. 13 | For recording chart confirmation. 14 | """, 15 | zh: """ 16 | 在歌曲开始界面, 把 TRACK X 字样, DX/标准谱面的显示框, 以及画面下方的滴蜡熊隐藏掉 17 | 录制谱面确认用 18 | """)] 19 | public class DisableTrackStartTabs 20 | { 21 | // 在歌曲开始界面, 把 TRACK X 字样, DX/标准谱面的显示框, 以及画面下方的滴蜡熊隐藏掉, 让他看起来不那么 sinmai, 更像是 majdata 22 | 23 | [HarmonyPostfix] 24 | [HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")] 25 | private static void DisableTabs( 26 | SpriteCounter ____trackNumber, SpriteCounter ____bossTrackNumber, SpriteCounter ____utageTrackNumber, 27 | MultipleImage ____musicTabImage, GameObject[] ____musicTabObj, GameObject ____derakkumaRoot, 28 | TimelineRoot ____musicDetail 29 | ) 30 | { 31 | ____trackNumber.transform.parent.gameObject.SetActive(false); 32 | ____bossTrackNumber.transform.parent.gameObject.SetActive(false); 33 | ____utageTrackNumber.transform.parent.gameObject.SetActive(false); 34 | ____musicTabImage.gameObject.SetActive(false); 35 | ____musicTabObj[0].gameObject.SetActive(false); 36 | ____musicTabObj[1].gameObject.SetActive(false); 37 | ____musicTabObj[2].gameObject.SetActive(false); 38 | ____derakkumaRoot.SetActive(false); 39 | var traverse = Traverse.Create(____musicDetail); 40 | traverse.Field("_achivement_Base").Value.ChangeSprite(1); 41 | traverse.Field("_clearRank_Base").Value.ChangeSprite(1); 42 | traverse.Field("_achivement_Text").Value.gameObject.SetActive(false); 43 | traverse.Field("_achivement_decimal_Text").Value.gameObject.SetActive(false); 44 | traverse.Field("_achivement_percent_Text").Value.gameObject.SetActive(false); 45 | traverse.Field("_clearRank_Image").Value.gameObject.SetActive(false); 46 | traverse.Field("_deluxScore_Obj").Value.SetActive(false); 47 | traverse.Field("_comboRank_Image").Value.ChangeSprite(0); 48 | traverse.Field("_syncRank_Image").Value.ChangeSprite(0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/FanJudgeFlip.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | 5 | namespace AquaMai.Mods.Fancy.GamePlay; 6 | 7 | [ConfigSection( 8 | en: """ 9 | Make the judgment display of WiFi Slide different in up and down (originally all WiFi judgment displays are towards the center), just like in majdata. 10 | The reason for this bug is that SEGA forgot to assign EndButtonId to WiFi. 11 | """, 12 | zh: """ 13 | 这个 Patch 让 WiFi Slide 的判定显示有上下的区别 (原本所有 WiFi 的判定显示都是朝向圆心的), 就像 majdata 里那样 14 | 这个 bug 产生的原因是 SBGA 忘记给 WiFi 的 EndButtonId 赋值了 15 | """)] 16 | public class FanJudgeFlip 17 | { 18 | /* 19 | * 这个 Patch 让 WiFi Slide 的判定显示有上下的区别 (原本所有 WiFi 的判定显示都是朝向圆心的), 就像 majdata 里那样 20 | * 这个 bug 产生的原因是 SBGA 忘记给 WiFi 的 EndButtonId 赋值了 21 | * 不过需要注意的是, 考虑到圆弧形 Slide 的判定显示就是永远朝向圆心的, 我个人会觉得这个 Patch 关掉更好看一点 22 | */ 23 | [HarmonyPostfix] 24 | [HarmonyPatch(typeof(SlideFan), "Initialize")] 25 | private static void FixFanJudgeFilp( 26 | int[] ___GoalButtonId, SlideJudge ___JudgeObj 27 | ) 28 | { 29 | if (null != ___JudgeObj) 30 | { 31 | if (2 <= ___GoalButtonId[1] && ___GoalButtonId[1] <= 5) 32 | { 33 | ___JudgeObj.Flip(false); 34 | ___JudgeObj.transform.Rotate(0.0f, 0.0f, 180f); 35 | } 36 | else 37 | { 38 | ___JudgeObj.Flip(true); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/HideHanabi.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using Fx; 3 | using HarmonyLib; 4 | using Monitor; 5 | using UnityEngine; 6 | 7 | namespace AquaMai.Mods.Fancy.GamePlay; 8 | 9 | [ConfigSection( 10 | en: "Hide hanabi completely.", 11 | zh: "完全隐藏烟花")] 12 | public class HideHanabi 13 | { 14 | [HarmonyPatch(typeof(TapCEffect), "SetUpParticle")] 15 | [HarmonyPostfix] 16 | public static void FixZeroSize(TapCEffect __instance, FX_Mai2_Note_Color ____particleControler) 17 | { 18 | var entities = ____particleControler.GetComponentsInChildren(true); 19 | foreach (var entity in entities) 20 | { 21 | entity.maxParticleSize = 0f; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/JudgeDisplay4B.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | using UnityEngine; 5 | 6 | namespace AquaMai.Mods.Fancy.GamePlay; 7 | 8 | [ConfigSection( 9 | en: """ 10 | More detailed judgment display. 11 | Requires CustomSkins to be enabled and the resource file to be downloaded. 12 | https://github.com/hykilpikonna/AquaDX/releases/download/nightly/JudgeDisplay4B.7z 13 | """, 14 | zh: """ 15 | 更精细的判定表示 16 | 需开启 CustomSkins 并下载资源文件 17 | https://github.com/hykilpikonna/AquaDX/releases/download/nightly/JudgeDisplay4B.7z 18 | """)] 19 | public class JudgeDisplay4B 20 | { 21 | // 精确到子判定的自定义判定显示, 需要启用自定义皮肤功能 (理论上不启用自定义皮肤不会崩游戏, 只不过此时这个功能显然不会生效) 22 | 23 | [HarmonyPostfix] 24 | [HarmonyPatch(typeof(SlideJudge), "Initialize")] 25 | private static void SlideJudgeDisplay4B( 26 | SpriteRenderer ___SpriteRenderAdd, SpriteRenderer ___SpriteRender, 27 | SlideJudge.SlideJudgeType ____judgeType, SlideJudge.SlideAngle ____angle, 28 | NoteJudge.ETiming judge, float msec, bool isBreak 29 | ) 30 | { 31 | var i = isBreak ? 1 : 0; 32 | Sprite sprite = CustomSkins.CustomJudgeSlide[i, (int)____judgeType, (int)____angle, (int)judge]; 33 | if (sprite != null) { 34 | ___SpriteRender.sprite = sprite; 35 | } 36 | 37 | if (isBreak && judge == NoteJudge.ETiming.Critical) 38 | { 39 | sprite = CustomSkins.CustomJudgeSlide[i, (int)____judgeType, (int)____angle, (int) NoteJudge.ETiming.End]; 40 | if (sprite != null) 41 | { 42 | ___SpriteRenderAdd.sprite = sprite; 43 | } 44 | } 45 | } 46 | 47 | 48 | [HarmonyPostfix] 49 | [HarmonyPatch(typeof(JudgeGrade), "Initialize")] 50 | private static void JudgeGradeDisplay4B( 51 | SpriteRenderer ___SpriteRender, 52 | NoteJudge.ETiming judge, float msec, NoteJudge.EJudgeType type 53 | ) 54 | { 55 | var i = (type == NoteJudge.EJudgeType.Break) ? 1 : 0; 56 | Sprite sprite = CustomSkins.CustomJudge[i, (int)judge]; 57 | if (sprite != null) { 58 | ___SpriteRender.sprite = sprite; 59 | } 60 | } 61 | 62 | [HarmonyPostfix] 63 | [HarmonyPatch(typeof(JudgeGrade), "InitializeBreak")] 64 | private static void JudgeGradeBreakDisplay4B( 65 | SpriteRenderer ___SpriteRenderAdd, 66 | NoteJudge.ETiming judge, float msec, NoteJudge.EJudgeType type 67 | ) 68 | { 69 | if (judge == NoteJudge.ETiming.Critical) 70 | { 71 | var sprite = CustomSkins.CustomJudge[1, (int) NoteJudge.ETiming.End]; 72 | if (sprite != null) 73 | { 74 | ___SpriteRenderAdd.sprite = sprite; 75 | } 76 | } 77 | } 78 | 79 | [HarmonyPrefix] 80 | [HarmonyPatch(typeof(JudgeGrade), "InitializeBreak")] 81 | private static void InitializeBreakFix(ref NoteJudge.EJudgeType type) 82 | { 83 | type = NoteJudge.EJudgeType.Break; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/RealisticRandomJudge.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Manager; 4 | 5 | namespace AquaMai.Mods.Fancy.GamePlay; 6 | 7 | [ConfigSection( 8 | en: """ 9 | Make the AutoPlay random judgment mode really randomize all judgments (down to sub-judgments). 10 | The original random judgment will only produce all 15 judgment results from Miss(TooFast) ~ Critical ~ Miss(TooLate). 11 | Here, it is changed to a triangular distribution to produce all 15 judgment results from Miss(TooFast) ~ Critical ~ Miss(TooLate). 12 | Of course, it will not consider whether the original Note really has a corresponding judgment (such as Slide should not have non-Critical Prefect). 13 | """, 14 | zh: """ 15 | 让 AutoPlay 的随机判定模式真的会随机产生所有的判定 (精确到子判定) 16 | 原本的随机判定只会等概率产生 Critical, LateGreat1st, LateGood, Miss(TooLate) 17 | 这里改成三角分布产生从 Miss(TooFast) ~ Critical ~ Miss(TooLate) 的所有 15 种判定结果 18 | 当然, 此处并不会考虑原本那个 Note 是不是真的有对应的判定 (比如 Slide 实际上不应该有小 p 之类的) 19 | """)] 20 | public class RealisticRandomJudge 21 | { 22 | // 让 AutoPlay 的随机判定模式真的会随机产生所有的判定 (精确到子判定) 23 | // 原本的随机判定只会等概率产生 Critical, LateGreat1st, LateGood, Miss(TooLate) 24 | // 这里改成三角分布产生从 Miss(TooFast) ~ Critical ~ Miss(TooLate) 的所有 15 种判定结果 25 | // 当然, 此处并不会考虑原本那个 Note 是不是真的有对应的判定 (比如 Slide 实际上不应该有小 p 之类的) 26 | 27 | [HarmonyPostfix] 28 | [HarmonyPatch(typeof(GameManager), "AutoJudge")] 29 | private static NoteJudge.ETiming RealAutoJudgeRandom(NoteJudge.ETiming retval) 30 | { 31 | if (GameManager.AutoPlay == GameManager.AutoPlayMode.Random) 32 | { 33 | var x = UnityEngine.Random.Range(0, 8); 34 | x += UnityEngine.Random.Range(0, 8); 35 | return (NoteJudge.ETiming) x; 36 | } 37 | return retval; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/SlideLayerReverse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Monitor; 5 | using UnityEngine; 6 | 7 | namespace AquaMai.Mods.Fancy.GamePlay; 8 | 9 | [ConfigSection( 10 | en: """ 11 | Invert the Slide hierarchy, so that the new Slide appears on top like Maimai classic. 12 | Enable to support color changing effects achieved by overlaying multiple stars. 13 | """, 14 | zh: """ 15 | 反转 Slide 层级, 使新出现的 Slide 像旧框一样显示在上层 16 | 启用以支持通过叠加多个星星达成的变色效果 17 | """)] 18 | public class SlideLayerReverse 19 | { 20 | [HarmonyPostfix] 21 | [HarmonyPatch(typeof(SlideRoot), "Initialize")] 22 | private static void CalcArrowLayer( 23 | bool ___BreakFlag, List ____spriteRenders, List ____breakSpriteRenders, 24 | int ___SlideIndex, int ____baseArrowSortingOrder 25 | ) 26 | { 27 | // 原本的 sortingOrder 是 -(SlideIndex + _baseArrowSortingOrder + index) 28 | // 令 orderBase = SlideIndex + _baseArrowSortingOrder 29 | // 分配给这条 slide 的 sortingOrder 范围是 -(orderBase + count - 1) ~ -(orderBase) 30 | // 现在要保留 slide 内部箭头顺序, 但使得 slide 间次序反转 31 | // 范围会变成 orderBase ~ orderBase + count - 1 32 | // 其中原本是 -(orderBase) 的箭头应该调整为 orderBase + count - 1 33 | 34 | var orderBase = ___SlideIndex + ____baseArrowSortingOrder; // SlideIndex + _baseArrowSortingOrder 35 | if (!___BreakFlag) 36 | { 37 | var lastIdx = ____spriteRenders.Count - 1; 38 | for (var index = 0; index < ____spriteRenders.Count; index++) 39 | { 40 | var renderer = ____spriteRenders[index]; 41 | renderer.sortingOrder = -32700 + orderBase + lastIdx - index; 42 | } 43 | } 44 | else 45 | { 46 | var lastIdx = ____breakSpriteRenders.Count - 1; 47 | for (var index = 0; index < ____breakSpriteRenders.Count; index++) 48 | { 49 | var breakSlide = ____breakSpriteRenders[index]; 50 | breakSlide.SetSortingOrder(-32700 + orderBase + lastIdx - index); 51 | } 52 | } 53 | } 54 | 55 | [HarmonyPostfix] 56 | [HarmonyPatch(typeof(SlideFan), "Initialize")] 57 | private static void CalcFanArrowLayer( 58 | SpriteRenderer[] ____spriteLines, SpriteRenderer[] ____effectSprites, 59 | int ___SlideIndex, int ____baseArrowSortingOrder 60 | ) 61 | { 62 | var orderBase = ___SlideIndex + ____baseArrowSortingOrder; // SlideIndex + _baseArrowSortingOrder 63 | var lastIdx = ____spriteLines.Length - 1; 64 | for (var index = 0; index < ____spriteLines.Length; index++) 65 | { 66 | var renderer = ____spriteLines[index]; 67 | renderer.sortingOrder = -32700 + orderBase + lastIdx - index; 68 | } 69 | lastIdx = ____effectSprites.Length - 1; 70 | for (var index = 0; index < ____effectSprites.Length; index++) 71 | { 72 | var renderer = ____effectSprites[index]; 73 | renderer.sortingOrder = 1000 + orderBase + lastIdx - index; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/GamePlay/TrackStartProcessTweak.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Process; 4 | using UnityEngine; 5 | 6 | namespace AquaMai.Mods.Fancy.GamePlay; 7 | 8 | [ConfigSection( 9 | en: """ 10 | Delayed the animation of the song start screen. 11 | For recording chart confirmation. 12 | """, 13 | zh: """ 14 | 推迟了歌曲开始界面的动画 15 | 录制谱面确认用 16 | """)] 17 | public class TrackStartProcessTweak 18 | { 19 | // 总之这个 Patch 没啥用, 是我个人用 sinmai 录谱面确认时用得到, 顺手也写进来了 20 | // 具体而言就是推迟了歌曲开始界面的动画便于后期剪辑 21 | 22 | [HarmonyPrefix] 23 | [HarmonyPatch(typeof(TrackStartProcess), "OnUpdate")] 24 | private static bool DelayAnimation( 25 | TrackStartProcess.TrackStartSequence ____state, 26 | ref float ____timeCounter, 27 | ProcessDataContainer ___container 28 | ) 29 | { 30 | if (____state == TrackStartProcess.TrackStartSequence.Wait) 31 | { 32 | // 将开始动画(就是“噔噔, 噔 噔噔”)推迟 33 | float temp = ____timeCounter + Time.deltaTime; 34 | if (____timeCounter < 1.0f && temp >= 1.0f) 35 | { 36 | // 这是用来让转场动画继续播放的, 原本就是这个时候 notify 的同时开始播放开始动画 37 | // 现在把开始动画往后延 38 | ___container.processManager.NotificationFadeIn(); 39 | } 40 | ____timeCounter = temp; 41 | if (____timeCounter >= 3.0f) 42 | { 43 | return true; 44 | // 原 method 的逻辑是这样 45 | // case TrackStartProcess.TrackStartSequence.Wait: 46 | // this._timeCounter += Time.deltaTime; 47 | // if ((double) this._timeCounter >= 1.0) 48 | // { 49 | // this._timeCounter = 0.0f; 50 | // this._state = TrackStartProcess.TrackStartSequence.Disp; 51 | // /* 一些开始播放开始动画的代码 */ 52 | // this.container.processManager.NotificationFadeIn(); 53 | // break; 54 | // } 55 | // break; 56 | // 所以只要在 prefix 里面等到 timeCounter 达到我们想要的值以后再执行原 method 就好 57 | // 这里有个细节: NotificationFadeIn() 会被执行两遍, 这其实不好, 是个潜在 bug 58 | // 不过由于此处把开始动画往后推了 2s, 转场动画已经结束把 Process 释放掉了, 所以第二遍会找不到 Process 就没效果 59 | } 60 | return false; 61 | } 62 | else if (____state == TrackStartProcess.TrackStartSequence.DispEnd) 63 | { 64 | // 将开始动画结束以后的转场动画推迟 65 | ____timeCounter += Time.deltaTime; // timeCounter 会在先前由原本的 method 归零 66 | if (____timeCounter >= 1.0f) 67 | { 68 | return true; 69 | } 70 | return false; 71 | } 72 | return true; 73 | } 74 | 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/HideMask.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | using UnityEngine; 5 | 6 | namespace AquaMai.Mods.Fancy; 7 | 8 | [ConfigSection( 9 | en: "Remove the circle mask of the game screen.", 10 | zh: "移除游戏画面的圆形遮罩")] 11 | public class HideMask 12 | { 13 | [HarmonyPrefix] 14 | [HarmonyPatch(typeof(Main.GameMain), "LateInitialize", typeof(MonoBehaviour), typeof(Transform), typeof(Transform))] 15 | public static void LateInitialize(MonoBehaviour gameMainObject) 16 | { 17 | GameObject.Find("Mask").SetActive(false); 18 | } 19 | 20 | [HarmonyPostfix] 21 | [HarmonyPatch(typeof(MonitorBackgroundTownController), "Awake")] 22 | public static void Awake(GameObject ____bgObject) 23 | { 24 | try 25 | { 26 | ____bgObject.transform.Find("BG").Find("Monitor_Mask").gameObject.SetActive(false); 27 | } 28 | catch 29 | { 30 | // ignored 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/README.md: -------------------------------------------------------------------------------- 1 | # Fancy 2 | 3 | All the fancy features, even if not required by most players, are welcomed to this category, whether for personalization, for beautify, for self-made charts or for other uncommon purposes. 4 | 5 | These patches may not well-tested by the project maintainers and could be enabled only if you know what you're doing. 6 | 7 | Patches affect the gameplay should go to the GamePlay subcategory. 8 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/RandomBgm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using AquaMai.Config.Attributes; 5 | using AquaMai.Core.Helpers; 6 | using HarmonyLib; 7 | using Mai2.Mai2Cue; 8 | using MAI2.Util; 9 | using Manager; 10 | using MelonLoader; 11 | 12 | namespace AquaMai.Mods.Fancy; 13 | 14 | [ConfigSection( 15 | en: """ 16 | Random BGM. 17 | Put Mai2Cue.{acb,awb} of old version of the game in the configured directory and rename them. 18 | Won't work with 2P mode. 19 | """, 20 | zh: """ 21 | 在配置的目录下放置了旧版游戏的 Mai2Cue.{acb,awb} 并重命名的话,可以在播放游戏 BGM 的时候随机播放这里面的旧版游戏 BGM 22 | 无法在 2P 模式下工作 23 | """)] 24 | public class RandomBgm 25 | { 26 | [ConfigEntry] 27 | private static readonly string mai2CueDir = "LocalAssets/Mai2Cue"; 28 | 29 | private static List _acbs = new List(); 30 | 31 | [HarmonyPostfix] 32 | [HarmonyPatch(typeof(SoundManager), "Initialize")] 33 | public static void Init() 34 | { 35 | var resolvedDir = FileSystem.ResolvePath(mai2CueDir); 36 | if (!Directory.Exists(resolvedDir)) return; 37 | var files = Directory.EnumerateFiles(resolvedDir); 38 | foreach (var file in files) 39 | { 40 | if (!file.EndsWith(".acb")) continue; 41 | // Seems there's limit for max opened ACB files 42 | _acbs.Add(Path.ChangeExtension(file, null)); 43 | } 44 | 45 | MelonLogger.Msg($"Random BGM loaded {_acbs.Count} files"); 46 | } 47 | 48 | [HarmonyPrefix] 49 | [HarmonyPatch(typeof(SoundManager), "Play")] 50 | public static void PrePlay(ref SoundManager.AcbID acbID, int cueID) 51 | { 52 | if (acbID != SoundManager.AcbID.Default) return; 53 | if (_acbs.Count == 0) return; 54 | var cueIndex = (Cue)cueID; 55 | switch (cueIndex) 56 | { 57 | case Cue.BGM_ENTRY: 58 | case Cue.BGM_COLLECTION: 59 | case Cue.BGM_RESULT_CLEAR: 60 | case Cue.BGM_RESULT: 61 | var acb = _acbs[UnityEngine.Random.Range(0, _acbs.Count)]; 62 | acbID = SoundManager.AcbID.Max; 63 | var result = Singleton.Instance.LoadCueSheet((int)acbID, acb); 64 | MelonLogger.Msg($"Picked {acb} for {cueIndex}, result: {result}"); 65 | return; 66 | default: 67 | return; 68 | } 69 | } 70 | 71 | [HarmonyPrefix] 72 | [HarmonyPatch(typeof(SoundManager), "PlayBGM")] 73 | public static bool PrePlayBGM(ref int target) 74 | { 75 | switch (target) 76 | { 77 | case 0: 78 | return true; 79 | case 1: 80 | return false; 81 | case 2: 82 | target = 0; 83 | return true; 84 | default: 85 | return false; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fancy/Triggers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using AquaMai.Config.Attributes; 4 | using HarmonyLib; 5 | using Process; 6 | 7 | namespace AquaMai.Mods.Fancy; 8 | 9 | [ConfigSection( 10 | en: "Triggers for executing commands at certain events.", 11 | zh: "在一定时机执行命令的触发器")] 12 | public class Triggers 13 | { 14 | [ConfigEntry( 15 | en: "Execute some command on game idle.", 16 | zh: """ 17 | 在游戏闲置的时候执行指定的命令脚本 18 | 比如说可以在游戏闲置时降低显示器的亮度 19 | """)] 20 | private static readonly string execOnIdle = ""; 21 | 22 | [ConfigEntry( 23 | en: "Execute some command on game start.", 24 | zh: "在玩家登录的时候执行指定的命令脚本")] 25 | private static readonly string execOnEntry = ""; 26 | 27 | [HarmonyPrefix] 28 | [HarmonyPatch(typeof(AdvertiseProcess), "OnStart")] 29 | public static void AdvertiseProcessPreStart() 30 | { 31 | if (!string.IsNullOrWhiteSpace(execOnIdle)) 32 | { 33 | Exec(execOnIdle); 34 | } 35 | } 36 | 37 | [HarmonyPrefix] 38 | [HarmonyPatch(typeof(EntryProcess), "OnStart")] 39 | public static void EntryProcessPreStart() 40 | { 41 | if (!string.IsNullOrWhiteSpace(execOnEntry)) 42 | { 43 | Exec(execOnEntry); 44 | } 45 | } 46 | 47 | [HarmonyPrefix] 48 | [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] 49 | public static void MusicSelectProcessPreStart() 50 | { 51 | if (!string.IsNullOrWhiteSpace(execOnEntry)) 52 | { 53 | Exec(execOnEntry); 54 | } 55 | } 56 | 57 | private static void Exec(string command) 58 | { 59 | var process = new System.Diagnostics.Process(); 60 | process.StartInfo.FileName = "cmd.exe"; 61 | process.StartInfo.Arguments = "/c " + command; 62 | process.StartInfo.UseShellExecute = true; 63 | process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; 64 | process.StartInfo.WorkingDirectory = Environment.CurrentDirectory; 65 | 66 | process.Start(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fix/DisableReboot.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Reflection; 3 | using AquaMai.Config.Attributes; 4 | using AquaMai.Core.Attributes; 5 | using HarmonyLib; 6 | using Manager.Operation; 7 | using MelonLoader; 8 | 9 | namespace AquaMai.Mods.Fix; 10 | 11 | [ConfigSection(exampleHidden: true, defaultOn: true)] 12 | public class DisableReboot 13 | { 14 | private static bool forceOfflineTimerExists = false; 15 | 16 | public static void OnBeforePatch() 17 | { 18 | forceOfflineTimerExists = typeof(MaintenanceTimer).GetProperty( 19 | "ForceOfflineRemainingMinutes", 20 | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) != null; 21 | } 22 | 23 | // IsAutoRebootNeeded 24 | [HarmonyPrefix] 25 | [HarmonyPatch(typeof(MaintenanceTimer), "IsAutoRebootNeeded")] 26 | public static bool IsAutoRebootNeeded(ref bool __result) 27 | { 28 | __result = false; 29 | return false; 30 | } 31 | 32 | // IsUnderServerMaintenance 33 | [HarmonyPrefix] 34 | [HarmonyPatch(typeof(MaintenanceTimer), "IsUnderServerMaintenance")] 35 | public static bool IsUnderServerMaintenance(ref bool __result) 36 | { 37 | __result = false; 38 | return false; 39 | } 40 | 41 | // RemainingMinutes 42 | // Original: private int RemainingMinutes => (this._secServerMaintenance + 59) / 60; 43 | [HarmonyPrefix] 44 | [HarmonyPatch(typeof(MaintenanceTimer), "RemainingMinutes", MethodType.Getter)] 45 | public static bool RemainingMinutes(ref int __result) 46 | { 47 | __result = 600; 48 | return false; 49 | } 50 | 51 | // GetAutoRebootSec 52 | [HarmonyPrefix] 53 | [HarmonyPatch(typeof(MaintenanceTimer), "GetAutoRebootSec")] 54 | public static bool GetAutoRebootSec(ref int __result) 55 | { 56 | __result = 60 * 60 * 10; 57 | return false; 58 | } 59 | 60 | // GetServerMaintenanceSec 61 | [HarmonyPrefix] 62 | [HarmonyPatch(typeof(MaintenanceTimer), "GetServerMaintenanceSec")] 63 | public static bool GetServerMaintenanceSec(ref int __result) 64 | { 65 | __result = 60 * 60 * 10; 66 | return false; 67 | } 68 | 69 | // Execute 70 | [HarmonyPrefix] 71 | [HarmonyPatch(typeof(MaintenanceTimer), "Execute")] 72 | public static bool Execute(MaintenanceTimer __instance) 73 | { 74 | return false; 75 | } 76 | 77 | // UpdateTimes 78 | [HarmonyPrefix] 79 | [HarmonyPatch(typeof(MaintenanceTimer), "UpdateTimes")] 80 | public static bool UpdateTimes(MaintenanceTimer __instance) 81 | { 82 | return false; 83 | } 84 | 85 | [HarmonyPrefix] 86 | [HarmonyPatch(typeof(ClosingTimer), "IsShowRemainingMinutes")] 87 | public static bool IsShowRemainingMinutes(ref bool __result) 88 | { 89 | __result = false; 90 | return false; 91 | } 92 | 93 | [HarmonyPrefix] 94 | [HarmonyPatch(typeof(ClosingTimer), "IsClosed")] 95 | public static bool IsClosed(ref bool __result) 96 | { 97 | __result = false; 98 | return false; 99 | } 100 | 101 | [EnableIf(nameof(forceOfflineTimerExists))] 102 | [HarmonyPrefix] 103 | [HarmonyPatch(typeof(MaintenanceTimer), "ForceOfflineRemainingMinutes", MethodType.Getter)] 104 | public static bool ForceOfflineRemainingMinutes(ref int __result) 105 | { 106 | __result = 600; 107 | return false; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fix/FixCheckAuth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | using AMDaemon.Allnet; 4 | using AquaMai.Config.Attributes; 5 | using HarmonyLib; 6 | using Manager; 7 | using Manager.Operation; 8 | 9 | namespace AquaMai.Mods.Fix; 10 | 11 | [ConfigSection(exampleHidden: true, defaultOn: true)] 12 | public class FixCheckAuth 13 | { 14 | [ConfigEntry] 15 | private static readonly bool allowHttpsUpgrade = true; 16 | 17 | [HarmonyPostfix] 18 | [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] 19 | private static void PostCheckAuthProc(ref OperationData ____operationData) 20 | { 21 | if (Auth.GameServerUri.StartsWith("http://") || Auth.GameServerUri.StartsWith("https://")) 22 | { 23 | ____operationData.ServerUri = Auth.GameServerUri; 24 | 25 | // Host is used only for "CheckServerHash" and it is disabled now 26 | // it originally contains server's tls cert hash 27 | // So this can be used to transfer ambiguous data 28 | // we use this to notify that we can upgrade the link to https 29 | // as if we originally pass a https link to game, games without CheckServerHash will reject the link because ssl pinning 30 | if (Auth.GameServerHost == "_AquaMai_Upgrade_") 31 | { 32 | ____operationData.ServerUri = Auth.GameServerUri.Replace("http://", "https://"); 33 | } 34 | else if (upgradePort.IsMatch(Auth.GameServerHost)) 35 | { 36 | var match = upgradePort.Match(Auth.GameServerHost); 37 | var builder = new UriBuilder(Auth.GameServerUri) 38 | { 39 | Port = int.Parse(match.Groups[1].Value), 40 | Scheme = Uri.UriSchemeHttps, 41 | }; 42 | ____operationData.ServerUri = builder.ToString(); 43 | } 44 | } 45 | } 46 | 47 | private static readonly Regex upgradePort = new Regex(@"_AquaMai_UpgradPort_(\d+)_", RegexOptions.Compiled); 48 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Fix/FixConnSlide.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using AquaMai.Core.Attributes; 4 | using AquaMai.Config.Attributes; 5 | using HarmonyLib; 6 | using Manager; 7 | 8 | namespace AquaMai.Mods.Fix.Legacy; 9 | 10 | [ConfigSection(exampleHidden: true, defaultOn: true)] 11 | [EnableGameVersion(23000)] 12 | public class FixConnSlide 13 | { 14 | /* 这个 Patch 用于修复以下 bug: 15 | * 非 ConnSlide 被错误解析为 ConnSlide (Fes 首日刹那旅程爆机 bug) 16 | * 原 method 逻辑如下: 17 | * 18 | * if (this.IsSlideAll(noteData1.type) && (index1 + 1 < this._note._noteData.Count ? 1 : 0) != 0) 19 | * { 20 | * int targetNote = noteData1.slideData.targetNote; 21 | * if (noteData1.slideData != null) 22 | * targetNote = noteData1.slideData.targetNote; 23 | * for (int index3 = index1; index3 < this._note._noteData.Count; ++index3) 24 | * { 25 | * NoteData noteData3 = this._note._noteData[index3]; 26 | * if (this.IsSlideAll(noteData3.type) && noteData3.time == noteData1.end && noteData3.startButtonPos == targetNote && noteData3.parent == null) 27 | * { 28 | * noteData3.parent = noteData1.parent; 29 | * noteData1.child.Add(noteData3); 30 | * noteData3.isUsed = true; 31 | * noteData3.isJudged = true; 32 | * break; 33 | * } 34 | * } 35 | * } 36 | * 37 | * 修复 bug 需要把第二次调用 this.IsSlideAll() 更改为 this.IsConnectNote(), 这里使用 Transpiler 解决 38 | */ 39 | [HarmonyTranspiler] 40 | [HarmonyPatch(typeof(NotesReader), "calcSlide")] 41 | private static IEnumerable Fix(IEnumerable instructions) 42 | { 43 | List instList = new List(instructions); 44 | bool found = false; 45 | MethodInfo methodIsSlideAll = AccessTools.Method(typeof(NotesReader), "IsSlideAll"); 46 | MethodInfo methodIsConnectNote = AccessTools.Method(typeof(NotesReader), "IsConnectNote"); 47 | 48 | for (int i = 0; i < instList.Count; i++) 49 | { 50 | CodeInstruction inst = instList[i]; 51 | if (!found && inst.Calls(methodIsSlideAll)) 52 | { 53 | found = true; 54 | continue; 55 | } 56 | 57 | if (found && inst.Calls(methodIsSlideAll)) 58 | { 59 | inst.operand = methodIsConnectNote; 60 | // MelonLogger.Msg($"[FixConnSlide] Successfully patched NotesReader::calcSlide"); 61 | break; 62 | } 63 | } 64 | return instList; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fix/Legacy/FixQuickRetry130.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Core.Attributes; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Manager; 5 | 6 | namespace AquaMai.Mods.Fix.Legacy; 7 | 8 | [ConfigSection(exampleHidden: true, defaultOn: true)] 9 | [EnableGameVersion(23000, 23499, noWarn: true)] 10 | public class FixQuickRetry130 11 | { 12 | // Fix for the game not resetting Fast and Late counts when quick retrying 13 | // For game version < 1.35.0 14 | [HarmonyPostfix] 15 | [HarmonyPatch(typeof(GamePlayManager), "SetQuickRetryFrag")] 16 | public static void PostGamePlayManagerSetQuickRetryFrag(GamePlayManager __instance, bool flag) 17 | { 18 | // Since 1.35.0, `GameScoreList.Initialize()` resets the Fast and Late counts 19 | if (flag && !Traverse.Create(typeof(GameScoreList)).Methods().Contains("Initialize")) 20 | { 21 | for (int i = 0; i < 4; i++) 22 | { 23 | var gameScoreList = __instance.GetGameScore(i); 24 | var traverse = Traverse.Create(gameScoreList); 25 | traverse.Property("Fast").SetValue((uint)0); 26 | traverse.Property("Late").SetValue((uint)0); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AquaMai.Mods/Fix/README.md: -------------------------------------------------------------------------------- 1 | # Fix 2 | 3 | Fix of the game's bugs or removal of the game's annoying unuseful "features". 4 | 5 | Non-removal "Fix" patches should have no (negative, or any visual changing) side-effects on the original game. 6 | 7 | All patches under "Fix" should enabled by default and hide in example. They could be still turned off manually in the config. 8 | -------------------------------------------------------------------------------- /AquaMai.Mods/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | hidlibrary|Numerics 5 | $AquaMai.Mods$_ 6 | 7 | 8 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSettings/CreditConfig.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using AquaMai.Core.Attributes; 3 | using HarmonyLib; 4 | 5 | namespace AquaMai.Mods.GameSettings; 6 | 7 | [ConfigSection( 8 | en: "Set the game to Paid Play (lock credits) or Free Play.", 9 | zh: "设置游戏为付费游玩(锁定可用点数)或免费游玩")] 10 | public class CreditConfig 11 | { 12 | [ConfigEntry( 13 | en: "Set to Free Play (set to false for Paid Play).", 14 | zh: "是否免费游玩(设为 false 时为付费游玩)")] 15 | private static readonly bool isFreePlay = true; 16 | 17 | [HarmonyPrefix] 18 | [HarmonyPatch(typeof(Manager.Credit), "IsFreePlay")] 19 | public static bool PreIsFreePlay(ref bool __result) 20 | { 21 | __result = isFreePlay; 22 | return false; 23 | } 24 | 25 | [ConfigEntry( 26 | en: "Lock credits amount (only valid in Paid Play). Set to 0 to disable.", 27 | zh: "锁定可用点数数量(仅在付费游玩时有效),设为 0 以禁用")] 28 | private static readonly uint lockCredits = 24u; 29 | 30 | private static bool ShouldLockCredits => !isFreePlay && lockCredits > 0; 31 | 32 | [EnableIf(nameof(ShouldLockCredits))] 33 | [HarmonyPrefix] 34 | [HarmonyPatch(typeof(Manager.Credit), "IsGameCostEnough")] 35 | public static bool PreIsGameCostEnough(ref bool __result) 36 | { 37 | __result = true; 38 | return false; 39 | } 40 | 41 | [EnableIf(nameof(ShouldLockCredits))] 42 | [HarmonyPrefix] 43 | [HarmonyPatch(typeof(AMDaemon.CreditUnit), "Credit", MethodType.Getter)] 44 | public static bool PreCredit(ref uint __result) 45 | { 46 | __result = lockCredits; 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSettings/ForceAsServer.cs: -------------------------------------------------------------------------------- 1 | using AMDaemon; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | 5 | namespace AquaMai.Mods.GameSettings; 6 | 7 | [ConfigSection( 8 | en: "If you want to configure in-shop party-link, you should turn this off.", 9 | zh: "如果要配置店内招募的话,应该要把这个关闭", 10 | defaultOn: true)] 11 | public class ForceAsServer 12 | { 13 | [HarmonyPrefix] 14 | [HarmonyPatch(typeof(LanInstall), "IsServer", MethodType.Getter)] 15 | private static bool PreIsServer(ref bool __result) 16 | { 17 | __result = true; 18 | return false; 19 | } 20 | 21 | [HarmonyPrefix] 22 | [HarmonyPatch(typeof(Network), "IsLanAvailable", MethodType.Getter)] 23 | private static bool PreIsLanAvailable(ref bool __result) 24 | { 25 | __result = false; 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSettings/JudgeAdjust.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using IO; 5 | using Manager.UserDatas; 6 | 7 | namespace AquaMai.Mods.GameSettings; 8 | 9 | [ConfigSection( 10 | en: "Globally adjust A/B judgment (unit same as in-game options) or increase touch delay.", 11 | zh: "全局调整 A/B 判(单位和游戏里一样)或增加触摸延迟")] 12 | public class JudgeAdjust 13 | { 14 | [ConfigEntry( 15 | en: "Adjust A judgment.", 16 | zh: "调整 A 判")] 17 | private static readonly double a = 0; 18 | 19 | [ConfigEntry( 20 | en: "Adjust B judgment.", 21 | zh: "调整 B 判")] 22 | private static readonly double b = 0; 23 | 24 | [ConfigEntry( 25 | en: "Increase touch delay.", 26 | zh: "增加触摸延迟")] 27 | private static readonly uint touchDelay = 0; 28 | 29 | [HarmonyPostfix] 30 | [HarmonyPatch(typeof(UserOption), "GetAdjustMSec")] 31 | public static void GetAdjustMSec(ref float __result) 32 | { 33 | __result += (float)(a * 16.666666d); 34 | } 35 | 36 | [HarmonyPostfix] 37 | [HarmonyPatch(typeof(UserOption), "GetJudgeTimingFrame")] 38 | public static void GetJudgeTimingFrame(ref float __result) 39 | { 40 | __result += (float)b; 41 | } 42 | 43 | [HarmonyPrefix] 44 | [HarmonyPatch(typeof(NewTouchPanel), "Recv")] 45 | public static void NewTouchPanelRecv() 46 | { 47 | if (touchDelay <= 0) return; 48 | Thread.Sleep((int)touchDelay); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSettings/README.md: -------------------------------------------------------------------------------- 1 | # GameSettings 2 | 3 | Overriding or adjusting the game settings that're originally configurable / modifiable, but made into patches for unification, flexibility or convenience. 4 | 5 | Patches changing the way the game running / behaving which are not possible in the stock game may need to go to the GameSystem category. 6 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/Assets/LoadAssetBundleWithoutManifest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using HarmonyLib; 4 | using UnityEngine; 5 | using Manager; 6 | using Util; 7 | using AquaMai.Config.Attributes; 8 | 9 | namespace AquaMai.Mods.GameSystem.Assets; 10 | 11 | [ConfigSection( 12 | en: "Load all existing \".ab\" image resources regardless of the AssetBundleImages manifest.", 13 | zh: """ 14 | 加载所有存在的 .ab 图片资源(无视 AssetBundleImages.manifest) 15 | 导入了删除曲包之类的话,应该需要开启这个 16 | """)] 17 | public class LoadAssetBundleWithoutManifest 18 | { 19 | private static HashSet abFiles = new HashSet(); 20 | 21 | [HarmonyPostfix] 22 | [HarmonyPatch(typeof(OptionDataManager), "CheckAssetBundle")] 23 | public static void PostCheckAssetBundle(ref Safe.ReadonlySortedDictionary abs) 24 | { 25 | foreach (var ab in abs) 26 | { 27 | abFiles.Add(ab.Key); 28 | } 29 | } 30 | 31 | [HarmonyPrefix] 32 | [HarmonyPatch(typeof(AssetBundleManifest), "GetAllAssetBundles")] 33 | public static bool PreGetAllAssetBundles(AssetBundleManifest __instance, ref string[] __result) 34 | { 35 | __result = abFiles.ToArray(); 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/OptionLoadFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using AMDaemon; 4 | using AquaMai.Config.Attributes; 5 | using HarmonyLib; 6 | using Manager; 7 | using UnityEngine; 8 | 9 | namespace AquaMai.Mods.GameSystem; 10 | 11 | [ConfigSection( 12 | en: "When loading Opt in StreamingAssets, not only load those starting with A, but also those starting with the letter corresponding to the version", 13 | zh: "在 StreamingAssets 加载 Opt 时,不仅加载 A 开头的,也加载版本对应字母开头的", 14 | defaultOn: true)] 15 | public static class OptionLoadFix 16 | { 17 | [HarmonyPrefix] 18 | [HarmonyPatch(typeof(AppImage), "OptionMountRootPath", MethodType.Getter)] 19 | public static bool AppImageOptionMountRootPath(ref string __result) 20 | { 21 | __result = Application.streamingAssetsPath; 22 | 23 | return false; 24 | } 25 | 26 | // 如果 optDir 也是 A 开头的话,结果会重复,需要去重 27 | [HarmonyPostfix] 28 | [HarmonyPatch(typeof(DataManager), "GetDirs")] 29 | public static void DataManagerGetDirs(ref List dirs) 30 | { 31 | dirs = dirs.Distinct().ToList(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/QuickRetry.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Core.Attributes; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Manager; 5 | 6 | namespace AquaMai.Mods.GameSystem; 7 | 8 | [ConfigSection( 9 | en: "Hold the bottom four buttons (3456) for quick retry (like in Freedom Mode, default non-utage only).", 10 | zh: "按住下方四个按钮(3456)快速重开本局游戏(像在 Freedom Mode 中一样,默认仅对非宴谱有效)")] 11 | [EnableGameVersion(23000)] 12 | public class QuickRetry 13 | { 14 | [ConfigEntry( 15 | en: "Force enable in Utage.", 16 | zh: "在宴谱中强制启用")] 17 | private static readonly bool enableInUtage = false; 18 | 19 | [HarmonyPrefix] 20 | [HarmonyPatch(typeof(Monitor.QuickRetry), "IsQuickRetryEnable")] 21 | public static bool OnQuickRetryIsQuickRetryEnable(ref bool __result) 22 | { 23 | if (enableInUtage) 24 | { 25 | __result = true; 26 | } 27 | else 28 | { 29 | var isUtageProperty = Traverse.Create(typeof(GameManager)).Property("IsUtage"); 30 | __result = !isUtageProperty.PropertyExists() || !isUtageProperty.GetValue(); 31 | } 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/README.md: -------------------------------------------------------------------------------- 1 | # GameSystem 2 | 3 | Patches changing the way the game running / behaving which are not possible in the stock game. See also the [GameSettings README](../GameSettings/README.md) for differences. 4 | 5 | Game asset related patches should go to the Assets subcategory (or the Fancy category if they're too fancy). 6 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/RemoveEncryption.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using AquaMai.Config.Attributes; 5 | using AquaMai.Core.Helpers; 6 | using HarmonyLib; 7 | using Net.Packet; 8 | 9 | namespace AquaMai.Mods.GameSystem; 10 | 11 | [ConfigSection( 12 | en: """ 13 | If you are using an unmodified client, requests to the server will be encrypted by default, but requests to the private server should not be encrypted. 14 | With this option enabled, the connection will not be encrypted, and the suffix added by different versions of the client to the API names are also removed. 15 | Please keep this option enabled normally. 16 | """, 17 | zh: """ 18 | 如果你在用未经修改的客户端,会默认加密到服务器的连接,而连接私服的时候不应该加密 19 | 开了这个选项之后就不会加密连接了,同时也会移除不同版本的客户端可能会对 API 接口加的后缀 20 | 正常情况下,请保持这个选项开启 21 | """, 22 | defaultOn: true)] 23 | public class RemoveEncryption 24 | { 25 | [HarmonyPrefix] 26 | [HarmonyPatch(typeof(Packet), "Obfuscator", typeof(string))] 27 | public static bool PreObfuscator(string srcStr, ref string __result) 28 | { 29 | __result = Shim.RemoveApiSuffix(srcStr); 30 | return false; 31 | } 32 | 33 | [HarmonyPatch] 34 | public class EncryptDecrypt 35 | { 36 | public static IEnumerable TargetMethods() 37 | { 38 | var methods = AccessTools.TypeByName("Net.CipherAES").GetMethods(); 39 | return 40 | [ 41 | methods.FirstOrDefault(it => it.Name == "Encrypt" && it.IsPublic), 42 | methods.FirstOrDefault(it => it.Name == "Decrypt" && it.IsPublic) 43 | ]; 44 | } 45 | 46 | public static bool Prefix(object[] __args, ref object __result) 47 | { 48 | if (__args.Length == 1) 49 | { 50 | // public static byte[] Encrypt(byte[] data) 51 | // public static byte[] Decrypt(byte[] encryptData) 52 | __result = __args[0]; 53 | } 54 | else if (__args.Length == 2) 55 | { 56 | // public static bool Encrypt(byte[] data, out byte[] encryptData) 57 | // public static bool Decrypt(byte[] encryptData, out byte[] plainData) 58 | __args[1] = __args[0]; 59 | __result = true; 60 | } 61 | return false; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/Sound.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Manager; 5 | 6 | namespace AquaMai.Mods.GameSystem; 7 | 8 | [ConfigSection( 9 | zh: "音频独占与八声道设置", 10 | en: "Audio Exclusive and 8-Channel Settings")] 11 | public static class Sound 12 | { 13 | [ConfigEntry( 14 | zh: "是否启用音频独占", 15 | en: "Enable Audio Exclusive")] 16 | private readonly static bool enableExclusive = false; 17 | 18 | [ConfigEntry( 19 | zh: "是否启用八声道", 20 | en: "Enable 8-Channel")] 21 | private readonly static bool enable8Channel = false; 22 | 23 | private static CriAtomUserExtension.AudioClientShareMode AudioShareMode => enableExclusive ? CriAtomUserExtension.AudioClientShareMode.Exclusive : CriAtomUserExtension.AudioClientShareMode.Shared; 24 | 25 | private const ushort wBitsPerSample = 32; 26 | private const uint nSamplesPerSec = 48000u; 27 | private static ushort nChannels => enable8Channel ? (ushort)8 : (ushort)2; 28 | private static ushort nBlockAlign => (ushort)(wBitsPerSample / 8 * nChannels); 29 | private static uint nAvgBytesPerSec => nSamplesPerSec * nBlockAlign; 30 | 31 | private static CriAtomUserExtension.WaveFormatExtensible CreateFormat() => 32 | new() 33 | { 34 | Format = new CriAtomUserExtension.WaveFormatEx 35 | { 36 | wFormatTag = 65534, 37 | nSamplesPerSec = nSamplesPerSec, 38 | wBitsPerSample = wBitsPerSample, 39 | cbSize = 22, 40 | nChannels = nChannels, 41 | nBlockAlign = nBlockAlign, 42 | nAvgBytesPerSec = nAvgBytesPerSec 43 | }, 44 | Samples = new CriAtomUserExtension.Samples 45 | { 46 | wValidBitsPerSample = 24, 47 | }, 48 | dwChannelMask = enable8Channel ? 1599u : 3u, 49 | SubFormat = new Guid("00000001-0000-0010-8000-00aa00389b71") 50 | }; 51 | 52 | [HarmonyPrefix] 53 | // Original typo 54 | [HarmonyPatch(typeof(WasapiExclusive), "Intialize")] 55 | public static bool InitializePrefix() 56 | { 57 | CriAtomUserExtension.SetAudioClientShareMode(AudioShareMode); 58 | CriAtomUserExtension.SetAudioBufferTime(160000uL); 59 | var format = CreateFormat(); 60 | CriAtomUserExtension.SetAudioClientFormat(ref format); 61 | return false; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/TestProof.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq; 3 | using AquaMai.Config.Attributes; 4 | using AquaMai.Config.Types; 5 | using AquaMai.Core; 6 | using AquaMai.Core.Attributes; 7 | using AquaMai.Core.Helpers; 8 | using AquaMai.Mods.Tweaks; 9 | using AquaMai.Mods.UX; 10 | using AquaMai.Mods.UX.PracticeMode; 11 | using HarmonyLib; 12 | using Manager; 13 | 14 | namespace AquaMai.Mods.GameSystem; 15 | 16 | [ConfigSection( 17 | en: """ 18 | When enabled, test button must be long pressed to enter game test mode. 19 | When test button is bound to other features, this option is enabled automatically. 20 | """, 21 | zh: """ 22 | 启用后,测试键必须长按才能进入游戏测试模式 23 | 当测试键被绑定到其它功能时,此选项自动开启 24 | """)] 25 | [EnableImplicitlyIf(nameof(ShouldEnableImplicitly))] 26 | public class TestProof 27 | { 28 | public static bool ShouldEnableImplicitly 29 | { 30 | get 31 | { 32 | (System.Type section, KeyCodeOrName key)[] featureKeys = 33 | [ 34 | (typeof(OneKeyEntryEnd), OneKeyEntryEnd.key), 35 | (typeof(OneKeyRetrySkip), OneKeyRetrySkip.retryKey), 36 | (typeof(OneKeyRetrySkip), OneKeyRetrySkip.skipKey), 37 | (typeof(HideSelfMadeCharts), HideSelfMadeCharts.key), 38 | (typeof(PracticeMode), PracticeMode.key), 39 | (typeof(ResetTouch), ResetTouch.key), 40 | ]; 41 | var keyMapEnabled = ConfigLoader.Config.GetSectionState(typeof(KeyMap)).Enabled; 42 | return featureKeys.Any(it => 43 | // The feature is enabled and... 44 | ConfigLoader.Config.GetSectionState(it.section).Enabled && 45 | ( 46 | // and the key is test, or... 47 | it.key == KeyCodeOrName.Test || 48 | // or the key have been mapped to the same key as test. 49 | (keyMapEnabled && it.key.ToString() == KeyMap.Test.ToString()))); 50 | } 51 | } 52 | 53 | [ConfigEntry( 54 | en: "Change it to a value other than Test to enable long pressing of a specific key to enter the game test mode, so that the Test key can be fully used to implement custom functions", 55 | zh: "修改为 Test 以外的值来实现长按特定的键进入游戏测试模式,这样 Test 键就可以完全用来实现自定义功能了")] 56 | private static readonly KeyCodeOrName testKey = KeyCodeOrName.Test; 57 | 58 | [HarmonyPrefix] 59 | [HarmonyPatch(typeof(InputManager), "GetSystemInputDown")] 60 | public static bool GetSystemInputDown(ref bool __result, InputManager.SystemButtonSetting button, bool[] ___SystemButtonDown) 61 | { 62 | __result = ___SystemButtonDown[(int)button]; 63 | if (button != InputManager.SystemButtonSetting.ButtonTest) 64 | return false; 65 | 66 | var stackTrace = new StackTrace(); // get call stack 67 | var stackFrames = stackTrace.GetFrames(); // get method calls (frames) 68 | 69 | if (stackFrames.Any(it => it.GetMethod().Name == "DMD")) 70 | { 71 | __result = KeyListener.GetKeyDownOrLongPress(testKey, true); 72 | } 73 | 74 | return false; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/TouchPanelBaudRate.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using IO; 4 | 5 | namespace AquaMai.Mods.GameSystem; 6 | 7 | [ConfigSection( 8 | en: """ 9 | Adjust the baud rate of the touch screen serial port, default value is 9600. 10 | Requires hardware support. If you are unsure, don't use it. 11 | """, 12 | zh: """ 13 | 调整触摸屏串口波特率,默认值 9600 14 | 需要硬件配合,如果你不清楚你是否可以使用,请不要使用 15 | """)] 16 | public class TouchPanelBaudRate 17 | { 18 | [ConfigEntry( 19 | en: "Baud rate.", 20 | zh: "波特率")] 21 | private static readonly int baudRate = 9600; 22 | 23 | [HarmonyPatch(typeof(NewTouchPanel), "Open")] 24 | [HarmonyPrefix] 25 | private static void OpenPrefix(ref int ___BaudRate) 26 | { 27 | if (baudRate <= 0) return; 28 | ___BaudRate = baudRate; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AquaMai.Mods/GameSystem/TouchToButtonInput.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using AquaMai.Config.Attributes; 4 | using HarmonyLib; 5 | using Process; 6 | using static Manager.InputManager; 7 | 8 | namespace AquaMai.Mods.GameSystem; 9 | 10 | [ConfigSection( 11 | en: "Map touch actions to buttons.", 12 | zh: "映射触摸操作至实体按键")] 13 | public class TouchToButtonInput 14 | { 15 | private static bool _isPlaying = false; 16 | 17 | [HarmonyPostfix] 18 | [HarmonyPatch(typeof(GameProcess), "OnStart")] 19 | public static void OnGameProcessStart(GameProcess __instance) 20 | { 21 | _isPlaying = true; 22 | } 23 | 24 | [HarmonyPostfix] 25 | [HarmonyPatch(typeof(GameProcess), "OnRelease")] 26 | public static void OnGameProcessRelease(GameProcess __instance) 27 | { 28 | _isPlaying = false; 29 | } 30 | 31 | [HarmonyPostfix] 32 | [HarmonyPatch(typeof(Manager.InputManager), "GetButtonDown")] 33 | public static void GetButtonDown(ref bool __result, int monitorId, ButtonSetting button) 34 | { 35 | if (_isPlaying || __result) return; 36 | if (button.ToString().StartsWith("Button")) 37 | { 38 | __result = GetTouchPanelAreaDown(monitorId, (TouchPanelArea)button); 39 | } 40 | else if (button.ToString().Equals("Select")) 41 | { 42 | __result = GetTouchPanelAreaLongPush(monitorId, TouchPanelArea.C1, 500L) || GetTouchPanelAreaLongPush(monitorId, TouchPanelArea.C2, 500L); 43 | } 44 | } 45 | 46 | [HarmonyPatch] 47 | public static class GetMonitorButtonDown 48 | { 49 | public static IEnumerable TargetMethods() 50 | { 51 | yield return AccessTools.Method(typeof(Manager.InputManager), "GetMonitorButtonDown", [typeof(int).MakeByRefType(), typeof(ButtonSetting)]); 52 | } 53 | 54 | public static void Postfix(ref bool __result, ref int monitorId, ButtonSetting button) 55 | { 56 | if (_isPlaying || __result) return; 57 | for (int i = 0; i < 2; i++) 58 | { 59 | if (button.ToString().StartsWith("Button")) 60 | { 61 | __result = GetTouchPanelAreaDown(i, (TouchPanelArea)button); 62 | } 63 | else if (button.ToString().Equals("Select")) 64 | { 65 | __result = GetTouchPanelAreaLongPush(i, TouchPanelArea.C1, 500L) || GetTouchPanelAreaLongPush(i, TouchPanelArea.C2, 500L); 66 | } 67 | 68 | if (__result) 69 | { 70 | monitorId = i; 71 | break; 72 | } 73 | } 74 | } 75 | } 76 | 77 | [HarmonyPostfix] 78 | [HarmonyPatch(typeof(Manager.InputManager), "GetButtonPush")] 79 | public static void GetButtonPush(ref bool __result, int monitorId, ButtonSetting button) 80 | { 81 | if (_isPlaying || __result) return; 82 | if (button.ToString().StartsWith("Button")) __result = GetTouchPanelAreaPush(monitorId, (TouchPanelArea)button); 83 | } 84 | 85 | [HarmonyPostfix] 86 | [HarmonyPatch(typeof(Manager.InputManager), "GetButtonLongPush")] 87 | public static void GetButtonLongPush(ref bool __result, int monitorId, ButtonSetting button, long msec) 88 | { 89 | if (_isPlaying || __result) return; 90 | if (button.ToString().StartsWith("Button")) __result = GetTouchPanelAreaLongPush(monitorId, (TouchPanelArea)button, msec); 91 | } 92 | } -------------------------------------------------------------------------------- /AquaMai.Mods/General.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | 3 | namespace AquaMai.Mods; 4 | 5 | // This class is for settings only. Don't patch anything here. 6 | 7 | [ConfigSection( 8 | en: "AquaMai's general settings.", 9 | zh: "AquaMai 的通用设置", 10 | alwaysEnabled: true)] 11 | public class General 12 | { 13 | [ConfigEntry( 14 | en: """ 15 | Language for mod UI (en and zh supported). 16 | If empty, the system language will be used. 17 | The config file will also be saved in this language. 18 | """, 19 | zh: """ 20 | Mod 界面的语言,支持 en 和 zh 21 | 如果为空,将使用系统语言 22 | 配置文件也将以此语言保存 23 | """, 24 | specialConfigEntry: SpecialConfigEntry.Locale)] 25 | public static readonly string locale = ""; 26 | } 27 | 28 | // Please add/remove corresponding entries in SectionNameOrder enum when adding/removing sections. 29 | public enum SectionNameOrder 30 | { 31 | DeprecationWarning, 32 | General, 33 | Fix, 34 | GameSystem_Assets, 35 | GameSystem, 36 | GameSettings, 37 | Tweaks, 38 | Tweaks_TimeSaving, 39 | UX, 40 | Utils, 41 | Fancy, 42 | Enhancement, 43 | } 44 | -------------------------------------------------------------------------------- /AquaMai.Mods/Polyfills.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices 2 | { 3 | internal static class IsExternalInit {} 4 | } 5 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/IgnoreAimeServerError.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Manager; 4 | 5 | namespace AquaMai.Mods.Tweaks; 6 | 7 | [ConfigSection( 8 | en: "Prevent gray network caused by mistakenly thinking it's an AimeDB server issue.", 9 | zh: "防止因错误认为 AimeDB 服务器问题引起的灰网,建议开启")] 10 | public class IgnoreAimeServerError 11 | { 12 | [HarmonyPatch(typeof(OperationManager), "IsAliveAimeServer", MethodType.Getter)] 13 | [HarmonyPrefix] 14 | public static bool Prefix(ref bool __result) 15 | { 16 | __result = true; 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/LockFrameRate.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using UnityEngine; 3 | 4 | namespace AquaMai.Mods.Tweaks; 5 | 6 | [ConfigSection( 7 | en: """ 8 | Force the frame rate limit to 60 FPS and disable vSync. 9 | Do not use if your game has no issues. 10 | """, 11 | zh: """ 12 | 强制设置帧率上限为 60 帧并关闭垂直同步 13 | 如果你的游戏没有问题,请不要使用 14 | """)] 15 | public class LockFrameRate 16 | { 17 | [ConfigEntry( 18 | zh: "目标帧率,不建议修改。除非你知道你在做什么")] 19 | public static readonly int targetFrameRate = 60; 20 | 21 | public static void OnBeforePatch() 22 | { 23 | Application.targetFrameRate = targetFrameRate; 24 | QualitySettings.vSyncCount = 0; 25 | } 26 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/README.md: -------------------------------------------------------------------------------- 1 | # Tweaks 2 | 3 | Patches to make the game more stable, more robust and less annoying. The game is playable at all without them, but sometimes they help a lot. 4 | 5 | These patches don't change the way the game behaving, otherwise they may go to the GameSystem category. 6 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/ResetTouch.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using AquaMai.Config.Types; 3 | using AquaMai.Core.Helpers; 4 | using AquaMai.Core.Resources; 5 | using HarmonyLib; 6 | using MAI2.Util; 7 | using Main; 8 | using Manager; 9 | using Process; 10 | 11 | namespace AquaMai.Mods.Tweaks; 12 | 13 | [ConfigSection( 14 | en: "Reset touch panel manually or after playing track.", 15 | zh: "重置触摸面板")] 16 | public class ResetTouch 17 | { 18 | [ConfigEntry(en: "Reset touch panel after playing track.", zh: "玩完一首歌自动重置")] 19 | private static bool afterTrack = false; 20 | 21 | [ConfigEntry(en: "Reset manually.", zh: "按键重置")] 22 | public static readonly KeyCodeOrName key = KeyCodeOrName.None; 23 | 24 | [ConfigEntry] private static readonly bool longPress = false; 25 | 26 | [HarmonyPostfix] 27 | [HarmonyPatch(typeof(ResultProcess), "OnStart")] 28 | public static void ResultProcessOnStart() 29 | { 30 | if (!afterTrack) return; 31 | SingletonStateMachine.Instance.StartTouchPanel(); 32 | MelonLoader.MelonLogger.Msg("[TouchResetAfterTrack] Touch panel reset"); 33 | } 34 | 35 | [HarmonyPostfix] 36 | [HarmonyPatch(typeof(GameMainObject), "Update")] 37 | public static void OnGameMainObjectUpdate() 38 | { 39 | if (!KeyListener.GetKeyDownOrLongPress(key, longPress)) return; 40 | SingletonStateMachine.Instance.StartTouchPanel(); 41 | MessageHelper.ShowMessage(Locale.TouchPanelReset); 42 | } 43 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/SkipUserVersionCheck.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Process.Entry.State; 4 | 5 | namespace AquaMai.Mods.Tweaks; 6 | 7 | [ConfigSection( 8 | en: "Allow login with higher data version.", 9 | zh: """ 10 | 原先如果你的账号版本比当前游戏设定的版本高的话,就会不能登录 11 | 开了这个选项之后就可以登录了,不过你的账号版本还是会被设定为当前游戏的版本 12 | """)] 13 | public class SkipUserVersionCheck 14 | { 15 | [HarmonyPrefix] 16 | [HarmonyPatch(typeof(ConfirmPlay), "IsValidVersion")] 17 | public static bool IsValidVersion(ref bool __result) 18 | { 19 | __result = true; 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/EntryToMusicSelection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using AquaMai.Config.Attributes; 3 | using AquaMai.Core.Helpers; 4 | using DB; 5 | using HarmonyLib; 6 | using MAI2.Util; 7 | using Manager; 8 | using Monitor; 9 | using Monitor.ModeSelect; 10 | using Process; 11 | using Process.Information; 12 | 13 | namespace AquaMai.Mods.Tweaks.TimeSaving; 14 | 15 | [ConfigSection( 16 | en: "Directly enter the song selection screen after login.", 17 | zh: "登录完成后直接进入选歌界面")] 18 | public class EntryToMusicSelection 19 | { 20 | /* 21 | * Highly experimental, may well break some stuff 22 | * Works by overriding the info screen (where it shows new events and stuff) 23 | * to directly exit to the music selection screen, skipping character and 24 | * event selection, among others 25 | */ 26 | [HarmonyPrefix] 27 | [HarmonyPatch(typeof(InformationProcess), "OnUpdate")] 28 | public static bool OnUpdate(InformationProcess __instance, ProcessDataContainer ___container) 29 | { 30 | GameManager.SetMaxTrack(); 31 | SharedInstances.GameMainObject.StartCoroutine(GraduallyIncreaseHeadphoneVolumeCoroutine()); 32 | ___container.processManager.AddProcess(new MusicSelectProcess(___container)); 33 | ___container.processManager.ReleaseProcess(__instance); 34 | return false; 35 | } 36 | 37 | [HarmonyPrefix] 38 | [HarmonyPatch(typeof(MapResultMonitor), "Initialize")] 39 | public static void MapResultMonitorPreInitialize(int monIndex) 40 | { 41 | var userData = Singleton.Instance.GetUserData(monIndex); 42 | var index = userData.MapList.FindIndex((m) => m.ID == userData.Detail.SelectMapID); 43 | if (index >= 0) return; 44 | userData.MapList.Clear(); 45 | } 46 | 47 | // Gradually increase headphone volume 48 | private static IEnumerator GraduallyIncreaseHeadphoneVolumeCoroutine() 49 | { 50 | CommonValue[] _volumeFadeIns = [null, null]; 51 | 52 | for (var i = 0; i < 2; i++) 53 | { 54 | var userData = UserDataManager.Instance.GetUserData(i); 55 | if (!userData.IsEntry) continue; 56 | _volumeFadeIns[i] = new CommonValue(); 57 | var value = userData.Option.HeadPhoneVolume.GetValue(); 58 | if (GameManager.IsSelectContinue[i]) 59 | { 60 | _volumeFadeIns[i].start = value; 61 | _volumeFadeIns[i].current = value; 62 | } 63 | else 64 | { 65 | _volumeFadeIns[i].start = 0.05f; 66 | _volumeFadeIns[i].current = 0.05f; 67 | } 68 | 69 | _volumeFadeIns[i].end = value; 70 | _volumeFadeIns[i].diff = (_volumeFadeIns[i].end - _volumeFadeIns[i].start) / 90f; 71 | } 72 | 73 | yield return null; 74 | 75 | 76 | for (var timer = 90; timer >= 0; timer--) 77 | { 78 | for (var i = 0; i < 2; i++) 79 | { 80 | if (_volumeFadeIns[i] == null) continue; 81 | if (timer == 0) 82 | { 83 | SoundManager.SetHeadPhoneVolume(i, _volumeFadeIns[i].end); 84 | } 85 | else if (!_volumeFadeIns[i].UpdateValue()) 86 | { 87 | SoundManager.SetHeadPhoneVolume(i, _volumeFadeIns[i].current); 88 | } 89 | } 90 | 91 | yield return null; 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/ExitToSave.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Process; 4 | 5 | namespace AquaMai.Mods.Tweaks.TimeSaving; 6 | 7 | [ConfigSection( 8 | en: "Skip uploading photos and collectibles after the game ends and log out directly.", 9 | zh: "游戏结束后跳过上传照片和收藏品直接登出")] 10 | public class ExitToSave 11 | { 12 | [HarmonyPrefix] 13 | [HarmonyPatch(typeof(PhotoEditProcess), nameof(PhotoEditProcess.OnUpdate))] 14 | public static bool SkipPhotoEditProcess(ProcessDataContainer ___container, PhotoEditProcess __instance) 15 | { 16 | ___container.processManager.AddProcess(new DataSaveProcess(___container)); 17 | ___container.processManager.ReleaseProcess(__instance); 18 | return false; 19 | } 20 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/IWontTapOrSlideVigorously.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | 5 | namespace AquaMai.Mods.Tweaks.TimeSaving; 6 | 7 | [ConfigSection( 8 | en: "Skip the \"Do not tap or slide vigorously\" screen, immediately proceed to the next screen once data is loaded.", 9 | zh: "跳过“不要大力拍打或滑动哦”这个界面,数据一旦加载完就立马进入下一个界面")] 10 | public class IWontTapOrSlideVigorously 11 | { 12 | [HarmonyPrefix] 13 | [HarmonyPatch(typeof(PlInformationMonitor), "IsPlayPlInfoEnd")] 14 | public static bool Patch(ref bool __result) 15 | { 16 | __result = true; 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/SkipEventInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Process; 5 | using Process.Information; 6 | 7 | namespace AquaMai.Mods.Tweaks.TimeSaving; 8 | 9 | [ConfigSection( 10 | en: "Skip possible prompts like \"New area discovered\", \"New songs added\", \"There are events\" during game login/registration.", 11 | zh: "跳过登录 / 注册游戏时候可能的 “发现了新的区域哟” “乐曲增加” “有活动哟” 之类的提示")] 12 | public class SkipEventInfo 13 | { 14 | [HarmonyPostfix] 15 | [HarmonyPatch(typeof(InformationProcess), "OnStart")] 16 | public static void InformationProcessPostStart(ref uint ____state) 17 | { 18 | ____state = 3; 19 | } 20 | 21 | [HarmonyPostfix] 22 | [HarmonyPatch(typeof(RegionalSelectProcess), "OnStart")] 23 | public static void RegionalSelectProcessPreStart(ref Queue[] ____discoverList) 24 | { 25 | ____discoverList = new Queue[] { new Queue(), new Queue() }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/SkipGoodbyeScreen.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | using Process; 5 | 6 | namespace AquaMai.Mods.Tweaks.TimeSaving; 7 | 8 | [ConfigSection( 9 | en: "Skip the \"Goodbye\" screen at the end of the game.", 10 | zh: "跳过游戏结束的「再见」界面")] 11 | public class SkipGoodbyeScreen 12 | { 13 | [HarmonyPrefix] 14 | [HarmonyPatch(typeof(GameOverMonitor), "IsPlayEnd")] 15 | public static bool GameOverMonitorPlayEnd(ref bool __result) 16 | { 17 | __result = true; 18 | return false; 19 | } 20 | 21 | [HarmonyPrefix] 22 | [HarmonyPatch(typeof(GameOverProcess), "OnUpdate")] 23 | public static void GameOverProcessOnUpdate(ref GameOverProcess.GameOverSequence ____state) 24 | { 25 | if (____state == GameOverProcess.GameOverSequence.SkyChange) 26 | { 27 | ____state = GameOverProcess.GameOverSequence.Disp; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/SkipStartupDelays.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Process; 5 | 6 | namespace AquaMai.Mods.Tweaks.TimeSaving; 7 | 8 | [ConfigSection( 9 | en: "Skip useless 2s delays to speed up the game boot process.", 10 | zh: """ 11 | 在自检界面,每个屏幕结束的时候都会等两秒才进入下一个屏幕,很浪费时间 12 | 开了这个选项之后就不会等了 13 | """)] 14 | public class SkipStartupDelays 15 | { 16 | [HarmonyPrefix] 17 | [HarmonyPatch(typeof(PowerOnProcess), "OnStart")] 18 | public static void PrePowerOnStart(ref float ____waitTime) 19 | { 20 | ____waitTime = 0f; 21 | } 22 | 23 | [HarmonyPrefix] 24 | [HarmonyPatch(typeof(StartupProcess), "OnUpdate")] 25 | public static void PreStartupUpdate(byte ____state, ref Stopwatch ___timer) 26 | { 27 | if (____state == 8) 28 | { 29 | Traverse.Create(___timer).Field("elapsed").SetValue(2 * 10000000L); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/SkipStartupWarning.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Monitor; 4 | 5 | namespace AquaMai.Mods.Tweaks.TimeSaving; 6 | 7 | [ConfigSection( 8 | en: "Skip SDEZ's warning screen and logo shown after the POST sequence.", 9 | zh: "跳过 SDEZ 启动时的 WARNING 界面")] 10 | public class SkipStartupWarning 11 | { 12 | /* 13 | * Patch PlayLogo to disable the warning screen 14 | */ 15 | [HarmonyPrefix] 16 | [HarmonyPatch(typeof (WarningMonitor), "PlayLogo")] 17 | public static bool PlayLogo() 18 | { 19 | // Return false to block the original method 20 | return false; 21 | } 22 | 23 | [HarmonyPrefix] 24 | [HarmonyPatch(typeof (WarningMonitor), "IsLogoAnimationEnd")] 25 | public static bool IsLogoAnimationEnd(ref bool __result) 26 | { 27 | // Always return true to indicate the animation has ended 28 | __result = true; 29 | return false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AquaMai.Mods/Tweaks/TimeSaving/SkipTrackStart.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using Manager; 4 | using Process; 5 | 6 | namespace AquaMai.Mods.Tweaks.TimeSaving; 7 | 8 | [ConfigSection( 9 | en: "Skip TrackStart screen.", 10 | zh: "跳过乐曲开始界面")] 11 | public class SkipTrackStart 12 | { 13 | [HarmonyPostfix] 14 | [HarmonyPatch(typeof (TrackStartProcess), "OnStart")] 15 | public static void OnStart(ref TrackStartProcess.TrackStartSequence ____state) 16 | { 17 | ____state = TrackStartProcess.TrackStartSequence.DispEnd; 18 | } 19 | 20 | [HarmonyPrefix] 21 | [HarmonyPatch(typeof (MusicSelectProcess), "GameStart")] 22 | public static bool GameStart(MusicSelectProcess __instance, ProcessDataContainer ___container) 23 | { 24 | ___container.processManager.AddProcess(new TrackStartProcess(___container), 50); 25 | ___container.processManager.ReleaseProcess(__instance); 26 | SoundManager.PreviewEnd(); 27 | return false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AquaMai.Mods/Types/ConditionalMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using AquaMai.Core.Helpers; 3 | using JetBrains.Annotations; 4 | 5 | namespace AquaMai.Mods.Types; 6 | 7 | public abstract class ConditionalMessage 8 | { 9 | public string[] locales = []; 10 | [CanBeNull] public string minimumAquaMaiVersion = null; 11 | [CanBeNull] public string maximumAquaMaiVersion = null; 12 | public int minimumGameVersion = 0; 13 | public int maximumGameVersion = 0; 14 | 15 | public bool ShouldShow() 16 | { 17 | if (locales != null && locales.Length != 0 && !locales.Contains(General.locale)) 18 | { 19 | return false; 20 | } 21 | 22 | var aquaMaiVersion = new System.Version(Core.BuildInfo.Version); 23 | if (minimumAquaMaiVersion != null && aquaMaiVersion < new System.Version(minimumAquaMaiVersion)) 24 | { 25 | return false; 26 | } 27 | 28 | if (maximumAquaMaiVersion != null && aquaMaiVersion > new System.Version(maximumAquaMaiVersion)) 29 | { 30 | return false; 31 | } 32 | 33 | var gameVersion = GameInfo.GameVersion; 34 | if (minimumGameVersion != 0 && gameVersion < minimumGameVersion) 35 | { 36 | return false; 37 | } 38 | 39 | if (maximumGameVersion != 0 && gameVersion > maximumGameVersion) 40 | { 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | } -------------------------------------------------------------------------------- /AquaMai.Mods/UX/DisableLightOutGame.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using HarmonyLib; 3 | using IO; 4 | using Mecha; 5 | using MelonLoader; 6 | using Monitor; 7 | using Process; 8 | 9 | namespace AquaMai.Mods.UX; 10 | 11 | [ConfigSection( 12 | en: "Disable button LED when not playing", 13 | zh: """ 14 | 在游戏闲置时关闭外键和框体的灯光 15 | “一闪一闪的 闪的我心发慌” 16 | """)] 17 | public static class DisableLightOutGame 18 | { 19 | [HarmonyPrefix] 20 | [HarmonyPatch(typeof(Bd15070_4IF), nameof(Bd15070_4IF.SetColorFetAutoFade))] 21 | public static bool SetColorFetAutoFadePrefix() 22 | { 23 | #if DEBUG 24 | MelonLogger.Msg("[DisableLightOutGame] 拦截 Bd15070_4IF.SetColorFetAutoFade"); 25 | #endif 26 | return false; 27 | } 28 | 29 | [HarmonyPrefix] 30 | [HarmonyPatch(typeof(AdvDemoMonitor), "BeatUpdate")] 31 | public static bool BeatUpdate() 32 | { 33 | #if DEBUG 34 | MelonLogger.Msg("[DisableLightOutGame] 拦截 AdvDemoMonitor.BeatUpdate"); 35 | #endif 36 | return false; 37 | } 38 | 39 | [HarmonyPostfix] 40 | [HarmonyPatch(typeof(AdvDemoProcess), "OnStart")] 41 | public static void AdvDemoProcessStart() 42 | { 43 | MechaManager.SetAllCuOff(); 44 | } 45 | 46 | [HarmonyPostfix] 47 | [HarmonyPatch(typeof(AdvertiseProcess), "OnStart")] 48 | public static void AdvertiseProcessStart() 49 | { 50 | MechaManager.SetAllCuOff(); 51 | } 52 | } -------------------------------------------------------------------------------- /AquaMai.Mods/UX/NoAmDaemonAlert.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using AquaMai.Config.Attributes; 3 | using AquaMai.Core.Resources; 4 | using HarmonyLib; 5 | using Main; 6 | using UnityEngine; 7 | 8 | namespace AquaMai.Mods.UX; 9 | 10 | [ConfigSection( 11 | defaultOn: true, 12 | zh: "因 AmDaemon 未运行而黑屏时显示警告")] 13 | public class NoAmDaemonAlert : MonoBehaviour 14 | { 15 | private static NoAmDaemonAlert display; 16 | private Stopwatch stopwatch = new Stopwatch(); 17 | 18 | [HarmonyPostfix] 19 | [HarmonyPatch(typeof(GameMainObject), "Awake")] 20 | public static void OnGameMainObjectAwake() 21 | { 22 | var go = new GameObject("黑屏提示组件"); 23 | display = go.AddComponent(); 24 | } 25 | 26 | [HarmonyPrefix] 27 | [HarmonyPatch(typeof(Main.GameMain), "LateInitialize", typeof(MonoBehaviour), typeof(Transform), typeof(Transform))] 28 | public static void LateInitialize(MonoBehaviour gameMainObject) 29 | { 30 | if (display == null) return; 31 | Destroy(display); 32 | } 33 | 34 | private void Start() 35 | { 36 | stopwatch.Start(); 37 | } 38 | 39 | private void OnGUI() 40 | { 41 | if (stopwatch.ElapsedMilliseconds < 2000) 42 | { 43 | return; 44 | } 45 | GUIStyle styleTitle = new GUIStyle(GUI.skin.label) 46 | { 47 | fontSize = 35, 48 | alignment = TextAnchor.MiddleCenter, 49 | wordWrap = true 50 | }; 51 | GUIStyle style = new GUIStyle(GUI.skin.label) 52 | { 53 | fontSize = 25, 54 | alignment = TextAnchor.MiddleLeft, 55 | wordWrap = true 56 | }; 57 | 58 | GUI.Label(new Rect(50, 50, Screen.width - 50, Screen.height - 500), Locale.NoAmDaemonAlertTitle, styleTitle); 59 | GUI.Label(new Rect(50, 50, Screen.width - 50, Screen.height - 50), Locale.NoAmDaemonAlertMessage, style); 60 | } 61 | } -------------------------------------------------------------------------------- /AquaMai.Mods/UX/OneKeyRetrySkip.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using AquaMai.Config.Types; 3 | using AquaMai.Core.Helpers; 4 | using HarmonyLib; 5 | using MAI2.Util; 6 | using Manager; 7 | using MelonLoader; 8 | using Process; 9 | 10 | namespace AquaMai.Mods.UX; 11 | 12 | [ConfigSection( 13 | en: "One key to retry (1.30+) or skip current chart in gameplay.", 14 | zh: "在游戏中途一键重试(1.30+)或跳过当前谱面")] 15 | public class OneKeyRetrySkip 16 | { 17 | [ConfigEntry] 18 | public static readonly KeyCodeOrName retryKey = KeyCodeOrName.Service; 19 | 20 | [ConfigEntry] 21 | public static readonly bool retryLongPress = false; 22 | 23 | [ConfigEntry] 24 | public static readonly KeyCodeOrName skipKey = KeyCodeOrName.Service; 25 | 26 | [ConfigEntry] 27 | public static readonly bool skipLongPress = true; 28 | 29 | private static bool dirty = false; 30 | 31 | [HarmonyPostfix] 32 | [HarmonyPatch(typeof(GameProcess), "OnStart")] 33 | public static void PostGameProcessStart() 34 | { 35 | #if DEBUG 36 | MelonLogger.Msg("[OneKeyRetrySkip] Dirty flag reset"); 37 | #endif 38 | dirty = false; 39 | } 40 | 41 | [HarmonyPostfix] 42 | [HarmonyPatch(typeof(GameProcess), "OnUpdate")] 43 | public static void PostGameProcessUpdate(GameProcess __instance, Message[] ____message, ProcessDataContainer ___container) 44 | { 45 | if (dirty) return; 46 | 47 | if (KeyListener.GetKeyDownOrLongPress(skipKey, skipLongPress)) 48 | { 49 | #if DEBUG 50 | MelonLogger.Msg("[OneKeyRetrySkip] Skip key pressed."); 51 | #endif 52 | dirty = true; 53 | var traverse = Traverse.Create(__instance); 54 | ___container.processManager.SendMessage(____message[0]); 55 | Singleton.Instance.SetSyncResult(0); 56 | traverse.Method("SetRelease").GetValue(); 57 | } 58 | 59 | else if (KeyListener.GetKeyDownOrLongPress(retryKey, retryLongPress) && GameInfo.GameVersion >= 23000) 60 | { 61 | #if DEBUG 62 | MelonLogger.Msg("[OneKeyRetrySkip] Retry key pressed."); 63 | #endif 64 | dirty = true; 65 | // This is original typo in Assembly-CSharp 66 | Singleton.Instance.SetQuickRetryFrag(flag: true); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /AquaMai.Mods/UX/README.md: -------------------------------------------------------------------------------- 1 | # UX 2 | 3 | Features aiming at improving the user experience and can't fit in other categories go here. Generally, UX features are triggered by some user action or let the user perceive. 4 | -------------------------------------------------------------------------------- /AquaMai.Mods/UX/TestModeHook.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using AquaMai.Core; 3 | using DB; 4 | using HarmonyLib; 5 | 6 | namespace AquaMai.Mods.UX; 7 | 8 | [ConfigSection(defaultOn: true, exampleHidden: true)] 9 | public static class TestModeHook 10 | { 11 | [HarmonyPostfix] 12 | [HarmonyPatch(typeof(TestmodeRootTableRecord), MethodType.Constructor, typeof(int), typeof(string), typeof(string), typeof(string))] 13 | public static void ShowAquaMaiVersion(int EnumValue, ref string ___Name) 14 | { 15 | if (EnumValue != 0) return; 16 | ___Name += $"\nAquaMai {BuildInfo.GitVersion}"; 17 | } 18 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Utils/AntiLag.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Main; 5 | using MelonLoader; 6 | using UnityEngine; 7 | 8 | namespace AquaMai.Mods.Utils; 9 | 10 | [ConfigSection( 11 | en: "Some tricks to prevent the system from lagging", 12 | zh: "奇妙的防掉帧,如果你有莫名其妙的掉帧,可以试试这个")] 13 | public class AntiLag : MonoBehaviour 14 | { 15 | [ConfigEntry(zh: "游戏未取得焦点时也运行")] 16 | private static readonly bool activateWhileBackground = false; 17 | 18 | [HarmonyPostfix] 19 | [HarmonyPatch(typeof(GameMainObject), "Awake")] 20 | public static void OnGameMainObjectAwake() 21 | { 22 | var go = new GameObject("妙妙防掉帧"); 23 | go.AddComponent(); 24 | } 25 | 26 | private void Awake() 27 | { 28 | InvokeRepeating(nameof(OnTimer), 10f, 10f); 29 | } 30 | 31 | [DllImport("user32.dll", EntryPoint = "keybd_event", SetLastError = true)] 32 | private static extern void keybd_event(uint bVk, byte bScan, uint dwFlags, uint dwExtraInfo); 33 | 34 | const int KEYEVENTF_KEYDOWN = 0x0000; 35 | const int KEYEVENTF_KEYUP = 0x0002; 36 | const int CTRL = 17; 37 | 38 | private void OnTimer() 39 | { 40 | if (!Application.isFocused && !activateWhileBackground) return; 41 | #if DEBUG 42 | MelonLogger.Msg("[AntiLag] Trigger"); 43 | #endif 44 | keybd_event(CTRL, 0, KEYEVENTF_KEYDOWN, 0); 45 | keybd_event(CTRL, 0, KEYEVENTF_KEYUP, 0); 46 | } 47 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Utils/DisplayFrameRate.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using AquaMai.Core.Helpers; 3 | using HarmonyLib; 4 | using Main; 5 | using UnityEngine; 6 | 7 | namespace AquaMai.Mods.Utils; 8 | 9 | [ConfigSection( 10 | en: "Display framerate.", 11 | zh: "显示帧率")] 12 | public class DisplayFrameRate 13 | { 14 | [HarmonyPatch(typeof(GameMainObject), "Awake")] 15 | [HarmonyPostfix] 16 | public static void ShowUi(GameMainObject __instance) 17 | { 18 | __instance.gameObject.AddComponent(); 19 | } 20 | 21 | private class Ui : MonoBehaviour 22 | { 23 | private static float sampleTime = 1f; 24 | private static int frame; 25 | private static float time; 26 | private static float fps; 27 | 28 | public void OnGUI() 29 | { 30 | var labelStyle = GUI.skin.GetStyle("label"); 31 | labelStyle.fontSize = GuiSizes.FontSize; 32 | labelStyle.alignment = TextAnchor.MiddleCenter; 33 | 34 | const float x = 10f; 35 | const float y = 10f; 36 | var width = GuiSizes.FontSize * 7f; 37 | var height = GuiSizes.LabelHeight * 1.5f; 38 | 39 | frame += 1; 40 | time += Time.deltaTime; 41 | 42 | if (time >= sampleTime) 43 | { 44 | fps = frame / time; 45 | frame = 0; 46 | time = 0; 47 | } 48 | 49 | GUI.Box(new Rect(x, y, width, height), ""); 50 | GUI.Label(new Rect(x, y, width, height), $"{fps:0.0} FPS"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /AquaMai.Mods/Utils/LogNetworkErrors.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using Manager; 5 | using Manager.Operation; 6 | using MelonLoader; 7 | using Net.Packet; 8 | 9 | namespace AquaMai.Mods.Utils; 10 | 11 | [ConfigSection(exampleHidden: true, defaultOn: true)] 12 | public class LogNetworkErrors 13 | { 14 | [HarmonyPostfix] 15 | [HarmonyPatch(typeof(Packet), "ProcImpl")] 16 | public static void Postfix(PacketState __result, Packet __instance) 17 | { 18 | if (__result == PacketState.Error) 19 | { 20 | MelonLogger.Msg($"[LogNetworkErrors] {__instance.Query.Api}: {__instance.Status}"); 21 | } 22 | } 23 | 24 | [HarmonyPostfix] 25 | [HarmonyPatch(typeof(DataDownloader), "NotifyOffline")] 26 | public static void DataDownloader() 27 | { 28 | MelonLogger.Msg("[LogNetworkErrors] DataDownloader NotifyOffline"); 29 | var stackTrace = new StackTrace(); 30 | MelonLogger.Msg(stackTrace.ToString()); 31 | } 32 | 33 | [HarmonyPostfix] 34 | [HarmonyPatch(typeof(OnlineCheckInterval), "NotifyOffline")] 35 | public static void OnlineCheckInterval() 36 | { 37 | MelonLogger.Msg("[LogNetworkErrors] OnlineCheckInterval NotifyOffline"); 38 | var stackTrace = new StackTrace(); 39 | MelonLogger.Msg(stackTrace.ToString()); 40 | } 41 | 42 | [HarmonyPostfix] 43 | [HarmonyPatch(typeof(OperationManager), "IsAliveAimeServer", MethodType.Getter)] 44 | public static void IsAliveAimeServer(bool __result) 45 | { 46 | if (__result == false) 47 | MelonLogger.Msg($"[LogNetworkErrors] IsAliveAimeServer Is {__result}"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AquaMai.Mods/Utils/LogUnity.cs: -------------------------------------------------------------------------------- 1 | using AquaMai.Config.Attributes; 2 | using MelonLoader; 3 | using UnityEngine; 4 | 5 | namespace AquaMai.Mods.Utils; 6 | 7 | [ConfigSection( 8 | en: "Output Unity logs (for debugging purposes)", 9 | zh: "输出 Unity 日志(调试用)", 10 | exampleHidden: true)] 11 | public static class LogUnity 12 | { 13 | public static void OnBeforePatch() 14 | { 15 | Application.logMessageReceived += Log; 16 | } 17 | 18 | private static void Log(string msg, string stackTrace, LogType type) 19 | { 20 | MelonLogger.Msg("[Unity] " + msg); 21 | MelonLogger.Msg("[Unity] " + stackTrace); 22 | } 23 | } -------------------------------------------------------------------------------- /AquaMai.Mods/Utils/LogUserId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AquaMai.Config.Attributes; 3 | using HarmonyLib; 4 | using MelonLoader; 5 | using Net.Packet; 6 | using Net.Packet.Mai2; 7 | using Net.VO.Mai2; 8 | 9 | namespace AquaMai.Mods.Utils; 10 | 11 | [ConfigSection( 12 | en: "Log user ID on login.", 13 | zh: "登录时将 UserID 输出到日志")] 14 | public class LogUserId 15 | { 16 | [HarmonyPostfix] 17 | [HarmonyPatch(typeof(PacketGetUserPreview), MethodType.Constructor, typeof(ulong), typeof(string), typeof(Action), typeof(Action))] 18 | public static void Postfix(ulong userId) 19 | { 20 | MelonLogger.Msg($"[LogUserId] UserLogin: {userId}"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AquaMai.Mods/Utils/README.md: -------------------------------------------------------------------------------- 1 | # Utils 2 | 3 | Debugging or diagnostic purpose utilities goes here. They don't affect the game play at all, but may show UIs if needed. 4 | -------------------------------------------------------------------------------- /AquaMai.Mods/Utils/ShowNetErrorDetail.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AquaMai.Config.Attributes; 3 | using AquaMai.Core.Helpers; 4 | using AquaMai.Core.Resources; 5 | using HarmonyLib; 6 | using MAI2.Util; 7 | using Manager; 8 | using Monitor; 9 | using Process; 10 | using UnityEngine; 11 | 12 | namespace AquaMai.Mods.Utils; 13 | 14 | [ConfigSection( 15 | en: "Show Network error detail in the game when gray network icon appears.", 16 | zh: "出现灰网时显示原因")] 17 | public class ShowNetErrorDetail 18 | { 19 | [HarmonyPatch(typeof(CommonProcess), "OnStart")] 20 | [HarmonyPostfix] 21 | public static void SetIconStatus(CommonMonitor[] ____monitors) 22 | { 23 | ____monitors[0].gameObject.AddComponent(); 24 | } 25 | 26 | private class DetailUi : MonoBehaviour 27 | { 28 | public void OnGUI() 29 | { 30 | var errors = new List(); 31 | if (!Singleton.Instance.IsAliveServer) 32 | { 33 | errors.Add(Locale.NetErrIsAliveServer); 34 | } 35 | 36 | if (!Singleton.Instance.IsAliveAimeReader) 37 | { 38 | errors.Add(Locale.NetErrIsAliveAimeReader); 39 | } 40 | 41 | if (!Singleton.Instance.IsAliveAimeServer) 42 | { 43 | errors.Add(Locale.NetErrIsAliveAimeServer); 44 | } 45 | 46 | if (!Singleton.Instance.WasDownloadSuccessOnce) 47 | { 48 | errors.Add(Locale.NetErrWasDownloadSuccessOnce); 49 | } 50 | 51 | if (errors.Count == 0) 52 | { 53 | return; 54 | } 55 | 56 | var labelStyle = GUI.skin.GetStyle("label"); 57 | labelStyle.fontSize = GuiSizes.FontSize; 58 | labelStyle.alignment = TextAnchor.MiddleCenter; 59 | 60 | var x = GuiSizes.PlayerCenter + GuiSizes.PlayerWidth * .2f; 61 | var y = Screen.height * .01f; 62 | var width = GuiSizes.FontSize * 15f; 63 | var height = GuiSizes.LabelHeight * errors.Count + GuiSizes.Margin * 2; 64 | 65 | GUI.Box(new Rect(x, y, width, height), ""); 66 | for (var i = 0; i < errors.Count; i++) 67 | { 68 | GUI.Label(new Rect(x, y + GuiSizes.Margin + GuiSizes.LabelHeight * i, width, GuiSizes.LabelHeight), errors[i]); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /AquaMai/AssemblyLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Reflection; 6 | 7 | namespace AquaMai; 8 | 9 | public static class AssemblyLoader 10 | { 11 | public enum AssemblyName 12 | { 13 | ConfigInterfaces, 14 | Config, 15 | Core, 16 | Mods, 17 | } 18 | 19 | private static readonly Dictionary Assemblies = new() 20 | { 21 | [AssemblyName.ConfigInterfaces] = "AquaMai.Config.Interfaces.dll", 22 | [AssemblyName.Config] = "AquaMai.Config.dll", 23 | [AssemblyName.Core] = "AquaMai.Core.dll", 24 | [AssemblyName.Mods] = "AquaMai.Mods.dll", 25 | }; 26 | 27 | private static readonly Dictionary LoadedAssemblies = []; 28 | 29 | public static Assembly GetAssembly(AssemblyName assemblyName) => LoadedAssemblies[assemblyName]; 30 | 31 | public static void LoadAssemblies() 32 | { 33 | foreach (var (assemblyName, assemblyFileName) in Assemblies) 34 | { 35 | # if DEBUG 36 | MelonLoader.MelonLogger.Msg($"Loading assembly \"{assemblyFileName}\"..."); 37 | # endif 38 | LoadedAssemblies[assemblyName] = LoadAssemblyFromResource(assemblyFileName); 39 | } 40 | } 41 | 42 | public static Assembly LoadAssemblyFromResource(string assemblyName) 43 | { 44 | var executingAssembly = Assembly.GetExecutingAssembly(); 45 | using var decompressedStream = executingAssembly.GetManifestResourceStream(assemblyName); 46 | if (decompressedStream != null) 47 | { 48 | return AppDomain.CurrentDomain.Load(StreamToBytes(decompressedStream)); 49 | } 50 | using var compressedStream = executingAssembly.GetManifestResourceStream($"{assemblyName}.compressed"); 51 | if (compressedStream != null) 52 | { 53 | return AppDomain.CurrentDomain.Load(DecompressToBytes(compressedStream)); 54 | } 55 | throw new Exception($"Embedded assembly \"{assemblyName}\" not found."); 56 | } 57 | 58 | private static byte[] StreamToBytes(Stream stream) 59 | { 60 | if (stream == null) 61 | { 62 | return []; 63 | } 64 | using var memoryStream = new MemoryStream(); 65 | stream.CopyTo(memoryStream); 66 | return memoryStream.ToArray(); 67 | } 68 | 69 | private static byte[] DecompressToBytes(Stream stream) => StreamToBytes(new DeflateStream(stream, CompressionMode.Decompress)); 70 | } 71 | -------------------------------------------------------------------------------- /AquaMai/BuildInfo.cs: -------------------------------------------------------------------------------- 1 | namespace AquaMai; 2 | 3 | public static partial class BuildInfo 4 | { 5 | public const string Name = "AquaMai"; 6 | public const string Description = "Mod for Sinmai"; 7 | public const string Author = "Aza ft. Clansty ft. Menci"; 8 | public const string Company = null; 9 | public const string DownloadLink = null; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /AquaMai/Main.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using MelonLoader; 4 | 5 | namespace AquaMai; 6 | 7 | public class AquaMai : MelonMod 8 | { 9 | public const string AQUAMAI_SAY = """ 10 | 如果你在 dnSpy / ILSpy 里看到了这行字,请从 resources 中解包 DLLs。 11 | If you see this line in dnSpy / ILSpy, please unpack the DLLs from resources. 12 | """; 13 | 14 | [DllImport("kernel32.dll", SetLastError = true)] 15 | private static extern bool SetConsoleOutputCP(uint wCodePageID); 16 | 17 | private void SetCoreBuildInfo(Assembly coreAssembly) 18 | { 19 | var coreBuildInfo = coreAssembly.GetType("AquaMai.Core.BuildInfo"); 20 | var buildInfo = typeof(BuildInfo); 21 | foreach (var field in buildInfo.GetFields()) 22 | { 23 | coreBuildInfo.GetField(field.Name)?.SetValue(null, field.GetValue(null)); 24 | } 25 | coreBuildInfo.GetField("ModAssembly")?.SetValue(null, MelonAssembly); 26 | } 27 | 28 | private static MethodInfo onGUIMethod; 29 | 30 | public override void OnInitializeMelon() 31 | { 32 | // Prevent Chinese characters from being garbled 33 | SetConsoleOutputCP(65001); 34 | 35 | AssemblyLoader.LoadAssemblies(); 36 | 37 | var modsAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Mods); 38 | var coreAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Core); 39 | SetCoreBuildInfo(coreAssembly); 40 | coreAssembly.GetType("AquaMai.Core.Startup") 41 | .GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static) 42 | .Invoke(null, [modsAssembly, HarmonyInstance]); 43 | onGUIMethod = coreAssembly.GetType("AquaMai.Core.Startup") 44 | .GetMethod("OnGUI", BindingFlags.Public | BindingFlags.Static); 45 | } 46 | 47 | public override void OnGUI() 48 | { 49 | base.OnGUI(); 50 | onGUIMethod?.Invoke(null, []); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /AquaMai/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using MelonLoader; 3 | 4 | [assembly: AssemblyTitle(AquaMai.BuildInfo.Description)] 5 | [assembly: AssemblyDescription(AquaMai.BuildInfo.Description)] 6 | [assembly: AssemblyCompany(AquaMai.BuildInfo.Company)] 7 | [assembly: AssemblyProduct(AquaMai.BuildInfo.Name)] 8 | [assembly: AssemblyCopyright("Created by " + AquaMai.BuildInfo.Author)] 9 | [assembly: AssemblyTrademark(AquaMai.BuildInfo.Company)] 10 | [assembly: AssemblyVersion(AquaMai.BuildInfo.Version)] 11 | [assembly: AssemblyFileVersion(AquaMai.BuildInfo.GitVersion)] 12 | [assembly: MelonInfo(typeof(AquaMai.AquaMai), AquaMai.BuildInfo.Name, AquaMai.BuildInfo.GitVersion, AquaMai.BuildInfo.Author, AquaMai.BuildInfo.DownloadLink)] 13 | [assembly: MelonColor()] 14 | [assembly: HarmonyDontPatchAll] 15 | 16 | // Create and Setup a MelonGame Attribute to mark a Melon as Universal or Compatible with specific Games. 17 | // 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. 18 | // 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. 19 | [assembly: MelonGame(null, null)] 20 | -------------------------------------------------------------------------------- /AquaMai/configSort.yaml: -------------------------------------------------------------------------------- 1 | 常用: 2 | - General 3 | - GameSystem.SinglePlayer 4 | - GameSystem.DisableTimeout 5 | - UX.ImmediateSave 6 | - GameSystem.QuickRetry 7 | - UX.OneKeyEntryEnd 8 | - UX.OneKeyRetrySkip 9 | - GameSystem.TouchToButtonInput 10 | - GameSystem.TestProof 11 | - GameSettings.CreditConfig 12 | - Fancy.GamePlay.ExtendNotesPool 13 | 14 | 直读: 15 | - GameSystem.Assets.LoadAssetBundleWithoutManifest 16 | - GameSystem.Assets.LoadLocalImages 17 | - GameSystem.Assets.MovieLoader 18 | 19 | 全解和跳过: 20 | - GameSystem.Unlock 21 | - Tweaks.TimeSaving.SkipStartupWarning 22 | - Tweaks.TimeSaving.EntryToMusicSelection 23 | - Tweaks.TimeSaving.ExitToSave 24 | - Tweaks.TimeSaving.SkipStartupDelays 25 | - Tweaks.TimeSaving.SkipEventInfo 26 | - Tweaks.TimeSaving.SkipTrackStart 27 | - Tweaks.TimeSaving.SkipGoodbyeScreen 28 | - Tweaks.TimeSaving.IWontTapOrSlideVigorously 29 | - UX.QuickEndPlay 30 | 31 | 美化: 32 | - GameSystem.Assets.Fonts 33 | - Fancy.CustomLogo 34 | - Fancy.CustomPlaceName 35 | - Fancy.CustomVersionString 36 | - Fancy.CustomCreditsString 37 | - Fancy.RandomBgm 38 | - Fancy.CustomSkins 39 | - Fancy.CustomButton 40 | - Fancy.DemoMaster 41 | - Fancy.GamePlay.AlignCircleSlideJudgeDisplay 42 | - Fancy.GamePlay.SlideArrowAnimation 43 | - Fancy.GamePlay.SlideFadeInTweak 44 | - Fancy.GamePlay.SlideLayerReverse 45 | 46 | 实用工具: 47 | - UX.HideSelfMadeCharts 48 | - UX.JudgeAccuracyInfo 49 | - UX.PracticeMode 50 | - UX.SelectionDetail 51 | - UX.DisableLightOutGame 52 | - Fancy.GamePlay.HideHanabi 53 | - Fancy.GamePlay.JudgeDisplay4B 54 | - Fancy.Triggers 55 | 56 | 键位和灵敏度: 57 | - GameSystem.HidInput 58 | - GameSystem.AdxHidInput 59 | - GameSystem.KeyMap 60 | - GameSettings.TouchSensitivity 61 | 62 | 调试: 63 | - GameSystem.Window 64 | - Utils.AntiLag 65 | - GameSettings.JudgeAdjust 66 | - Tweaks.IgnoreAimeServerError 67 | - Utils.ShowNetErrorDetail 68 | - Tweaks.LockFrameRate 69 | - Tweaks.ResetTouch 70 | - Tweaks.SkipUserVersionCheck 71 | - Utils.DisplayFrameRate 72 | - Utils.LogUserId 73 | - Utils.LogNetworkRequests 74 | - Utils.ShowErrorLog 75 | - UX.NoAmDaemonAlert 76 | 77 | 高级设置: 78 | - Fancy.HideMask 79 | - GameSettings.ForceAsServer 80 | - GameSystem.RemoveEncryption 81 | - GameSystem.Sound 82 | - GameSystem.TouchPanelBaudRate 83 | - GameSystem.TouchPanelBaudRate 84 | - GameSystem.CustomCameraId 85 | - GameSystem.OptionLoadFix 86 | - UX.ServerAnnouncement 87 | 88 | 过新过热: 89 | - Fancy.CustomTrackStartDiff 90 | - Fancy.GamePlay.BreakSlideJudgeBlink 91 | - Fancy.GamePlay.CustomNoteTypes 92 | - Fancy.GamePlay.DisableTrackStartTabs 93 | - Fancy.GamePlay.FanJudgeFlip 94 | - Fancy.GamePlay.RealisticRandomJudge 95 | - Fancy.GamePlay.TrackStartProcessTweak 96 | -------------------------------------------------------------------------------- /Libs/.gitignore: -------------------------------------------------------------------------------- 1 | /Assembly-CSharp.dll 2 | /AMDaemon.NET.dll 3 | -------------------------------------------------------------------------------- /Libs/0Harmony.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/0Harmony.dll -------------------------------------------------------------------------------- /Libs/Assembly-CSharp-firstpass.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/Assembly-CSharp-firstpass.dll -------------------------------------------------------------------------------- /Libs/MelonLoader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/MelonLoader.dll -------------------------------------------------------------------------------- /Libs/Mono.Cecil.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/Mono.Cecil.dll -------------------------------------------------------------------------------- /Libs/Mono.Posix.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/Mono.Posix.dll -------------------------------------------------------------------------------- /Libs/Mono.Security.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/Mono.Security.dll -------------------------------------------------------------------------------- /Libs/System.Configuration.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/System.Configuration.dll -------------------------------------------------------------------------------- /Libs/System.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/System.Core.dll -------------------------------------------------------------------------------- /Libs/System.Numerics.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/System.Numerics.dll -------------------------------------------------------------------------------- /Libs/System.Security.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/System.Security.dll -------------------------------------------------------------------------------- /Libs/System.Xml.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/System.Xml.dll -------------------------------------------------------------------------------- /Libs/System.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/System.dll -------------------------------------------------------------------------------- /Libs/Unity.Analytics.DataPrivacy.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/Unity.Analytics.DataPrivacy.dll -------------------------------------------------------------------------------- /Libs/Unity.TextMeshPro.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/Unity.TextMeshPro.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.AIModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.AIModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ARModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ARModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.AccessibilityModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.AccessibilityModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.AnimationModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.AnimationModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.AssetBundleModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.AssetBundleModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.AudioModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.AudioModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.BaselibModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.BaselibModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ClothModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ClothModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ClusterInputModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ClusterInputModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ClusterRendererModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ClusterRendererModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.CoreModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.CoreModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.CrashReportingModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.CrashReportingModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.DirectorModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.DirectorModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.FileSystemHttpModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.FileSystemHttpModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.GameCenterModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.GameCenterModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.GridModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.GridModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.HotReloadModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.HotReloadModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.IMGUIModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.IMGUIModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ImageConversionModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ImageConversionModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.InputModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.InputModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.JSONSerializeModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.JSONSerializeModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.LocalizationModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.LocalizationModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.Networking.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.Networking.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ParticleSystemModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ParticleSystemModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.PerformanceReportingModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.PerformanceReportingModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.Physics2DModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.Physics2DModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.PhysicsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.PhysicsModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ProfilerModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ProfilerModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.ScreenCaptureModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.ScreenCaptureModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.SharedInternalsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.SharedInternalsModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.SpatialTracking.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.SpatialTracking.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.SpriteMaskModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.SpriteMaskModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.SpriteShapeModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.SpriteShapeModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.StreamingModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.StreamingModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.StyleSheetsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.StyleSheetsModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.SubstanceModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.SubstanceModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.TLSModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.TLSModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.TerrainModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.TerrainModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.TerrainPhysicsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.TerrainPhysicsModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.TextCoreModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.TextCoreModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.TextRenderingModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.TextRenderingModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.TilemapModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.TilemapModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.Timeline.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.Timeline.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.TimelineModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.TimelineModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UI.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UI.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UIElementsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UIElementsModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UIModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UIModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UNETModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UNETModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UmbraModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UmbraModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityAnalyticsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityAnalyticsModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityConnectModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityConnectModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityTestProtocolModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityTestProtocolModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityWebRequestAssetBundleModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityWebRequestAssetBundleModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityWebRequestAudioModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityWebRequestAudioModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityWebRequestModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityWebRequestModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityWebRequestTextureModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityWebRequestTextureModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.UnityWebRequestWWWModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.UnityWebRequestWWWModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.VFXModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.VFXModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.VRModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.VRModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.VehiclesModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.VehiclesModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.VideoModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.VideoModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.WindModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.WindModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.XRModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.XRModule.dll -------------------------------------------------------------------------------- /Libs/UnityEngine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/UnityEngine.dll -------------------------------------------------------------------------------- /Libs/mscorlib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MewoLab/AquaMai/e8bd6c67fd7ee5ebd5d63a2aef7d35576f5883aa/Libs/mscorlib.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AquaMai 2 | 3 | ## Installation 4 | 5 | 1. Build the project or find an existing build somewhere™ 6 | 2. Download [MelonLoader.x64.zip](https://github.com/LavaGang/MelonLoader/releases/download/v0.7.0/MelonLoader.x64.zip) 7 | 3. Extract MelonLoader zip to where your Sinmai.exe is 8 | 4. Make a Mods folder and put AquaMai.dll inside it 9 | 5. Pet your cat 10 | 6. Launch! 11 | 12 | ## Features 13 | 14 | **Cheats** 15 | 16 | * Unlock all tickets 17 | 18 | **UX Optimization** 19 | 20 | * Remove the starting logo and warning cutscene 21 | * Single Player (1P) mode 22 | * Skip from card scanning directly to music selection (experimental) 23 | * Disable daily automatic reboot 24 | * Customize version text 25 | * Skip the current song by holding 7 26 | * Skip "new event" and "information" screen for new players. 27 | 28 | **Bug Fixes** 29 | 30 | * Fix crash in the character selection screen 31 | 32 | **Performance** 33 | 34 | * Speed up things 35 | 36 | ## Development 37 | 38 | 1. Copy `Assembly-CSharp.dll` and `AMDaemon.NET.dll` to `Libs` folder. 39 | 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) 40 | 1. Run `build.ps1`. 41 | 1. Copy `Output/AquaMai.dll` to `Mods` folder. 42 | 1. Configure and copy `AquaMai.toml` to the same folder as your game executable: `Sinmai.exe` 43 | 44 | ## Relevant Links 45 | 46 | * [MelonLoader Wiki](https://melonwiki.xyz/#/modders/quickstart) 47 | * [Harmony Docs](https://harmony.pardeike.net/articles/patching-prefix.html) 48 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | #addin nuget:?package=Cake.Git&version=5.0.1 2 | #addin nuget:?package=Cake.FileHelpers&version=7.0.0 3 | 4 | var target = Argument("target", "Build"); 5 | var configuration = Argument("configuration", "Release"); 6 | 7 | Task("Restore") 8 | .Does(() => 9 | { 10 | // 运行 dotnet restore 11 | DotNetRestore("./AquaMai.sln"); 12 | }); 13 | 14 | Task("PreBuild") 15 | .Does(() => 16 | { 17 | var gitDescribe = GitDescribe(".", GitDescribeStrategy.Tags).Substring(1); // 获取 git describe 的输出 18 | var buildDate = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); 19 | 20 | var shortVers = gitDescribe.Split('-'); 21 | string shortVer; 22 | if (shortVers.Length > 1) 23 | { 24 | shortVer = $"{shortVers[0]}.{shortVers[1]}"; 25 | } 26 | else 27 | { 28 | shortVer = shortVers[0]; 29 | } 30 | 31 | var versionContent = $@" 32 | // Auto-generated file. Do not modify manually. 33 | namespace AquaMai; 34 | 35 | public static partial class BuildInfo 36 | {{ 37 | public const string Version = ""{shortVer}""; 38 | public const string GitVersion = ""{gitDescribe}""; 39 | public const string BuildDate = ""{buildDate}""; 40 | }} 41 | "; 42 | FileWriteText("./AquaMai/BuildInfo.g.cs", versionContent); 43 | }); 44 | 45 | Task("Build") 46 | .IsDependentOn("PreBuild") 47 | .IsDependentOn("Restore") 48 | .Does(() => 49 | { 50 | // 使用 dotnet build 进行构建 51 | DotNetBuild("./AquaMai.sln", new DotNetBuildSettings 52 | { 53 | Configuration = configuration 54 | }); 55 | }); 56 | 57 | RunTarget(target); 58 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | Set-Location -LiteralPath $PSScriptRoot 4 | 5 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' 6 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = '1' 7 | $env:DOTNET_NOLOGO = '1' 8 | 9 | dotnet tool restore 10 | if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } 11 | 12 | dotnet cake @args 13 | if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } 14 | --------------------------------------------------------------------------------