From 2482881117b18852f2b40dea2146d0e8a35d2df2 Mon Sep 17 00:00:00 2001 From: Clansty Date: Sat, 30 Nov 2024 15:18:08 +0800 Subject: [PATCH] [RF] AquaMai moved to new repo --- .github/workflows/aquamai.yaml | 72 --- AquaMai/.gitignore | 374 ------------ AquaMai/AquaMai.Build/AquaMai.Build.csproj | 50 -- .../AquaMai.Build/GenerateExampleConfig.cs | 42 -- AquaMai/AquaMai.Build/PostBuildPatch.cs | 52 -- .../AquaMai.Config.HeadlessLoader.csproj | 45 -- .../ConfigAssemblyLoader.cs | 68 --- .../CustomAssemblyResolver.cs | 11 - .../HeadlessConfigInterface.cs | 65 -- .../HeadlessConfigLoader.cs | 62 -- .../Polyfills.cs | 4 - .../ResourceLoader.cs | 42 -- .../AquaMai.Config.Interfaces.csproj | 40 -- AquaMai/AquaMai.Config.Interfaces/IConfig.cs | 28 - .../IConfigComment.cs | 8 - .../IConfigEntryAttribute.cs | 7 - .../IConfigMigrationManager.cs | 8 - .../IConfigParser.cs | 7 - .../IConfigSectionAttribute.cs | 9 - .../IConfigSerializer.cs | 13 - .../AquaMai.Config.Interfaces/IConfigView.cs | 11 - .../IReflectionManager.cs | 43 -- .../IReflectionProvider.cs | 30 - .../AquaMai.Config.Interfaces/Polyfills.cs | 4 - AquaMai/AquaMai.Config/ApiVersion.cs | 9 - AquaMai/AquaMai.Config/AquaMai.Config.csproj | 60 -- .../ConfigCollapseNamespaceAttribute.cs | 9 - .../Attributes/ConfigComment.cs | 14 - .../Attributes/ConfigEntryAttribute.cs | 25 - .../Attributes/ConfigSectionAttribute.cs | 22 - .../Attributes/EnableCondition.cs | 78 --- AquaMai/AquaMai.Config/Config.cs | 105 ---- AquaMai/AquaMai.Config/ConfigParser.cs | 125 ---- AquaMai/AquaMai.Config/ConfigSerializer.cs | 186 ------ AquaMai/AquaMai.Config/ConfigView.cs | 142 ----- AquaMai/AquaMai.Config/FodyWeavers.xml | 7 - AquaMai/AquaMai.Config/FodyWeavers.xsd | 111 ---- .../Migration/ConfigMigrationManager.cs | 62 -- .../Migration/ConfigMigration_V1_0_V2_0.cs | 350 ----------- .../Migration/ConfigMigration_V2_0_V2_1.cs | 44 -- .../Migration/IConfigMigration.cs | 10 - AquaMai/AquaMai.Config/Polyfills.cs | 4 - .../MonoCecilAssemblyReflectionProvider.cs | 254 -------- .../Reflection/ReflectionManager.cs | 178 ------ .../Reflection/SystemReflectionProvider.cs | 47 -- AquaMai/AquaMai.Config/Types/KeyCodeID.cs | 146 ----- AquaMai/AquaMai.Config/Types/KeyCodeOrName.cs | 53 -- AquaMai/AquaMai.Config/Utility.cs | 193 ------ AquaMai/AquaMai.Core/AquaMai.Core.csproj | 132 ---- .../Attributes/EnableGameVersionAttribute.cs | 18 - .../Attributes/EnableIfAttribute.cs | 79 --- .../Attributes/EnableImplicitlyIfAttribute.cs | 12 - AquaMai/AquaMai.Core/ConfigLoader.cs | 83 --- .../Helpers/EnableConditionHelper.cs | 72 --- AquaMai/AquaMai.Core/Helpers/FileSystem.cs | 15 - AquaMai/AquaMai.Core/Helpers/GameInfo.cs | 21 - AquaMai/AquaMai.Core/Helpers/GuiSizes.cs | 57 -- AquaMai/AquaMai.Core/Helpers/KeyListener.cs | 146 ----- AquaMai/AquaMai.Core/Helpers/MessageHelper.cs | 39 -- .../AquaMai.Core/Helpers/MusicDirHelper.cs | 31 - .../AquaMai.Core/Helpers/SharedInstances.cs | 25 - AquaMai/AquaMai.Core/Helpers/Shim.cs | 133 ---- .../Resources/I18nSingleAssemblyHook.cs | 32 - .../AquaMai.Core/Resources/Locale.Designer.cs | 361 ----------- AquaMai/AquaMai.Core/Resources/Locale.resx | 122 ---- AquaMai/AquaMai.Core/Resources/Locale.zh.resx | 115 ---- AquaMai/AquaMai.Core/Startup.cs | 199 ------ AquaMai/AquaMai.Mods/AquaMai.Mods.csproj | 128 ---- AquaMai/AquaMai.Mods/DeprecationWarning.cs | 29 - AquaMai/AquaMai.Mods/Fancy/CustomLogo.cs | 73 --- AquaMai/AquaMai.Mods/Fancy/CustomPlaceName.cs | 45 -- AquaMai/AquaMai.Mods/Fancy/CustomSkins.cs | 402 ------------ .../Fancy/CustomTrackStartDiff.cs | 78 --- .../AquaMai.Mods/Fancy/CustomVersionString.cs | 30 - AquaMai/AquaMai.Mods/Fancy/DemoMaster.cs | 34 -- .../GamePlay/AlignCircleSlideJudgeDisplay.cs | 51 -- .../Fancy/GamePlay/BreakSlideJudgeBlink.cs | 33 - .../CustomNoteTypes/CustomNoteTypes.cs | 433 ------------- .../Libs/CustomSlideNoteData.cs | 51 -- .../CustomNoteTypes/Libs/MaiGeometry.cs | 107 ---- .../Libs/ParametricSlidePath.cs | 226 ------- .../CustomNoteTypes/Libs/SlideCodeParser.cs | 260 -------- .../CustomNoteTypes/Libs/SlideDataBuilder.cs | 473 -------------- .../Libs/SlidePathGenerator.cs | 131 ---- .../Fancy/GamePlay/DisableTrackStartTabs.cs | 50 -- .../Fancy/GamePlay/ExtendNotesPool.cs | 129 ---- .../Fancy/GamePlay/FanJudgeFlip.cs | 42 -- .../AquaMai.Mods/Fancy/GamePlay/HideHanabi.cs | 24 - .../Fancy/GamePlay/JudgeDisplay4B.cs | 86 --- .../Fancy/GamePlay/RealisticRandomJudge.cs | 39 -- .../Fancy/GamePlay/SlideArrowAnimation.cs | 118 ---- .../Fancy/GamePlay/SlideFadeInTweak.cs | 129 ---- .../Fancy/GamePlay/SlideLayerReverse.cs | 76 --- .../Fancy/GamePlay/TrackStartProcessTweak.cs | 77 --- AquaMai/AquaMai.Mods/Fancy/HideMask.cs | 18 - AquaMai/AquaMai.Mods/Fancy/README.md | 7 - AquaMai/AquaMai.Mods/Fancy/RandomBgm.cs | 88 --- AquaMai/AquaMai.Mods/Fancy/Triggers.cs | 68 --- AquaMai/AquaMai.Mods/Fix/Common.cs | 139 ----- AquaMai/AquaMai.Mods/Fix/DebugFeature.cs | 212 ------- AquaMai/AquaMai.Mods/Fix/DisableReboot.cs | 87 --- AquaMai/AquaMai.Mods/Fix/FixCheckAuth.cs | 21 - AquaMai/AquaMai.Mods/Fix/FixConnSlide.cs | 66 -- AquaMai/AquaMai.Mods/Fix/FixLevelDisplay.cs | 80 --- AquaMai/AquaMai.Mods/Fix/FixSlideAutoPlay.cs | 104 ---- .../Fix/Legacy/FixQuickRetry130.cs | 30 - AquaMai/AquaMai.Mods/Fix/README.md | 7 - .../Fix/Stability/FixMissingCharaCrash.cs | 45 -- .../AquaMai.Mods/GameSettings/CreditConfig.cs | 49 -- .../GameSettings/ForceAsServer.cs | 28 - .../AquaMai.Mods/GameSettings/JudgeAdjust.cs | 50 -- AquaMai/AquaMai.Mods/GameSettings/README.md | 5 - .../GameSettings/TouchSensitivity.cs | 154 ----- .../AquaMai.Mods/GameSystem/Assets/Fonts.cs | 146 ----- .../Assets/LoadAssetBundleWithoutManifest.cs | 38 -- .../GameSystem/Assets/LoadLocalImages.cs | 577 ------------------ .../Assets/UseJacketAsDummyMovie.cs | 54 -- .../AquaMai.Mods/GameSystem/CustomCameraId.cs | 142 ----- .../AquaMai.Mods/GameSystem/DisableTimeout.cs | 65 -- AquaMai/AquaMai.Mods/GameSystem/KeyMap.cs | 80 --- AquaMai/AquaMai.Mods/GameSystem/QuickRetry.cs | 34 -- AquaMai/AquaMai.Mods/GameSystem/README.md | 5 - .../GameSystem/RemoveEncryption.cs | 64 -- .../AquaMai.Mods/GameSystem/SinglePlayer.cs | 114 ---- AquaMai/AquaMai.Mods/GameSystem/TestProof.cs | 71 --- .../GameSystem/TouchPanelBaudRate.cs | 30 - .../GameSystem/TouchToButtonInput.cs | 59 -- AquaMai/AquaMai.Mods/GameSystem/Unlock.cs | 90 --- AquaMai/AquaMai.Mods/GameSystem/Window.cs | 107 ---- AquaMai/AquaMai.Mods/General.cs | 42 -- .../Tweaks/IgnoreAimeServerError.cs | 19 - AquaMai/AquaMai.Mods/Tweaks/LockFrameRate.cs | 22 - AquaMai/AquaMai.Mods/Tweaks/README.md | 5 - AquaMai/AquaMai.Mods/Tweaks/ResetTouch.cs | 43 -- .../Tweaks/SkipUserVersionCheck.cs | 22 - .../TimeSaving/EntryToMusicSelection.cs | 52 -- .../TimeSaving/IWontTapOrSlideVigorously.cs | 19 - .../Tweaks/TimeSaving/SkipEventInfo.cs | 27 - .../Tweaks/TimeSaving/SkipGoodbyeScreen.cs | 30 - .../Tweaks/TimeSaving/SkipStartupDelays.cs | 32 - .../Tweaks/TimeSaving/SkipStartupWarning.cs | 31 - .../Tweaks/TimeSaving/SkipTrackStart.cs | 19 - AquaMai/AquaMai.Mods/UX/CiBuildAlert.cs | 26 - AquaMai/AquaMai.Mods/UX/HideSelfMadeCharts.cs | 136 ----- AquaMai/AquaMai.Mods/UX/ImmediateSave.cs | 241 -------- AquaMai/AquaMai.Mods/UX/JudgeAccuracyInfo.cs | 251 -------- AquaMai/AquaMai.Mods/UX/OneKeyEntryEnd.cs | 65 -- AquaMai/AquaMai.Mods/UX/OneKeyRetrySkip.cs | 46 -- .../UX/PracticeMode/Libs/PractiseModeUI.cs | 151 ----- .../UX/PracticeMode/PracticeMode.cs | 282 --------- AquaMai/AquaMai.Mods/UX/QuickEndPlay.cs | 70 --- AquaMai/AquaMai.Mods/UX/README.md | 3 - AquaMai/AquaMai.Mods/UX/SelectionDetail.cs | 143 ----- .../AquaMai.Mods/Utils/DisplayFrameRate.cs | 53 -- .../AquaMai.Mods/Utils/LogNetworkErrors.cs | 49 -- .../AquaMai.Mods/Utils/LogNetworkRequests.cs | 188 ------ AquaMai/AquaMai.Mods/Utils/LogUnity.cs | 23 - AquaMai/AquaMai.Mods/Utils/LogUserId.cs | 22 - AquaMai/AquaMai.Mods/Utils/README.md | 3 - AquaMai/AquaMai.Mods/Utils/ShowErrorLog.cs | 100 --- .../AquaMai.Mods/Utils/ShowNetErrorDetail.cs | 72 --- AquaMai/AquaMai.sln | 82 --- AquaMai/AquaMai/AquaMai.csproj | 74 --- AquaMai/AquaMai/AssemblyLoader.cs | 70 --- AquaMai/AquaMai/BuildInfo.cs | 12 - AquaMai/AquaMai/Main.cs | 39 -- AquaMai/AquaMai/Properties/AssemblyInfo.cs | 19 - AquaMai/Libs/.gitignore | 2 - AquaMai/Libs/0Harmony.dll | Bin 263168 -> 0 bytes AquaMai/Libs/Assembly-CSharp-firstpass.dll | Bin 189952 -> 0 bytes AquaMai/Libs/MelonLoader.dll | Bin 755200 -> 0 bytes AquaMai/Libs/Mono.Cecil.dll | Bin 339456 -> 0 bytes AquaMai/Libs/Mono.Posix.dll | Bin 212480 -> 0 bytes AquaMai/Libs/Mono.Security.dll | Bin 309248 -> 0 bytes AquaMai/Libs/System.Configuration.dll | Bin 124928 -> 0 bytes AquaMai/Libs/System.Core.dll | Bin 1046016 -> 0 bytes AquaMai/Libs/System.Numerics.dll | Bin 133976 -> 0 bytes AquaMai/Libs/System.Security.dll | Bin 184320 -> 0 bytes AquaMai/Libs/System.Xml.dll | Bin 3138048 -> 0 bytes AquaMai/Libs/System.dll | Bin 2714624 -> 0 bytes AquaMai/Libs/Unity.Analytics.DataPrivacy.dll | Bin 7680 -> 0 bytes AquaMai/Libs/Unity.TextMeshPro.dll | Bin 331776 -> 0 bytes AquaMai/Libs/UnityEngine.AIModule.dll | Bin 42496 -> 0 bytes AquaMai/Libs/UnityEngine.ARModule.dll | Bin 12288 -> 0 bytes .../Libs/UnityEngine.AccessibilityModule.dll | Bin 10752 -> 0 bytes AquaMai/Libs/UnityEngine.AnimationModule.dll | Bin 137216 -> 0 bytes .../Libs/UnityEngine.AssetBundleModule.dll | Bin 20992 -> 0 bytes AquaMai/Libs/UnityEngine.AudioModule.dll | Bin 58368 -> 0 bytes AquaMai/Libs/UnityEngine.BaselibModule.dll | Bin 8192 -> 0 bytes AquaMai/Libs/UnityEngine.ClothModule.dll | Bin 14336 -> 0 bytes .../Libs/UnityEngine.ClusterInputModule.dll | Bin 9728 -> 0 bytes .../UnityEngine.ClusterRendererModule.dll | Bin 8704 -> 0 bytes AquaMai/Libs/UnityEngine.CoreModule.dll | Bin 848896 -> 0 bytes .../Libs/UnityEngine.CrashReportingModule.dll | Bin 9216 -> 0 bytes AquaMai/Libs/UnityEngine.DirectorModule.dll | Bin 12800 -> 0 bytes .../Libs/UnityEngine.FileSystemHttpModule.dll | Bin 8192 -> 0 bytes AquaMai/Libs/UnityEngine.GameCenterModule.dll | Bin 26112 -> 0 bytes AquaMai/Libs/UnityEngine.GridModule.dll | Bin 13312 -> 0 bytes AquaMai/Libs/UnityEngine.HotReloadModule.dll | Bin 8192 -> 0 bytes AquaMai/Libs/UnityEngine.IMGUIModule.dll | Bin 146944 -> 0 bytes .../UnityEngine.ImageConversionModule.dll | Bin 9728 -> 0 bytes AquaMai/Libs/UnityEngine.InputModule.dll | Bin 11264 -> 0 bytes .../Libs/UnityEngine.JSONSerializeModule.dll | Bin 10240 -> 0 bytes .../Libs/UnityEngine.LocalizationModule.dll | Bin 9216 -> 0 bytes AquaMai/Libs/UnityEngine.Networking.dll | Bin 260608 -> 0 bytes .../Libs/UnityEngine.ParticleSystemModule.dll | Bin 127488 -> 0 bytes ...UnityEngine.PerformanceReportingModule.dll | Bin 8704 -> 0 bytes AquaMai/Libs/UnityEngine.Physics2DModule.dll | Bin 93184 -> 0 bytes AquaMai/Libs/UnityEngine.PhysicsModule.dll | Bin 84992 -> 0 bytes AquaMai/Libs/UnityEngine.ProfilerModule.dll | Bin 8192 -> 0 bytes .../Libs/UnityEngine.ScreenCaptureModule.dll | Bin 9216 -> 0 bytes .../UnityEngine.SharedInternalsModule.dll | Bin 19456 -> 0 bytes AquaMai/Libs/UnityEngine.SpatialTracking.dll | Bin 11264 -> 0 bytes AquaMai/Libs/UnityEngine.SpriteMaskModule.dll | Bin 9728 -> 0 bytes .../Libs/UnityEngine.SpriteShapeModule.dll | Bin 9728 -> 0 bytes AquaMai/Libs/UnityEngine.StreamingModule.dll | Bin 8704 -> 0 bytes .../Libs/UnityEngine.StyleSheetsModule.dll | Bin 16384 -> 0 bytes AquaMai/Libs/UnityEngine.SubstanceModule.dll | Bin 12800 -> 0 bytes AquaMai/Libs/UnityEngine.TLSModule.dll | Bin 8192 -> 0 bytes AquaMai/Libs/UnityEngine.TerrainModule.dll | Bin 62976 -> 0 bytes .../Libs/UnityEngine.TerrainPhysicsModule.dll | Bin 8704 -> 0 bytes AquaMai/Libs/UnityEngine.TextCoreModule.dll | Bin 32256 -> 0 bytes .../Libs/UnityEngine.TextRenderingModule.dll | Bin 26624 -> 0 bytes AquaMai/Libs/UnityEngine.TilemapModule.dll | Bin 24064 -> 0 bytes AquaMai/Libs/UnityEngine.Timeline.dll | Bin 98304 -> 0 bytes AquaMai/Libs/UnityEngine.TimelineModule.dll | Bin 8192 -> 0 bytes AquaMai/Libs/UnityEngine.UI.dll | Bin 252928 -> 0 bytes AquaMai/Libs/UnityEngine.UIElementsModule.dll | Bin 351744 -> 0 bytes AquaMai/Libs/UnityEngine.UIModule.dll | Bin 22016 -> 0 bytes AquaMai/Libs/UnityEngine.UNETModule.dll | Bin 76800 -> 0 bytes AquaMai/Libs/UnityEngine.UmbraModule.dll | Bin 8192 -> 0 bytes .../Libs/UnityEngine.UnityAnalyticsModule.dll | Bin 24064 -> 0 bytes .../Libs/UnityEngine.UnityConnectModule.dll | Bin 10240 -> 0 bytes .../UnityEngine.UnityTestProtocolModule.dll | Bin 8192 -> 0 bytes ...ngine.UnityWebRequestAssetBundleModule.dll | Bin 11264 -> 0 bytes ...UnityEngine.UnityWebRequestAudioModule.dll | Bin 10752 -> 0 bytes .../UnityEngine.UnityWebRequestModule.dll | Bin 41984 -> 0 bytes ...ityEngine.UnityWebRequestTextureModule.dll | Bin 10240 -> 0 bytes .../UnityEngine.UnityWebRequestWWWModule.dll | Bin 19968 -> 0 bytes AquaMai/Libs/UnityEngine.VFXModule.dll | Bin 27136 -> 0 bytes AquaMai/Libs/UnityEngine.VRModule.dll | Bin 29184 -> 0 bytes AquaMai/Libs/UnityEngine.VehiclesModule.dll | Bin 11776 -> 0 bytes AquaMai/Libs/UnityEngine.VideoModule.dll | Bin 26624 -> 0 bytes AquaMai/Libs/UnityEngine.WindModule.dll | Bin 9216 -> 0 bytes AquaMai/Libs/UnityEngine.XRModule.dll | Bin 44544 -> 0 bytes AquaMai/Libs/UnityEngine.dll | Bin 72192 -> 0 bytes AquaMai/Libs/mscorlib.dll | Bin 3939840 -> 0 bytes AquaMai/README.md | 41 -- README.md | 2 +- 249 files changed, 1 insertion(+), 13936 deletions(-) delete mode 100644 .github/workflows/aquamai.yaml delete mode 100644 AquaMai/.gitignore delete mode 100644 AquaMai/AquaMai.Build/AquaMai.Build.csproj delete mode 100644 AquaMai/AquaMai.Build/GenerateExampleConfig.cs delete mode 100644 AquaMai/AquaMai.Build/PostBuildPatch.cs delete mode 100644 AquaMai/AquaMai.Config.HeadlessLoader/AquaMai.Config.HeadlessLoader.csproj delete mode 100644 AquaMai/AquaMai.Config.HeadlessLoader/ConfigAssemblyLoader.cs delete mode 100644 AquaMai/AquaMai.Config.HeadlessLoader/CustomAssemblyResolver.cs delete mode 100644 AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigInterface.cs delete mode 100644 AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigLoader.cs delete mode 100644 AquaMai/AquaMai.Config.HeadlessLoader/Polyfills.cs delete mode 100644 AquaMai/AquaMai.Config.HeadlessLoader/ResourceLoader.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfig.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfigComment.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfigEntryAttribute.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfigMigrationManager.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfigParser.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfigSectionAttribute.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfigSerializer.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IConfigView.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IReflectionManager.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/IReflectionProvider.cs delete mode 100644 AquaMai/AquaMai.Config.Interfaces/Polyfills.cs delete mode 100644 AquaMai/AquaMai.Config/ApiVersion.cs delete mode 100644 AquaMai/AquaMai.Config/AquaMai.Config.csproj delete mode 100644 AquaMai/AquaMai.Config/Attributes/ConfigCollapseNamespaceAttribute.cs delete mode 100644 AquaMai/AquaMai.Config/Attributes/ConfigComment.cs delete mode 100644 AquaMai/AquaMai.Config/Attributes/ConfigEntryAttribute.cs delete mode 100644 AquaMai/AquaMai.Config/Attributes/ConfigSectionAttribute.cs delete mode 100644 AquaMai/AquaMai.Config/Attributes/EnableCondition.cs delete mode 100644 AquaMai/AquaMai.Config/Config.cs delete mode 100644 AquaMai/AquaMai.Config/ConfigParser.cs delete mode 100644 AquaMai/AquaMai.Config/ConfigSerializer.cs delete mode 100644 AquaMai/AquaMai.Config/ConfigView.cs delete mode 100644 AquaMai/AquaMai.Config/FodyWeavers.xml delete mode 100644 AquaMai/AquaMai.Config/FodyWeavers.xsd delete mode 100644 AquaMai/AquaMai.Config/Migration/ConfigMigrationManager.cs delete mode 100644 AquaMai/AquaMai.Config/Migration/ConfigMigration_V1_0_V2_0.cs delete mode 100644 AquaMai/AquaMai.Config/Migration/ConfigMigration_V2_0_V2_1.cs delete mode 100644 AquaMai/AquaMai.Config/Migration/IConfigMigration.cs delete mode 100644 AquaMai/AquaMai.Config/Polyfills.cs delete mode 100644 AquaMai/AquaMai.Config/Reflection/MonoCecilAssemblyReflectionProvider.cs delete mode 100644 AquaMai/AquaMai.Config/Reflection/ReflectionManager.cs delete mode 100644 AquaMai/AquaMai.Config/Reflection/SystemReflectionProvider.cs delete mode 100644 AquaMai/AquaMai.Config/Types/KeyCodeID.cs delete mode 100644 AquaMai/AquaMai.Config/Types/KeyCodeOrName.cs delete mode 100644 AquaMai/AquaMai.Config/Utility.cs delete mode 100644 AquaMai/AquaMai.Core/AquaMai.Core.csproj delete mode 100644 AquaMai/AquaMai.Core/Attributes/EnableGameVersionAttribute.cs delete mode 100644 AquaMai/AquaMai.Core/Attributes/EnableIfAttribute.cs delete mode 100644 AquaMai/AquaMai.Core/Attributes/EnableImplicitlyIfAttribute.cs delete mode 100644 AquaMai/AquaMai.Core/ConfigLoader.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/EnableConditionHelper.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/FileSystem.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/GameInfo.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/GuiSizes.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/KeyListener.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/MessageHelper.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/MusicDirHelper.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/SharedInstances.cs delete mode 100644 AquaMai/AquaMai.Core/Helpers/Shim.cs delete mode 100644 AquaMai/AquaMai.Core/Resources/I18nSingleAssemblyHook.cs delete mode 100644 AquaMai/AquaMai.Core/Resources/Locale.Designer.cs delete mode 100644 AquaMai/AquaMai.Core/Resources/Locale.resx delete mode 100644 AquaMai/AquaMai.Core/Resources/Locale.zh.resx delete mode 100644 AquaMai/AquaMai.Core/Startup.cs delete mode 100644 AquaMai/AquaMai.Mods/AquaMai.Mods.csproj delete mode 100644 AquaMai/AquaMai.Mods/DeprecationWarning.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/CustomLogo.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/CustomPlaceName.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/CustomSkins.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/CustomTrackStartDiff.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/CustomVersionString.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/DemoMaster.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/AlignCircleSlideJudgeDisplay.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/BreakSlideJudgeBlink.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/MaiGeometry.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/ParametricSlidePath.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideDataBuilder.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlidePathGenerator.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/DisableTrackStartTabs.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/ExtendNotesPool.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/FanJudgeFlip.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/HideHanabi.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/JudgeDisplay4B.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/RealisticRandomJudge.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideArrowAnimation.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideFadeInTweak.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideLayerReverse.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/GamePlay/TrackStartProcessTweak.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/HideMask.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/README.md delete mode 100644 AquaMai/AquaMai.Mods/Fancy/RandomBgm.cs delete mode 100644 AquaMai/AquaMai.Mods/Fancy/Triggers.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/Common.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/DebugFeature.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/DisableReboot.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/FixCheckAuth.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/FixConnSlide.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/FixLevelDisplay.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/FixSlideAutoPlay.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/Legacy/FixQuickRetry130.cs delete mode 100644 AquaMai/AquaMai.Mods/Fix/README.md delete mode 100644 AquaMai/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSettings/CreditConfig.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSettings/ForceAsServer.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSettings/JudgeAdjust.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSettings/README.md delete mode 100644 AquaMai/AquaMai.Mods/GameSettings/TouchSensitivity.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/Assets/Fonts.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/Assets/LoadAssetBundleWithoutManifest.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/Assets/UseJacketAsDummyMovie.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/CustomCameraId.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/DisableTimeout.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/KeyMap.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/QuickRetry.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/README.md delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/RemoveEncryption.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/SinglePlayer.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/TestProof.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/TouchPanelBaudRate.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/TouchToButtonInput.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/Unlock.cs delete mode 100644 AquaMai/AquaMai.Mods/GameSystem/Window.cs delete mode 100644 AquaMai/AquaMai.Mods/General.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/IgnoreAimeServerError.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/LockFrameRate.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/README.md delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/ResetTouch.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/SkipUserVersionCheck.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/TimeSaving/EntryToMusicSelection.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/TimeSaving/IWontTapOrSlideVigorously.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipEventInfo.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipGoodbyeScreen.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupDelays.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupWarning.cs delete mode 100644 AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipTrackStart.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/CiBuildAlert.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/HideSelfMadeCharts.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/ImmediateSave.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/JudgeAccuracyInfo.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/OneKeyEntryEnd.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/OneKeyRetrySkip.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/PracticeMode/Libs/PractiseModeUI.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/PracticeMode/PracticeMode.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/QuickEndPlay.cs delete mode 100644 AquaMai/AquaMai.Mods/UX/README.md delete mode 100644 AquaMai/AquaMai.Mods/UX/SelectionDetail.cs delete mode 100644 AquaMai/AquaMai.Mods/Utils/DisplayFrameRate.cs delete mode 100644 AquaMai/AquaMai.Mods/Utils/LogNetworkErrors.cs delete mode 100644 AquaMai/AquaMai.Mods/Utils/LogNetworkRequests.cs delete mode 100644 AquaMai/AquaMai.Mods/Utils/LogUnity.cs delete mode 100644 AquaMai/AquaMai.Mods/Utils/LogUserId.cs delete mode 100644 AquaMai/AquaMai.Mods/Utils/README.md delete mode 100644 AquaMai/AquaMai.Mods/Utils/ShowErrorLog.cs delete mode 100644 AquaMai/AquaMai.Mods/Utils/ShowNetErrorDetail.cs delete mode 100644 AquaMai/AquaMai.sln delete mode 100644 AquaMai/AquaMai/AquaMai.csproj delete mode 100644 AquaMai/AquaMai/AssemblyLoader.cs delete mode 100644 AquaMai/AquaMai/BuildInfo.cs delete mode 100644 AquaMai/AquaMai/Main.cs delete mode 100644 AquaMai/AquaMai/Properties/AssemblyInfo.cs delete mode 100644 AquaMai/Libs/.gitignore delete mode 100644 AquaMai/Libs/0Harmony.dll delete mode 100644 AquaMai/Libs/Assembly-CSharp-firstpass.dll delete mode 100644 AquaMai/Libs/MelonLoader.dll delete mode 100644 AquaMai/Libs/Mono.Cecil.dll delete mode 100644 AquaMai/Libs/Mono.Posix.dll delete mode 100644 AquaMai/Libs/Mono.Security.dll delete mode 100644 AquaMai/Libs/System.Configuration.dll delete mode 100644 AquaMai/Libs/System.Core.dll delete mode 100644 AquaMai/Libs/System.Numerics.dll delete mode 100644 AquaMai/Libs/System.Security.dll delete mode 100644 AquaMai/Libs/System.Xml.dll delete mode 100644 AquaMai/Libs/System.dll delete mode 100644 AquaMai/Libs/Unity.Analytics.DataPrivacy.dll delete mode 100644 AquaMai/Libs/Unity.TextMeshPro.dll delete mode 100644 AquaMai/Libs/UnityEngine.AIModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.ARModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.AccessibilityModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.AnimationModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.AssetBundleModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.AudioModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.BaselibModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.ClothModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.ClusterInputModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.ClusterRendererModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.CoreModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.CrashReportingModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.DirectorModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.FileSystemHttpModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.GameCenterModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.GridModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.HotReloadModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.IMGUIModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.ImageConversionModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.InputModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.JSONSerializeModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.LocalizationModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.Networking.dll delete mode 100644 AquaMai/Libs/UnityEngine.ParticleSystemModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.PerformanceReportingModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.Physics2DModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.PhysicsModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.ProfilerModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.ScreenCaptureModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.SharedInternalsModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.SpatialTracking.dll delete mode 100644 AquaMai/Libs/UnityEngine.SpriteMaskModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.SpriteShapeModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.StreamingModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.StyleSheetsModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.SubstanceModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.TLSModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.TerrainModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.TerrainPhysicsModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.TextCoreModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.TextRenderingModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.TilemapModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.Timeline.dll delete mode 100644 AquaMai/Libs/UnityEngine.TimelineModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UI.dll delete mode 100644 AquaMai/Libs/UnityEngine.UIElementsModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UIModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UNETModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UmbraModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityAnalyticsModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityConnectModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityTestProtocolModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityWebRequestAssetBundleModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityWebRequestAudioModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityWebRequestModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityWebRequestTextureModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.UnityWebRequestWWWModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.VFXModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.VRModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.VehiclesModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.VideoModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.WindModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.XRModule.dll delete mode 100644 AquaMai/Libs/UnityEngine.dll delete mode 100644 AquaMai/Libs/mscorlib.dll delete mode 100644 AquaMai/README.md diff --git a/.github/workflows/aquamai.yaml b/.github/workflows/aquamai.yaml deleted file mode 100644 index 93836c47..00000000 --- a/.github/workflows/aquamai.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: AquaMai Build - -on: - workflow_dispatch: - push: - paths: - - AquaMai/** - branches: - - v1-dev - pull_request_target: - paths: - - AquaMai/** - branches: - - v1-dev - -jobs: - build: - runs-on: windows-latest - steps: - - name: Fix Git line encoding bug - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - - uses: actions/checkout@v4 - - - name: Checkout Assets - uses: clansty/checkout@main - with: - repository: MewoLab/AquaMai-Build-Assets - ssh-key: ${{ secrets.BUILD_ASSETS_KEY }} - path: build-assets - max-attempts: 50 - min-retry-interval: 1 - max-retry-interval: 5 - - - name: Build AquaMai - shell: cmd - run: | - copy /y build-assets\SDEZ\* AquaMai\Libs - cd AquaMai - dotnet build -c Release /p:DefineConstants="CI" - - - name: Prepare artifact - shell: cmd - run: | - cd AquaMai\Output - mkdir Upload - move AquaMai.dll Upload - move AquaMai.*.toml Upload - - - uses: actions/upload-artifact@v4 - with: - name: AquaMai - path: AquaMai\Output\Upload - - - name: Send to Telegram - if: github.event_name != 'pull_request_target' - run: | - $Uri = "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMediaGroup" - $Form = @{ - chat_id = "-1002231087502" - media = @( - @{ type = "document"; media = "attach://aquamai_main"; caption = "${{ github.event.commits[0].message }}" }, - @{ type = "document"; media = "attach://aquamai_zh" } - @{ type = "document"; media = "attach://aquamai_en" } - ) | ConvertTo-Json - aquamai_main = Get-Item AquaMai\Output\Upload\AquaMai.dll - aquamai_zh = Get-Item AquaMai\Output\Upload\AquaMai.zh.toml - aquamai_en = Get-Item AquaMai\Output\Upload\AquaMai.en.toml - } - Invoke-RestMethod -Uri $uri -Form $Form -Method Post diff --git a/AquaMai/.gitignore b/AquaMai/.gitignore deleted file mode 100644 index df21fd82..00000000 --- a/AquaMai/.gitignore +++ /dev/null @@ -1,374 +0,0 @@ - -# Created by https://www.toptal.com/developers/gitignore/api/git,visualstudio -# Edit at https://www.toptal.com/developers/gitignore?templates=git,visualstudio - -### Git ### -# Created by git for backups. To disable backups in Git: -# $ git config --global mergetool.keepBackup false -*.orig - -# Created by git when using merge tools for conflicts -*.BACKUP.* -*.BASE.* -*.LOCAL.* -*.REMOTE.* -*_BACKUP_*.txt -*_BASE_*.txt -*_LOCAL_*.txt -*_REMOTE_*.txt - -### VisualStudio ### -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*[.json, .xml, .info] - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# End of https://www.toptal.com/developers/gitignore/api/git,visualstudio - -Output -.idea diff --git a/AquaMai/AquaMai.Build/AquaMai.Build.csproj b/AquaMai/AquaMai.Build/AquaMai.Build.csproj deleted file mode 100644 index ff6b804d..00000000 --- a/AquaMai/AquaMai.Build/AquaMai.Build.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - Release - AnyCPU - {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5} - Library - AquaMai.Build - AquaMai.Build - netstandard2.0 - 512 - true - 12 - 414;NU1702 - $(ProjectDir)../Libs/ - $(ProjectDir)../Output/ - false - false - - - - false - None - true - prompt - 4 - true - false - - - - DEBUG - - - - - - - - - $(LibsPath)Mono.Cecil.dll - - - - - - - - - diff --git a/AquaMai/AquaMai.Build/GenerateExampleConfig.cs b/AquaMai/AquaMai.Build/GenerateExampleConfig.cs deleted file mode 100644 index 4e7b0028..00000000 --- a/AquaMai/AquaMai.Build/GenerateExampleConfig.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.IO; -using AquaMai.Config.Interfaces; -using AquaMai.Config.HeadlessLoader; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -public class GenerateExampleConfig : Task -{ - [Required] - public string DllPath { get; set; } - - [Required] - public string OutputPath { get; set; } - - public override bool Execute() - { - try - { - var configInterface = HeadlessConfigLoader.LoadFromPacked(DllPath); - var config = configInterface.CreateConfig(); - foreach (var lang in (string[]) ["en", "zh"]) - { - var configSerializer = configInterface.CreateConfigSerializer(new IConfigSerializer.Options() - { - Lang = lang, - IncludeBanner = true, - OverrideLocaleValue = true - }); - var example = configSerializer.Serialize(config); - File.WriteAllText(Path.Combine(OutputPath, $"AquaMai.{lang}.toml"), example); - } - - return true; - } - catch (Exception e) - { - Log.LogErrorFromException(e, true); - return false; - } - } -} diff --git a/AquaMai/AquaMai.Build/PostBuildPatch.cs b/AquaMai/AquaMai.Build/PostBuildPatch.cs deleted file mode 100644 index f2bb2a0a..00000000 --- a/AquaMai/AquaMai.Build/PostBuildPatch.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Mono.Cecil; - -public class PostBuildPatch : Task -{ - [Required] - public string DllPath { get; set; } - - public override bool Execute() - { - try - { - var assembly = AssemblyDefinition.ReadAssembly(new MemoryStream(File.ReadAllBytes(DllPath))); - CompressEmbeddedAssemblies(assembly); - var outputStream = new MemoryStream(); - assembly.Write(outputStream); - File.WriteAllBytes(DllPath, outputStream.ToArray()); - return true; - } - catch (Exception e) - { - Log.LogErrorFromException(e, true); - return false; - } - } - - private void CompressEmbeddedAssemblies(AssemblyDefinition assembly) - { - foreach (var resource in assembly.MainModule.Resources.ToList()) - { - if (resource.Name.EndsWith(".dll") && resource is EmbeddedResource embeddedResource) - { - using var compressedStream = new MemoryStream(); - using (var deflateStream = new DeflateStream(compressedStream, CompressionLevel.Optimal)) - { - embeddedResource.GetResourceStream().CopyTo(deflateStream); - } - var compressedBytes = compressedStream.ToArray(); - - Log.LogMessage($"Compressed {resource.Name} from {embeddedResource.GetResourceStream().Length} to {compressedBytes.Length} bytes"); - - assembly.MainModule.Resources.Remove(resource); - assembly.MainModule.Resources.Add(new EmbeddedResource(resource.Name + ".compressed", resource.Attributes, compressedBytes)); - } - } - } -} diff --git a/AquaMai/AquaMai.Config.HeadlessLoader/AquaMai.Config.HeadlessLoader.csproj b/AquaMai/AquaMai.Config.HeadlessLoader/AquaMai.Config.HeadlessLoader.csproj deleted file mode 100644 index e23c3ba3..00000000 --- a/AquaMai/AquaMai.Config.HeadlessLoader/AquaMai.Config.HeadlessLoader.csproj +++ /dev/null @@ -1,45 +0,0 @@ - - - - Release - AnyCPU - {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66} - Library - AquaMai.Config.HeadlessLoader - AquaMai.Config.HeadlessLoader - netstandard2.0 - 512 - true - 12 - 414;NU1702 - $(ProjectDir)../Libs/ - $(ProjectDir)../Output/ - false - false - - - - false - None - true - prompt - 4 - true - false - - - - DEBUG - - - - - - - - - $(LibsPath)Mono.Cecil.dll - - - - diff --git a/AquaMai/AquaMai.Config.HeadlessLoader/ConfigAssemblyLoader.cs b/AquaMai/AquaMai.Config.HeadlessLoader/ConfigAssemblyLoader.cs deleted file mode 100644 index 22e1f96f..00000000 --- a/AquaMai/AquaMai.Config.HeadlessLoader/ConfigAssemblyLoader.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using Mono.Cecil; - -namespace AquaMai.Config.HeadlessLoader; - -class ConfigAssemblyLoader -{ - public static Assembly LoadConfigAssembly(AssemblyDefinition assembly) - { - var references = assembly.MainModule.AssemblyReferences; - foreach (var reference in references) - { - if (reference.Name == "mscorlib" || reference.Name == "System" || reference.Name.StartsWith("System.")) - { - reference.Name = "netstandard"; - reference.Version = new Version(2, 0, 0, 0); - reference.PublicKeyToken = null; - } - } - - var targetFrameworkAttribute = assembly.CustomAttributes.FirstOrDefault(attr => attr.AttributeType.Name == "TargetFrameworkAttribute"); - if (targetFrameworkAttribute != null) - { - targetFrameworkAttribute.ConstructorArguments.Clear(); - targetFrameworkAttribute.ConstructorArguments.Add(new CustomAttributeArgument( - assembly.MainModule.TypeSystem.String, ".NETStandard,Version=v2.0")); - targetFrameworkAttribute.Properties.Clear(); - targetFrameworkAttribute.Properties.Add(new Mono.Cecil.CustomAttributeNamedArgument( - "FrameworkDisplayName", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, ".NET Standard 2.0"))); - } - - var stream = new MemoryStream(); - assembly.Write(stream); - FixLoadedAssemblyResolution(); - return AppDomain.CurrentDomain.Load(stream.ToArray()); - } - - private static bool FixedLoadedAssemblyResolution = false; - - // XXX: Why, without this, the already loaded assemblies are not resolved? - public static void FixLoadedAssemblyResolution() - { - if (FixedLoadedAssemblyResolution) - { - return; - } - FixedLoadedAssemblyResolution = true; - - var loadedAssemblies = new Dictionary(); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - loadedAssemblies[assembly.FullName] = assembly; - } - - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => - { - if (loadedAssemblies.TryGetValue(args.Name, out var assembly)) - { - return assembly; - } - return null; - }; - } -} diff --git a/AquaMai/AquaMai.Config.HeadlessLoader/CustomAssemblyResolver.cs b/AquaMai/AquaMai.Config.HeadlessLoader/CustomAssemblyResolver.cs deleted file mode 100644 index 4bf96750..00000000 --- a/AquaMai/AquaMai.Config.HeadlessLoader/CustomAssemblyResolver.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Mono.Cecil; - -namespace AquaMai.Config.HeadlessLoader; - -public class CustomAssemblyResolver : DefaultAssemblyResolver -{ - public new void RegisterAssembly(AssemblyDefinition assembly) - { - base.RegisterAssembly(assembly); - } -} diff --git a/AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigInterface.cs b/AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigInterface.cs deleted file mode 100644 index a86fcf64..00000000 --- a/AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigInterface.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Reflection; -using AquaMai.Config.Interfaces; -using Mono.Cecil; - -namespace AquaMai.Config.HeadlessLoader; - -public class HeadlessConfigInterface -{ - private readonly Assembly loadedConfigAssembly; - - public IReflectionProvider ReflectionProvider { get; init; } - public IReflectionManager ReflectionManager { get; init; } - - public string ApiVersion { get; init; } - - public HeadlessConfigInterface(Assembly loadedConfigAssembly, AssemblyDefinition modsAssembly) - { - this.loadedConfigAssembly = loadedConfigAssembly; - - ReflectionProvider = Activator.CreateInstance( - loadedConfigAssembly.GetType("AquaMai.Config.Reflection.MonoCecilReflectionProvider"), [modsAssembly]) as IReflectionProvider; - ReflectionManager = Activator.CreateInstance( - loadedConfigAssembly.GetType("AquaMai.Config.Reflection.ReflectionManager"), [ReflectionProvider]) as IReflectionManager; - ApiVersion = loadedConfigAssembly - .GetType("AquaMai.Config.ApiVersion") - .GetField("Version", BindingFlags.Public | BindingFlags.Static) - .GetRawConstantValue() as string; - } - - public IConfigView CreateConfigView(string tomlString = null) - { - return Activator.CreateInstance( - loadedConfigAssembly.GetType("AquaMai.Config.ConfigView"), - tomlString == null ? [] : [tomlString]) as IConfigView; - } - - public IConfig CreateConfig() - { - return Activator.CreateInstance( - loadedConfigAssembly.GetType("AquaMai.Config.Config"), [ReflectionManager]) as IConfig; - } - - public IConfigParser GetConfigParser() - { - return loadedConfigAssembly - .GetType("AquaMai.Config.ConfigParser") - .GetField("Instance", BindingFlags.Public | BindingFlags.Static) - .GetValue(null) as IConfigParser; - } - - public IConfigSerializer CreateConfigSerializer(IConfigSerializer.Options options) - { - return Activator.CreateInstance( - loadedConfigAssembly.GetType("AquaMai.Config.ConfigSerializer"), [options]) as IConfigSerializer; - } - - public IConfigMigrationManager GetConfigMigrationManager() - { - return loadedConfigAssembly - .GetType("AquaMai.Config.Migration.ConfigMigrationManager") - .GetField("Instance", BindingFlags.Public | BindingFlags.Static) - .GetValue(null) as IConfigMigrationManager; - } -} diff --git a/AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigLoader.cs b/AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigLoader.cs deleted file mode 100644 index e685c26c..00000000 --- a/AquaMai/AquaMai.Config.HeadlessLoader/HeadlessConfigLoader.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Mono.Cecil; - -namespace AquaMai.Config.HeadlessLoader; - -public class HeadlessConfigLoader -{ - public static HeadlessConfigInterface LoadFromPacked(string fileName) - { - using var file = new FileStream(fileName, FileMode.Open); - return LoadFromPacked(file); - } - - public static HeadlessConfigInterface LoadFromPacked(byte[] assemblyBinary) - => LoadFromPacked(new MemoryStream(assemblyBinary)); - - public static HeadlessConfigInterface LoadFromPacked(Stream assemblyStream) - => LoadFromPacked(AssemblyDefinition.ReadAssembly(assemblyStream)); - - public static HeadlessConfigInterface LoadFromPacked(AssemblyDefinition assembly) - { - return LoadFromUnpacked( - ResourceLoader.LoadEmbeddedAssemblies(assembly).Values); - } - - public static HeadlessConfigInterface LoadFromUnpacked(IEnumerable assemblyBinariess) => - LoadFromUnpacked(assemblyBinariess.Select(binary => new MemoryStream(binary))); - - public static HeadlessConfigInterface LoadFromUnpacked(IEnumerable assemblyStreams) - { - var resolver = new CustomAssemblyResolver(); - var assemblies = assemblyStreams - .Select( - assemblyStream => - AssemblyDefinition.ReadAssembly( - assemblyStream, - new ReaderParameters() { - AssemblyResolver = resolver - })) - .ToArray(); - foreach (var assembly in assemblies) - { - resolver.RegisterAssembly(assembly); - } - - var configAssembly = assemblies.First(assembly => assembly.Name.Name == "AquaMai.Config"); - if (configAssembly == null) - { - throw new InvalidOperationException("AquaMai.Config assembly not found"); - } - var loadedConfigAssembly = ConfigAssemblyLoader.LoadConfigAssembly(configAssembly); - var modsAssembly = assemblies.First(assembly => assembly.Name.Name == "AquaMai.Mods"); - if (modsAssembly == null) - { - throw new InvalidOperationException("AquaMai.Mods assembly not found"); - } - return new(loadedConfigAssembly, modsAssembly); - } -} diff --git a/AquaMai/AquaMai.Config.HeadlessLoader/Polyfills.cs b/AquaMai/AquaMai.Config.HeadlessLoader/Polyfills.cs deleted file mode 100644 index eb2da113..00000000 --- a/AquaMai/AquaMai.Config.HeadlessLoader/Polyfills.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit {} -} diff --git a/AquaMai/AquaMai.Config.HeadlessLoader/ResourceLoader.cs b/AquaMai/AquaMai.Config.HeadlessLoader/ResourceLoader.cs deleted file mode 100644 index e57a1357..00000000 --- a/AquaMai/AquaMai.Config.HeadlessLoader/ResourceLoader.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using Mono.Cecil; - -namespace AquaMai.Config.HeadlessLoader; - -public class ResourceLoader -{ - private const string DLL_SUFFIX = ".dll"; - private const string COMPRESSED_SUFFIX = ".compressed"; - private const string DLL_COMPRESSED_SUFFIX = $"{DLL_SUFFIX}{COMPRESSED_SUFFIX}"; - - public static Dictionary LoadEmbeddedAssemblies(AssemblyDefinition assembly) - { - return assembly.MainModule.Resources - .Where(resource => resource.Name.ToLower().EndsWith(DLL_SUFFIX) || resource.Name.ToLower().EndsWith(DLL_COMPRESSED_SUFFIX)) - .Select(LoadResource) - .Where(data => data.Name != null) - .ToDictionary(data => data.Name, data => data.Stream); - } - - public static (string Name, Stream Stream) LoadResource(Resource resource) - { - if (resource is EmbeddedResource embeddedResource) - { - if (resource.Name.ToLower().EndsWith(COMPRESSED_SUFFIX)) - { - var decompressedStream = new MemoryStream(); - using (var deflateStream = new DeflateStream(embeddedResource.GetResourceStream(), CompressionMode.Decompress)) - { - deflateStream.CopyTo(decompressedStream); - } - decompressedStream.Position = 0; - return (resource.Name.Substring(0, resource.Name.Length - COMPRESSED_SUFFIX.Length), decompressedStream); - } - return (resource.Name, embeddedResource.GetResourceStream()); - } - return (null, null); - } -} diff --git a/AquaMai/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj b/AquaMai/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj deleted file mode 100644 index 95c911c2..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - Release - AnyCPU - {DF1536F9-3B06-4463-B654-4CC3E708B610} - Library - AquaMai.Config.Interfaces - AquaMai.Config.Interfaces - net472 - 512 - true - 12 - 414 - $(ProjectDir)../Libs/;$(AssemblySearchPaths) - $(ProjectDir)../Output/ - false - false - - - - false - None - true - prompt - 4 - true - false - - - - DEBUG - - - - - - - - diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfig.cs b/AquaMai/AquaMai.Config.Interfaces/IConfig.cs deleted file mode 100644 index 5f3f4a02..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfig.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace AquaMai.Config.Interfaces; - -public interface IConfig -{ - public interface IEntryState - { - public bool IsDefault { get; } - public object DefaultValue { get; } - public object Value { get; set; } - } - - public interface ISectionState - { - public bool IsDefault { get; set; } - public bool DefaultEnabled { get; } - public bool Enabled { get; set; } - } - - public IReflectionManager ReflectionManager { get; } - - public ISectionState GetSectionState(IReflectionManager.ISection section); - public ISectionState GetSectionState(Type type); - public void SetSectionEnabled(IReflectionManager.ISection section, bool enabled); - public IEntryState GetEntryState(IReflectionManager.IEntry entry); - public void SetEntryValue(IReflectionManager.IEntry entry, object value); -} diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfigComment.cs b/AquaMai/AquaMai.Config.Interfaces/IConfigComment.cs deleted file mode 100644 index db219eb3..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfigComment.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AquaMai.Config.Interfaces; - -public interface IConfigComment -{ - string CommentEn { get; init; } - string CommentZh { get; init; } - public string GetLocalized(string lang); -} diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfigEntryAttribute.cs b/AquaMai/AquaMai.Config.Interfaces/IConfigEntryAttribute.cs deleted file mode 100644 index 9d1564bc..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfigEntryAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace AquaMai.Config.Interfaces; - -public interface IConfigEntryAttribute -{ - IConfigComment Comment { get; } - bool HideWhenDefault { get; } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfigMigrationManager.cs b/AquaMai/AquaMai.Config.Interfaces/IConfigMigrationManager.cs deleted file mode 100644 index 0c343b5e..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfigMigrationManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AquaMai.Config.Interfaces; - -public interface IConfigMigrationManager -{ - public IConfigView Migrate(IConfigView config); - public string GetVersion(IConfigView config); - public string LatestVersion { get; } -} diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfigParser.cs b/AquaMai/AquaMai.Config.Interfaces/IConfigParser.cs deleted file mode 100644 index 5dda2d83..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfigParser.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace AquaMai.Config.Interfaces; - -public interface IConfigParser -{ - public void Parse(IConfig config, string tomlString); - public void Parse(IConfig config, IConfigView configView); -} diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfigSectionAttribute.cs b/AquaMai/AquaMai.Config.Interfaces/IConfigSectionAttribute.cs deleted file mode 100644 index b8bdb41d..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfigSectionAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AquaMai.Config.Interfaces; - -public interface IConfigSectionAttribute -{ - IConfigComment Comment { get; } - bool ExampleHidden { get; } - bool DefaultOn { get; } - bool AlwaysEnabled { get; } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfigSerializer.cs b/AquaMai/AquaMai.Config.Interfaces/IConfigSerializer.cs deleted file mode 100644 index e1b8fe83..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfigSerializer.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace AquaMai.Config.Interfaces; - -public interface IConfigSerializer -{ - public record Options - { - public string Lang { get; init; } - public bool IncludeBanner { get; init; } - public bool OverrideLocaleValue { get; init; } - } - - public string Serialize(IConfig config); -} diff --git a/AquaMai/AquaMai.Config.Interfaces/IConfigView.cs b/AquaMai/AquaMai.Config.Interfaces/IConfigView.cs deleted file mode 100644 index 29094741..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IConfigView.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace AquaMai.Config.Interfaces; - -public interface IConfigView -{ - public void SetValue(string path, object value); - public T GetValueOrDefault(string path, T defaultValue = default); - public bool TryGetValue(string path, out T resultValue); - public bool Remove(string path); - public string ToToml(); - public IConfigView Clone(); -} diff --git a/AquaMai/AquaMai.Config.Interfaces/IReflectionManager.cs b/AquaMai/AquaMai.Config.Interfaces/IReflectionManager.cs deleted file mode 100644 index 41be9ef9..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IReflectionManager.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using System; - -namespace AquaMai.Config.Interfaces; - -public interface IReflectionManager -{ - public interface IEntry - { - public string Path { get; } - public string Name { get; } - public IReflectionField Field { get; } - public IConfigEntryAttribute Attribute { get; init; } - } - - public interface ISection - { - public string Path { get; } - public IReflectionType Type { get; } - public List Entries { get; } - public IConfigSectionAttribute Attribute { get; init; } - } - - public IEnumerable Sections { get; } - - public IEnumerable Entries { get; } - - public bool ContainsSection(string path); - - public bool TryGetSection(string path, out ISection section); - - public bool TryGetSection(Type type, out ISection section); - - public ISection GetSection(string path); - - public ISection GetSection(Type type); - - public bool ContainsEntry(string path); - - public bool TryGetEntry(string path, out IEntry entry); - - public IEntry GetEntry(string path); -} diff --git a/AquaMai/AquaMai.Config.Interfaces/IReflectionProvider.cs b/AquaMai/AquaMai.Config.Interfaces/IReflectionProvider.cs deleted file mode 100644 index 19f7fc10..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/IReflectionProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace AquaMai.Config.Interfaces; - -public interface IReflectionField -{ - public string Name { get; } - public Type FieldType { get; } - - public T GetCustomAttribute() where T : Attribute; - public object GetValue(object objIsNull); - public void SetValue(object objIsNull, object value); -} - -public interface IReflectionType -{ - public string FullName { get; } - public string Namespace { get; } - - public T GetCustomAttribute() where T : Attribute; - public IReflectionField[] GetFields(BindingFlags bindingAttr); -} - -public interface IReflectionProvider -{ - public IReflectionType[] GetTypes(); - public Dictionary GetEnum(string enumName); -} diff --git a/AquaMai/AquaMai.Config.Interfaces/Polyfills.cs b/AquaMai/AquaMai.Config.Interfaces/Polyfills.cs deleted file mode 100644 index eb2da113..00000000 --- a/AquaMai/AquaMai.Config.Interfaces/Polyfills.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit {} -} diff --git a/AquaMai/AquaMai.Config/ApiVersion.cs b/AquaMai/AquaMai.Config/ApiVersion.cs deleted file mode 100644 index 35959b54..00000000 --- a/AquaMai/AquaMai.Config/ApiVersion.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AquaMai.Config; - -public static class ApiVersion -{ - // Using a raw string for API version instead of a constant for maximum compatibility. - // When breaking changes are made, increment the major version. - // When new APIs are added in a backwards-compatible but non-forward-compatible manner, increment the minor version. - public const string Version = "1.0"; -} diff --git a/AquaMai/AquaMai.Config/AquaMai.Config.csproj b/AquaMai/AquaMai.Config/AquaMai.Config.csproj deleted file mode 100644 index d80f92bd..00000000 --- a/AquaMai/AquaMai.Config/AquaMai.Config.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - - Release - AnyCPU - {DF1536F9-3B06-4463-B654-4CC3E708B610} - Library - AquaMai.Config - AquaMai.Config - net472 - 512 - true - 12 - 414 - $(ProjectDir)../Libs/;$(AssemblySearchPaths) - $(ProjectDir)../Output/ - false - false - - - - false - None - true - prompt - 4 - true - false - - - - DEBUG - - - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - all - - - - - diff --git a/AquaMai/AquaMai.Config/Attributes/ConfigCollapseNamespaceAttribute.cs b/AquaMai/AquaMai.Config/Attributes/ConfigCollapseNamespaceAttribute.cs deleted file mode 100644 index dab14350..00000000 --- a/AquaMai/AquaMai.Config/Attributes/ConfigCollapseNamespaceAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace AquaMai.Config.Attributes; - -// When The most inner namespace is the same name of the class, it should be collapsed. -// The class must be the only class in the namespace with a [ConfigSection] attribute. -[AttributeUsage(AttributeTargets.Class)] -public class ConfigCollapseNamespaceAttribute : Attribute -{} diff --git a/AquaMai/AquaMai.Config/Attributes/ConfigComment.cs b/AquaMai/AquaMai.Config/Attributes/ConfigComment.cs deleted file mode 100644 index 4a9ebb03..00000000 --- a/AquaMai/AquaMai.Config/Attributes/ConfigComment.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using AquaMai.Config.Interfaces; - -namespace AquaMai.Config.Attributes; - -public record ConfigComment(string CommentEn, string CommentZh) : IConfigComment -{ - public string GetLocalized(string lang) => lang switch - { - "en" => CommentEn ?? "", - "zh" => CommentZh ?? "", - _ => throw new ArgumentException($"Unsupported language: {lang}") - }; -} diff --git a/AquaMai/AquaMai.Config/Attributes/ConfigEntryAttribute.cs b/AquaMai/AquaMai.Config/Attributes/ConfigEntryAttribute.cs deleted file mode 100644 index 4ea36334..00000000 --- a/AquaMai/AquaMai.Config/Attributes/ConfigEntryAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using AquaMai.Config.Interfaces; - -namespace AquaMai.Config.Attributes; - -public enum SpecialConfigEntry -{ - None, - Locale -} - -[AttributeUsage(AttributeTargets.Field)] -public class ConfigEntryAttribute( - string en = null, - string zh = null, - // NOTE: Don't use this argument to hide any useful options. - // Only use it to hide options that really won't be used. - bool hideWhenDefault = false, - // NOTE: Use this argument to mark special config entries that need special handling. - SpecialConfigEntry specialConfigEntry = SpecialConfigEntry.None) : Attribute, IConfigEntryAttribute -{ - public IConfigComment Comment { get; } = new ConfigComment(en, zh); - public bool HideWhenDefault { get; } = hideWhenDefault; - public SpecialConfigEntry SpecialConfigEntry { get; } = specialConfigEntry; -} diff --git a/AquaMai/AquaMai.Config/Attributes/ConfigSectionAttribute.cs b/AquaMai/AquaMai.Config/Attributes/ConfigSectionAttribute.cs deleted file mode 100644 index 100c4c44..00000000 --- a/AquaMai/AquaMai.Config/Attributes/ConfigSectionAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using AquaMai.Config.Interfaces; - -namespace AquaMai.Config.Attributes; - -[AttributeUsage(AttributeTargets.Class)] -public class ConfigSectionAttribute( - string en = null, - string zh = null, - // It will be hidden if the default value is preserved. - bool exampleHidden = false, - // A "Disabled = true" entry is required to disable the section. - bool defaultOn = false, - // NOTE: You probably shouldn't use this. Only the "General" section is using this. - // Implies defaultOn = true. - bool alwaysEnabled = false) : Attribute, IConfigSectionAttribute -{ - public IConfigComment Comment { get; } = new ConfigComment(en, zh); - public bool ExampleHidden { get; } = exampleHidden; - public bool DefaultOn { get; } = defaultOn || alwaysEnabled; - public bool AlwaysEnabled { get; } = alwaysEnabled; -} diff --git a/AquaMai/AquaMai.Config/Attributes/EnableCondition.cs b/AquaMai/AquaMai.Config/Attributes/EnableCondition.cs deleted file mode 100644 index 2a1e8d99..00000000 --- a/AquaMai/AquaMai.Config/Attributes/EnableCondition.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; - -namespace AquaMai.Config.Attributes; - -public enum EnableConditionOperator -{ - Equal, - NotEqual, - GreaterThan, - LessThan, - GreaterThanOrEqual, - LessThanOrEqual -} - -public class EnableCondition( - Type referenceType, - string referenceMember, - EnableConditionOperator @operator, - object rightSideValue) : Attribute -{ - public Type ReferenceType { get; } = referenceType; - public string ReferenceMember { get; } = referenceMember; - public EnableConditionOperator Operator { get; } = @operator; - public object RightSideValue { get; } = rightSideValue; - - // Referencing a field in another class and checking if it's true. - public EnableCondition(Type referenceType, string referenceMember) - : this(referenceType, referenceMember, EnableConditionOperator.Equal, true) - { } - - // Referencing a field in the same class and comparing it with a value. - public EnableCondition(string referenceMember, EnableConditionOperator condition, object value) - : this(null, referenceMember, condition, value) - { } - - // Referencing a field in the same class and checking if it's true. - public EnableCondition(string referenceMember) - : this(referenceMember, EnableConditionOperator.Equal, true) - { } - - public bool Evaluate(Type selfType) - { - var referenceType = ReferenceType ?? selfType; - var referenceField = referenceType.GetField( - ReferenceMember, - System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); - var referenceProperty = referenceType.GetProperty( - ReferenceMember, - System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); - if (referenceField == null && referenceProperty == null) - { - throw new ArgumentException($"Field or property {ReferenceMember} not found in {referenceType.FullName}"); - } - var referenceMemberValue = referenceField != null ? referenceField.GetValue(null) : referenceProperty.GetValue(null); - switch (Operator) - { - case EnableConditionOperator.Equal: - return referenceMemberValue.Equals(RightSideValue); - case EnableConditionOperator.NotEqual: - return !referenceMemberValue.Equals(RightSideValue); - case EnableConditionOperator.GreaterThan: - case EnableConditionOperator.LessThan: - case EnableConditionOperator.GreaterThanOrEqual: - case EnableConditionOperator.LessThanOrEqual: - var comparison = (IComparable)referenceMemberValue; - return Operator switch - { - EnableConditionOperator.GreaterThan => comparison.CompareTo(RightSideValue) > 0, - EnableConditionOperator.LessThan => comparison.CompareTo(RightSideValue) < 0, - EnableConditionOperator.GreaterThanOrEqual => comparison.CompareTo(RightSideValue) >= 0, - EnableConditionOperator.LessThanOrEqual => comparison.CompareTo(RightSideValue) <= 0, - _ => throw new NotImplementedException(), - }; - default: - throw new NotImplementedException(); - } - } -} diff --git a/AquaMai/AquaMai.Config/Config.cs b/AquaMai/AquaMai.Config/Config.cs deleted file mode 100644 index 9e445c2d..00000000 --- a/AquaMai/AquaMai.Config/Config.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using AquaMai.Config.Interfaces; -using AquaMai.Config.Reflection; - -namespace AquaMai.Config; - -public class Config : IConfig -{ - // NOTE: If a section's state is default, all underlying entries' states are default as well. - - public record SectionState : IConfig.ISectionState - { - public bool IsDefault { get; set; } - public bool DefaultEnabled { get; init; } - public bool Enabled { get; set; } - } - - public record EntryState : IConfig.IEntryState - { - public bool IsDefault { get; set; } - public object DefaultValue { get; init; } - public object Value { get; set; } - } - - private readonly Dictionary sections = new(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary entries = new(StringComparer.OrdinalIgnoreCase); - - public readonly ReflectionManager reflectionManager; - public IReflectionManager ReflectionManager => reflectionManager; - - public Config(ReflectionManager reflectionManager) - { - this.reflectionManager = reflectionManager; - - foreach (var section in reflectionManager.SectionValues) - { - InitializeSection(section); - } - } - - private void InitializeSection(ReflectionManager.Section section) - { - sections.Add(section.Path, new SectionState() - { - IsDefault = true, - DefaultEnabled = section.Attribute.DefaultOn, - Enabled = section.Attribute.DefaultOn - }); - - foreach (var entry in section.Entries) - { - var defaultValue = entry.Field.GetValue(null); - if (defaultValue == null) - { - throw new InvalidOperationException($"Null default value for entry {entry.Path} is not allowed."); - } - - entries.Add(entry.Path, new EntryState() - { - IsDefault = true, - DefaultValue = defaultValue, - Value = defaultValue - }); - } - } - - public IConfig.ISectionState GetSectionState(IReflectionManager.ISection section) - { - return sections[section.Path]; - } - - public IConfig.ISectionState GetSectionState(Type type) - { - if (!ReflectionManager.TryGetSection(type, out var section)) - { - throw new ArgumentException($"Type {type.FullName} is not a config section."); - } - - return sections[section.Path]; - } - - public void SetSectionEnabled(IReflectionManager.ISection section, bool enabled) - { - sections[section.Path].IsDefault = false; - sections[section.Path].Enabled = enabled; - } - - public IConfig.IEntryState GetEntryState(IReflectionManager.IEntry entry) - { - return entries[entry.Path]; - } - - public void SetEntryValue(IReflectionManager.IEntry entry, object value) - { - if (value.GetType() != entry.Field.FieldType) - { - throw new ArgumentException($"Value type {value.GetType().FullName} does not match entry type {entry.Field.FieldType.FullName}."); - } - - entry.Field.SetValue(null, value); - entries[entry.Path].IsDefault = false; - entries[entry.Path].Value = value; - } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Config/ConfigParser.cs b/AquaMai/AquaMai.Config/ConfigParser.cs deleted file mode 100644 index cbca0980..00000000 --- a/AquaMai/AquaMai.Config/ConfigParser.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using Tomlet.Models; -using AquaMai.Config.Interfaces; -using AquaMai.Config.Reflection; -using AquaMai.Config.Migration; -using System.Linq; - -namespace AquaMai.Config; - -public class ConfigParser : IConfigParser -{ - public readonly static ConfigParser Instance = new(); - - private readonly static string[] supressUnrecognizedConfigPaths = ["Version"]; - private readonly static string[] supressUnrecognizedConfigPathSuffixes = [ - ".Disabled", // For section enable state. - ".Disable", // For section enable state, but the wrong key, warn later. - ".Enabled", // For section enable state, but the wrong key, warn later. - ".Enable", // For section enable state, but the wrong key, warn later. - ]; - - private ConfigParser() - {} - - public void Parse(IConfig config, string tomlString) - { - var configView = new ConfigView(tomlString); - Parse(config, configView); - } - - public void Parse(IConfig config, IConfigView configView) - { - var configVersion = ConfigMigrationManager.Instance.GetVersion(configView); - if (configVersion != ConfigMigrationManager.Instance.LatestVersion) - { - throw new InvalidOperationException($"Config version mismatch: expected {ConfigMigrationManager.Instance.LatestVersion}, got {configVersion}"); - } - Hydrate((Config)config, ((ConfigView)configView).root, ""); - } - - private static void Hydrate(Config config, TomlValue value, string path) - { - if (config.ReflectionManager.TryGetSection(path, out var section)) - { - ParseSectionEnableState(config, (ReflectionManager.Section)section, value, path); - } - - if (value is TomlTable table) - { - bool isLeaf = true; - foreach (var subKey in table.Keys) - { - var subValue = table.GetValue(subKey); - var subPath = path == "" ? subKey : $"{path}.{subKey}"; - if (subValue is TomlTable) - { - isLeaf = false; - } - Hydrate(config, subValue, subPath); - } - // A leaf dictionary, which has no child dictionaries, must be a section. - if (isLeaf && section == null) - { - Utility.Log($"Unrecognized config section: {path}"); - } - } - else - { - // It's an config entry value (or a primitive type for enabling a section). - if (!config.ReflectionManager.ContainsSection(path) && - !config.ReflectionManager.ContainsEntry(path) && - !supressUnrecognizedConfigPaths.Any(s => path.Equals(s, StringComparison.OrdinalIgnoreCase)) && - !supressUnrecognizedConfigPathSuffixes.Any(suffix => path.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))) - { - Utility.Log($"Unrecognized config entry: {path}"); - return; - } - - if (config.ReflectionManager.TryGetEntry(path, out var entry)) - { - try - { - var parsedValue = Utility.ParseTomlValue(entry.Field.FieldType, value); - config.SetEntryValue(entry, parsedValue); - } - catch (Exception e) - { - Utility.Log($"Error hydrating config ({path} = {value.StringValue}): {e.Message}"); - } - } - } - } - - public static void ParseSectionEnableState( - Config config, - ReflectionManager.Section section, - TomlValue value, - string path) - { - if (value is TomlTable table) - { - foreach (var unexpectedKey in (string[]) ["Enable", "Enabled", "Disable"]) - { - if (Utility.TomlContainsKeyCaseInsensitive(table, unexpectedKey)) - { - Utility.Log($"Unexpected key \"{unexpectedKey}\" for enable status under \"{path}\". Only \"Disabled\" is parsed."); - } - } - - if (Utility.TomlTryGetValueCaseInsensitive(table, "Disabled", out var disableValue) && !section.Attribute.AlwaysEnabled) - { - var disabled = Utility.IsTruty(disableValue, path + ".Disabled"); - config.SetSectionEnabled(section, !disabled); - } - else - { - config.SetSectionEnabled(section, true); - } - } - else - { - config.SetSectionEnabled(section, Utility.IsTruty(value, path)); - } - } -} diff --git a/AquaMai/AquaMai.Config/ConfigSerializer.cs b/AquaMai/AquaMai.Config/ConfigSerializer.cs deleted file mode 100644 index eb9da5ac..00000000 --- a/AquaMai/AquaMai.Config/ConfigSerializer.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Reflection; -using System.Text; -using AquaMai.Config.Attributes; -using AquaMai.Config.Interfaces; -using AquaMai.Config.Migration; -using Tomlet.Models; - -namespace AquaMai.Config; - -public class ConfigSerializer(IConfigSerializer.Options Options) : IConfigSerializer -{ - private const string BANNER_ZH = - """ - 这是 AquaMai 的 TOML 配置文件 - - - 井号 # 开头的行为注释,被注释掉的内容不会生效 - - 为方便使用 VSCode 等编辑器进行编辑,被注释掉的配置内容使用一个井号 #,而注释文本使用两个井号 ## - - 以方括号包裹的行,如 [OptionalCategory.Section],为一个栏目 - - 将默认被注释(即默认禁用)的栏目取消注释即可启用 - - 若要禁用一个默认启用的栏目,请在栏目下添加「Disabled = true」配置项,删除它/注释它不会有效 - - 形如「键 = 值」为一个配置项 - - 配置项应用到其上方最近的栏目,请不要在一个栏目被注释掉的情况下开启其配置项(会加到上一个栏目,无效) - - 当对应栏目启用时,配置项生效,无论是否将其取消注释 - - 注释掉的配置项保留其注释中的默认值,默认值可能会随版本更新而变化 - - 该文件的格式和文字注释是固定的,配置文件将在启动时被重写,无法解析的内容将被删除 - - 试试使用 MaiChartManager 图形化配置 AquaMai 吧! - https://github.com/clansty/MaiChartManager - """; - - private const string BANNER_EN = - """ - This is the TOML configuration file of AquaMai. - - - Lines starting with a hash # are comments. Commented content will not take effect. - - For easily editing with editors (e.g. VSCode), commented configuration content uses a single hash #, while comment text uses two hashes ##. - - Lines with square brackets like [OptionalCategory.Section] are sections. - - Uncomment a section that is commented out by default (i.e. disabled by default) to enable it. - - To disable a section that is enabled by default, add a "Disable = true" entry under the section. Removing/commenting it will not work. - - Lines like "Key = Value" is a configuration entry. - - Configuration entries apply to the nearest section above them. Do not enable a configuration entry when its section is commented out (will be added to the previous section, which is invalid). - - Configuration entries take effect when the corresponding section is enabled, regardless of whether they are uncommented. - - Commented configuration entries retain their default values (shown in the comment), which may change with version updates. - - The format and text comments of this file are fixed. The configuration file will be rewritten at startup, and unrecognizable content will be deleted. - """; - - private readonly IConfigSerializer.Options Options = Options; - - public string Serialize(IConfig config) - { - StringBuilder sb = new(); - if (Options.IncludeBanner) - { - var banner = Options.Lang == "zh" ? BANNER_ZH : BANNER_EN; - if (banner != null) - { - AppendComment(sb, banner.TrimEnd()); - sb.AppendLine(); - } - } - - // Version - AppendEntry(sb, null, "Version", ConfigMigrationManager.Instance.LatestVersion); - - foreach (var section in ((Config)config).reflectionManager.SectionValues) - { - var sectionState = config.GetSectionState(section); - - // If the state is default, print the example only. If the example is hidden, skip it. - if (sectionState.IsDefault && section.Attribute.ExampleHidden) - { - continue; - } - sb.AppendLine(); - - AppendComment(sb, section.Attribute.Comment); - - if (// If the section is hidden and hidden by default, print it as commented. - sectionState.IsDefault && !sectionState.Enabled && - // If the section is marked as always enabled, print it normally. - !section.Attribute.AlwaysEnabled) - { - sb.AppendLine($"#[{section.Path}]"); - } - else // If the section is overridden, or is enabled by any means, print it normally. - { - sb.AppendLine($"[{section.Path}]"); - } - - if (!section.Attribute.AlwaysEnabled) - { - // If the section is disabled explicitly, print the "Disabled" meta entry. - if (!sectionState.IsDefault && !sectionState.Enabled) - { - AppendEntry(sb, null, "Disabled", value: true); - } - // If the section is enabled by default, print the "Disabled" meta entry as commented. - else if (sectionState.IsDefault && section.Attribute.DefaultOn) - { - AppendEntry(sb, null, "Disabled", value: false, commented: true); - } - // Otherwise, don't print the "Disabled" meta entry. - } - - // Print entries. - foreach (var entry in section.entries) - { - var entryState = config.GetEntryState(entry); - AppendComment(sb, entry.Attribute.Comment); - if (((ConfigEntryAttribute)entry.Attribute).SpecialConfigEntry == SpecialConfigEntry.Locale && Options.OverrideLocaleValue) - { - AppendEntry(sb, section.Path, entry.Name, Options.Lang); - } - else - { - AppendEntry(sb, section.Path, entry.Name, entryState.Value, entryState.IsDefault); - } - } - } - - return sb.ToString(); - } - - private static string SerializeTomlValue(string diagnosticPath, object value) - { - var type = value.GetType(); - if (value is bool b) - { - return b ? "true" : "false"; - } - else if (value is string str) - { - return new TomlString(str).SerializedValue; - } - else if (type.IsEnum) - { - return new TomlString(value.ToString()).SerializedValue; - } - else if (Utility.IsIntegerType(type)) - { - return value.ToString(); - } - else if (Utility.IsFloatType(type)) - { - return new TomlDouble(Convert.ToDouble(value)).SerializedValue; - } - else - { - var currentMethod = MethodBase.GetCurrentMethod(); - throw new NotImplementedException($"Unsupported config entry type: {type.FullName} ({diagnosticPath}). Please implement in {currentMethod.DeclaringType.FullName}.{currentMethod.Name}"); - } - } - - private void AppendComment(StringBuilder sb, IConfigComment comment) - { - if (comment != null) - { - AppendComment(sb, comment.GetLocalized(Options.Lang)); - } - } - - private static void AppendComment(StringBuilder sb, string comment) - { - comment = comment.Trim(); - if (!string.IsNullOrEmpty(comment)) - { - foreach (var line in comment.Split('\n')) - { - sb.AppendLine($"## {line}"); - } - } - } - - private static void AppendEntry(StringBuilder sb, string diagnosticsSection, string key, object value, bool commented = false) - { - if (commented) - { - sb.Append('#'); - } - var diagnosticsPath = string.IsNullOrEmpty(diagnosticsSection) - ? key - : $"{diagnosticsSection}.{key}"; - sb.AppendLine($"{key} = {SerializeTomlValue(diagnosticsPath, value)}"); - } -} diff --git a/AquaMai/AquaMai.Config/ConfigView.cs b/AquaMai/AquaMai.Config/ConfigView.cs deleted file mode 100644 index 5344aaee..00000000 --- a/AquaMai/AquaMai.Config/ConfigView.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Linq; -using AquaMai.Config.Interfaces; -using Tomlet; -using Tomlet.Models; - -namespace AquaMai.Config; - -public class ConfigView : IConfigView -{ - public readonly TomlTable root; - - public ConfigView() - { - root = new TomlTable(); - } - - public ConfigView(TomlTable root) - { - this.root = root; - } - - public ConfigView(string tomlString) - { - var tomlValue = new TomlParser().Parse(tomlString); - if (tomlValue is not TomlTable tomlTable) - { - throw new ArgumentException($"Invalid TOML, expected a table, got: {tomlValue.GetType()}"); - } - root = tomlTable; - } - - public TomlTable EnsureDictionary(string path) - { - var pathComponents = path.Split('.'); - var current = root; - foreach (var component in pathComponents) - { - if (!current.TryGetValue(component, out var next)) - { - next = new TomlTable(); - current.Put(component, next); - } - current = (TomlTable)next; - } - return current; - } - - public void SetValue(string path, object value) - { - var pathComponents = path.Split('.'); - var current = root; - foreach (var component in pathComponents.Take(pathComponents.Length - 1)) - { - if (!current.TryGetValue(component, out var next)) - { - next = new TomlTable(); - current.Put(component, next); - } - current = (TomlTable)next; - } - - if (value == null) - { - current.Keys.Remove(pathComponents.Last()); - return; - } - current.Put(pathComponents.Last(), value); - } - - public T GetValueOrDefault(string path, T defaultValue = default) - { - return TryGetValue(path, out T resultValue) ? resultValue : defaultValue; - } - - public bool TryGetValue(string path, out T resultValue) - { - var pathComponents = path.Split('.'); - var current = root; - foreach (var component in pathComponents.Take(pathComponents.Length - 1)) - { - if (!Utility.TomlTryGetValueCaseInsensitive(current, component, out var next) || next is not TomlTable nextTable) - { - resultValue = default; - return false; - } - current = nextTable; - } - if (!Utility.TomlTryGetValueCaseInsensitive(current, pathComponents.Last(), out var value)) - { - resultValue = default; - return false; - } - if (typeof(T) == typeof(object)) - { - resultValue = (T)(object)value; - return true; - } - try - { - resultValue = Utility.ParseTomlValue(value); - return true; - } - catch (Exception e) - { - Utility.Log($"Failed to parse value at {path}: {e.Message}"); - resultValue = default; - return false; - } - } - - public bool Remove(string path) - { - var pathComponents = path.Split('.'); - var current = root; - foreach (var component in pathComponents.Take(pathComponents.Length - 1)) - { - if (!Utility.TomlTryGetValueCaseInsensitive(current, component, out var next) || next is not TomlTable nextTable) - { - return false; - } - current = (TomlTable)next; - } - var keyToRemove = pathComponents.Last(); - var keysCaseSensitive = current.Keys.Where(k => string.Equals(k, keyToRemove, StringComparison.OrdinalIgnoreCase)); - foreach (var key in keysCaseSensitive) - { - current.Entries.Remove(key); - } - return keysCaseSensitive.Any(); - } - - public string ToToml() - { - return root.SerializedValue; - } - - public IConfigView Clone() - { - return new ConfigView(ToToml()); - } -} diff --git a/AquaMai/AquaMai.Config/FodyWeavers.xml b/AquaMai/AquaMai.Config/FodyWeavers.xml deleted file mode 100644 index 645a09e7..00000000 --- a/AquaMai/AquaMai.Config/FodyWeavers.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - tomlet - $AquaMai.Config$_ - - diff --git a/AquaMai/AquaMai.Config/FodyWeavers.xsd b/AquaMai/AquaMai.Config/FodyWeavers.xsd deleted file mode 100644 index 968f9b5e..00000000 --- a/AquaMai/AquaMai.Config/FodyWeavers.xsd +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - A regular expression matching the assembly names to include in merging. - - - - - A regular expression matching the assembly names to exclude from merging. - - - - - A regular expression matching the resource names to include in merging. - - - - - A regular expression matching the resource names to exclude from merging. - - - - - A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true' - - - - - A string that is used as prefix for the namespace of the imported types. - - - - - A switch to control whether to import the full assemblies or only the referenced types. Default is 'false' - - - - - A switch to enable compacting of the target assembly by skipping properties, events and unused methods. Default is 'false' - - - - - - A regular expression matching the assembly names to include in merging. - - - - - A regular expression matching the assembly names to exclude from merging. - - - - - A regular expression matching the resource names to include in merging. - - - - - A regular expression matching the resource names to exclude from merging. - - - - - A switch to control whether the imported types are hidden (made private) or keep their visibility unchanged. Default is 'true' - - - - - A string that is used as prefix for the namespace of the imported types. - - - - - A switch to control whether to import the full assemblies or only the referenced types. Default is 'false' - - - - - A switch to enable compacting of the target assembly by skipping properties, events and unused methods. Default is 'false' - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/AquaMai/AquaMai.Config/Migration/ConfigMigrationManager.cs b/AquaMai/AquaMai.Config/Migration/ConfigMigrationManager.cs deleted file mode 100644 index fe4ce8f0..00000000 --- a/AquaMai/AquaMai.Config/Migration/ConfigMigrationManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AquaMai.Config.Interfaces; - -namespace AquaMai.Config.Migration; - -public class ConfigMigrationManager : IConfigMigrationManager -{ - public static readonly ConfigMigrationManager Instance = new(); - - private readonly Dictionary migrationMap = - new List - { - new ConfigMigration_V1_0_V2_0(), - new ConfigMigration_V2_0_V2_1() - }.ToDictionary(m => m.FromVersion); - - public string LatestVersion { get; } - - private ConfigMigrationManager() - { - LatestVersion = migrationMap.Values - .Select(m => m.ToVersion) - .OrderByDescending(version => - { - var versionParts = version.Split('.').Select(int.Parse).ToArray(); - return versionParts[0] * 100000 + versionParts[1]; - }) - .First(); - } - - public IConfigView Migrate(IConfigView config) - { - var currentVersion = GetVersion(config); - while (migrationMap.ContainsKey(currentVersion)) - { - var migration = migrationMap[currentVersion]; - Utility.Log($"Migrating config from v{migration.FromVersion} to v{migration.ToVersion}"); - config = migration.Migrate(config); - currentVersion = migration.ToVersion; - } - - if (currentVersion != LatestVersion) - { - throw new ArgumentException($"Could not migrate the config from v{currentVersion} to v{LatestVersion}"); - } - - return config; - } - - public string GetVersion(IConfigView config) - { - if (config.TryGetValue("Version", out var version)) - { - return version; - } - - // Assume v1.0 if not found - return "1.0"; - } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Config/Migration/ConfigMigration_V1_0_V2_0.cs b/AquaMai/AquaMai.Config/Migration/ConfigMigration_V1_0_V2_0.cs deleted file mode 100644 index 23c26637..00000000 --- a/AquaMai/AquaMai.Config/Migration/ConfigMigration_V1_0_V2_0.cs +++ /dev/null @@ -1,350 +0,0 @@ -using System; -using System.Collections.Generic; -using AquaMai.Config.Interfaces; -using AquaMai.Config.Types; - -namespace AquaMai.Config.Migration; - -public class ConfigMigration_V1_0_V2_0 : IConfigMigration -{ - public string FromVersion => "1.0"; - public string ToVersion => "2.0"; - - public IConfigView Migrate(IConfigView src) - { - var dst = new ConfigView(); - - dst.SetValue("Version", ToVersion); - - // UX (legacy) - MapBooleanTrueToSectionEnable(src, dst, "UX.TestProof", "GameSystem.TestProof"); - if (src.GetValueOrDefault("UX.QuickSkip")) - { - // NOTE: UX.QuickSkip was a 4-in-1 large patch in earlier V1, then split since ModKeyMap was introduced. - dst.SetValue("UX.OneKeyEntryEnd.Key", "Service"); - dst.SetValue("UX.OneKeyEntryEnd.LongPress", true); - dst.SetValue("UX.OneKeyRetrySkip.RetryKey", "Service"); - dst.SetValue("UX.OneKeyRetrySkip.RetryLongPress", false); - dst.SetValue("UX.OneKeyRetrySkip.SkipKey", "Select1P"); - dst.SetValue("UX.OneKeyRetrySkip.SkipLongPress", false); - dst.EnsureDictionary("GameSystem.QuickRetry"); - } - if (src.GetValueOrDefault("UX.HideSelfMadeCharts")) - { - dst.SetValue("UX.HideSelfMadeCharts.Key", "Service"); - dst.SetValue("UX.HideSelfMadeCharts.LongPress", false); - } - MapBooleanTrueToSectionEnable(src, dst, "UX.LoadJacketPng", "GameSystem.Assets.LoadLocalImages"); - MapBooleanTrueToSectionEnable(src, dst, "UX.SkipWarningScreen", "Tweaks.TimeSaving.SkipStartupWarning"); - MapBooleanTrueToSectionEnable(src, dst, "UX.SkipToMusicSelection", "Tweaks.TimeSaving.EntryToMusicSelection"); - MapBooleanTrueToSectionEnable(src, dst, "UX.SkipEventInfo", "Tweaks.TimeSaving.SkipEventInfo"); - MapBooleanTrueToSectionEnable(src, dst, "UX.SelectionDetail", "UX.SelectionDetail"); - if (src.GetValueOrDefault("UX.CustomNoteSkin") || - src.GetValueOrDefault("UX.CustomSkins")) - { - dst.SetValue("Fancy.CustomSkins.SkinsDir", "LocalAssets/Skins"); - } - MapBooleanTrueToSectionEnable(src, dst, "UX.JudgeDisplay4B", "Fancy.GamePlay.JudgeDisplay4B"); - MapBooleanTrueToSectionEnable(src, dst, "UX.CustomTrackStartDiff", "Fancy.CustomTrackStartDiff"); - MapBooleanTrueToSectionEnable(src, dst, "UX.TrackStartProcessTweak", "Fancy.GamePlay.TrackStartProcessTweak"); - MapBooleanTrueToSectionEnable(src, dst, "UX.DisableTrackStartTabs", "Fancy.GamePlay.DisableTrackStartTabs"); - MapBooleanTrueToSectionEnable(src, dst, "UX.RealisticRandomJudge", "Fancy.GamePlay.RealisticRandomJudge"); - - // Utils (legacy) - if (src.GetValueOrDefault("Utils.Windowed") || - src.GetValueOrDefault("Utils.Width") != 0 || - src.GetValueOrDefault("Utils.Height") != 0) - { - // NOTE: the default "false, 0, 0" was effective earlier in V1, but won't be migrated as enabled in V2. - MapValueOrDefaultToEntryValue(src, dst, "Utils.Windowed", "GameSystem.Window.Windowed", false); - MapValueOrDefaultToEntryValue(src, dst, "Utils.Width", "GameSystem.Window.Width", 0); - MapValueOrDefaultToEntryValue(src, dst, "Utils.Height", "GameSystem.Window.Height", 0); - } - if (src.GetValueOrDefault("Utils.PracticeMode") || src.GetValueOrDefault("Utils.PractiseMode")) // Typo of typo is the correct word - { - dst.SetValue("UX.PracticeMode.Key", "Test"); - dst.SetValue("UX.PracticeMode.LongPress", false); - } - - // Fix (legacy) - MapBooleanTrueToSectionEnable(src, dst, "Fix.SlideJudgeTweak", "Fancy.GamePlay.BreakSlideJudgeBlink"); - MapBooleanTrueToSectionEnable(src, dst, "Fix.BreakSlideJudgeBlink", "Fancy.GamePlay.BreakSlideJudgeBlink"); - MapBooleanTrueToSectionEnable(src, dst, "Fix.SlideJudgeTweak", "Fancy.GamePlay.FanJudgeFlip"); - MapBooleanTrueToSectionEnable(src, dst, "Fix.FanJudgeFlip", "Fancy.GamePlay.FanJudgeFlip"); - // NOTE: This (FixCircleSlideJudge) was enabled by default in V1, but non-default in V2 since it has visual changes - MapBooleanTrueToSectionEnable(src, dst, "Fix.SlideJudgeTweak", "Fancy.GamePlay.AlignCircleSlideJudgeDisplay"); - MapBooleanTrueToSectionEnable(src, dst, "Fix.FixCircleSlideJudge", "Fancy.GamePlay.AlignCircleSlideJudgeDisplay"); - - // Performance (legacy) - MapBooleanTrueToSectionEnable(src, dst, "Performance.ImproveLoadSpeed", "Tweaks.TimeSaving.SkipStartupDelays"); - - // TimeSaving (legacy) - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.ShowNetErrorDetail", "Utils.ShowNetErrorDetail"); - - // UX - MapValueToEntryValueIfNonNullOrDefault(src, dst, "UX.Locale", "General.Locale", ""); - MapBooleanTrueToSectionEnable(src, dst, "UX.SinglePlayer", "GameSystem.SinglePlayer"); - MapBooleanTrueToSectionEnable(src, dst, "UX.HideMask", "Fancy.HideMask"); - MapBooleanTrueToSectionEnable(src, dst, "UX.LoadAssetsPng", "GameSystem.Assets.LoadLocalImages"); - MapBooleanTrueToSectionEnable(src, dst, "UX.LoadAssetBundleWithoutManifest", "GameSystem.Assets.LoadAssetBundleWithoutManifest"); - MapBooleanTrueToSectionEnable(src, dst, "UX.RandomBgm", "Fancy.RandomBgm"); - MapBooleanTrueToSectionEnable(src, dst, "UX.DemoMaster", "Fancy.DemoMaster"); - MapBooleanTrueToSectionEnable(src, dst, "UX.ExtendTimer", "GameSystem.DisableTimeout"); - MapBooleanTrueToSectionEnable(src, dst, "UX.ImmediateSave", "UX.ImmediateSave"); - MapBooleanTrueToSectionEnable(src, dst, "UX.LoadLocalBga", "GameSystem.Assets.UseJacketAsDummyMovie"); - if (src.GetValueOrDefault("UX.CustomFont")) - { - dst.SetValue("GameSystem.Assets.Fonts.Paths", "LocalAssets/font.ttf"); - dst.SetValue("GameSystem.Assets.Fonts.AddAsFallback", false); - } - MapBooleanTrueToSectionEnable(src, dst, "UX.TouchToButtonInput", "GameSystem.TouchToButtonInput"); - MapBooleanTrueToSectionEnable(src, dst, "UX.HideHanabi", "Fancy.GamePlay.HideHanabi"); - MapBooleanTrueToSectionEnable(src, dst, "UX.SlideFadeInTweak", "Fancy.GamePlay.SlideFadeInTweak"); - MapBooleanTrueToSectionEnable(src, dst, "UX.JudgeAccuracyInfo", "UX.JudgeAccuracyInfo"); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "UX.CustomVersionString", "Fancy.CustomVersionString.VersionString", ""); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "UX.CustomPlaceName", "Fancy.CustomPlaceName.PlaceName", ""); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "UX.ExecOnIdle", "Fancy.Triggers.ExecOnIdle", ""); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "UX.ExecOnEntry", "Fancy.Triggers.ExecOnEntry", ""); - - // Cheat - var unlockTickets = src.GetValueOrDefault("Cheat.TicketUnlock"); - var unlockMaps = src.GetValueOrDefault("Cheat.MapUnlock"); - var unlockUtage = src.GetValueOrDefault("Cheat.UnlockUtage"); - if (unlockTickets || - unlockMaps || - unlockUtage) - { - dst.SetValue("GameSystem.Unlock.Tickets", unlockTickets); - dst.SetValue("GameSystem.Unlock.Maps", unlockMaps); - dst.SetValue("GameSystem.Unlock.Utage", unlockUtage); - } - - // Fix - MapBooleanTrueToSectionEnable(src, dst, "Fix.SkipVersionCheck", "Tweaks.SkipUserVersionCheck"); - if (!src.GetValueOrDefault("Fix.RemoveEncryption")) - { - dst.SetValue("GameSystem.RemoveEncryption.Disabled", true); // Enabled by default in V2 - } - if (!src.GetValueOrDefault("Fix.ForceAsServer", true)) - { - dst.SetValue("GameSettings.ForceAsServer.Disabled", true); // Enabled by default in V2 - } - if (src.GetValueOrDefault("Fix.ForceFreePlay")) - { - dst.SetValue("GameSettings.CreditConfig.IsFreePlay", true); - } - if (src.GetValueOrDefault("Fix.ForcePaidPlay")) - { - dst.SetValue("GameSettings.CreditConfig.IsFreePlay", false); - dst.SetValue("GameSettings.CreditConfig.LockCredits", 24u); - } - MapValueToEntryValueIfNonNullOrDefault(src, dst, "Fix.ExtendNotesPool", "Fancy.GamePlay.ExtendNotesPool.Count", 0); - MapBooleanTrueToSectionEnable(src, dst, "Fix.FrameRateLock", "Tweaks.LockFrameRate"); - if (src.GetValueOrDefault("Font.FontFix") && - !src.GetValueOrDefault("UX.CustomFont")) - { - dst.SetValue("GameSystem.Assets.Fonts.Paths", "%SYSTEMROOT%/Fonts/msyhbd.ttc"); - dst.SetValue("GameSystem.Assets.Fonts.AddAsFallback", true); - } - MapBooleanTrueToSectionEnable(src, dst, "Fix.RealisticRandomJudge", "Fancy.GamePlay.RealisticRandomJudge"); - if (src.GetValueOrDefault("UX.SinglePlayer")) - { - if (src.TryGetValue("Fix.HanabiFix", out bool hanabiFix)) - { - // If it's enabled or disabled explicitly, use the value, otherwise left empty use the default V2 value (enabled). - dst.SetValue("GameSystem.SinglePlayer.FixHanabi", hanabiFix); - } - } - MapBooleanTrueToSectionEnable(src, dst, "Fix.IgnoreAimeServerError", "Tweaks.IgnoreAimeServerError"); - MapBooleanTrueToSectionEnable(src, dst, "Fix.TouchResetAfterTrack", "Tweaks.ResetTouchAfterTrack"); - - // Utils - MapBooleanTrueToSectionEnable(src, dst, "Utils.LogUserId", "Utils.LogUserId"); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "Utils.JudgeAdjustA", "GameSettings.JudgeAdjust.A", 0); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "Utils.JudgeAdjustB", "GameSettings.JudgeAdjust.B", 0); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "Utils.TouchDelay", "GameSettings.JudgeAdjust.TouchDelay", 0u); - MapBooleanTrueToSectionEnable(src, dst, "Utils.SelectionDetail", "UX.SelectionDetail"); - MapBooleanTrueToSectionEnable(src, dst, "Utils.ShowNetErrorDetail", "Utils.ShowNetErrorDetail"); - MapBooleanTrueToSectionEnable(src, dst, "Utils.ShowErrorLog", "Utils.ShowErrorLog"); - MapBooleanTrueToSectionEnable(src, dst, "Utils.FrameRateDisplay", "Utils.DisplayFrameRate"); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "Utils.TouchPanelBaudRate", "GameSystem.TouchPanelBaudRate.BaudRate", 0); - - // TimeSaving - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.SkipWarningScreen", "Tweaks.TimeSaving.SkipStartupWarning"); - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.ImproveLoadSpeed", "Tweaks.TimeSaving.SkipStartupDelays"); - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.SkipToMusicSelection", "Tweaks.TimeSaving.EntryToMusicSelection"); - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.SkipEventInfo", "Tweaks.TimeSaving.SkipEventInfo"); - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.IWontTapOrSlideVigorously", "Tweaks.TimeSaving.IWontTapOrSlideVigorously"); - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.SkipGameOverScreen", "Tweaks.TimeSaving.SkipGoodbyeScreen"); - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.SkipTrackStart", "Tweaks.TimeSaving.SkipTrackStart"); - MapBooleanTrueToSectionEnable(src, dst, "TimeSaving.ShowQuickEndPlay", "UX.QuickEndPlay"); - - // Visual - if (src.GetValueOrDefault("Visual.CustomSkins")) - { - dst.SetValue("Fancy.CustomSkins.SkinsDir", "LocalAssets/Skins"); - } - MapBooleanTrueToSectionEnable(src, dst, "Visual.JudgeDisplay4B", "Fancy.GamePlay.JudgeDisplay4B"); - MapBooleanTrueToSectionEnable(src, dst, "Visual.CustomTrackStartDiff", "Fancy.CustomTrackStartDiff"); - MapBooleanTrueToSectionEnable(src, dst, "Visual.TrackStartProcessTweak", "Fancy.GamePlay.TrackStartProcessTweak"); - MapBooleanTrueToSectionEnable(src, dst, "Visual.DisableTrackStartTabs", "Fancy.GamePlay.DisableTrackStartTabs"); - MapBooleanTrueToSectionEnable(src, dst, "Visual.FanJudgeFlip", "Fancy.GamePlay.FanJudgeFlip"); - MapBooleanTrueToSectionEnable(src, dst, "Visual.BreakSlideJudgeBlink", "Fancy.GamePlay.BreakSlideJudgeBlink"); - MapBooleanTrueToSectionEnable(src, dst, "Visual.SlideArrowAnimation", "Fancy.GamePlay.SlideArrowAnimation"); - MapBooleanTrueToSectionEnable(src, dst, "Visual.SlideLayerReverse", "Fancy.GamePlay.SlideLayerReverse"); - - // ModKeyMap - var keyQuickSkip = src.GetValueOrDefault("ModKeyMap.QuickSkip", "None"); - var keyInGameRetry = src.GetValueOrDefault("ModKeyMap.InGameRetry", "None"); - var keyInGameSkip = src.GetValueOrDefault("ModKeyMap.InGameSkip", "None"); - var keyPractiseMode = src.GetValueOrDefault("ModKeyMap.PractiseMode", "None"); - var keyHideSelfMadeCharts = src.GetValueOrDefault("ModKeyMap.HideSelfMadeCharts", "None"); - if (keyQuickSkip != "None") - { - dst.SetValue("UX.OneKeyEntryEnd.Key", keyQuickSkip); - MapValueToEntryValueIfNonNull(src, dst, "ModKeyMap.QuickSkipLongPress", "UX.OneKeyEntryEnd.LongPress"); - } - if (keyInGameRetry != "None" || keyInGameSkip != "None") - { - dst.SetValue("UX.OneKeyRetrySkip.RetryKey", keyInGameRetry); - if (keyInGameRetry != "None") - { - MapValueToEntryValueIfNonNull(src, dst, "ModKeyMap.InGameRetryLongPress", "UX.OneKeyRetrySkip.RetryLongPress"); - } - dst.SetValue("UX.OneKeyRetrySkip.SkipKey", keyInGameSkip); - if (keyInGameSkip != "None") - { - MapValueToEntryValueIfNonNull(src, dst, "ModKeyMap.InGameSkipLongPress", "UX.OneKeyRetrySkip.SkipLongPress"); - } - } - if (keyPractiseMode != "None") - { - dst.SetValue("UX.PracticeMode.Key", keyPractiseMode); - MapValueToEntryValueIfNonNull(src, dst, "ModKeyMap.PractiseModeLongPress", "UX.PracticeMode.LongPress"); - } - if (keyHideSelfMadeCharts != "None") - { - dst.SetValue("UX.HideSelfMadeCharts.Key", keyHideSelfMadeCharts); - MapValueToEntryValueIfNonNull(src, dst, "ModKeyMap.HideSelfMadeChartsLongPress", "UX.HideSelfMadeCharts.LongPress"); - } - MapBooleanTrueToSectionEnable(src, dst, "ModKeyMap.EnableNativeQuickRetry", "GameSystem.QuickRetry"); - if (src.TryGetValue("ModKeyMap.TestMode", out var testMode) && - testMode != "" && - testMode != "Test") - { - dst.SetValue("DeprecationWarning.v1_0_ModKeyMap_TestMode", true); - } - MapBooleanTrueToSectionEnable(src, dst, "ModKeyMap.TestModeLongPress", "GameSystem.TestProof"); - - // WindowState - if (src.GetValueOrDefault("WindowState.Enable")) - { - MapValueOrDefaultToEntryValue(src, dst, "WindowState.Windowed", "GameSystem.Window.Windowed", false); - MapValueOrDefaultToEntryValue(src, dst, "WindowState.Width", "GameSystem.Window.Width", 0); - MapValueOrDefaultToEntryValue(src, dst, "WindowState.Height", "GameSystem.Window.Height", 0); - } - - // CustomCameraId - if (src.GetValueOrDefault("CustomCameraId.Enable")) - { - dst.EnsureDictionary("GameSystem.CustomCameraId"); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "CustomCameraId.PrintCameraList", "GameSystem.CustomCameraId.PrintCameraList", false); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "CustomCameraId.LeftQrCamera", "GameSystem.CustomCameraId.LeftQrCamera", 0); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "CustomCameraId.RightQrCamera", "GameSystem.CustomCameraId.RightQrCamera", 0); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "CustomCameraId.PhotoCamera", "GameSystem.CustomCameraId.PhotoCamera", 0); - MapValueToEntryValueIfNonNullOrDefault(src, dst, "CustomCameraId.ChimeCamera", "GameSystem.CustomCameraId.ChimeCamera", 0); - } - - // TouchSensitivity - if (src.GetValueOrDefault("TouchSensitivity.Enable")) - { - dst.EnsureDictionary("GameSettings.TouchSensitivity"); - var areas = new[] - { - "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", - "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", - "C1", "C2", - "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", - "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", - }; - foreach (var area in areas) - { - MapValueToEntryValueIfNonNull(src, dst, $"TouchSensitivity.{area}", $"GameSettings.TouchSensitivity.{area}"); - } - } - - // CustomKeyMap - if (src.GetValueOrDefault("CustomKeyMap.Enable")) - { - dst.EnsureDictionary("GameSystem.KeyMap"); - var keys = new[] - { - "Test", "Service", - "Button1_1P", "Button3_1P", "Button4_1P", "Button2_1P", "Button5_1P", "Button6_1P", "Button7_1P", "Button8_1P", - "Select_1P", - "Button1_2P", "Button2_2P", "Button3_2P", "Button4_2P", "Button5_2P", "Button6_2P", "Button7_2P", "Button8_2P", - "Select_2P" - }; - foreach (var key in keys) - { - if (src.TryGetValue($"CustomKeyMap.{key}", out var value) && - Enum.TryParse(value, out var keyCode)) - { - dst.SetValue($"GameSystem.KeyMap.{key}", keyCode.ToString()); - } - } - } - - // MaimaiDX2077 (WTF is the name?) - MapBooleanTrueToSectionEnable(src, dst, "MaimaiDX2077.CustomNoteTypePatch", "Fancy.GamePlay.CustomNoteTypes"); - - // Default enabled in V2 - dst.EnsureDictionary("GameSystem.RemoveEncryption"); - dst.EnsureDictionary("GameSettings.ForceAsServer"); - - return dst; - } - - // An value in the old config maps to an entry value in the new config. - // Any existing value, including zero, is valid. - private void MapValueToEntryValueIfNonNull(IConfigView src, ConfigView dst, string srcKey, string dstKey) - { - if (src.TryGetValue(srcKey, out var value)) - { - dst.SetValue(dstKey, value); - } - } - - // An value in the old config maps to an entry value in the new config. - // Null or default value is ignored. - private void MapValueToEntryValueIfNonNullOrDefault(IConfigView src, ConfigView dst, string srcKey, string dstKey, T defaultValue) - { - if (src.TryGetValue(srcKey, out var value) && !EqualityComparer.Default.Equals(value, defaultValue)) - { - dst.SetValue(dstKey, value); - } - } - - // An value in the old config maps to an entry value in the new config. - // Null value is replaced with a default value. - private void MapValueOrDefaultToEntryValue(IConfigView src, ConfigView dst, string srcKey, string dstKey, T defaultValue) - { - if (src.TryGetValue(srcKey, out var value)) - { - dst.SetValue(dstKey, value); - } - else - { - dst.SetValue(dstKey, defaultValue); - } - } - - // An boolean value in the old config maps to a default-off section's enable in the new config. - private void MapBooleanTrueToSectionEnable(IConfigView src, ConfigView dst, string srcKey, string dstKey) - { - if (src.GetValueOrDefault(srcKey)) - { - dst.EnsureDictionary(dstKey); - } - } -} diff --git a/AquaMai/AquaMai.Config/Migration/ConfigMigration_V2_0_V2_1.cs b/AquaMai/AquaMai.Config/Migration/ConfigMigration_V2_0_V2_1.cs deleted file mode 100644 index e41058ad..00000000 --- a/AquaMai/AquaMai.Config/Migration/ConfigMigration_V2_0_V2_1.cs +++ /dev/null @@ -1,44 +0,0 @@ -using AquaMai.Config.Interfaces; -using Tomlet.Models; - -namespace AquaMai.Config.Migration; - -public class ConfigMigration_V2_0_V2_1 : IConfigMigration -{ - public string FromVersion => "2.0"; - public string ToVersion => "2.1"; - - public IConfigView Migrate(IConfigView src) - { - var dst = src.Clone(); - dst.SetValue("Version", ToVersion); - - if (IsSectionEnabled(src, "Tweaks.ResetTouchAfterTrack")) - { - dst.Remove("Tweaks.ResetTouchAfterTrack"); - dst.SetValue("Tweaks.ResetTouch.AfterTrack", true); - } - - return dst; - } - - public bool IsSectionEnabled(IConfigView src, string path) - { - if (src.TryGetValue(path, out object section)) - { - if (section is bool enabled) - { - return enabled; - } - else if (section is TomlTable table) - { - if (Utility.TomlTryGetValueCaseInsensitive(table, "Disabled", out var disabled)) - { - return !Utility.IsTrutyOrDefault(disabled); - } - return true; - } - } - return false; - } -} diff --git a/AquaMai/AquaMai.Config/Migration/IConfigMigration.cs b/AquaMai/AquaMai.Config/Migration/IConfigMigration.cs deleted file mode 100644 index 2c7c9d11..00000000 --- a/AquaMai/AquaMai.Config/Migration/IConfigMigration.cs +++ /dev/null @@ -1,10 +0,0 @@ -using AquaMai.Config.Interfaces; - -namespace AquaMai.Config.Migration; - -public interface IConfigMigration -{ - public string FromVersion { get; } - public string ToVersion { get; } - public IConfigView Migrate(IConfigView config); -} diff --git a/AquaMai/AquaMai.Config/Polyfills.cs b/AquaMai/AquaMai.Config/Polyfills.cs deleted file mode 100644 index eb2da113..00000000 --- a/AquaMai/AquaMai.Config/Polyfills.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit {} -} diff --git a/AquaMai/AquaMai.Config/Reflection/MonoCecilAssemblyReflectionProvider.cs b/AquaMai/AquaMai.Config/Reflection/MonoCecilAssemblyReflectionProvider.cs deleted file mode 100644 index 69afece2..00000000 --- a/AquaMai/AquaMai.Config/Reflection/MonoCecilAssemblyReflectionProvider.cs +++ /dev/null @@ -1,254 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using AquaMai.Config.Attributes; -using AquaMai.Config.Interfaces; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace AquaMai.Config.Reflection; - -public class MonoCecilReflectionProvider : IReflectionProvider -{ - public record ReflectionField( - string Name, - Type FieldType, - object Value, - IDictionary Attributes) : IReflectionField - { - public object Value { get; set; } = Value; - - public T GetCustomAttribute() where T : Attribute => Attributes.TryGetValue(typeof(T), out var value) ? (T)value : null; - public object GetValue(object obj) => Value; - public void SetValue(object obj, object value) => Value = value; - } - - public record ReflectionType( - string FullName, - string Namespace, - IReflectionField[] Fields, - IDictionary Attributes) : IReflectionType - { - public T GetCustomAttribute() where T : Attribute => Attributes.TryGetValue(typeof(T), out var value) ? (T)value : null; - public IReflectionField[] GetFields(BindingFlags bindingAttr) => Fields; - } - - private static readonly Type[] attributeTypes = - [ - typeof(ConfigCollapseNamespaceAttribute), - typeof(ConfigSectionAttribute), - typeof(ConfigEntryAttribute), - ]; - - private readonly IReflectionType[] reflectionTypes = []; - private readonly Dictionary> enums = []; - - public IReflectionType[] GetTypes() => reflectionTypes; - public Dictionary GetEnum(string enumName) => enums[enumName]; - - public MonoCecilReflectionProvider(AssemblyDefinition assembly) - { - reflectionTypes = assembly.MainModule.Types.Select(cType => { - var typeAttributes = InstantiateAttributes(cType.CustomAttributes); - var fields = cType.Fields.Select(cField => { - try - { - var fieldAttributes = InstantiateAttributes(cField.CustomAttributes); - if (fieldAttributes.Count == 0) - { - return null; - } - var type = GetRuntimeType(cField.FieldType); - var defaultValue = GetFieldDefaultValue(cType, cField, type); - return new ReflectionField(cField.Name, type, defaultValue, fieldAttributes); - } - catch (Exception e) - { - Console.WriteLine(e); - } - return null; - }).Where(field => field != null).ToArray(); - return new ReflectionType(cType.FullName, cType.Namespace, fields, typeAttributes); - }).ToArray(); - enums = assembly.MainModule.Types - .Where(cType => cType.IsEnum) - .ToDictionary(cType => - cType.FullName, - cType => cType.Fields - .Where(cField => cField.IsPublic && cField.IsStatic && cField.Constant != null) - .ToDictionary(cField => cField.Name, cField => cField.Constant)); - } - - private Dictionary InstantiateAttributes(ICollection attribute) => - attribute - .Select(InstantiateAttribute) - .Where(a => a != null) - .ToDictionary(a => a.GetType(), a => a); - - private object InstantiateAttribute(CustomAttribute attribute) => - attributeTypes.FirstOrDefault(t => t.FullName == attribute.AttributeType.FullName) switch - { - Type type => Activator.CreateInstance(type, - attribute.Constructor.Parameters - .Select((parameter, i) => - { - var runtimeType = GetRuntimeType(parameter.ParameterType); - var value = attribute.ConstructorArguments[i].Value; - if (runtimeType.IsEnum) - { - return Enum.Parse(runtimeType, value.ToString()); - } - return value; - }) - .ToArray()), - _ => null - }; - - private Type GetRuntimeType(TypeReference typeReference) { - if (typeReference.IsGenericInstance) - { - var genericInstance = (GenericInstanceType)typeReference; - var genericType = GetRuntimeType(genericInstance.ElementType); - var genericArguments = genericInstance.GenericArguments.Select(GetRuntimeType).ToArray(); - return genericType.MakeGenericType(genericArguments); - } - - var type = Type.GetType(typeReference.FullName); - if (type == null) - { - throw new TypeLoadException($"Type {typeReference.FullName} not found."); - } - return type; - } - - private static object GetFieldDefaultValue(TypeDefinition cType, FieldDefinition cField, Type fieldType) - { - object defaultValue = null; - var cctor = cType.Methods.SingleOrDefault(m => m.Name == ".cctor"); - if (cctor != null) - { - var store = cctor.Body.Instructions.SingleOrDefault(i => i.OpCode == OpCodes.Stsfld && i.Operand == cField); - if (store != null) - { - var loadOperand = ParseConstantLoadOperand(store.Previous); - if (fieldType == typeof(bool)) - { - defaultValue = Convert.ToBoolean(loadOperand); - } - else - { - defaultValue = loadOperand; - } - } - } - - if (defaultValue == null && cField.HasDefault) - { - throw new InvalidOperationException($"Field {cType.FullName}.{cField.Name} has default value but no .cctor stsfld instruction."); - } - defaultValue ??= GetDefaultValue(fieldType); - - if (fieldType.IsEnum) - { - var enumType = fieldType.GetEnumUnderlyingType(); - // Assume casting is safe since we're getting the default value from the field - var castedValue = Convert.ChangeType(defaultValue, enumType); - if (Enum.IsDefined(fieldType, castedValue)) - { - return Enum.ToObject(fieldType, castedValue); - } - } - - return defaultValue; - } - - private static object ParseConstantLoadOperand(Instruction instruction) - { - if (instruction.OpCode == OpCodes.Ldc_I4_M1) - { - return -1; - } - if (instruction.OpCode == OpCodes.Ldc_I4_0) - { - return 0; - } - if (instruction.OpCode == OpCodes.Ldc_I4_1) - { - return 1; - } - if (instruction.OpCode == OpCodes.Ldc_I4_2) - { - return 2; - } - if (instruction.OpCode == OpCodes.Ldc_I4_3) - { - return 3; - } - if (instruction.OpCode == OpCodes.Ldc_I4_4) - { - return 4; - } - if (instruction.OpCode == OpCodes.Ldc_I4_5) - { - return 5; - } - if (instruction.OpCode == OpCodes.Ldc_I4_6) - { - return 6; - } - if (instruction.OpCode == OpCodes.Ldc_I4_7) - { - return 7; - } - if (instruction.OpCode == OpCodes.Ldc_I4_8) - { - return 8; - } - if (instruction.OpCode == OpCodes.Ldc_I4_S) - { - return Convert.ToInt32((sbyte)instruction.Operand); - } - if (instruction.OpCode == OpCodes.Ldc_I4) - { - return (int)instruction.Operand; - } - if (instruction.OpCode == OpCodes.Ldc_I8) - { - return (long)instruction.Operand; - } - if (instruction.OpCode == OpCodes.Ldc_R4) - { - return (float)instruction.Operand; - } - if (instruction.OpCode == OpCodes.Ldc_R8) - { - return (double)instruction.Operand; - } - if (instruction.OpCode == OpCodes.Ldstr) - { - return (string)instruction.Operand; - } - else - { - var currentMethod = MethodBase.GetCurrentMethod(); - throw new NotImplementedException($"Unsupported constant load: {instruction}. Please implement in {currentMethod.DeclaringType.FullName}.{currentMethod.Name}"); - } - } - - private static object GetDefaultValue(Type type) - { - if (type.IsValueType) - { - return Activator.CreateInstance(type); - } - else if (type == typeof(string)) - { - return string.Empty; - } - else - { - return null; - } - } -} diff --git a/AquaMai/AquaMai.Config/Reflection/ReflectionManager.cs b/AquaMai/AquaMai.Config/Reflection/ReflectionManager.cs deleted file mode 100644 index 7fb1ae0a..00000000 --- a/AquaMai/AquaMai.Config/Reflection/ReflectionManager.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System.Reflection; -using System.Linq; -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using AquaMai.Config.Interfaces; -using System; - -namespace AquaMai.Config.Reflection; - -public class ReflectionManager : IReflectionManager -{ - public record Entry : IReflectionManager.IEntry - { - public string Path { get; init; } - public string Name { get; init; } - public IReflectionField Field { get; init; } - public IConfigEntryAttribute Attribute { get; init; } - } - - public record Section : IReflectionManager.ISection - { - public string Path { get; init; } - public IReflectionType Type { get; init; } - public IConfigSectionAttribute Attribute { get; init; } - public List entries; - public List Entries => entries.Cast().ToList(); - } - - private readonly Dictionary sections = new(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary entries = new(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary sectionsByFullName = []; - - public ReflectionManager(IReflectionProvider reflectionProvider) - { - var prefix = "AquaMai.Mods."; - var types = reflectionProvider.GetTypes().Where(t => t.FullName.StartsWith(prefix)); - var collapsedNamespaces = new HashSet(); - foreach (var type in types) - { - var sectionAttribute = type.GetCustomAttribute(); - if (sectionAttribute == null) continue; - if (collapsedNamespaces.Contains(type.Namespace)) - { - throw new Exception($"Collapsed namespace {type.Namespace} contains multiple sections"); - } - var path = type.FullName.Substring(prefix.Length); - if (type.GetCustomAttribute() != null) - { - var separated = path.Split('.'); - if (separated[separated.Length - 2] != separated[separated.Length - 1]) - { - throw new Exception($"Type {type.FullName} is not collapsable"); - } - path = string.Join(".", separated.Take(separated.Length - 1)); - collapsedNamespaces.Add(type.Namespace); - } - - var sectionEntries = new List(); - foreach (var field in type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) - { - var entryAttribute = field.GetCustomAttribute(); - if (entryAttribute == null) continue; - var transformedName = Utility.ToPascalCase(field.Name); - var entryPath = $"{path}.{transformedName}"; - var entry = new Entry() - { - Path = entryPath, - Name = transformedName, - Field = field, - Attribute = entryAttribute - }; - sectionEntries.Add(entry); - entries.Add(entryPath, entry); - } - - var section = new Section() - { - Path = path, - Type = type, - Attribute = sectionAttribute, - entries = sectionEntries - }; - sections.Add(path, section); - sectionsByFullName.Add(type.FullName, section); - } - - var order = reflectionProvider.GetEnum("AquaMai.Mods.SectionNameOrder"); - sections = sections - .OrderBy(x => x.Key) - .OrderBy(x => - { - var parts = x.Key.Split('.'); - for (int i = parts.Length; i > 0; i--) - { - var key = string.Join("_", parts.Take(i)); - if (order.TryGetValue(key, out var value)) - { - return (int)value; - } - } - Utility.Log($"Section {x.Key} has no order defined, defaulting to int.MaxValue"); - return int.MaxValue; - }) - .ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase); - } - - public IEnumerable
SectionValues => sections.Values; - public IEnumerable Sections => sections.Values.Cast(); - - public IEnumerable EntryValues => entries.Values; - public IEnumerable Entries => entries.Values.Cast(); - - public bool ContainsSection(string path) - { - return sections.ContainsKey(path); - } - - public bool TryGetSection(string path, out IReflectionManager.ISection section) - { - if (sections.TryGetValue(path, out var sectionValue)) - { - section = sectionValue; - return true; - } - section = null; - return false; - } - - public bool TryGetSection(Type type, out IReflectionManager.ISection section) - { - bool result = sectionsByFullName.TryGetValue(type.FullName, out var sectionValue); - section = sectionValue; - return result; - } - - public IReflectionManager.ISection GetSection(string path) - { - if (!TryGetSection(path, out var section)) - { - throw new KeyNotFoundException($"Section {path} not found"); - } - return section; - } - - public IReflectionManager.ISection GetSection(Type type) - { - if (!TryGetSection(type.FullName, out var section)) - { - throw new KeyNotFoundException($"Section {type.FullName} not found"); - } - return section; - } - - public bool ContainsEntry(string path) - { - return entries.ContainsKey(path); - } - - public bool TryGetEntry(string path, out IReflectionManager.IEntry entry) - { - if (entries.TryGetValue(path, out var entryValue)) - { - entry = entryValue; - return true; - } - entry = null; - return false; - } - - public IReflectionManager.IEntry GetEntry(string path) - { - if (!TryGetEntry(path, out var entry)) - { - throw new KeyNotFoundException($"Entry {path} not found"); - } - return entry; - } -} diff --git a/AquaMai/AquaMai.Config/Reflection/SystemReflectionProvider.cs b/AquaMai/AquaMai.Config/Reflection/SystemReflectionProvider.cs deleted file mode 100644 index fe03a0de..00000000 --- a/AquaMai/AquaMai.Config/Reflection/SystemReflectionProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using AquaMai.Config.Interfaces; - -namespace AquaMai.Config.Reflection; - -public class SystemReflectionProvider(Assembly assembly) : IReflectionProvider -{ - public class ReflectionField(FieldInfo field) : IReflectionField - { - public FieldInfo UnderlyingField { get; } = field; - - public string Name => UnderlyingField.Name; - public Type FieldType => UnderlyingField.FieldType; - public T GetCustomAttribute() where T : Attribute => UnderlyingField.GetCustomAttribute(); - public object GetValue(object obj) => UnderlyingField.GetValue(obj); - public void SetValue(object obj, object value) => UnderlyingField.SetValue(obj, value); - } - - public class ReflectionType(Type type) : IReflectionType - { - public Type UnderlyingType { get; } = type; - - public string FullName => UnderlyingType.FullName; - public string Namespace => UnderlyingType.Namespace; - public T GetCustomAttribute() where T : Attribute => UnderlyingType.GetCustomAttribute(); - public IReflectionField[] GetFields(BindingFlags bindingAttr) => Array.ConvertAll(UnderlyingType.GetFields(bindingAttr), f => new ReflectionField(f)); - } - - public Assembly UnderlyingAssembly { get; } = assembly; - - public IReflectionType[] GetTypes() => Array.ConvertAll(UnderlyingAssembly.GetTypes(), t => new ReflectionType(t)); - - public Dictionary GetEnum(string enumName) - { - var enumType = UnderlyingAssembly.GetType(enumName); - if (enumType == null) return null; - var enumValues = Enum.GetValues(enumType); - var enumDict = new Dictionary(); - foreach (var enumValue in enumValues) - { - enumDict.Add(enumValue.ToString(), enumValue); - } - return enumDict; - } -} diff --git a/AquaMai/AquaMai.Config/Types/KeyCodeID.cs b/AquaMai/AquaMai.Config/Types/KeyCodeID.cs deleted file mode 100644 index f1c7615c..00000000 --- a/AquaMai/AquaMai.Config/Types/KeyCodeID.cs +++ /dev/null @@ -1,146 +0,0 @@ -namespace AquaMai.Config.Types; - -public enum KeyCodeID -{ - None, - Backspace, - Tab, - Clear, - Return, - Pause, - Escape, - Space, - Exclaim, - DoubleQuote, - Hash, - Dollar, - Ampersand, - Quote, - LeftParen, - RightParen, - Asterisk, - Plus, - Comma, - Minus, - Period, - Slash, - Alpha0, - Alpha1, - Alpha2, - Alpha3, - Alpha4, - Alpha5, - Alpha6, - Alpha7, - Alpha8, - Alpha9, - Colon, - Semicolon, - Less, - Equals, - Greater, - Question, - At, - LeftBracket, - Backslash, - RightBracket, - Caret, - Underscore, - BackQuote, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - Delete, - Keypad0, - Keypad1, - Keypad2, - Keypad3, - Keypad4, - Keypad5, - Keypad6, - Keypad7, - Keypad8, - Keypad9, - KeypadPeriod, - KeypadDivide, - KeypadMultiply, - KeypadMinus, - KeypadPlus, - KeypadEnter, - KeypadEquals, - UpArrow, - DownArrow, - RightArrow, - LeftArrow, - Insert, - Home, - End, - PageUp, - PageDown, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - Numlock, - CapsLock, - ScrollLock, - RightShift, - LeftShift, - RightControl, - LeftControl, - RightAlt, - LeftAlt, - RightCommand, - RightApple, - LeftCommand, - LeftApple, - LeftWindows, - RightWindows, - AltGr, - Help, - Print, - SysReq, - Break, - Menu, - Mouse0, - Mouse1, - Mouse2, - Mouse3, - Mouse4, - Mouse5, - Mouse6, -} diff --git a/AquaMai/AquaMai.Config/Types/KeyCodeOrName.cs b/AquaMai/AquaMai.Config/Types/KeyCodeOrName.cs deleted file mode 100644 index 400de783..00000000 --- a/AquaMai/AquaMai.Config/Types/KeyCodeOrName.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace AquaMai.Config.Types; - -public enum KeyCodeOrName -{ - None, - Alpha0, - Alpha1, - Alpha2, - Alpha3, - Alpha4, - Alpha5, - Alpha6, - Alpha7, - Alpha8, - Alpha9, - Keypad0, - Keypad1, - Keypad2, - Keypad3, - Keypad4, - Keypad5, - Keypad6, - Keypad7, - Keypad8, - Keypad9, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - Insert, - Delete, - Home, - End, - PageUp, - PageDown, - UpArrow, - DownArrow, - LeftArrow, - RightArrow, - - Select1P, - Select2P, - Service, - Test, -} diff --git a/AquaMai/AquaMai.Config/Utility.cs b/AquaMai/AquaMai.Config/Utility.cs deleted file mode 100644 index 73af4f18..00000000 --- a/AquaMai/AquaMai.Config/Utility.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Reflection; -using Tomlet.Models; - -namespace AquaMai.Config; - -public static class Utility -{ - public static Action LogFunction = Console.WriteLine; - - public static bool IsTruty(TomlValue value, string path = null) - { - return value switch - { - TomlBoolean boolean => boolean.Value, - TomlLong @long => @long.Value != 0, - _ => throw new ArgumentException( - path == null - ? $"Non-boolish TOML type {value.GetType().Name} value: {value}" - : $"When parsing {path}, got non-boolish TOML type {value.GetType().Name} value: {value}") - }; - } - - public static bool IsTrutyOrDefault(TomlValue value, bool defaultValue = false) - { - return value switch - { - TomlBoolean boolean => boolean.Value, - TomlLong @long => @long.Value != 0, - _ => defaultValue - }; - } - - public static bool IsIntegerType(Type type) - { - return type == typeof(sbyte) || type == typeof(short) || type == typeof(int) || type == typeof(long) - || type == typeof(byte) || type == typeof(ushort) || type == typeof(uint) || type == typeof(ulong); - } - - public static bool IsFloatType(Type type) - { - return type == typeof(float) || type == typeof(double); - } - - public static bool IsNumberType(Type type) - { - return IsIntegerType(type) || IsFloatType(type); - } - - public static T ParseTomlValue(TomlValue value) - { - return (T)ParseTomlValue(typeof(T), value); - } - - public static object ParseTomlValue(Type type, TomlValue value) - { - if (type == typeof(bool)) - { - return IsTruty(value); - } - else if (IsNumberType(type)) - { - if (TryGetTomlNumberObject(value, out var numberObject)) - { - return Convert.ChangeType(numberObject, type); - } - else - { - throw new InvalidCastException($"Non-number TOML type: {value.GetType().Name}"); - } - } - else if (type == typeof(string)) - { - if (value is TomlString @string) - { - return @string.Value; - } - else - { - throw new InvalidCastException($"Non-string TOML type: {value.GetType().Name}"); - } - } - else if (type.IsEnum) - { - if (value is TomlString @string) - { - try - { - return Enum.Parse(type, @string.Value); - } - catch - { - throw new InvalidCastException($"Invalid enum {type.FullName} value: {@string.SerializedValue}"); - } - } - else if (value is TomlLong @long) - { - if (Enum.IsDefined(type, @long.Value)) - { - try - { - return Enum.ToObject(type, @long.Value); - } - catch - {} - } - throw new InvalidCastException($"Invalid enum {type.FullName} value: {@long.Value}"); - } - else - { - throw new InvalidCastException($"Non-enum TOML type: {value.GetType().Name}"); - } - } - else - { - var currentMethod = MethodBase.GetCurrentMethod(); - throw new NotImplementedException($"Unsupported config entry type: {type.FullName}. Please implement in {currentMethod.DeclaringType.FullName}.{currentMethod.Name}"); - } - } - - private static bool TryGetTomlNumberObject(TomlValue value, out object numberObject) - { - if (value is TomlLong @long) - { - numberObject = @long.Value; - return true; - } - else if (value is TomlDouble @double) - { - numberObject = @double.Value; - return true; - } - else - { - numberObject = null; - return false; - } - } - - public static bool TomlTryGetValueCaseInsensitive(TomlTable table, string key, out TomlValue value) - { - // Prefer exact match - if (table.TryGetValue(key, out value)) - { - return true; - } - // Fallback to case-insensitive match - foreach (var kvp in table) - { - if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) - { - value = kvp.Value; - return true; - } - } - value = null; - return false; - } - - public static bool TomlContainsKeyCaseInsensitive(TomlTable table, string key) - { - // Prefer exact match - if (table.ContainsKey(key)) - { - return true; - } - // Fallback to case-insensitive match - foreach (var kvp in table) - { - if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - return false; - } - - public static string ToPascalCase(string str) - { - return str.Length switch - { - 0 => str, - 1 => char.ToUpperInvariant(str[0]).ToString(), - _ => char.ToUpperInvariant(str[0]) + str.Substring(1) - }; - } - - // We can test the configuration related code without loading the mod into the game. - public static void Log(string message) - { - LogFunction(message); - } -} diff --git a/AquaMai/AquaMai.Core/AquaMai.Core.csproj b/AquaMai/AquaMai.Core/AquaMai.Core.csproj deleted file mode 100644 index 5a2a9837..00000000 --- a/AquaMai/AquaMai.Core/AquaMai.Core.csproj +++ /dev/null @@ -1,132 +0,0 @@ - - - - Release - AnyCPU - {33C0D4ED-6A84-4659-9A05-12D43D75D0B3} - Library - AquaMai.Core - AquaMai.Core - net472 - 512 - true - 12 - 414 - $(ProjectDir)../Libs/;$(AssemblySearchPaths) - $(ProjectDir)../Output/ - false - false - - - - false - None - true - prompt - 4 - true - false - - - - DEBUG - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PublicResXFileCodeGenerator - Locale.Designer.cs - - - Locale.resx - - - - diff --git a/AquaMai/AquaMai.Core/Attributes/EnableGameVersionAttribute.cs b/AquaMai/AquaMai.Core/Attributes/EnableGameVersionAttribute.cs deleted file mode 100644 index 97191007..00000000 --- a/AquaMai/AquaMai.Core/Attributes/EnableGameVersionAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace AquaMai.Core.Attributes; - -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] -public class EnableGameVersionAttribute(uint minVersion = 0, uint maxVersion = 0, bool noWarn = false) : Attribute -{ - public uint MinVersion { get; } = minVersion; - public uint MaxVersion { get; } = maxVersion; - public bool NoWarn { get; } = noWarn; - - public bool ShouldEnable(uint gameVersion) - { - if (MinVersion > 0 && MinVersion > gameVersion) return false; - if (MaxVersion > 0 && MaxVersion < gameVersion) return false; - return true; - } -} diff --git a/AquaMai/AquaMai.Core/Attributes/EnableIfAttribute.cs b/AquaMai/AquaMai.Core/Attributes/EnableIfAttribute.cs deleted file mode 100644 index 43f3cc8f..00000000 --- a/AquaMai/AquaMai.Core/Attributes/EnableIfAttribute.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; - -namespace AquaMai.Core.Attributes; - -public enum EnableConditionOperator -{ - Equal, - NotEqual, - GreaterThan, - LessThan, - GreaterThanOrEqual, - LessThanOrEqual -} - -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] -public class EnableIfAttribute( - Type referenceType, - string referenceMember, - EnableConditionOperator @operator, - object rightSideValue) : Attribute -{ - public Type ReferenceType { get; } = referenceType; - public string ReferenceMember { get; } = referenceMember; - public EnableConditionOperator Operator { get; } = @operator; - public object RightSideValue { get; } = rightSideValue; - - // Referencing a field in another class and checking if it's true. - public EnableIfAttribute(Type referenceType, string referenceMember) - : this(referenceType, referenceMember, EnableConditionOperator.Equal, true) - { } - - // Referencing a field in the same class and comparing it with a value. - public EnableIfAttribute(string referenceMember, EnableConditionOperator condition, object value) - : this(null, referenceMember, condition, value) - { } - - // Referencing a field in the same class and checking if it's true. - public EnableIfAttribute(string referenceMember) - : this(referenceMember, EnableConditionOperator.Equal, true) - { } - - public bool ShouldEnable(Type selfType) - { - var referenceType = ReferenceType ?? selfType; - var referenceField = referenceType.GetField( - ReferenceMember, - System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); - var referenceProperty = referenceType.GetProperty( - ReferenceMember, - System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); - if (referenceField == null && referenceProperty == null) - { - throw new ArgumentException($"Field or property {ReferenceMember} not found in {referenceType.FullName}"); - } - var referenceMemberValue = referenceField != null ? referenceField.GetValue(null) : referenceProperty.GetValue(null); - switch (Operator) - { - case EnableConditionOperator.Equal: - return referenceMemberValue.Equals(RightSideValue); - case EnableConditionOperator.NotEqual: - return !referenceMemberValue.Equals(RightSideValue); - case EnableConditionOperator.GreaterThan: - case EnableConditionOperator.LessThan: - case EnableConditionOperator.GreaterThanOrEqual: - case EnableConditionOperator.LessThanOrEqual: - var comparison = (IComparable)referenceMemberValue; - return Operator switch - { - EnableConditionOperator.GreaterThan => comparison.CompareTo(RightSideValue) > 0, - EnableConditionOperator.LessThan => comparison.CompareTo(RightSideValue) < 0, - EnableConditionOperator.GreaterThanOrEqual => comparison.CompareTo(RightSideValue) >= 0, - EnableConditionOperator.LessThanOrEqual => comparison.CompareTo(RightSideValue) <= 0, - _ => throw new NotImplementedException(), - }; - default: - throw new NotImplementedException(); - } - } -} diff --git a/AquaMai/AquaMai.Core/Attributes/EnableImplicitlyIfAttribute.cs b/AquaMai/AquaMai.Core/Attributes/EnableImplicitlyIfAttribute.cs deleted file mode 100644 index 71fb02bd..00000000 --- a/AquaMai/AquaMai.Core/Attributes/EnableImplicitlyIfAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace AquaMai.Core.Attributes; - -// If the field or property with this name is true, the patch will be implicitly enabled, regardless of the config state. -// This is handled outside the config module, while The config state won't be actually set to enabled by it. -// Won't bypass the restriction of [EnableIf()] and [EnableGameVersion()]. -[AttributeUsage(AttributeTargets.Class)] -public class EnableImplicitlyIf(string memberName) : Attribute -{ - public string MemberName { get; } = memberName; -} diff --git a/AquaMai/AquaMai.Core/ConfigLoader.cs b/AquaMai/AquaMai.Core/ConfigLoader.cs deleted file mode 100644 index e87088fc..00000000 --- a/AquaMai/AquaMai.Core/ConfigLoader.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using MelonLoader; -using AquaMai.Config; -using AquaMai.Config.Interfaces; -using AquaMai.Config.Migration; - -namespace AquaMai.Core; - -public static class ConfigLoader -{ - private static string ConfigFile => "AquaMai.toml"; - private static string ConfigExampleFile(string lang) => $"AquaMai.{lang}.toml"; - private static string OldConfigFile(string version) => $"AquaMai.toml.old-v{version}."; - - private static Config.Config config; - - public static Config.Config Config => config; - - public static bool LoadConfig(Assembly modsAssembly) - { - Utility.LogFunction = MelonLogger.Msg; - - config = new( - new Config.Reflection.ReflectionManager( - new Config.Reflection.SystemReflectionProvider(modsAssembly))); - - if (!File.Exists(ConfigFile)) - { - var examples = GenerateExamples(); - foreach (var (lang, example) in examples) - { - var filename = ConfigExampleFile(lang); - File.WriteAllText(filename, example); - } - MelonLogger.Error("======================================!!!"); - MelonLogger.Error("AquaMai.toml not found! Please create it."); - MelonLogger.Error("找不到配置文件 AquaMai.toml!请创建。"); - MelonLogger.Error("Example copied to AquaMai.en.toml"); - MelonLogger.Error("示例已复制到 AquaMai.zh.toml"); - MelonLogger.Error("========================================="); - return false; - } - - var configText = File.ReadAllText(ConfigFile); - var configView = new ConfigView(configText); - var configVersion = ConfigMigrationManager.Instance.GetVersion(configView); - if (configVersion != ConfigMigrationManager.Instance.LatestVersion) - { - File.WriteAllText(OldConfigFile(configVersion), configText); - configView = (ConfigView)ConfigMigrationManager.Instance.Migrate(configView); - } - - // Read AquaMai.toml to load settings - ConfigParser.Instance.Parse(config, configView); - - return true; - } - - public static void SaveConfig(string lang) - { - File.WriteAllText(ConfigFile, SerailizeCurrentConfig(lang)); - } - - private static string SerailizeCurrentConfig(string lang) => - new ConfigSerializer(new IConfigSerializer.Options() - { - Lang = lang, - IncludeBanner = true, - OverrideLocaleValue = true - }).Serialize(config); - - private static IDictionary GenerateExamples() - { - var examples = new Dictionary(); - foreach (var lang in (string[]) ["en", "zh"]) - { - examples[lang] = SerailizeCurrentConfig(lang); - } - return examples; - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/EnableConditionHelper.cs b/AquaMai/AquaMai.Core/Helpers/EnableConditionHelper.cs deleted file mode 100644 index 475c24ae..00000000 --- a/AquaMai/AquaMai.Core/Helpers/EnableConditionHelper.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections; -using System.Reflection; -using AquaMai.Core.Attributes; -using AquaMai.Core.Resources; -using HarmonyLib; -using MelonLoader; - -namespace AquaMai.Core.Helpers; - -public class EnableConditionHelper -{ - [HarmonyPostfix] - [HarmonyPatch("HarmonyLib.PatchTools", "GetPatchMethod")] - public static void PostGetPatchMethod(ref MethodInfo __result) - { - if (__result != null) - { - if (ShouldSkipMethodOrClass(__result.GetCustomAttribute, __result.ReflectedType, __result.Name)) - { - __result = null; - } - } - } - - [HarmonyPostfix] - [HarmonyPatch("HarmonyLib.PatchTools", "GetPatchMethods")] - public static void PostGetPatchMethods(ref IList __result) - { - for (int i = 0; i < __result.Count; i++) - { - var harmonyMethod = Traverse.Create(__result[i]).Field("info").GetValue() as HarmonyMethod; - var method = harmonyMethod.method; - if (ShouldSkipMethodOrClass(method.GetCustomAttribute, method.ReflectedType, method.Name)) - { - __result.RemoveAt(i); - i--; - } - } - } - - public static bool ShouldSkipClass(Type type) - { - return ShouldSkipMethodOrClass(type.GetCustomAttribute, type); - } - - private static bool ShouldSkipMethodOrClass(Func getCustomAttribute, Type type, string methodName = "") - { - var displayName = type.FullName + (string.IsNullOrEmpty(methodName) ? "" : $".{methodName}"); - var enableIf = (EnableIfAttribute)getCustomAttribute(typeof(EnableIfAttribute)); - if (enableIf != null && !enableIf.ShouldEnable(type)) - { -# if DEBUG - MelonLogger.Msg($"Skipping {displayName} due to EnableIf condition"); -# endif - return true; - } - var enableGameVersion = (EnableGameVersionAttribute)getCustomAttribute(typeof(EnableGameVersionAttribute)); - if (enableGameVersion != null && !enableGameVersion.ShouldEnable(GameInfo.GameVersion)) - { -# if DEBUG - MelonLogger.Msg($"Skipping {displayName} due to EnableGameVersion condition"); -# endif - if (!enableGameVersion.NoWarn) - { - MelonLogger.Warning(string.Format(Locale.SkipIncompatiblePatch, type)); - } - return true; - } - return false; - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/FileSystem.cs b/AquaMai/AquaMai.Core/Helpers/FileSystem.cs deleted file mode 100644 index 3eab6f0b..00000000 --- a/AquaMai/AquaMai.Core/Helpers/FileSystem.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.IO; - -namespace AquaMai.Core.Helpers; - -public static class FileSystem -{ - public static string ResolvePath(string path) - { - var varExpanded = Environment.ExpandEnvironmentVariables(path); - return Path.IsPathRooted(varExpanded) - ? varExpanded - : Path.Combine(Environment.CurrentDirectory, varExpanded); - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/GameInfo.cs b/AquaMai/AquaMai.Core/Helpers/GameInfo.cs deleted file mode 100644 index 117f724b..00000000 --- a/AquaMai/AquaMai.Core/Helpers/GameInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using MAI2System; - -namespace AquaMai.Core.Helpers; - -public class GameInfo -{ - public static uint GameVersion { get; } = GetGameVersion(); - - private static uint GetGameVersion() - { - return (uint)typeof(ConstParameter).GetField("NowGameVersion", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null); - } - - public static string GameId { get; } = GetGameId(); - - private static string GetGameId() - { - return typeof(ConstParameter).GetField("GameIDStr", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null) as string; - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/GuiSizes.cs b/AquaMai/AquaMai.Core/Helpers/GuiSizes.cs deleted file mode 100644 index 30efa7b2..00000000 --- a/AquaMai/AquaMai.Core/Helpers/GuiSizes.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using HarmonyLib; -using UnityEngine; - -namespace AquaMai.Core.Helpers; - -public static class GuiSizes -{ - public static bool SinglePlayer { get; set; } = false; - public static float PlayerWidth => Screen.height / 1920f * 1080; - public static float PlayerCenter => SinglePlayer ? Screen.width / 2f : Screen.width / 2f - PlayerWidth / 2; - public static int FontSize => (int)(PlayerWidth * .015f); - public static float LabelHeight => FontSize * 1.5f; - public static float Margin => PlayerWidth * .005f; - - private static Color backgroundColor = new(147 / 256f, 160 / 256f, 173 / 256f, .8f); - - public static void SetupStyles() - { - var buttonStyle = GUI.skin.button; - buttonStyle.normal.textColor = Color.white; - buttonStyle.normal.background = Texture2D.whiteTexture; - buttonStyle.hover.background = Texture2D.whiteTexture; - buttonStyle.active.background = Texture2D.whiteTexture; - buttonStyle.border = new RectOffset(0, 0, 0, 0); - buttonStyle.margin = new RectOffset(0, 0, 0, 0); - buttonStyle.padding = new RectOffset(10, 10, 10, 10); - buttonStyle.overflow = new RectOffset(0, 0, 0, 0); - - var boxStyle = GUI.skin.box; - boxStyle.border = new RectOffset(0, 0, 0, 0); - boxStyle.normal.background = Texture2D.whiteTexture; - - GUI.backgroundColor = backgroundColor; - } - - [HarmonyPatch] - public class BoxBackground - { - public static IEnumerable TargetMethods() - { - return typeof(GUI).GetMethods().Where(x => x.Name == "Box"); - } - - public static void Prefix() - { - GUI.backgroundColor = new Color(62 / 256f, 62 / 256f, 66 / 256f, .6f); - } - - public static void Postfix() - { - GUI.backgroundColor = backgroundColor; - } - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/KeyListener.cs b/AquaMai/AquaMai.Core/Helpers/KeyListener.cs deleted file mode 100644 index 0215dd4d..00000000 --- a/AquaMai/AquaMai.Core/Helpers/KeyListener.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using AquaMai.Config.Types; -using HarmonyLib; -using Main; -using Manager; -using MelonLoader; -using UnityEngine; - -namespace AquaMai.Core.Helpers; - -public static class KeyListener -{ - private static readonly Dictionary _keyPressFrames = []; - private static readonly Dictionary _keyPressFramesPrev = []; - - static KeyListener() - { - foreach (KeyCodeOrName key in Enum.GetValues(typeof(KeyCodeOrName))) - { - _keyPressFrames[key] = 0; - _keyPressFramesPrev[key] = 0; - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameMainObject), "Update")] - public static void CheckLongPush() - { - foreach (KeyCodeOrName key in Enum.GetValues(typeof(KeyCodeOrName))) - { - _keyPressFramesPrev[key] = _keyPressFrames[key]; - if (GetKeyPush(key)) - { -# if DEBUG - MelonLogger.Msg($"CheckLongPush {key} is push {_keyPressFrames[key]}"); -# endif - _keyPressFrames[key]++; - } - else - { - _keyPressFrames[key] = 0; - } - } - } - - public static bool GetKeyPush(KeyCodeOrName key) => - key switch - { - KeyCodeOrName.None => false, - < KeyCodeOrName.Select1P => Input.GetKey(key.GetKeyCode()), - KeyCodeOrName.Test => InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonTest), - KeyCodeOrName.Service => InputManager.GetSystemInputPush(InputManager.SystemButtonSetting.ButtonService), - KeyCodeOrName.Select1P => InputManager.GetButtonPush(0, InputManager.ButtonSetting.Select), - KeyCodeOrName.Select2P => InputManager.GetButtonPush(1, InputManager.ButtonSetting.Select), - _ => throw new ArgumentOutOfRangeException(nameof(key), key, "我也不知道这是什么键") - }; - - public static bool GetKeyDown(KeyCodeOrName key) - { - // return key switch - // { - // KeyCodeOrName.None => false, - // < KeyCodeOrName.Select1P => Input.GetKeyDown(key.GetKeyCode()), - // KeyCodeOrName.Test => InputManager.GetSystemInputDown(InputManager.SystemButtonSetting.ButtonTest), - // KeyCodeOrName.Service => InputManager.GetSystemInputDown(InputManager.SystemButtonSetting.ButtonService), - // KeyCodeOrName.Select1P => InputManager.GetButtonDown(0, InputManager.ButtonSetting.Select), - // KeyCodeOrName.Select2P => InputManager.GetButtonDown(1, InputManager.ButtonSetting.Select), - // _ => throw new ArgumentOutOfRangeException(nameof(key), key, "我也不知道这是什么键") - // }; - - // 不用这个,我们检测按键是否弹起以及弹起之前按下的时间是否小于 30,这样可以防止要长按时按下的时候就触发 - return _keyPressFrames[key] == 0 && 0 < _keyPressFramesPrev[key] && _keyPressFramesPrev[key] < 30; - } - - public static bool GetKeyDownOrLongPress(KeyCodeOrName key, bool isLongPress) - { - bool ret; - if (isLongPress) - { - ret = _keyPressFrames[key] == 60; - } - else - { - ret = GetKeyDown(key); - } - -# if DEBUG - if (ret) - { - MelonLogger.Msg($"Key {key} is pressed, long press: {isLongPress}"); - MelonLogger.Msg(new StackTrace()); - } -# endif - return ret; - } - - private static KeyCode GetKeyCode(this KeyCodeOrName keyCodeOrName) => - keyCodeOrName switch - { - KeyCodeOrName.Alpha0 => KeyCode.Alpha0, - KeyCodeOrName.Alpha1 => KeyCode.Alpha1, - KeyCodeOrName.Alpha2 => KeyCode.Alpha2, - KeyCodeOrName.Alpha3 => KeyCode.Alpha3, - KeyCodeOrName.Alpha4 => KeyCode.Alpha4, - KeyCodeOrName.Alpha5 => KeyCode.Alpha5, - KeyCodeOrName.Alpha6 => KeyCode.Alpha6, - KeyCodeOrName.Alpha7 => KeyCode.Alpha7, - KeyCodeOrName.Alpha8 => KeyCode.Alpha8, - KeyCodeOrName.Alpha9 => KeyCode.Alpha9, - KeyCodeOrName.Keypad0 => KeyCode.Keypad0, - KeyCodeOrName.Keypad1 => KeyCode.Keypad1, - KeyCodeOrName.Keypad2 => KeyCode.Keypad2, - KeyCodeOrName.Keypad3 => KeyCode.Keypad3, - KeyCodeOrName.Keypad4 => KeyCode.Keypad4, - KeyCodeOrName.Keypad5 => KeyCode.Keypad5, - KeyCodeOrName.Keypad6 => KeyCode.Keypad6, - KeyCodeOrName.Keypad7 => KeyCode.Keypad7, - KeyCodeOrName.Keypad8 => KeyCode.Keypad8, - KeyCodeOrName.Keypad9 => KeyCode.Keypad9, - KeyCodeOrName.F1 => KeyCode.F1, - KeyCodeOrName.F2 => KeyCode.F2, - KeyCodeOrName.F3 => KeyCode.F3, - KeyCodeOrName.F4 => KeyCode.F4, - KeyCodeOrName.F5 => KeyCode.F5, - KeyCodeOrName.F6 => KeyCode.F6, - KeyCodeOrName.F7 => KeyCode.F7, - KeyCodeOrName.F8 => KeyCode.F8, - KeyCodeOrName.F9 => KeyCode.F9, - KeyCodeOrName.F10 => KeyCode.F10, - KeyCodeOrName.F11 => KeyCode.F11, - KeyCodeOrName.F12 => KeyCode.F12, - KeyCodeOrName.Insert => KeyCode.Insert, - KeyCodeOrName.Delete => KeyCode.Delete, - KeyCodeOrName.Home => KeyCode.Home, - KeyCodeOrName.End => KeyCode.End, - KeyCodeOrName.PageUp => KeyCode.PageUp, - KeyCodeOrName.PageDown => KeyCode.PageDown, - KeyCodeOrName.UpArrow => KeyCode.UpArrow, - KeyCodeOrName.DownArrow => KeyCode.DownArrow, - KeyCodeOrName.LeftArrow => KeyCode.LeftArrow, - KeyCodeOrName.RightArrow => KeyCode.RightArrow, - _ => throw new ArgumentOutOfRangeException(nameof(keyCodeOrName), keyCodeOrName, "游戏功能键需要单独处理") - }; -} diff --git a/AquaMai/AquaMai.Core/Helpers/MessageHelper.cs b/AquaMai/AquaMai.Core/Helpers/MessageHelper.cs deleted file mode 100644 index b0e6b64f..00000000 --- a/AquaMai/AquaMai.Core/Helpers/MessageHelper.cs +++ /dev/null @@ -1,39 +0,0 @@ -using DB; -using HarmonyLib; -using Manager; -using MelonLoader; -using Process; - -namespace AquaMai.Core.Helpers; - -public class MessageHelper -{ - private static IGenericManager _genericManager = null; - - [HarmonyPostfix] - [HarmonyPatch(typeof(ProcessManager), "SetMessageManager")] - private static void OnSetMessageManager(IGenericManager genericManager) - { - _genericManager = genericManager; - } - - public static void ShowMessage(string message, WindowSizeID size = WindowSizeID.Middle, string title = null) - { - if (_genericManager is null) - { - MelonLogger.Error($"[MessageHelper] Unable to show message: `{message}` GenericManager is null"); - return; - } - - _genericManager.Enqueue(0, WindowMessageID.CollectionAttentionEmptyFavorite, new WindowParam() - { - hideTitle = title is null, - replaceTitle = true, - title = title, - replaceText = true, - text = message, - changeSize = true, - sizeID = size, - }); - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/MusicDirHelper.cs b/AquaMai/AquaMai.Core/Helpers/MusicDirHelper.cs deleted file mode 100644 index 112a4e62..00000000 --- a/AquaMai/AquaMai.Core/Helpers/MusicDirHelper.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using HarmonyLib; - -namespace AquaMai.Core.Helpers; - -public class MusicDirHelper -{ - private static Dictionary _map = new(); - - [HarmonyPostfix] - [HarmonyPatch(typeof(Manager.MaiStudio.Serialize.MusicData), "AddPath")] - private static void AddPath(Manager.MaiStudio.Serialize.MusicData __instance, string parentPath) - { - _map[__instance.GetID()] = parentPath; - } - - public static string LookupPath(int id) - { - return _map.GetValueOrDefault(id); - } - - public static string LookupPath(Manager.MaiStudio.Serialize.MusicData musicData) - { - return LookupPath(musicData.GetID()); - } - - public static string LookupPath(Manager.MaiStudio.MusicData musicData) - { - return LookupPath(musicData.GetID()); - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/SharedInstances.cs b/AquaMai/AquaMai.Core/Helpers/SharedInstances.cs deleted file mode 100644 index 6f05f1fa..00000000 --- a/AquaMai/AquaMai.Core/Helpers/SharedInstances.cs +++ /dev/null @@ -1,25 +0,0 @@ -using HarmonyLib; -using Main; -using Process; - -namespace AquaMai.Core.Helpers; - -public class SharedInstances -{ - public static ProcessDataContainer ProcessDataContainer { get; private set; } - public static GameMainObject GameMainObject { get; private set; } - - [HarmonyPrefix] - [HarmonyPatch(typeof(ProcessDataContainer), MethodType.Constructor)] - public static void OnCreateProcessDataContainer(ProcessDataContainer __instance) - { - ProcessDataContainer = __instance; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(GameMainObject), "Awake")] - public static void OnCreateGameMainObject(GameMainObject __instance) - { - GameMainObject = __instance; - } -} diff --git a/AquaMai/AquaMai.Core/Helpers/Shim.cs b/AquaMai/AquaMai.Core/Helpers/Shim.cs deleted file mode 100644 index 44b9725f..00000000 --- a/AquaMai/AquaMai.Core/Helpers/Shim.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Manager.UserDatas; -using MelonLoader; -using Net; -using Net.Packet; -using Net.Packet.Mai2; -using Net.VO; - -namespace AquaMai.Core.Helpers; - -public static class Shim -{ - private static T Iife(Func func) => func(); - - public static readonly string apiSuffix = Iife(() => - { - try - { - var baseNetQueryConstructor = typeof(NetQuery) - .GetConstructors() - .First(); - return ((INetQuery)baseNetQueryConstructor.Invoke( - baseNetQueryConstructor - .GetParameters() - .Select((parameter, i) => i == 0 ? "" : parameter.DefaultValue) - .ToArray())).Api; - } - catch (Exception e) - { - MelonLogger.Error($"Failed to resolve the API suffix: {e}"); - return null; - } - }); - - public static string RemoveApiSuffix(string api) - { - return !string.IsNullOrEmpty(apiSuffix) && api.EndsWith(apiSuffix) - ? api.Substring(0, api.Length - apiSuffix.Length) - : api; - } - - public delegate string GetAccessTokenMethod(int index); - public static readonly GetAccessTokenMethod GetAccessToken = Iife(() => - { - var tOperationManager = Traverse.Create(Singleton.Instance); - var tGetAccessToken = tOperationManager.Method("GetAccessToken", [typeof(int)]); - if (!tGetAccessToken.MethodExists()) - { - return (index) => throw new MissingMethodException("No matching OperationManager.GetAccessToken() method found"); - } - return (index) => tGetAccessToken.GetValue(index); - }); - - public delegate PacketUploadUserPlaylog PacketUploadUserPlaylogCreator(int index, UserData src, int trackNo, Action onDone, Action onError = null); - public static readonly PacketUploadUserPlaylogCreator CreatePacketUploadUserPlaylog = Iife(() => - { - var type = typeof(PacketUploadUserPlaylog); - if (type.GetConstructor([typeof(int), typeof(UserData), typeof(int), typeof(Action), typeof(Action)]) is ConstructorInfo ctor1) - { - return (index, src, trackNo, onDone, onError) => - { - var args = new object[] { index, src, trackNo, onDone, onError }; - return (PacketUploadUserPlaylog)ctor1.Invoke(args); - }; - } - else if (type.GetConstructor([typeof(int), typeof(UserData), typeof(int), typeof(string), typeof(Action), typeof(Action)]) is ConstructorInfo ctor2) - { - return (index, src, trackNo, onDone, onError) => - { - var accessToken = GetAccessToken(index); - var args = new object[] { index, src, trackNo, accessToken, onDone, onError }; - return (PacketUploadUserPlaylog)ctor2.Invoke(args); - }; - } - else - { - throw new MissingMethodException("No matching PacketUploadUserPlaylog constructor found"); - } - }); - - public delegate PacketUpsertUserAll PacketUpsertUserAllCreator(int index, UserData src, Action onDone, Action onError = null); - public static readonly PacketUpsertUserAllCreator CreatePacketUpsertUserAll = Iife(() => - { - var type = typeof(PacketUpsertUserAll); - if (type.GetConstructor([typeof(int), typeof(UserData), typeof(Action), typeof(Action)]) is ConstructorInfo ctor1) - { - return (index, src, onDone, onError) => - { - var args = new object[] { index, src, onDone, onError }; - return (PacketUpsertUserAll)ctor1.Invoke(args); - }; - } - else if (type.GetConstructor([typeof(int), typeof(UserData), typeof(string), typeof(Action), typeof(Action)]) is ConstructorInfo ctor2) - { - return (index, src, onDone, onError) => - { - var accessToken = GetAccessToken(index); - var args = new object[] { index, src, accessToken, onDone, onError }; - return (PacketUpsertUserAll)ctor2.Invoke(args); - }; - } - else - { - throw new MissingMethodException("No matching PacketUpsertUserAll constructor found"); - } - }); - - public static IEnumerable[] GetUserScoreList(UserData userData) - { - var tUserData = Traverse.Create(userData); - - var tScoreList = tUserData.Property("ScoreList"); - if (tScoreList.PropertyExists()) - { - return tScoreList.GetValue[]>(); - } - - var tScoreDic = tUserData.Property("ScoreDic"); - if (tScoreDic.PropertyExists()) - { - var scoreDic = tScoreDic.GetValue[]>(); - return scoreDic.Select(dic => dic.Values).ToArray(); - } - - throw new MissingFieldException("No matching UserData.ScoreList/ScoreDic found"); - } -} diff --git a/AquaMai/AquaMai.Core/Resources/I18nSingleAssemblyHook.cs b/AquaMai/AquaMai.Core/Resources/I18nSingleAssemblyHook.cs deleted file mode 100644 index 2fdd490d..00000000 --- a/AquaMai/AquaMai.Core/Resources/I18nSingleAssemblyHook.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Globalization; -using System.Resources; -using HarmonyLib; - -namespace AquaMai.Core.Resources; - -public class I18nSingleAssemblyHook -{ - [HarmonyPatch(typeof(ResourceManager), "InternalGetResourceSet", typeof(CultureInfo), typeof(bool), typeof(bool))] - [HarmonyPrefix] - public static bool GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents, ref ResourceSet __result, ResourceManager __instance) - { - var GetResourceFileName = __instance.GetType().GetMethod("GetResourceFileName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - var resourceFileName = (string)GetResourceFileName.Invoke(__instance, [culture]); - var ResourcesAssembly = typeof(I18nSingleAssemblyHook).Assembly; - var manifestResourceStream = ResourcesAssembly.GetManifestResourceStream(resourceFileName); - if (manifestResourceStream == null) - { - return true; - } - - var resourceGroveler = __instance.GetType().GetField("resourceGroveler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance); - var CreateResourceSet = resourceGroveler.GetType().GetMethod("CreateResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - var resourceSet = CreateResourceSet.Invoke(resourceGroveler, [manifestResourceStream, ResourcesAssembly]); - var AddResourceSet = __instance.GetType().GetMethod("AddResourceSet", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - var localResourceSets = __instance.GetType().GetField("_resourceSets", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(__instance); - object[] args = [localResourceSets, culture.Name, resourceSet]; - AddResourceSet.Invoke(null, args); - __result = (ResourceSet)args[2]; - return false; - } -} diff --git a/AquaMai/AquaMai.Core/Resources/Locale.Designer.cs b/AquaMai/AquaMai.Core/Resources/Locale.Designer.cs deleted file mode 100644 index e46e620e..00000000 --- a/AquaMai/AquaMai.Core/Resources/Locale.Designer.cs +++ /dev/null @@ -1,361 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace AquaMai.Core.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Locale { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Locale() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AquaMai.Core.Resources.Locale", typeof(Locale).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to You are using AquaMai CI build version. This version is built from the latest mainline code and may contain undocumented configuration changes or potential issues.. - /// - public static string CiBuildAlertContent { - get { - return ResourceManager.GetString("CiBuildAlertContent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Important Notice: Test Version. - /// - public static string CiBuildAlertTitle { - get { - return ResourceManager.GetString("CiBuildAlertTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loaded!. - /// - public static string Loaded { - get { - return ResourceManager.GetString("Loaded", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Errors detected while loading! - ///- Are you using a modified Assembly-CSharp.dll, which will cause inconsistent functions and cannot find the functions that need to be modified - ///- Check for conflicting mods, or enabled incompatible options. - /// - public static string LoadError { - get { - return ResourceManager.GetString("LoadError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to End. - /// - public static string MarkRepeatEnd { - get { - return ResourceManager.GetString("MarkRepeatEnd", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Start. - /// - public static string MarkRepeatStart { - get { - return ResourceManager.GetString("MarkRepeatStart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Aime reader error. - /// - public static string NetErrIsAliveAimeReader { - get { - return ResourceManager.GetString("NetErrIsAliveAimeReader", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Aime server error. - /// - public static string NetErrIsAliveAimeServer { - get { - return ResourceManager.GetString("NetErrIsAliveAimeServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Server communication error. - /// - public static string NetErrIsAliveServer { - get { - return ResourceManager.GetString("NetErrIsAliveServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data download not success. - /// - public static string NetErrWasDownloadSuccessOnce { - get { - return ResourceManager.GetString("NetErrWasDownloadSuccessOnce", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pause. - /// - public static string Pause { - get { - return ResourceManager.GetString("Pause", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Play Count:{0}. - /// - public static string PlayCount { - get { - return ResourceManager.GetString("PlayCount", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Playlog save error. - /// - public static string PlaylogSaveError { - get { - return ResourceManager.GetString("PlaylogSaveError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SSS+ => DXRating += {0}. - /// - public static string RatingUpWhenSSSp { - get { - return ResourceManager.GetString("RatingUpWhenSSSp", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Repeat end time cannot be less than repeat start time. - /// - public static string RepeatEndTimeLessThenStartTime { - get { - return ResourceManager.GetString("RepeatEndTimeLessThenStartTime", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loop Not Set. - /// - public static string RepeatNotSet { - get { - return ResourceManager.GetString("RepeatNotSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reset. - /// - public static string RepeatReset { - get { - return ResourceManager.GetString("RepeatReset", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loop Set. - /// - public static string RepeatStartEndSet { - get { - return ResourceManager.GetString("RepeatStartEndSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loop Start Set. - /// - public static string RepeatStartSet { - get { - return ResourceManager.GetString("RepeatStartSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Please set repeat start time first. - /// - public static string RepeatStartTimeNotSet { - get { - return ResourceManager.GetString("RepeatStartTimeNotSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Saving... Do not exit the game. - /// - public static string SavingDontExit { - get { - return ResourceManager.GetString("SavingDontExit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Seek <<. - /// - public static string SeekBackward { - get { - return ResourceManager.GetString("SeekBackward", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Seek >>. - /// - public static string SeekForward { - get { - return ResourceManager.GetString("SeekForward", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Hide Self-Made Charts. - /// - public static string SelfMadeChartsHide { - get { - return ResourceManager.GetString("SelfMadeChartsHide", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Show Self-Made Charts. - /// - public static string SelfMadeChartsShow { - get { - return ResourceManager.GetString("SelfMadeChartsShow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Skip. - /// - public static string Skip { - get { - return ResourceManager.GetString("Skip", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to > Skipping incompatible patch: {0}. - /// - public static string SkipIncompatiblePatch { - get { - return ResourceManager.GetString("SkipIncompatiblePatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Speed. - /// - public static string Speed { - get { - return ResourceManager.GetString("Speed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Speed -. - /// - public static string SpeedDown { - get { - return ResourceManager.GetString("SpeedDown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Speed Reset. - /// - public static string SpeedReset { - get { - return ResourceManager.GetString("SpeedReset", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Speed +. - /// - public static string SpeedUp { - get { - return ResourceManager.GetString("SpeedUp", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Touch panel reset. - /// - public static string TouchPanelReset { - get { - return ResourceManager.GetString("TouchPanelReset", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UserAll Upsert Error. - /// - public static string UserAllUpsertError { - get { - return ResourceManager.GetString("UserAllUpsertError", resourceCulture); - } - } - } -} diff --git a/AquaMai/AquaMai.Core/Resources/Locale.resx b/AquaMai/AquaMai.Core/Resources/Locale.resx deleted file mode 100644 index 8ee645de..00000000 --- a/AquaMai/AquaMai.Core/Resources/Locale.resx +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Seek << - - - Seek >> - - - Pause - - - Start - - - End - - - Reset - - - Loop Not Set - - - Loop Start Set - - - Loop Set - - - Speed - - - - Speed + - - - Speed - - - Speed Reset - - - Errors detected while loading! -- Are you using a modified Assembly-CSharp.dll, which will cause inconsistent functions and cannot find the functions that need to be modified -- Check for conflicting mods, or enabled incompatible options - - - Saving... Do not exit the game - - - Loaded! - - - Server communication error - - - Aime reader error - - - Aime server error - - - Data download not success - - - SSS+ => DXRating += {0} - - - Skip - - - > Skipping incompatible patch: {0} - - - Please set repeat start time first - - - Repeat end time cannot be less than repeat start time - - - Important Notice: Test Version - - - You are using AquaMai CI build version. This version is built from the latest mainline code and may contain undocumented configuration changes or potential issues. - - - Play Count:{0} - - - Hide Self-Made Charts - - - Show Self-Made Charts - - - UserAll Upsert Error - - - Playlog save error - - - Touch panel reset - - diff --git a/AquaMai/AquaMai.Core/Resources/Locale.zh.resx b/AquaMai/AquaMai.Core/Resources/Locale.zh.resx deleted file mode 100644 index fac48af0..00000000 --- a/AquaMai/AquaMai.Core/Resources/Locale.zh.resx +++ /dev/null @@ -1,115 +0,0 @@ - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 倒退 << - - - 快进 >> - - - 暂停 - - - 标记结尾 - - - 标记开头 - - - 循环未设定 - - - 循环解除 - - - 循环已设定 - - - 循环开头已设定 - - - 速度 - - - 速度 - - - - 速度重置 - - - 速度 + - - - 加载过程中检测到错误! -- 你是否正在使用魔改的 Assembly-CSharp.dll,这会导致函数不一致而无法找到需要修改的函数 -- 请检查是否有冲突的 Mod,或者开启了不兼容的选项 - - - 正在保存… 请不要关闭游戏 - - - 加载完成! - - - 主服务器通信错误 - - - Aime 读卡器错误 - - - AimeDB 通信错误 - - - 数据下载不成功 - - - 推到鸟加可上 {0} 分 - - - 跳过 - - - > 已跳过加载不兼容的功能: {0} - - - 循环结束时间不能早于开始时间 - - - 请先设置循环开始时间 - - - 重要提示:测试版本 - - - 您正在使用的是 AquaMai CI 构建版本。由于该版本基于最新的主线代码构建,可能包含未通知的配置文件变更或潜在问题。 - - - 游玩次数: {0} - - - 已隐藏所有自制谱 - - - 已显示自制谱 - - - 保存 UserAll 失败 - - - 保存 Playlog 失败 - - - 触摸面板已重置 - - diff --git a/AquaMai/AquaMai.Core/Startup.cs b/AquaMai/AquaMai.Core/Startup.cs deleted file mode 100644 index 8695ad87..00000000 --- a/AquaMai/AquaMai.Core/Startup.cs +++ /dev/null @@ -1,199 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using AquaMai.Core.Attributes; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using MelonLoader; -using UnityEngine; - -namespace AquaMai.Core; - -public class Startup -{ - private static HarmonyLib.Harmony _harmony; - - private static bool _hasErrors; - - private enum ModLifecycleMethod - { - // Invoked before all patches are applied, including core patches - OnBeforeAllPatch, - // Invoked after all patches are applied - OnAfterAllPatch, - // Invoked before the current patch is applied - OnBeforePatch, - // Invoked after the current patch is applied - // Subclasses are treated as separate patches - OnAfterPatch, - // Invoked when an error occurs applying the current patch - // Lifecycle methods' excpetions not included - // Subclasses' error not included - OnPatchError - } - - private static bool ShouldEnableImplicitly(Type type) - { - var implicitEnableAttribute = type.GetCustomAttribute(); - if (implicitEnableAttribute == null) return false; - var referenceField = type.GetField(implicitEnableAttribute.MemberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - var referenceProperty = type.GetProperty(implicitEnableAttribute.MemberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (referenceField == null && referenceProperty == null) - { - throw new ArgumentException($"Field or property {implicitEnableAttribute.MemberName} not found in {type.FullName}"); - } - var referenceMemberValue = referenceField != null ? referenceField.GetValue(null) : referenceProperty.GetValue(null); - if ((bool)referenceMemberValue) - { - MelonLogger.Msg($"Enabled {type.FullName} implicitly"); - return true; - } - return false; - } - - private static void InvokeLifecycleMethod(Type type, ModLifecycleMethod methodName) - { - var method = type.GetMethod(methodName.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - if (method == null) - { - return; - } - var parameters = method.GetParameters(); - var arguments = parameters.Select(p => - { - if (p.ParameterType == typeof(HarmonyLib.Harmony)) return _harmony; - throw new InvalidOperationException($"Unsupported parameter type {p.ParameterType} in lifecycle method {type.FullName}.{methodName}"); - }).ToArray(); - try - { - method.Invoke(null, arguments); - } - catch (TargetInvocationException e) - { - MelonLogger.Error($"Failed to invoke lifecycle method {type.FullName}.{methodName}: {e.InnerException}"); - _hasErrors = true; - } - } - - private static void CollectWantedPatches(List wantedPatches, Type type) - { - if (EnableConditionHelper.ShouldSkipClass(type)) - { - return; - } - - wantedPatches.Add(type); - foreach (var nested in type.GetNestedTypes()) - { - CollectWantedPatches(wantedPatches, nested); - } - } - - private static void ApplyPatch(Type type) - { - MelonLogger.Msg($"> Applying {type}"); - try - { - InvokeLifecycleMethod(type, ModLifecycleMethod.OnBeforePatch); - _harmony.PatchAll(type); - InvokeLifecycleMethod(type, ModLifecycleMethod.OnAfterPatch); - } - catch (Exception e) - { - MelonLogger.Error($"Failed to patch {type}: {e}"); - InvokeLifecycleMethod(type, ModLifecycleMethod.OnPatchError); - _hasErrors = true; - } - } - - private static string ResolveLocale() - { - var localeConfigEntry = ConfigLoader.Config.ReflectionManager.GetEntry("General.Locale"); - var localeValue = (string)ConfigLoader.Config.GetEntryState(localeConfigEntry).Value; - return localeValue switch - { - "en" => localeValue, - "zh" => localeValue, - _ => Application.systemLanguage switch - { - SystemLanguage.Chinese or SystemLanguage.ChineseSimplified or SystemLanguage.ChineseTraditional => "zh", - SystemLanguage.English => "en", - _ => "en" - } - }; - } - - public static void Initialize(Assembly modsAssembly, HarmonyLib.Harmony harmony) - { - MelonLogger.Msg("Loading mod settings..."); - - var configLoaded = ConfigLoader.LoadConfig(modsAssembly); - var lang = ResolveLocale(); - if (configLoaded) - { - ConfigLoader.SaveConfig(lang); // Re-save the config as soon as possible - } - - _harmony = harmony; - - // Init locale with patching C# runtime - // https://stackoverflow.com/questions/1952638/single-assembly-multi-language-windows-forms-deployment-ilmerge-and-satellite-a - ApplyPatch(typeof(I18nSingleAssemblyHook)); - Locale.Culture = CultureInfo.GetCultureInfo(lang); // Must be called after I18nSingleAssemblyHook patched - - // The patch list is ordered - List wantedPatches = []; - - // Must be patched first to support [EnableIf(...)] and [EnableGameVersion(...)] - CollectWantedPatches(wantedPatches, typeof(EnableConditionHelper)); - // Core helpers patched first - CollectWantedPatches(wantedPatches, typeof(MessageHelper)); - CollectWantedPatches(wantedPatches, typeof(MusicDirHelper)); - CollectWantedPatches(wantedPatches, typeof(SharedInstances)); - CollectWantedPatches(wantedPatches, typeof(GuiSizes)); - CollectWantedPatches(wantedPatches, typeof(KeyListener)); - - // Collect patches based on the config - var config = ConfigLoader.Config; - foreach (var section in config.ReflectionManager.Sections) - { - var reflectionType = (Config.Reflection.SystemReflectionProvider.ReflectionType)section.Type; - var type = reflectionType.UnderlyingType; - if (!config.GetSectionState(section).Enabled && !ShouldEnableImplicitly(type)) continue; - CollectWantedPatches(wantedPatches, type); - } - - foreach (var type in wantedPatches) - { - InvokeLifecycleMethod(type, ModLifecycleMethod.OnBeforeAllPatch); - } - foreach (var type in wantedPatches) - { - ApplyPatch(type); - } - foreach (var type in wantedPatches) - { - InvokeLifecycleMethod(type, ModLifecycleMethod.OnAfterAllPatch); - } - - if (_hasErrors) - { - MelonLogger.Warning("========================================================================!!!\n" + Locale.LoadError); - MelonLogger.Warning("==========================================================================="); - } - -# if CI - MelonLogger.Warning(Locale.CiBuildAlertTitle); - MelonLogger.Warning(Locale.CiBuildAlertContent); -# endif - - MelonLogger.Msg(Locale.Loaded); - } - - public static void OnGUI() - { - GuiSizes.SetupStyles(); - } -} diff --git a/AquaMai/AquaMai.Mods/AquaMai.Mods.csproj b/AquaMai/AquaMai.Mods/AquaMai.Mods.csproj deleted file mode 100644 index b04c32a1..00000000 --- a/AquaMai/AquaMai.Mods/AquaMai.Mods.csproj +++ /dev/null @@ -1,128 +0,0 @@ - - - - Release - AnyCPU - {8731C0E0-53BE-4B1B-9828-193E738C6865} - Library - AquaMai.Mods - AquaMai.Mods - net472 - 512 - true - 12 - 414 - $(ProjectDir)../Libs/;$(AssemblySearchPaths) - $(ProjectDir)../Output/ - false - false - - - - false - None - true - prompt - 4 - true - false - - - - DEBUG - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AquaMai/AquaMai.Mods/DeprecationWarning.cs b/AquaMai/AquaMai.Mods/DeprecationWarning.cs deleted file mode 100644 index 4afacde8..00000000 --- a/AquaMai/AquaMai.Mods/DeprecationWarning.cs +++ /dev/null @@ -1,29 +0,0 @@ -using AquaMai.Config.Attributes; - -namespace AquaMai.Mods; - -[ConfigSection( - en: """ - These options have been deprecated and no longer work in the current version. - Remove them to get rid of the warning message at startup. - """, - zh: """ - 这些配置项已经被废弃,在当前版本不再生效 - 删除它们以去除启动时的警告信息 - """, - exampleHidden: true)] -public class DeprecationWarning -{ - [ConfigEntry(hideWhenDefault: true)] - public static readonly bool v1_0_ModKeyMap_TestMode; - - // Print friendly warning messages here. - // Please keep them up-to-date while refactoring the config. - public static void OnBeforeAllPatch() - { - if (v1_0_ModKeyMap_TestMode) - { - MelonLoader.MelonLogger.Warning("ModKeyMap.TestMode has been deprecated (> v1.0). Please use GameSystem.KeyMap.Test instead."); - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/CustomLogo.cs b/AquaMai/AquaMai.Mods/Fancy/CustomLogo.cs deleted file mode 100644 index 54c127f9..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/CustomLogo.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using HarmonyLib; -using Monitor; -using Process; -using UnityEngine; -using UnityEngine.UI; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: "Replace the \"SEGA\" and \"ALL.Net\" logos with custom ones.", - zh: "用自定义的图片替换「SEGA」和「ALL.Net」的标志")] -public class CustomLogo -{ - [ConfigEntry( - en: "Replace the \"SEGA\" logo with a random PNG image from this directory.", - zh: "从此目录中随机选择一张 PNG 图片用于「SEGA」标志")] - private static readonly string segaLogoDir = "LocalAssets/SegaLogo"; - - [ConfigEntry( - en: "Replace the \"ALL.Net\" logo with a random PNG image from this directory.", - zh: "从此目录中随机选择一张 PNG 图片用于「ALL.Net」标志")] - private static readonly string allNetLogoDir = "LocalAssets/AllNetLogo"; - - private readonly static List segaLogo = []; - private readonly static List allNetLogo = []; - - public static void OnBeforePatch() - { - EnumSprite(segaLogo, FileSystem.ResolvePath(segaLogoDir)); - EnumSprite(allNetLogo, FileSystem.ResolvePath(allNetLogoDir)); - } - - private static void EnumSprite(List collection, string path) - { - if (!Directory.Exists(path)) return; - foreach (var file in Directory.EnumerateFiles(path, "*.png")) - { - var data = File.ReadAllBytes(file); - var texture2D = new Texture2D(1, 1, TextureFormat.RGBA32, false); - if (texture2D.LoadImage(data)) - { - collection.Add(Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f))); - } - } - } - - [HarmonyPatch(typeof(AdvertiseProcess), "OnStart")] - [HarmonyPostfix] - private static void AdvProcessPostFix(AdvertiseMonitor[] ____monitors) - { - if (segaLogo.Count > 0) - { - var logo = segaLogo[UnityEngine.Random.Range(0, segaLogo.Count)]; - foreach (var monitor in ____monitors) - { - monitor.transform.Find("Canvas/Main/SegaAllNet_LOGO/NUL_ADT_SegaAllNet_LOGO/SegaLogo").GetComponent().sprite = logo; - } - } - - if (allNetLogo.Count > 0) - { - var logo = allNetLogo[UnityEngine.Random.Range(0, allNetLogo.Count)]; - foreach (var monitor in ____monitors) - { - monitor.transform.Find("Canvas/Main/SegaAllNet_LOGO/NUL_ADT_SegaAllNet_LOGO/AllNetLogo").GetComponent().sprite = logo; - } - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/CustomPlaceName.cs b/AquaMai/AquaMai.Mods/Fancy/CustomPlaceName.cs deleted file mode 100644 index a54f3e72..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/CustomPlaceName.cs +++ /dev/null @@ -1,45 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: """ - Custom shop name in photo. - Also enable shop name display in SDGA. - """, - zh: """ - 自定义拍照的店铺名称 - 同时在 SDGA 中会启用店铺名称的显示(但是不会在游戏里有设置) - """)] -public class CustomPlaceName -{ - [ConfigEntry] - private static readonly string placeName = ""; - - [HarmonyPostfix] - [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] - public static void CheckAuth_Proc(OperationManager __instance) - { - if (string.IsNullOrEmpty(placeName)) - { - return; - } - - __instance.ShopData.ShopName = placeName; - __instance.ShopData.ShopNickName = placeName; - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(ResultCardBaseController), "Initialize")] - public static void Initialize(ResultCardBaseController __instance) - { - if (string.IsNullOrEmpty(placeName)) - { - return; - } - - __instance.SetVisibleStoreName(true); - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/CustomSkins.cs b/AquaMai/AquaMai.Mods/Fancy/CustomSkins.cs deleted file mode 100644 index 46f6f422..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/CustomSkins.cs +++ /dev/null @@ -1,402 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using HarmonyLib; -using MelonLoader; -using Monitor; -using Monitor.Game; -using Process; -using UnityEngine; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: """ - Provide the ability to use custom skins (advanced feature). - Load skin textures from custom paths. - """, - zh: """ - 提供自定义皮肤的能力(高级功能) - 从自定义路径中加载皮肤贴图 - """)] -public class CustomSkins -{ - [ConfigEntry] - private static readonly string skinsDir = "LocalAssets/Skins"; - - private static readonly List ImageExts = [".png", ".jpg", ".jpeg"]; - private static readonly List SlideFanFields = ["_normalSlideFan", "_eachSlideFan", "_breakSlideFan", "_breakSlideFanEff"]; - private static readonly List CustomTrackStartFields = ["_musicBase", "_musicTab", "_musicLvBase", "_musicLvText"]; - - private static Sprite customOutline; - private readonly static Sprite[,] customSlideFan = new Sprite[4, 11]; - - public static readonly Sprite[,] CustomJudge = new Sprite[2, ((int)NoteJudge.ETiming.End + 1)]; - public static readonly Sprite[,,,] CustomJudgeSlide = new Sprite[2, 3, 2, ((int)NoteJudge.ETiming.End + 1)]; - public static readonly Texture2D[] CustomTrackStart = new Texture2D[4]; - - private static bool LoadIntoGameNoteImageContainer(string fieldName, int? idx1, int? idx2, Texture2D texture) - { - // 先确定确实有这个 Field, 如果没有的话可以直接跳过这个文件 - var fieldTraverse = Traverse.Create(typeof(GameNoteImageContainer)).Field(fieldName); - if (!fieldTraverse.FieldExists()) - { - MelonLogger.Msg($"[CustomNoteSkin] Cannot found field {fieldName}"); - return false; - } - - var fieldType = fieldTraverse.GetValueType(); - if (!idx1.HasValue) - { - // 目标 Field 应当是单个 Sprite - if (fieldType != typeof(Sprite)) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite"); - return false; - } - - var target = fieldTraverse.GetValue(); - var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height); - var custom = Sprite.Create( - texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, - 0, SpriteMeshType.Tight, target.border - ); - fieldTraverse.SetValue(custom); - } - else if (!idx2.HasValue) - { - // 目标 Field 是一维数组 - if (fieldType != typeof(Sprite[])) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[]"); - return false; - } - - var targetArray = fieldTraverse.GetValue(); - var target = targetArray[idx1.Value]; - var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height); - var custom = Sprite.Create( - texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, - 0, SpriteMeshType.Tight, target.border - ); - targetArray[idx1.Value] = custom; - } - else - { - // 目标 Field 是二维数组 - if (fieldType != typeof(Sprite[,])) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[,]"); - return false; - } - - var targetArray = fieldTraverse.GetValue(); - var target = targetArray[idx1.Value, idx2.Value]; - var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height); - var custom = Sprite.Create( - texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, - 0, SpriteMeshType.Tight, target.border - ); - targetArray[idx1.Value, idx2.Value] = custom; - } - - return true; - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameNotePrefabContainer), "Initialize")] - private static void LoadNoteSkin() - { - var resolvedDir = FileSystem.ResolvePath(skinsDir); - if (!Directory.Exists(resolvedDir)) return; - - foreach (var laFile in Directory.EnumerateFiles(resolvedDir)) - { - if (!ImageExts.Contains(Path.GetExtension(laFile).ToLowerInvariant())) continue; - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(laFile)); - - var name = Path.GetFileNameWithoutExtension(laFile); - var args = name.Split('_'); - // 文件名的格式是 XXXXXXXX_A_B 表示 GameNoteImageContainer._XXXXXXXX[A, B] - // 视具体情况, A, B 可能不存在 - var fieldName = '_' + args[0]; - int? idx1 = (args.Length < 2) ? null : (int.TryParse(args[1], out var temp) ? temp : null); - int? idx2 = (args.Length < 3) ? null : (int.TryParse(args[2], out temp) ? temp : null); - int? idx3 = (args.Length < 4) ? null : (int.TryParse(args[3], out temp) ? temp : null); - - Traverse traverse; - - if (CustomTrackStartFields.Contains(fieldName)) - { - var i = CustomTrackStartFields.IndexOf(fieldName); - CustomTrackStart[i] = texture; - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (fieldName == "_outline") - { - customOutline = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 1f); - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (fieldName == "_judgeNormal" || fieldName == "_judgeBreak") - { - if (!idx1.HasValue) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); - continue; - } - - var i = (fieldName == "_judgeBreak") ? 1 : 0; - CustomJudge[i, idx1.Value] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 1f); - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (fieldName == "_judgeSlideNormal" || fieldName == "_judgeSlideBreak") - { - if (!idx1.HasValue || !idx2.HasValue || !idx3.HasValue) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs 3 indices"); - continue; - } - - var i = (fieldName == "_judgeSlideBreak") ? 1 : 0; - Vector2 pivot; - switch (idx1.Value) - { - case 0 when idx2.Value == 0: - pivot = new Vector2(0f, 0.5f); - break; - case 0 when idx2.Value == 1: - pivot = new Vector2(1f, 0.5f); - break; - case 1 when idx2.Value == 0: - pivot = new Vector2(0f, 0.3f); - break; - case 1 when idx2.Value == 1: - pivot = new Vector2(1f, 0.3f); - break; - case 2 when idx2.Value == 0: - pivot = new Vector2(0.5f, 0.8f); - break; - case 2 when idx2.Value == 1: - pivot = new Vector2(0.5f, 0.2f); - break; - default: - pivot = new Vector2(0.5f, 0.5f); - break; - } - CustomJudgeSlide[i, idx1.Value, idx2.Value, idx3.Value] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f); - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (SlideFanFields.Contains(fieldName)) - { - if (!idx1.HasValue) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); - continue; - } - - var i = SlideFanFields.IndexOf(fieldName); - customSlideFan[i, idx1.Value] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(1f, 0.5f), 1f); - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (fieldName == "_touchJust") - { - traverse = Traverse.Create(GameNotePrefabContainer.TouchTapB); - var noticeObject = traverse.Field("NoticeObject").Value; - var target = noticeObject.GetComponent(); - var pivot = new Vector2( - target.sprite.pivot.x / target.sprite.rect.width, - target.sprite.pivot.y / target.sprite.rect.height - ); - var custom = Sprite.Create( - texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, - 0, SpriteMeshType.Tight, target.sprite.border - ); - target.sprite = custom; - - traverse = Traverse.Create(GameNotePrefabContainer.TouchTapC); - noticeObject = traverse.Field("NoticeObject").Value; - noticeObject.GetComponent().sprite = custom; - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (fieldName == "_touchHold") - { - if (!idx1.HasValue) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); - continue; - } - - traverse = Traverse.Create(GameNotePrefabContainer.TouchHoldC); - var target = traverse.Field("ColorsObject").Value; - var renderer = target[idx1.Value]; - var pivot = new Vector2( - renderer.sprite.pivot.x / renderer.sprite.rect.width, - renderer.sprite.pivot.y / renderer.sprite.rect.height - ); - var custom = Sprite.Create( - texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, - 0, SpriteMeshType.Tight, renderer.sprite.border - ); - renderer.sprite = custom; - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (fieldName == "_normalTouchBorder") - { - if (!idx1.HasValue) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); - continue; - } - - traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve); - var target = traverse.Field("_reserveSingleSprite").Value; - var targetSprite = target[idx1.Value - 2]; - var pivot = new Vector2( - targetSprite.pivot.x / targetSprite.rect.width, - targetSprite.pivot.y / targetSprite.rect.height - ); - target[idx1.Value - 2] = Sprite.Create( - texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, - 0, SpriteMeshType.Tight, targetSprite.border - ); - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (fieldName == "_eachTouchBorder") - { - if (!idx1.HasValue) - { - MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index"); - continue; - } - - traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve); - var target = traverse.Field("_reserveEachSprite").Value; - var targetSprite = target[idx1.Value - 2]; - var pivot = new Vector2( - targetSprite.pivot.x / targetSprite.rect.width, - targetSprite.pivot.y / targetSprite.rect.height - ); - target[idx1.Value - 2] = Sprite.Create( - texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f, - 0, SpriteMeshType.Tight, targetSprite.border - ); - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - continue; - } - - if (LoadIntoGameNoteImageContainer(fieldName, idx1, idx2, texture)) - { - MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}"); - } - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameCtrl), "Initialize")] - private static void ChangeOutlineTexture(GameObject ____guideEndPointObj) - { - if (____guideEndPointObj != null && customOutline != null) - { - ____guideEndPointObj.GetComponent().sprite = customOutline; - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideFan), "Initialize")] - private static void ChangeFanTexture( - SpriteRenderer[] ____spriteLines, SpriteRenderer[] ____effectSprites, bool ___BreakFlag, bool ___EachFlag - ) - { - Vector3 position; - Sprite sprite; - if (___BreakFlag) - { - for (var i = 0; i < 11; i++) - { - sprite = customSlideFan[2, i]; - if (sprite != null) - { - ____spriteLines[2 * i].sprite = sprite; - position = ____spriteLines[2 * i].transform.localPosition; - ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); - ____spriteLines[2 * i].color = Color.white; - - ____spriteLines[2 * i + 1].sprite = sprite; - position = ____spriteLines[2 * i + 1].transform.localPosition; - ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); - ____spriteLines[2 * i + 1].color = Color.white; - } - - sprite = customSlideFan[3, i]; - if (sprite != null) - { - ____effectSprites[2 * i].sprite = sprite; - position = ____effectSprites[2 * i].transform.localPosition; - ____effectSprites[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); - ____effectSprites[2 * i].color = Color.white; - - ____effectSprites[2 * i + 1].sprite = sprite; - position = ____effectSprites[2 * i + 1].transform.localPosition; - ____effectSprites[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); - ____effectSprites[2 * i + 1].color = Color.white; - } - } - } - else if (___EachFlag) - { - for (var i = 0; i < 11; i++) - { - sprite = customSlideFan[1, i]; - if (sprite != null) - { - ____spriteLines[2 * i].sprite = sprite; - position = ____spriteLines[2 * i].transform.localPosition; - ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); - ____spriteLines[2 * i].color = Color.white; - - ____spriteLines[2 * i + 1].sprite = sprite; - position = ____spriteLines[2 * i + 1].transform.localPosition; - ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); - ____spriteLines[2 * i + 1].color = Color.white; - } - } - } - else - { - for (var i = 0; i < 11; i++) - { - sprite = customSlideFan[0, i]; - if (sprite != null) - { - ____spriteLines[2 * i].sprite = sprite; - position = ____spriteLines[2 * i].transform.localPosition; - ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z); - ____spriteLines[2 * i].color = Color.white; - - ____spriteLines[2 * i + 1].sprite = sprite; - position = ____spriteLines[2 * i + 1].transform.localPosition; - ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z); - ____spriteLines[2 * i + 1].color = Color.white; - } - } - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/CustomTrackStartDiff.cs b/AquaMai/AquaMai.Mods/Fancy/CustomTrackStartDiff.cs deleted file mode 100644 index 41f3623c..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/CustomTrackStartDiff.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; -using UI; -using UnityEngine; -using UnityEngine.UI; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: """ - Custom track start difficulty image (not really custom difficulty). - Requires CustomSkins to be enabled. - Will load four image resources through custom skins: musicBase, musicTab, musicLvBase, musicLvText. - """, - zh: """ - 自定义在歌曲开始界面上显示的难度贴图 (并不是真的自定义难度) - 需要启用自定义皮肤功能 - 会通过自定义皮肤加载四个图片资源: musicBase, musicTab, musicLvBase, musicLvText - """)] -public class CustomTrackStartDiff -{ - // 自定义在歌曲开始界面上显示的难度 (并不是真的自定义难度) - // 需要启用自定义皮肤功能 - // 会加载四个图片资源: musicBase, musicTab, musicLvBase, musicLvText - - [HarmonyPostfix] - [HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")] - private static void DisableTabs( - MultipleImage ____musicBaseImage, - MultipleImage ____musicTabImage, - SpriteCounter ____difficultySingle, - SpriteCounter ____difficultyDouble, - Image ____levelTextImage, - List ____musicLevelSpriteSheets, - TimelineRoot ____musicDetail - ) - { - var texture = CustomSkins.CustomTrackStart[0]; - if (texture != null) - { - ____musicBaseImage.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f); - ____musicBaseImage.ChangeSprite(6); - } - - texture = CustomSkins.CustomTrackStart[1]; - if (texture != null) - { - ____musicTabImage.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f); - ____musicTabImage.ChangeSprite(6); - } - - texture = CustomSkins.CustomTrackStart[2]; - if (texture != null) - { - var lvBase = Traverse.Create(____musicDetail).Field("_lv_Base").Value; - lvBase.MultiSprites[6] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f); - lvBase.ChangeSprite(6); - } - - texture = CustomSkins.CustomTrackStart[3]; - if (texture != null) - { - var original = ____musicLevelSpriteSheets[0].Sheet; - var sheet = new Sprite[original.Length]; - for (var i = 0; i < original.Length; i++) - { - var sprite = original[i]; - sheet[i] = Sprite.Create(texture, sprite.textureRect, new Vector2(0.5f, 0.5f), 100f); - } - - ____difficultySingle.SetSpriteSheet(sheet); - ____difficultyDouble.SetSpriteSheet(sheet); - ____levelTextImage.sprite = sheet[14]; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/CustomVersionString.cs b/AquaMai/AquaMai.Mods/Fancy/CustomVersionString.cs deleted file mode 100644 index cbada55d..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/CustomVersionString.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: "Set the version string displayed at the top-right corner of the screen.", - zh: "把右上角的版本更改为自定义文本")] -public class CustomVersionString -{ - [ConfigEntry] - private static readonly string versionString = ""; - - /* - * Patch displayVersionString Property Getter - */ - [HarmonyPrefix] - [HarmonyPatch(typeof(MAI2System.Config), "displayVersionString", MethodType.Getter)] - public static bool GetDisplayVersionString(ref string __result) - { - if (string.IsNullOrEmpty(versionString)) - { - return true; - } - - __result = versionString; - // Return false to block the original method - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/DemoMaster.cs b/AquaMai/AquaMai.Mods/Fancy/DemoMaster.cs deleted file mode 100644 index 12939c44..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/DemoMaster.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AquaMai.Config.Attributes; -using DB; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Process; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: "Play \"Master\" difficulty on Demo screen.", - zh: "在闲置时的演示画面上播放紫谱而不是绿谱")] -public class DemoMaster -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(AdvDemoProcess), "OnStart")] - public static void AdvDemoProcessPostStart() - { - for (int i = 0; i < 2; i++) - { - var userOption = Singleton.Instance.GetGameScore(i).UserOption; - userOption.NoteSpeed = OptionNotespeedID.Speed6_5; - userOption.TouchSpeed = OptionTouchspeedID.Speed7_0; - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(GamePlayManager), "InitializeAdvertise")] - public static void PreInitializeAdvertise() - { - GameManager.SelectDifficultyID[0] = 3; - GameManager.SelectDifficultyID[1] = 3; - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/AlignCircleSlideJudgeDisplay.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/AlignCircleSlideJudgeDisplay.cs deleted file mode 100644 index 508e5e88..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/AlignCircleSlideJudgeDisplay.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; -using Monitor; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - Make the judgment display of circular Slides align precisely with the judgment line (originally a bit off). - Just like in majdata. - """, - zh: """ - 让圆弧形的 Slide 的判定显示与判定线精确对齐 (原本会有一点歪) - 就像 majdata 里那样 - """)] -public class AlignCircleSlideJudgeDisplay -{ - /* - * 这个 Patch 让圆弧形的 Slide 的判定显示与判定线精确对齐 (原本会有一点歪), 就像 majdata 里那样 - */ - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideRoot), "Initialize")] - private static void FixJudgePosition( - SlideRoot __instance, SlideType ___EndSlideType, SlideJudge ___JudgeObj - ) - { - if (null != ___JudgeObj) - { - float z = ___JudgeObj.transform.localPosition.z; - if (___EndSlideType == SlideType.Slide_Circle_L) - { - float angle = -45.0f - 45.0f * __instance.EndButtonId; - double angleRad = Math.PI / 180.0 * (angle + 90 + 22.5 + 2.6415); - ___JudgeObj.transform.localPosition = new Vector3(480f * (float)Math.Cos(angleRad), 480f * (float)Math.Sin(angleRad), z); - ___JudgeObj.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, angle); - } - else if (___EndSlideType == SlideType.Slide_Circle_R) - { - float angle = -45.0f * __instance.EndButtonId; - double angleRad = Math.PI / 180.0 * (angle + 90 - 22.5 - 2.6415); - ___JudgeObj.transform.localPosition = new Vector3(480f * (float)Math.Cos(angleRad), 480f * (float)Math.Sin(angleRad), z); - ___JudgeObj.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, angle); - } - } - } - - -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/BreakSlideJudgeBlink.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/BreakSlideJudgeBlink.cs deleted file mode 100644 index 175f605c..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/BreakSlideJudgeBlink.cs +++ /dev/null @@ -1,33 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - This Patch makes the Critical judgment of BreakSlide also flicker like BreakTap. - Recommended to use with custom skins (otherwise the visual effect may not be good). - """, - zh: """ - 这个 Patch 让 BreakSlide 的 Critical 判定也可以像 BreakTap 一样闪烁 - 推荐与自定义皮肤一起使用 (否则视觉效果可能并不好) - """)] -public class BreakSlideJudgeBlink -{ - /* - * 这个 Patch 让 BreakSlide 的 Critical 判定也可以像 BreakTap 一样闪烁 - * 推荐与自定义皮肤一起使用 (否则视觉效果可能并不好) - */ - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideJudge), "UpdateBreakEffectAdd")] - private static void FixBreakSlideJudgeBlink( - SpriteRenderer ___SpriteRenderAdd, int ____addEffectCount - ) - { - if (!___SpriteRenderAdd.gameObject.activeSelf) return; - float num = (____addEffectCount & 0b10) >> 1; - ___SpriteRenderAdd.color = new Color(num, num, num, 1f); - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs deleted file mode 100644 index 0614c970..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/CustomNoteTypes.cs +++ /dev/null @@ -1,433 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using AquaMai.Config.Attributes; -using DB; -using HarmonyLib; -using MAI2.Util; -using Manager; -using MelonLoader; -using Monitor; -using UnityEngine; -using AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; - -namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes; - -[ConfigCollapseNamespace] -[ConfigSection( - en: "Custom Note Types.", - zh: "自定义 Note 类型" -)] -public class CustomNoteTypes -{ - /* - * ========== ========== ========== ========== ========== ========== ========== ========== - * 以下内容是为了添加新的 MA2 语法用于表示自定义的 note 类型 - * The following part is to add new MA2 command to Sinmai (representing custom note types) - * - * New note types: - * 1. Slide Super-new Super-hot (NMSSS, BRSSS, EXSSS, BXSSS, CNSSS): - * Definition: ??SSS [bar] [grid] [start pos] [wait] [duration] [end pos] [slide code (string)] - * Represent a slide note with highly customized path (using slide code) - * - * TODO (?) - * Mine notes (P.S. Mine-slides will automatically progress itself) - * Individual tracing duration in conn. slides - * Touch-slides / slides not ending in group A - * Non-C TouchHold - * Spinning tailless star (something like 1$$) - * Hyper Speed Definition ? - */ - public static int TotalMa2RecordCount = -1; - public static int LastMa2RecordID = -1; - public static Array Ma2FileRecordData; - - public static void OnAfterPatch() - { - var arrayTraverse = Traverse.Create(typeof(Ma2fileRecordID)).Field("s_Ma2fileRecord_Data"); - var targetArray = arrayTraverse.GetValue(); - - var nextId = targetArray.Length; - object[][] newEntries = - [ - [nextId++, "NMSSS", "过新过热Slide", NotesTypeID.Def.Slide, SlideType.Slide_MAX, 8, Ma2Category.MA2_Note, 2, 2, 2, 2, 2, 2, 0], - [nextId++, "BRSSS", "过新过热BreakSlide", NotesTypeID.Def.BreakSlide, SlideType.Slide_MAX, 8, Ma2Category.MA2_Note, 2, 2, 2, 2, 2, 2, 0], - [nextId++, "EXSSS", "过新过热ExSlide", NotesTypeID.Def.ExSlide, SlideType.Slide_MAX, 8, Ma2Category.MA2_Note, 2, 2, 2, 2, 2, 2, 0], - [nextId++, "BXSSS", "过新过热ExBreakSlide", NotesTypeID.Def.ExBreakSlide, SlideType.Slide_MAX, 8, Ma2Category.MA2_Note, 2, 2, 2, 2, 2, 2, 0], - [nextId++, "CNSSS", "过新过热ConnSlide", NotesTypeID.Def.ConnectSlide, SlideType.Slide_MAX, 8, Ma2Category.MA2_Note, 2, 2, 2, 2, 2, 2, 0], - ]; - - // Ma2fileRecordID.Ma2fileRecord_Data is private, so we need this shit. - var structType = targetArray.GetValue(0).GetType(); - var constructor = AccessTools.Constructor(structType, - [ - typeof(int), typeof(string), typeof(string), typeof(NotesTypeID.Def), typeof(SlideType), typeof(int), - typeof(Ma2Category), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), - typeof(int) - ]); - - Ma2FileRecordData = Array.CreateInstance(structType, targetArray.Length + newEntries.Length); - for (var i = 0; i < targetArray.Length; i++) - { - Ma2FileRecordData.SetValue(targetArray.GetValue(i), i); - } - - for (var i = 0; i < newEntries.Length; i++) - { - var j = targetArray.Length + i; - var obj = constructor.Invoke(newEntries[i]); - Ma2FileRecordData.SetValue(obj, j); - } - - arrayTraverse.SetValue(Ma2FileRecordData); - TotalMa2RecordCount = Ma2FileRecordData.Length; - LastMa2RecordID = TotalMa2RecordCount - 1; - MelonLogger.Msg($"[CustomNoteType] MA2 record data extended, total count: {TotalMa2RecordCount}"); - - // Initialize related classes ... - SlideDataBuilder.InitializeHitAreasLookup(); - MelonLogger.Msg($"[CustomNoteType] HitAreasLookup initialized, total count: {SlideDataBuilder.HitAreasLookup.Count}"); - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(Ma2fileRecordID), "findID")] - public static bool FindIDPrefix(string enumName, ref Ma2fileRecordID.Def __result) - { - // I don't know why but patching findID() leads to a completely invalid result - // Sometimes it will even throw an exception - // So I can only prefix it and override it - __result = Ma2fileRecordID.Def.Invalid; - for (var i = 0; i < TotalMa2RecordCount; i++) - { - var item = Ma2FileRecordData.GetValue(i); - if (Traverse.Create(item).Field("enumName").Value == enumName) - { - __result = (Ma2fileRecordID.Def)i; - } - } - - return false; - } - - [HarmonyPatch] - public static class Ma2RecordValidation - { - public static IEnumerable TargetMethods() - { - return - [ - // AccessTools.Method(typeof(Ma2fileRecordID), "findID"), - AccessTools.Method(typeof(Ma2fileRecordID), "clamp"), - AccessTools.Method(typeof(Ma2fileRecordID), "getClampValue"), - AccessTools.Method(typeof(Ma2fileRecordID), "isValid"), - AccessTools.Method(typeof(Ma2fileRecordID_Extension), "isValid"), - ]; - } - - public static IEnumerable Transpiler(IEnumerable instructions) - { - - foreach (var inst in instructions) - { - if (inst.LoadsConstant(142)) - { - var instNew = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(CustomNoteTypes), "TotalMa2RecordCount")); - yield return instNew; - } - else if (inst.LoadsConstant(141)) - { - var instNew = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(CustomNoteTypes), "LastMa2RecordID")); - yield return instNew; - } - else - { - yield return inst; - } - } - } - } - - /* - * ========== ========== ========== ========== ========== ========== ========== ========== - * 以下内容是给新的 MA2 语法写解析器 - */ - - /* - * 给新建的 noteData 初始化应有的数据, 仅仅是照搬了 NotesReader.loadNote - */ - public static void PrepareBasicNoteData(NoteData noteData, NotesReader reader, - MA2Record record, int index, ref int noteIndex, OptionMirrorID mirrorMode) - { - noteData.type = record.getType().getNotesTypeId(); - noteData.time.init(record.getBar(), record.getGrid(), reader); - noteData.end = noteData.time; - noteData.startButtonPos = MaiGeometry.MirrorInfo[(int)mirrorMode, record.getPos()]; - noteData.index = index; - var num = record.getGrid() % 96; - if (num == 0) - { - noteData.beatType = NoteData.BeatType.BeatType04; - } - else if (num % 48 == 0) - { - noteData.beatType = NoteData.BeatType.BeatType08; - } - else if (num % 24 == 0) - { - noteData.beatType = NoteData.BeatType.BeatType16; - } - else if (num % 16 == 0) - { - noteData.beatType = NoteData.BeatType.BeatType24; - } - else - { - noteData.beatType = NoteData.BeatType.BeatTypeOther; - } - noteData.indexNote = noteIndex; - ++noteIndex; - } - - /* - * 给新建的 noteData 填入基本的 slide 相关数据, 仅仅是照搬了 NotesReader.loadNote - */ - public static void PrepareBasicSlideData(NoteData noteData, NotesReader reader, MA2Record record, int noteIndex, - ref int slideIndex, OptionMirrorID mirrorMode) - { - noteData.indexSlide = slideIndex++; - var slideData = noteData.slideData; - var slideWaitLen = record.getSlideWaitLen(); - var slideShootLen = record.getSlideShootLen(); - slideData.targetNote = MaiGeometry.MirrorInfo[(int)mirrorMode, record.getSlideEndPos()]; - slideData.shoot.time.init(record.getBar(), record.getGrid() + slideWaitLen, reader); - slideData.shoot.index = noteIndex; - slideData.arrive.time.init(record.getBar(), record.getGrid() + slideWaitLen + slideShootLen, reader); - slideData.arrive.index = noteIndex; - noteData.end = slideData.arrive.time; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(NotesReader), "loadNote")] - public static bool LoadCustomNote(NotesReader __instance, ref bool __result, NotesData ____note, int ____playerID, - MA2Record rec, int index, ref int noteIndex, ref int slideIndex) - { - if (rec.getType() < Ma2fileRecordID.Def.End) - { - // builtin record type - return true; - } - - MelonLogger.Msg($"[CustomNoteType] Custom note | {rec._str.Count} | {rec.getStr(0)} {rec.getStr(1)} {rec.getStr(2)} {rec.getStr(3)} {rec.getStr(4)} {rec.getStr(5)} {rec.getStr(6)} {rec.getStr(7)} {rec.getStr(8)}"); - - var flag = true; - switch (rec.getType().getEnumName()) - { - case "NMSSS": - case "BRSSS": - case "EXSSS": - case "BXSSS": - case "CNSSS": - var noteData = new CustomSlideNoteData(); - var mirrorMode = Singleton.Instance.GetGameScore(____playerID).UserOption.MirrorMode; - PrepareBasicNoteData(noteData, __instance, rec, index, ref noteIndex, mirrorMode); - PrepareBasicSlideData(noteData, __instance, rec, noteIndex, ref slideIndex, mirrorMode); - var success = noteData.ParseSlideCode(rec.getStr(7), mirrorMode); - if (success) - { - ____note._noteData.Add(noteData); - } - else - { - flag = false; - } - break; - default: - flag = false; - break; - } - __result = flag; - return false; - } - - /* - * ========== ========== ========== ========== ========== ========== ========== ========== - * 以下内容是为了实现自定义 Slide - * - */ - - /* - * 把 GetSlidePath 和 GetSlideHitArea 和 GetSlideLength 重定向到我可以控制的函数上, 并且多推几个参数进来 - */ - [HarmonyPatch] - public static class SlideNoteDataHack - { - public static IEnumerable TargetMethods() - { - return - [ - AccessTools.Method(typeof(SlideRoot), "Initialize"), - AccessTools.Method(typeof(SlideRoot), "GetSlideArrowNum", [typeof(NoteData)]), - AccessTools.Method(typeof(StarNote), "Initialize"), - AccessTools.Method(typeof(BreakStarNote), "Initialize"), - ]; - } - - public static IEnumerable Transpiler(IEnumerable instructions) - { - var methodGetSlidePath = AccessTools.Method(typeof(SlideManager), "GetSlidePath"); - var methodGetSlidePathRedirect = AccessTools.Method(typeof(CustomNoteTypes), "GetSlidePathRedirect"); - var methodGetSlideHitArea = AccessTools.Method(typeof(SlideManager), "GetSlideHitArea"); - var methodGetSlideHitAreaRedirect = AccessTools.Method(typeof(CustomNoteTypes), "GetSlideHitAreaRedirect"); - var methodGetSlideLength = AccessTools.Method(typeof(SlideManager), "GetSlideLength"); - var methodGetSlideLengthRedirect = AccessTools.Method(typeof(CustomNoteTypes), "GetSlideLengthRedirect"); - var fieldSlideData = AccessTools.Field(typeof(NoteData), "slideData"); - - var oldInstList = new List(instructions); - var newInstList = new List(); - CodeInstruction instToInject = null; - - for (var i = 0; i < oldInstList.Count; ++i) - { - var inst = oldInstList[i]; - if (inst.LoadsField(fieldSlideData)) - { - // 以 GetSlidePath 为例, 我们需要把下面这个调用: - // Singleton.Instance.GetSlidePath( - // noteData.slideData.type, noteData.startButtonPos, - // noteData.slideData.targetNote, this.ButtonId - // ) - // 里的 noteData 拿到手 - // 所以就记录上一次 ldfld NoteData::slideData 的位置, 往前找一个 IL code - // 找到的就是 load 这个 noteData 的位置 - // 然后在后续调用 GetSlidePath 时, 先重复一遍 load 把这个 noteData 入栈, 然后重定向到一个新的函数上去 - instToInject = oldInstList[i - 1]; - newInstList.Add(inst); - } - else if (inst.Calls(methodGetSlidePath)) - { - newInstList.Add(instToInject!.Clone()); - newInstList.Add(new CodeInstruction(OpCodes.Call, methodGetSlidePathRedirect)); - instToInject = null; - } - else if (inst.Calls(methodGetSlideHitArea)) - { - newInstList.Add(instToInject!.Clone()); - newInstList.Add(new CodeInstruction(OpCodes.Call, methodGetSlideHitAreaRedirect)); - instToInject = null; - } - else if (inst.Calls(methodGetSlideLength)) - { - newInstList.Add(instToInject!.Clone()); - newInstList.Add(new CodeInstruction(OpCodes.Call, methodGetSlideLengthRedirect)); - instToInject = null; - } - else - { - newInstList.Add(inst); - } - } - return newInstList; - } - } - - - public static List GetSlidePathRedirect(SlideManager instance, SlideType slideType, int start, int end, - int starButton, NoteData noteData) - { - // MelonLogger.Msg($"[CustomNoteType] GetSlidePath Redirected!"); - // MelonLogger.Msg($"{noteData.indexNote} {noteData.indexSlide} {slideType} {start} {end} {starButton}"); - if (noteData is CustomSlideNoteData data) - { - // MelonLogger.Msg($"[CustomNoteType] Successfully injected custom path {data.SlideCode}"); - return data.SlidePathList[starButton]; - } - return instance.GetSlidePath(slideType, start, end, starButton); - } - - public static List GetSlideHitAreaRedirect(SlideManager instance, SlideType slideType, - int start, int end, int starButton, NoteData noteData) - { - // MelonLogger.Msg($"[CustomNoteType] GetSlideHitArea Redirected!"); - // MelonLogger.Msg($"{noteData.indexNote} {noteData.indexSlide} {slideType} {start} {end} {starButton}"); - if (noteData is CustomSlideNoteData data) - { - // MelonLogger.Msg($"[CustomNoteType] Successfully injected custom hit areas {data.SlideCode}"); - return data.SlideHitAreasList[starButton]; - } - return instance.GetSlideHitArea(slideType, start, end, starButton); - } - - public static float GetSlideLengthRedirect(SlideManager instance, SlideType slideType, - int start, int end, NoteData noteData) - { - // MelonLogger.Msg($"[CustomNoteType] GetSlideLength Redirected!"); - // MelonLogger.Msg($"{noteData.indexNote} {noteData.indexSlide} {slideType} {start} {end}"); - if (noteData is CustomSlideNoteData data) - { - // MelonLogger.Msg($"[CustomNoteType] Successfully injected custom path length {data.SlideCode}"); - return data.SlidePathLength; - } - return instance.GetSlideLength(slideType, start, end); - } - - - - [HarmonyPatch] - public static class Debuging - { - public static IEnumerable TargetMethods() - { - return - [ - AccessTools.Method(typeof(SlideRoot), "Initialize"), - // AccessTools.Method(typeof(SlideRoot), "GetSlideArrowNum", []), - // AccessTools.Method(typeof(SlideRoot), "GetSlideArrowNum", [typeof(NoteData)]), - // AccessTools.Method(typeof(SlideRoot), "GetArrowData"), - // AccessTools.Method(typeof(SlideRoot), "totalDistance"), - // AccessTools.Method(typeof(SlideRoot), "GetActiveArrowNum"), - // AccessTools.Method(typeof(SlideJudge), "SetJudgeType"), - ]; - } - - public static void Prefix(MethodBase __originalMethod, object[] __args) - { - var msg = "[CustomNoteType] Before "; - msg += __originalMethod.DeclaringType!.FullName + "." + __originalMethod.Name + " ("; - var infos = __originalMethod.GetParameters() - .Select((x, i) => x.ParameterType.FullName + " " + x.Name + " = " + GetString(__args[i])) - .ToArray(); - msg += infos.Length > 0 ? infos.Aggregate((a, b) => a + ", " + b) : "void"; - msg += ")"; - MelonLogger.Msg(msg); - } - - public static void Postfix(MethodBase __originalMethod, object[] __args) - { - var msg = "[CustomNoteType] After "; - msg += __originalMethod.DeclaringType!.FullName + "." + __originalMethod.Name + " ("; - var infos = __originalMethod.GetParameters() - .Select((x, i) => x.ParameterType.FullName + " " + x.Name + " = " + GetString(__args[i])) - .ToArray(); - msg += infos.Length > 0 ? infos.Aggregate((a, b) => a + ", " + b) : "void"; - msg += ")"; - MelonLogger.Msg(msg); - } - - public static string GetString(object value) - { - if (value is CustomSlideNoteData data) - { - return $""; - } - - if (value is NoteData data2) - { - return $""; - } - - return value.ToString(); - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs deleted file mode 100644 index a7531e8a..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/CustomSlideNoteData.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using DB; -using Manager; -using MelonLoader; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; - -public class CustomSlideNoteData: NoteData -{ - public string SlideCode; - public List> SlidePathList = new List>(); - public List> SlideHitAreasList = new List>(); - public float SlidePathLength; - - public bool ParseSlideCode(string slideCode, OptionMirrorID mirrorMode) - { - if (string.IsNullOrEmpty(slideCode)) - { - return false; - } - - SlidePathList.Clear(); - SlideHitAreasList.Clear(); - - this.SlideCode = slideCode; - var path = SlideCodeParser.Parse(slideCode); - if (path == null) - { - return false; - } - - var arrowData = SlideDataBuilder.BuildArrowData(path); - SlidePathLength = (float)path.GetPathLength(); - var hitAreaData = SlideDataBuilder.BuildHitAreas(path); - for (var i = 0; i < 8; i++) - { - SlidePathList.Add(SlideDataBuilder.ConvertAndRotateArrowData(arrowData, i, mirrorMode)); - SlideHitAreasList.Add(SlideDataBuilder.ConvertAndRotateHitAreas(hitAreaData, i, mirrorMode)); - } - - var msg = string.Join(", ", - hitAreaData.Select(x => x.PanelAreas).Select(x => string.Join("/", x.Cast()))); - MelonLogger.Msg(msg); - - this.slideData.type = path.GetEndType(mirrorMode); - - return true; - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/MaiGeometry.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/MaiGeometry.cs deleted file mode 100644 index 7866424b..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/MaiGeometry.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Numerics; - -namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; - -public static class MaiGeometry -{ - public struct CircleStruct(Complex center, double radius) - { - public Complex Center = center; - public double Radius = radius; - } - - public static readonly double CanvasWidth = 1080.0; - public static readonly double MainRadius = 480.0; - public static readonly double CenterRadius = MainRadius * Math.Cos(Math.PI * 3 / 8); - public static readonly double GroupBRadius = CenterRadius / Math.Cos(Math.PI / 8); - - private static readonly double _b = Math.Cos(Math.PI / 8) / 2; - private static readonly double _a = 1 - _b; - private static readonly double _theta = Math.PI / 4; - private static readonly double _s = (_a * _a + _b * _b - 2 * _a * _b * Math.Cos(_theta)) / - (2 * _a - 2 * _b * Math.Cos(_theta)); - - public static readonly double PPQQRadius = MainRadius * _b; - public static readonly double TransferRadius = MainRadius * (_b + _s); - public static readonly double EdgeTransferAngle = _theta; - public static readonly double PPQQTransferAngle = - Math.Acos((_s * _s + _b * _b - (_a - _s) * (_a - _s)) / (2 * _b * _s)); - - public static readonly double DefaultDistance = MainRadius * Math.PI / 32; - - public static readonly int[,] MirrorInfo = new int[4, 17] - { - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, // Normal - { 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 16 }, // L <-> R - { 3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12, 16 }, // U <-> D - { 4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11, 16 } // rotate 180 deg - }; - - /// - /// Note: idx is 1-based, not 0-based - /// - public static Complex PointGroupA(int idx) - { - var angle = Math.PI * (5.0 / 8.0 - idx / 4.0); - return Complex.FromPolarCoordinates(MainRadius, angle); - } - - /// - /// Note: idx is 1-based, not 0-based - /// - public static Complex PointGroupB(int idx) - { - var angle = Math.PI * (5.0 / 8.0 - idx / 4.0); - return Complex.FromPolarCoordinates(GroupBRadius, angle); - } - - public static Complex Center() - { - return Complex.Zero; - } - - /// - /// idx 0 is center circle, idx 1~8 are ppqq circles, idx 9 is outer circle - /// - public static CircleStruct GetCircle(int idx) - { - if (idx == 0) - { - return new CircleStruct(Complex.Zero, CenterRadius); - } - - if (idx == 9) - { - return new CircleStruct(Complex.Zero, MainRadius); - } - - var angle = Math.PI * (3.0 / 4.0 - idx / 4.0); - var center = Complex.FromPolarCoordinates(PPQQRadius, angle); - return new CircleStruct(center, PPQQRadius); - } - - /// - /// Note: idx is 1-based, not 0-based - /// - /// CircleStruct TransferCircle, double TransferStartAngle, double TransferEndAngle - public static Tuple TransferOutData(int idx, bool isccw) - { - var ppqqRad = Math.PI * (3.0 / 4.0 - idx / 4.0); - double startAngle, endAngle; - if (isccw) - { - startAngle = ppqqRad - PPQQTransferAngle; - endAngle = ppqqRad + EdgeTransferAngle; - } - else - { - startAngle = ppqqRad + PPQQTransferAngle; - endAngle = ppqqRad - EdgeTransferAngle; - } - var d = MainRadius - TransferRadius; - var center = Complex.FromPolarCoordinates(d, endAngle); - return new Tuple(new CircleStruct(center, TransferRadius), - Math.IEEERemainder(startAngle, Math.PI * 2), Math.IEEERemainder(endAngle, Math.PI * 2)); - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/ParametricSlidePath.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/ParametricSlidePath.cs deleted file mode 100644 index 26832c42..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/ParametricSlidePath.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using DB; -using Manager; - -namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; - -public class ParametricSlidePath -{ - public enum ParseMarker - { - None = 0, - SmoothAlign, - ForceAlign, - SharpCorner - } - - public abstract class PathSegment - { - public ParseMarker ParseMarker = ParseMarker.None; - public double ArrowDistance = MaiGeometry.DefaultDistance; - - public abstract bool DoAngleLerp { get; } - - public abstract Complex GetPointAt(double t); - - public abstract Complex GetTangentAt(double t); - - public abstract double GetSegmentLength(); - - public void SetParseMarker(ParseMarker marker) => ParseMarker = marker; - - public void SetArrowDistance(double distance) => ArrowDistance = distance; - } - - public class LineSegment(Complex start, Complex end) : PathSegment - { - public readonly Complex StartPoint = start; - public readonly Complex EndPoint = end; - - public override bool DoAngleLerp { get; } = false; - - public override Complex GetPointAt(double t) - { - return StartPoint + (EndPoint - StartPoint) * t; - } - - public override Complex GetTangentAt(double t) - { - var v = EndPoint - StartPoint; - return v / v.Magnitude; - } - - public override double GetSegmentLength() - { - return (EndPoint - StartPoint).Magnitude; - } - } - - public class ArcSegment(MaiGeometry.CircleStruct circle, double startAngle, double endAngle) : PathSegment - { - public readonly MaiGeometry.CircleStruct Circle = circle; - public readonly double StartAngle = startAngle; - public readonly double EndAngle = endAngle; - - public override bool DoAngleLerp { get; } = true; - - public override Complex GetPointAt(double t) - { - var angle = StartAngle + t * (EndAngle - StartAngle); - return Circle.Center + Complex.FromPolarCoordinates(Circle.Radius, angle); - } - - public override Complex GetTangentAt(double t) - { - var angle = StartAngle + t * (EndAngle - StartAngle); - if (StartAngle < EndAngle) - { - return Complex.FromPolarCoordinates(1, angle) * Complex.ImaginaryOne; - } - else - { - return Complex.FromPolarCoordinates(-1, angle) * Complex.ImaginaryOne; - } - } - - public override double GetSegmentLength() - { - return Math.Abs(EndAngle - StartAngle) * Circle.Radius; - } - } - - public class CircleSegment(MaiGeometry.CircleStruct circle, double startAngle, bool isCcw) : PathSegment - { - public readonly MaiGeometry.CircleStruct Circle = circle; - public readonly double StartAngle = startAngle; - public readonly bool IsCcw = isCcw; - - public override bool DoAngleLerp { get; } = true; - - public override Complex GetPointAt(double t) - { - double angle; - if (IsCcw) - { - angle = StartAngle + t * Math.PI * 2f; - } - else - { - angle = StartAngle - t * Math.PI * 2f; - } - - return Circle.Center + Complex.FromPolarCoordinates(Circle.Radius, angle); - } - - public override Complex GetTangentAt(double t) - { - double angle; - if (IsCcw) - { - angle = StartAngle + t * Math.PI * 2f; - return Complex.FromPolarCoordinates(1, angle) * Complex.ImaginaryOne; - } - else - { - angle = StartAngle - t * Math.PI * 2f; - return Complex.FromPolarCoordinates(-1, angle) * Complex.ImaginaryOne; - } - } - - public override double GetSegmentLength() - { - return Math.PI * Circle.Radius * 2; - } - } - - - public readonly PathSegment[] Segments; - public readonly double[] Fractions; - public readonly double[] AccumulatedLengths; - - public ParametricSlidePath(IEnumerable pathSegments) - { - Segments = pathSegments.ToArray(); - if (Segments.Length == 0) - { - throw new ArgumentException("At least one path segment is required."); - } - var lengths = Segments.Select(s => s.GetSegmentLength()); - var sum = 0.0; - AccumulatedLengths = lengths.Select(x => (sum += x)).ToArray(); - Fractions = AccumulatedLengths.Select(x => x / sum).ToArray(); - } - - public PathSegment GetSegmentAt(double t, out double segmentT) - { - if (t <= 0.0) - { - segmentT = 0.0; - return Segments[0]; - } - - if (t >= 1.0) - { - segmentT = 1.0; - return Segments[Segments.Length - 1]; - } - - var idx = Array.BinarySearch(Fractions, t); - if (idx < 0) - { - idx = ~idx; // first entry > t - } - // if idx >= 0 then idx is the entry == t - // so Fractions[idx-1] < t and Fractions[idx] >= t - // Note: Fractions[i] marks the end point of Segments[i] - - if (idx >= Segments.Length) - { - segmentT = 1.0; - return Segments[Segments.Length - 1]; - } - - if (idx == 0) - { - segmentT = t / Fractions[0]; - return Segments[0]; - } - - segmentT = (t - Fractions[idx - 1]) / (Fractions[idx] - Fractions[idx - 1]); - return Segments[idx]; - } - - public double GetPathLength() => AccumulatedLengths[AccumulatedLengths.Length - 1]; - - public Complex GetPointAt(double t) - { - var segment = GetSegmentAt(t, out var segT); - return segment.GetPointAt(segT); - } - - public Complex GetTangentAt(double t) - { - var segment = GetSegmentAt(t, out var segT); - return segment.GetTangentAt(segT); - } - - public SlideType GetEndType(OptionMirrorID mirrorMode) - { - var lastSegment = Segments[Segments.Length - 1]; - var flip = mirrorMode == OptionMirrorID.LR || mirrorMode == OptionMirrorID.UD; - if (lastSegment is CircleSegment circle) - { - return circle.IsCcw != flip ? SlideType.Slide_Circle_L : SlideType.Slide_Circle_R; - } - - if (lastSegment is ArcSegment arc) - { - return (arc.EndAngle > arc.StartAngle) != flip ? SlideType.Slide_Circle_L : SlideType.Slide_Circle_R; - } - - return SlideType.Slide_Straight; - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs deleted file mode 100644 index 1aaeb7a4..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideCodeParser.cs +++ /dev/null @@ -1,260 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using MelonLoader; - -namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; - -public static class SlideCodeParser -{ - public enum CommandType - { - Invalid = -1, - NodeA = 0, - NodeB = 1, - NodeC = 2, - OrbitCCW = 3, - OrbitCW = 4, - NodeEnd = 5 - } - - public struct Command(CommandType type, int value) - { - public CommandType Type = type; - public int Value = value; - - public static bool IsSame(Command a, Command b) - { - return a.Type == b.Type && a.Value == b.Value; - } - } - - public static readonly char[] CommandChars = - [ - 'A', 'B', 'C', 'P', 'Q', 'K' - ]; - - public static int TryParseDigit(char c) - { - if (c >= '0' && c <= '9') return c - '0'; - return -1; - } - - public static List ParseCommands(string code) - { - if (!CommandChars.Contains(code[1])) - { - throw new ArgumentException($"the 2nd char should be a command"); - } - - if (code[code.Length - 2] != 'K') - { - throw new ArgumentException($"should end with 'K' command"); - } - - var commands = new List(); - var currentType = CommandType.NodeA; - var value = TryParseDigit(code[0]); - if (value < 0) throw new ArgumentException($"invalid char '{code[0]}'"); - - commands.Add(new Command(currentType, value)); - - for (var ptr = 1; ptr < code.Length; ptr++) - { - var ch = code[ptr]; - if (CommandChars.Contains(ch)) - { - currentType = (CommandType) Array.IndexOf(CommandChars, ch); - if (currentType == CommandType.NodeC) - { - commands.Add(new Command(CommandType.NodeC, 0)); - } - } - else - { - value = TryParseDigit(ch); - if (value < 0) throw new ArgumentException($"invalid char '{ch}'"); - if (currentType == CommandType.NodeC) - { - throw new ArgumentException($"digit should not follow 'C'"); - } - commands.Add(new Command(currentType, value)); - } - } - return commands; - } - - public static Complex GetNodePosition(Command cmd) - { - switch (cmd.Type) - { - case CommandType.NodeA: - case CommandType.NodeEnd: - return MaiGeometry.PointGroupA(cmd.Value); - case CommandType.NodeB: - return MaiGeometry.PointGroupB(cmd.Value); - case CommandType.NodeC: - return MaiGeometry.Center(); - default: - throw new ArgumentException($"invalid type for node: {cmd.Type}"); - } - } - - public static void NodeToNode(SlidePathGenerator generator, Command last, Command current) - { - if (Command.IsSame(last, current)) return; - generator.LineToPoint(GetNodePosition(current)); - } - - public static void NodeToOrbit(SlidePathGenerator generator, Command last, Command current) - { - var isCcw = (current.Type == CommandType.OrbitCCW); - var node = GetNodePosition(last); - var orbit = MaiGeometry.GetCircle(current.Value); - var diff = node - orbit.Center; - if (Math.Abs(diff.Magnitude - orbit.Radius) < 0.1) - { - if (last.Type == CommandType.NodeA && current.Value == 9) - { - generator.TrySetLastParseMarker(ParametricSlidePath.ParseMarker.ForceAlign); - } - return; // node on circle, do nothing - } - - if (diff.Magnitude < orbit.Radius) - throw new ArgumentException($"impossible: {last.Type}{last.Value} -> Orbit{current.Value}"); - - generator.TangentToCircle(orbit, isCcw); - } - - public static void OrbitToNode(SlidePathGenerator generator, Command last, Command current) - { - var isCcw = (last.Type == CommandType.OrbitCCW); - var node = GetNodePosition(current); - var orbit = MaiGeometry.GetCircle(last.Value); - var diff = node - orbit.Center; - if (Math.Abs(diff.Magnitude - orbit.Radius) < 0.1) - { - generator.ArcToAngle(orbit.Center, diff.Phase, isCcw, false); - return; - } - - if (diff.Magnitude < orbit.Radius) - throw new ArgumentException($"impossible: Orbit{last.Value} -> {current.Type}{current.Value}"); - - generator.ArcToTangentTowards(node, orbit.Center, isCcw); - generator.LineToPoint(node); - } - - public static void OrbitToOrbit(SlidePathGenerator generator, Command last, Command current) - { - if (current.Type != last.Type) throw new ArgumentException($"orbit type mismatch"); - - var isCcw = (last.Type == CommandType.OrbitCCW); - var lastOrbit = MaiGeometry.GetCircle(last.Value); - var currentOrbit = MaiGeometry.GetCircle(current.Value); - if (current.Value == last.Value) - { - generator.FullCircle(lastOrbit.Center, isCcw); - return; - } - - if (last.Value == 0 && current.Value == 9 || last.Value == 9 && current.Value == 0) - throw new ArgumentException($"impossible: Orbit{last.Value} -> Orbit{current.Value}"); - - if (current.Value == 9) - { - var data = MaiGeometry.TransferOutData(last.Value, isCcw); - generator.ArcToAngle(lastOrbit.Center, data.Item2, isCcw, false); - generator.ArcToAngle(data.Item1.Center, data.Item3, isCcw, false); - generator.TrySetLastParseMarker(ParametricSlidePath.ParseMarker.SmoothAlign); - return; - } - - if (last.Value == 9) - { - var data = MaiGeometry.TransferOutData(current.Value, !isCcw); - generator.ArcToAngle(lastOrbit.Center, data.Item3, isCcw, true); - generator.ArcToAngle(data.Item1.Center, data.Item2, isCcw, false); - return; - } - - generator.ExternTangentTransfer(lastOrbit.Center, currentOrbit, isCcw); - } - - public static ParametricSlidePath Parse(string code) - { - try - { - var commands = ParseCommands(code); - var lastCmd = commands[0]; - // The first command is guarantee to be 'A' - var generator = SlidePathGenerator.BeginAt(MaiGeometry.PointGroupA(lastCmd.Value)); - - for (var i = 1; i < commands.Count; i++) - { - var cmd = commands[i]; - switch (cmd.Type) - { - case CommandType.NodeA: - case CommandType.NodeB: - case CommandType.NodeC: - case CommandType.NodeEnd: - switch (lastCmd.Type) - { - case CommandType.NodeA: - case CommandType.NodeB: - case CommandType.NodeC: - NodeToNode(generator, lastCmd, cmd); - break; - case CommandType.OrbitCCW: - case CommandType.OrbitCW: - OrbitToNode(generator, lastCmd, cmd); - break; - case CommandType.NodeEnd: - throw new ArgumentException($"'K' should be the last command"); - default: - throw new ArgumentOutOfRangeException(); - } - break; - case CommandType.OrbitCCW: - case CommandType.OrbitCW: - switch (lastCmd.Type) - { - case CommandType.NodeA: - case CommandType.NodeB: - case CommandType.NodeC: - NodeToOrbit(generator, lastCmd, cmd); - break; - case CommandType.OrbitCCW: - case CommandType.OrbitCW: - OrbitToOrbit(generator, lastCmd, cmd); - break; - case CommandType.NodeEnd: - throw new ArgumentException($"'K' should be the last command"); - default: - throw new ArgumentOutOfRangeException(); - } - break; - default: - throw new ArgumentOutOfRangeException(); - } - - lastCmd = cmd; - } - - return generator.GeneratePath(); - } - catch (ArgumentException e) - { - var msg = $"Invalid code: {code}"; - if (e.Message != "") - { - msg += $", {e.Message}"; - } - MelonLogger.Error(msg); - return null; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideDataBuilder.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideDataBuilder.cs deleted file mode 100644 index 6f6abb5b..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlideDataBuilder.cs +++ /dev/null @@ -1,473 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; -using DB; -using Manager; -using Vector4 = UnityEngine.Vector4; - -namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; - -public static class SlideDataBuilder -{ - public readonly struct ArrowData(Complex point, Complex tangent, double length) - { - public readonly Complex Point = point; - public readonly Complex Tangent = tangent; - public readonly double Length = length; - } - - public static List BuildArrowData(ParametricSlidePath path) - { - var result = new List(); - var totalLength = path.GetPathLength(); - var totalSegCount = path.Segments.Length; - - var length = 0.0; - var segIdx = 0; - var isSwitching = false; - - while (length < totalLength) - { - var t = length / totalLength; - var pt = path.GetPointAt(t); - var tg = path.GetTangentAt(t); - - var t2 = (length + 10.0) / totalLength; - if ((path.GetTangentAt(t2) - tg).Magnitude < 0.2) // 0.2 -> ~ 11.48 deg apart - { - // use secant instead of tangent (for better visual quality) - tg = path.GetPointAt(t2) - pt; - tg /= tg.Magnitude; - } - - if (isSwitching) - { - // around connecting point of 2 segments, smoothing the transition - var last = result[result.Count - 1]; - var vec = pt - last.Point; - vec /= vec.Magnitude; - if ((tg - last.Tangent).Magnitude < 0.2) - { - var x = 0.5 * (last.Tangent + vec); - x /= x.Magnitude; - result[result.Count - 1] = new ArrowData(last.Point, x, last.Length); - tg = 0.5 * (tg + vec); - tg /= tg.Magnitude; - } - } - - result.Add(new ArrowData(pt, tg, length)); - isSwitching = false; - - var nextLength = length + path.Segments[segIdx].ArrowDistance; - if (segIdx < totalSegCount - 1 && nextLength >= path.AccumulatedLengths[segIdx]) - { - isSwitching = true; - - if (path.Segments[segIdx].ParseMarker == ParametricSlidePath.ParseMarker.ForceAlign) - { - // in this case the next point is forced to be 1 unit after the connecting point - nextLength = path.AccumulatedLengths[segIdx] + path.Segments[segIdx + 1].ArrowDistance; - - // P.S. 这种情况一般是出现在一条直线连接到外圈, 这个处理是为了让外圈的箭头对齐 - } - - if (path.Segments[segIdx + 1].ParseMarker == ParametricSlidePath.ParseMarker.SmoothAlign) - { - // arrow distance of the next segment is tempered in order to align arrow - var delta = path.AccumulatedLengths[segIdx + 1] - length; - var n = Math.Round(delta / MaiGeometry.DefaultDistance); - path.Segments[segIdx + 1].SetArrowDistance(delta / n); - nextLength = length + delta / n; - - // P.S. 这种情况出现在 ppqq 圈进入外圈, 可以把转移轨道的箭头间距微调一下, 也是让外圈对齐 - } - - segIdx++; - } - - length = nextLength; - } - - // 把路径终点补上 - result.Add(new ArrowData(path.GetPointAt(1.0), path.GetTangentAt(1.0), totalLength)); - - return result; - } - - /// - /// Convert arrow data to sinmai format (Vector4) - /// - /// arrow data generated by BuildArrowData() - /// button index of slide-star - /// mirror mode in user option - /// sinmai format arrow data, referenced to slide-star - public static List ConvertAndRotateArrowData(IEnumerable data, int starButton, - OptionMirrorID mirrorMode) - { - // SBGA 用 Vector4 存储了 slide 箭头的坐标与取向 - // x, y 是平面坐标, z 是从起点到此处的路径长度 (px), w 是旋转的角度 (0 ~ 360 deg) (注意与切线方向差了 180 度) - // 坐标原点是屏幕中心, x 轴向右, y 轴向上 - // w 的零点是朝向正右 (对应于箭头朝向正左), 逆时针为正方向 - // 此外, sinmai 实际上是把所有 slide 路径相对于星星头存储的, 再在 SlideRoot 里通过 transform 转到合适的位置 - // 判定区也是相对于星星头存储, 用 InputManager.ConvertTouchPanelRotatePush() 执行旋转 - // 但是 slide code 定义的是绝对位置, 所以要逆向转回去, 以保证无论星星头在哪个键获取到的路径在处理过后都是一样的 - // 然后还需要处理镜像的问题 - - var arrowList = new List(); - var rotor = Complex.FromPolarCoordinates(1.0, Math.PI / 4.0 * starButton); - foreach (var arrow in data) - { - var pos = arrow.Point; - var tangent = arrow.Tangent; - switch (mirrorMode) - { - case OptionMirrorID.Normal: - break; - case OptionMirrorID.LR: - pos = Complex.Conjugate(pos) * -1.0; - tangent = Complex.Conjugate(tangent) * -1.0; - break; - case OptionMirrorID.UD: - pos = Complex.Conjugate(pos); - tangent = Complex.Conjugate(tangent); - break; - case OptionMirrorID.UDLR: - pos *= -1.0; - tangent *= -1.0; - break; - default: - break; - } - pos *= rotor; - tangent *= rotor; - var angle = tangent.Phase * 180.0 / Math.PI + 180.0; // Phase is in [-PI, PI] - arrowList.Add(new Vector4((float) pos.Real, (float) pos.Imaginary, (float) arrow.Length, (float) angle)); - } - return arrowList; - } - - public readonly struct HitAreaData(double push, double release, int[] areas) - { - public readonly double PushDistance = push; - public readonly double ReleaseDistance = release; - public readonly int[] PanelAreas = areas; - } - - public static readonly Dictionary HitAreasLookup = new Dictionary(); - - public static void InitializeHitAreasLookup() - { - for (var i = 0; i < 8; i++) - { - for (var j = 0; j < 8; j++) - { - var diff = (j - i) & 7; // you know this is actually % 8 ... for same negative number compat - int tmp, tmp2; - - // Ai -> Aj - var key = (i << 5) | j; - switch (diff) - { - case 1: - case 7: - HitAreasLookup[key] = - [ - new HitAreaData(0.32, 0.68, [i]), - new HitAreaData(1.00, 1.00, [j]) - ]; - break; - case 2: - tmp = (i + 1) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.20, 0.38, [i]), - new HitAreaData(0.62, 0.80, [tmp, tmp | 8]), - new HitAreaData(1.00, 1.00, [j]) - ]; - break; - case 6: - tmp = (i - 1) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.20, 0.38, [i]), - new HitAreaData(0.62, 0.80, [tmp, tmp | 8]), - new HitAreaData(1.00, 1.00, [j]) - ]; - break; - default: - break; - } - // Bi -> Bj - key = ((i | 8) << 5) | (j | 8); - switch (diff) - { - case 1: - case 7: - HitAreasLookup[key] = - [ - new HitAreaData(0.44, 0.56, [i | 8]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - break; - case 2: - tmp = (i + 1) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.22, 0.35, [i | 8]), - new HitAreaData(0.65, 0.78, [tmp | 8, 16]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - break; - case 6: - tmp = (i - 1) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.22, 0.35, [i | 8]), - new HitAreaData(0.65, 0.78, [tmp | 8, 16]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - break; - case 3: - tmp = (i + 1) & 7; - tmp2 = (i + 2) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.15, 0.28, [i | 8]), - new HitAreaData(0.48, 0.52, [tmp | 8, 16]), - new HitAreaData(0.72, 0.85, [tmp2 | 8, 16]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - break; - case 5: - tmp = (i - 1) & 7; - tmp2 = (i - 2) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.15, 0.28, [i | 8]), - new HitAreaData(0.48, 0.52, [tmp | 8, 16]), - new HitAreaData(0.72, 0.85, [tmp2 | 8, 16]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - break; - default: - break; - } - // Ai <-> Bj - key = (i << 5) | (j | 8); - var key2 = ((j | 8) << 5) | i; - switch (diff) - { - case 0: - HitAreasLookup[key] = - [ - new HitAreaData(0.60, 0.75, [i]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - HitAreasLookup[key2] = - [ - new HitAreaData(0.25, 0.40, [j | 8]), - new HitAreaData(1.00, 1.00, [i]) - ]; - break; - case 1: - case 7: - HitAreasLookup[key] = - [ - new HitAreaData(0.45, 0.77, [i]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - HitAreasLookup[key2] = - [ - new HitAreaData(0.23, 0.55, [j | 8]), - new HitAreaData(1.00, 1.00, [i]) - ]; - break; - case 3: - tmp = (i + 1) & 7; - tmp2 = (i + 2) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.25, 0.34, [i]), - new HitAreaData(0.54, 0.68, [i | 8, tmp | 8]), - new HitAreaData(0.85, 0.90, [tmp2 | 8, 16]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - HitAreasLookup[key2] = - [ - new HitAreaData(0.10, 0.15, [j | 8]), - new HitAreaData(0.32, 0.46, [tmp2 | 8, 16]), - new HitAreaData(0.66, 0.75, [i | 8, tmp | 8]), - new HitAreaData(1.00, 1.00, [i]) - ]; - break; - case 5: - tmp = (i - 1) & 7; - tmp2 = (i - 2) & 7; - HitAreasLookup[key] = - [ - new HitAreaData(0.25, 0.34, [i]), - new HitAreaData(0.54, 0.68, [i | 8, tmp | 8]), - new HitAreaData(0.85, 0.90, [tmp2 | 8, 16]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - HitAreasLookup[key2] = - [ - new HitAreaData(0.10, 0.15, [j | 8]), - new HitAreaData(0.32, 0.46, [tmp2 | 8, 16]), - new HitAreaData(0.66, 0.75, [i | 8, tmp | 8]), - new HitAreaData(1.00, 1.00, [i]) - ]; - break; - default: - break; - } - // C <-> Bj - key = (16 << 5) | (j | 8); - key2 = ((j | 8) << 5) | 16; - HitAreasLookup[key] = - [ - new HitAreaData(0.50, 0.70, [16]), - new HitAreaData(1.00, 1.00, [j | 8]) - ]; - HitAreasLookup[key2] = - [ - new HitAreaData(0.30, 0.50, [j | 8]), - new HitAreaData(1.00, 1.00, [16]) - ]; - } - } - } - - public static List BuildHitAreas(ParametricSlidePath path) - { - var nodeList = new List>(); - var totalLength = path.GetPathLength(); - var count = (int)Math.Round(totalLength / 10.0); - int? lastNode = null; - var enterLength = 0.0; - for (var i = 0; i < count; i++) - { - var t = (double)i / count; - var pt = path.GetPointAt(t); - int? node = null; - - if (pt.Magnitude < 55.0) - { - node = 16; - } - else for (var j = 0; j < 8; j++) - { - var phi = Math.PI * (3.0 / 8.0 - j / 4.0); - if ((pt - Complex.FromPolarCoordinates(440.0, phi)).Magnitude < 80.0) - { - node = j; - break; - } - - if ((pt - Complex.FromPolarCoordinates(210.0, phi)).Magnitude < 45.0) - { - node = j | 8; - break; - } - } - - if (lastNode != node) - { - var length = t * totalLength; - if (lastNode == null) - { - enterLength = length; - } - else - { - nodeList.Add(new Tuple(lastNode.Value, (length + enterLength) / 2.0)); - if (node != null) - { - enterLength = length; - } - } - } - - lastNode = node; - } - nodeList.Add(new Tuple(lastNode!.Value, totalLength)); - nodeList[0] = new Tuple(nodeList[0].Item1, 0.0); - - var result = new List(); - result.Add(new HitAreaData(0.0, 0.0, [nodeList[0].Item1])); - for (var i = 1; i < nodeList.Count; i++) - { - var key = (nodeList[i - 1].Item1 << 5) | nodeList[i].Item1; - var segmentLength = nodeList[i].Item2 - nodeList[i - 1].Item2; - var data = HitAreasLookup[key]; - var area = result[result.Count - 1]; - result[result.Count - 1] = new HitAreaData( - area.PushDistance + segmentLength * data[0].PushDistance, - area.ReleaseDistance + segmentLength * data[0].ReleaseDistance, - area.PanelAreas - ); - for (var j = 1; j < data.Length; j++) - { - result.Add(new HitAreaData( - segmentLength * (data[j].PushDistance - data[j - 1].ReleaseDistance), - segmentLength * (data[j].ReleaseDistance - data[j].PushDistance), - data[j].PanelAreas - )); - } - } - - double lastPushDistance = 0.0; - if (path.GetEndType(OptionMirrorID.Normal) == SlideType.Slide_Straight) - { - var diff = nodeList[nodeList.Count - 1].Item1 - nodeList[nodeList.Count - 2].Item1; - diff %= 8; - lastPushDistance = diff switch - { - 1 or 2 or 6 or 7 => 130.0, - _ => 159.0 - }; - } - else - { - lastPushDistance = 175.0; - } - - var last2ndArea = result[result.Count - 2]; - var lastArea = result[result.Count - 1]; - var distance = last2ndArea.ReleaseDistance + lastArea.PushDistance + lastArea.ReleaseDistance; - result[result.Count - 2] = new HitAreaData(last2ndArea.PushDistance, distance - lastPushDistance, last2ndArea.PanelAreas); - result[result.Count - 1] = new HitAreaData(lastPushDistance, 0.0, lastArea.PanelAreas); - - return result; - } - - - - /// - /// Convert hit area data to sinmai format (Vector4) - /// - /// hit area data generated by BuildHitAreas() - /// button index of slide-star - /// mirror mode in user option - /// sinmai format arrow data, referenced to slide-star - public static List ConvertAndRotateHitAreas(IEnumerable data, int starButton, - OptionMirrorID mirrorMode) - { - var hitAreaList = new List(); - foreach (var hitAreaData in data) - { - var hitArea = new SlideManager.HitArea(); - hitArea.PushDistance = hitAreaData.PushDistance; - hitArea.ReleaseDistance = hitAreaData.ReleaseDistance; - foreach (var pad in hitAreaData.PanelAreas) - { - var converted = MaiGeometry.MirrorInfo[(int) mirrorMode, pad]; - converted = converted == 16 ? 16 : (converted - starButton) & 0b111 | converted & 0b1000; - hitArea.HitPoints.Add((InputManager.TouchPanelArea) converted); - } - hitAreaList.Add(hitArea); - } - return hitAreaList; - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlidePathGenerator.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlidePathGenerator.cs deleted file mode 100644 index 788567a8..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/CustomNoteTypes/Libs/SlidePathGenerator.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; - -namespace AquaMai.Mods.Fancy.GamePlay.CustomNoteTypes.Libs; - -public class SlidePathGenerator -{ - public List PathSegments = new List(); - public Complex CurrentEndPoint = Complex.Zero; - - public static SlidePathGenerator BeginAt(Complex point) - { - var obj = new SlidePathGenerator(); - obj.CurrentEndPoint = point; - return obj; - } - - public static double CalcTangentAngle(Complex point, MaiGeometry.CircleStruct circle, bool isCcw) - { - var hypot = point - circle.Center; - var angleDelta = Math.Acos(circle.Radius / hypot.Magnitude); - var tanAngle = hypot.Phase + (isCcw ? angleDelta : -angleDelta); - return Math.IEEERemainder(tanAngle, Math.PI * 2.0); - } - - public void TrySetLastParseMarker(ParametricSlidePath.ParseMarker marker) - { - if (PathSegments.Count <= 0) return; - PathSegments[PathSegments.Count - 1].SetParseMarker(marker); - } - - public void LineToPoint(Complex point) - { - PathSegments.Add(new ParametricSlidePath.LineSegment(CurrentEndPoint, point)); - CurrentEndPoint = point; - } - - public void TangentToCircle(MaiGeometry.CircleStruct circle, bool isCcw) - { - var inAngle = CalcTangentAngle(CurrentEndPoint, circle, isCcw); - var inPoint = Complex.FromPolarCoordinates(circle.Radius, inAngle) + circle.Center; - LineToPoint(inPoint); - } - - /// Note: endAngle should be in range [-PI, PI] - public void ArcToAngle(Complex center, double endAngle, bool isCcw, bool skipIfZero) - { - var diff = CurrentEndPoint - center; - var circle = new MaiGeometry.CircleStruct(center, diff.Magnitude); - var startAngle = diff.Phase; - // startAngle and endAngle in range [-PI, PI] - if (isCcw) - { - if (startAngle > endAngle) - { - startAngle -= 2 * Math.PI; - } - - if (Math.Abs(endAngle - startAngle) < 0.001) - { - if (skipIfZero) return; - endAngle += 2 * Math.PI; - } - } - else - { - if (startAngle < endAngle) - { - startAngle += 2 * Math.PI; - } - - if (Math.Abs(endAngle - startAngle) < 0.001) - { - if (skipIfZero) return; - endAngle -= 2 * Math.PI; - } - } - - var seg = new ParametricSlidePath.ArcSegment(circle, startAngle, endAngle); - PathSegments.Add(seg); - CurrentEndPoint = seg.GetPointAt(1f); - } - - public void ArcToTangentTowards(Complex target, Complex center, bool isCcw) - { - var diff = CurrentEndPoint - center; - var endAngle = CalcTangentAngle(target, new MaiGeometry.CircleStruct(center, diff.Magnitude), !isCcw); - ArcToAngle(center, endAngle, isCcw, false); - } - - public void FullCircle(Complex center, bool isCcw) - { - var diff = CurrentEndPoint - center; - var circle = new MaiGeometry.CircleStruct(center, diff.Magnitude); - PathSegments.Add(new ParametricSlidePath.CircleSegment(circle, diff.Phase, isCcw)); - // CurrentEndPoint not changed - } - - public void ExternTangentTransfer(Complex currentCenter, MaiGeometry.CircleStruct targetCircle, bool isCcw) - { - var diff = CurrentEndPoint - currentCenter; - double endAngle; - if (Math.Abs(diff.Magnitude - targetCircle.Radius) < 0.001) - { - // two circles are approximately same radius - var vector = targetCircle.Center - currentCenter; - vector *= isCcw ? -Complex.ImaginaryOne : Complex.ImaginaryOne; - endAngle = vector.Phase; - } - else if (targetCircle.Radius > diff.Magnitude) - { - // target circle larger - var helperCircle = new MaiGeometry.CircleStruct(targetCircle.Center, targetCircle.Radius - diff.Magnitude); - endAngle = CalcTangentAngle(currentCenter, helperCircle, isCcw); - } - else - { - var helperCircle = new MaiGeometry.CircleStruct(currentCenter, diff.Magnitude - targetCircle.Radius); - endAngle = CalcTangentAngle(targetCircle.Center, helperCircle, !isCcw); - } - ArcToAngle(currentCenter, endAngle, isCcw, false); - var inPoint = Complex.FromPolarCoordinates(targetCircle.Radius, endAngle) + targetCircle.Center; - LineToPoint(inPoint); - } - - public ParametricSlidePath GeneratePath() - { - return new ParametricSlidePath(PathSegments); - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/DisableTrackStartTabs.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/DisableTrackStartTabs.cs deleted file mode 100644 index 439b8579..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/DisableTrackStartTabs.cs +++ /dev/null @@ -1,50 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; -using TMPro; -using UI; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - Disable the TRACK X text, DX/Standard display box, and the derakkuma at the bottom of the screen in the song start screen. - For recording chart confirmation. - """, - zh: """ - 在歌曲开始界面, 把 TRACK X 字样, DX/标准谱面的显示框, 以及画面下方的滴蜡熊隐藏掉 - 录制谱面确认用 - """)] -public class DisableTrackStartTabs -{ - // 在歌曲开始界面, 把 TRACK X 字样, DX/标准谱面的显示框, 以及画面下方的滴蜡熊隐藏掉, 让他看起来不那么 sinmai, 更像是 majdata - - [HarmonyPostfix] - [HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")] - private static void DisableTabs( - SpriteCounter ____trackNumber, SpriteCounter ____bossTrackNumber, SpriteCounter ____utageTrackNumber, - MultipleImage ____musicTabImage, GameObject[] ____musicTabObj, GameObject ____derakkumaRoot, - TimelineRoot ____musicDetail - ) - { - ____trackNumber.transform.parent.gameObject.SetActive(false); - ____bossTrackNumber.transform.parent.gameObject.SetActive(false); - ____utageTrackNumber.transform.parent.gameObject.SetActive(false); - ____musicTabImage.gameObject.SetActive(false); - ____musicTabObj[0].gameObject.SetActive(false); - ____musicTabObj[1].gameObject.SetActive(false); - ____musicTabObj[2].gameObject.SetActive(false); - ____derakkumaRoot.SetActive(false); - var traverse = Traverse.Create(____musicDetail); - traverse.Field("_achivement_Base").Value.ChangeSprite(1); - traverse.Field("_clearRank_Base").Value.ChangeSprite(1); - traverse.Field("_achivement_Text").Value.gameObject.SetActive(false); - traverse.Field("_achivement_decimal_Text").Value.gameObject.SetActive(false); - traverse.Field("_achivement_percent_Text").Value.gameObject.SetActive(false); - traverse.Field("_clearRank_Image").Value.gameObject.SetActive(false); - traverse.Field("_deluxScore_Obj").Value.SetActive(false); - traverse.Field("_comboRank_Image").Value.ChangeSprite(0); - traverse.Field("_syncRank_Image").Value.ChangeSprite(0); - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/ExtendNotesPool.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/ExtendNotesPool.cs deleted file mode 100644 index 245a01b0..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/ExtendNotesPool.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Monitor; -using Monitor.Game; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: "Add notes sprite to the pool to prevent use up.", - zh: "增加更多待命的音符贴图,防止奇怪的自制谱用完音符贴图池")] -[EnableGameVersion(23000)] -public class ExtendNotesPool -{ - [ConfigEntry( - en: "Number of objects to add.", - zh: "要增加的对象数量")] - private readonly static int count = 128; - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameCtrl), "CreateNotePool")] - public static void CreateNotePool(ref GameCtrl __instance, - GameObject ____tapListParent, List ____tapObjectList, - GameObject ____holdListParent, List ____holdObjectList, - GameObject ____breakHoldListParent, List ____breakHoldObjectList, - GameObject ____starListParent, List ____starObjectList, - GameObject ____breakStarListParent, List ____breakStarObjectList, - GameObject ____breakListParent, List ____breakObjectList, - GameObject ____touchListParent, List ____touchBObjectList, - GameObject ____touchCTapListParent, List ____touchCTapObjectList, - GameObject ____touchCHoldListParent, List ____touchCHoldObjectList, - GameObject ____slideListParent, List ____slideObjectList, - GameObject ____fanSlideListParent, List ____fanSlideObjectList, - GameObject ____slideJudgeListParent, List ____judgeSlideObjectList, - GameObject ____guideListParent, List ____guideObjectList, - GameObject ____barGuideListParent, List ____barGuideObjectList, - List ____arrowObjectList, List ____breakArrowObjectList - ) - { - for (var i = 0; i < count; i++) - { - var tapNote = Object.Instantiate(GameNotePrefabContainer.Tap, ____tapListParent.transform); - tapNote.gameObject.SetActive(false); - tapNote.ParentTransform = ____tapListParent.transform; - ____tapObjectList.Add(tapNote); - - var holdNote = Object.Instantiate(GameNotePrefabContainer.Hold, ____holdListParent.transform); - holdNote.gameObject.SetActive(false); - holdNote.ParentTransform = ____holdListParent.transform; - ____holdObjectList.Add(holdNote); - - var breakHoldNote = Object.Instantiate(GameNotePrefabContainer.BreakHold, ____breakHoldListParent.transform); - breakHoldNote.gameObject.SetActive(false); - breakHoldNote.ParentTransform = ____holdListParent.transform; - ____breakHoldObjectList.Add(breakHoldNote); - - var starNote = Object.Instantiate(GameNotePrefabContainer.Star, ____starListParent.transform); - starNote.gameObject.SetActive(false); - starNote.ParentTransform = ____starListParent.transform; - ____starObjectList.Add(starNote); - - var breakStarNote = Object.Instantiate(GameNotePrefabContainer.BreakStar, ____breakStarListParent.transform); - breakStarNote.gameObject.SetActive(false); - breakStarNote.ParentTransform = ____breakStarListParent.transform; - ____breakStarObjectList.Add(breakStarNote); - - var breakNote = Object.Instantiate(GameNotePrefabContainer.Break, ____breakListParent.transform); - breakNote.gameObject.SetActive(false); - breakNote.ParentTransform = ____breakListParent.transform; - ____breakObjectList.Add(breakNote); - - var touchNoteB = Object.Instantiate(GameNotePrefabContainer.TouchTapB, ____touchListParent.transform); - touchNoteB.gameObject.SetActive(false); - touchNoteB.ParentTransform = ____touchListParent.transform; - ____touchBObjectList.Add(touchNoteB); - - var touchNoteC = Object.Instantiate(GameNotePrefabContainer.TouchTapC, ____touchCTapListParent.transform); - touchNoteC.gameObject.SetActive(false); - touchNoteC.ParentTransform = ____touchCTapListParent.transform; - ____touchCTapObjectList.Add(touchNoteC); - - var touchHoldC = Object.Instantiate(GameNotePrefabContainer.TouchHoldC, ____touchCHoldListParent.transform); - touchHoldC.gameObject.SetActive(false); - touchHoldC.ParentTransform = ____touchCHoldListParent.transform; - ____touchCHoldObjectList.Add(touchHoldC); - - var slideRoot = Object.Instantiate(GameNotePrefabContainer.Slide, ____slideListParent.transform); - slideRoot.gameObject.SetActive(false); - slideRoot.ParentTransform = ____slideListParent.transform; - ____slideObjectList.Add(slideRoot); - - var slideFan = Object.Instantiate(GameNotePrefabContainer.SlideFan, ____fanSlideListParent.transform); - slideFan.gameObject.SetActive(false); - slideFan.ParentTransform = ____fanSlideListParent.transform; - ____fanSlideObjectList.Add(slideFan); - - var slideJudge = Object.Instantiate(GameNotePrefabContainer.SlideJudge, ____slideJudgeListParent.transform); - slideJudge.gameObject.SetActive(false); - slideJudge.ParentTransform = ____slideJudgeListParent.transform; - slideJudge.SetOption(Singleton.Instance.GetGameScore(__instance.MonitorIndex).UserOption.DispJudge); - ____judgeSlideObjectList.Add(slideJudge); - - for (var j = 0; j < 50; j++) - { - var spriteRenderer = Object.Instantiate(GameNotePrefabContainer.Arrow, ____slideListParent.transform); - spriteRenderer.gameObject.SetActive(false); - ____arrowObjectList.Add(spriteRenderer); - - var breakSlide = Object.Instantiate(GameNotePrefabContainer.BreakArrow, ____slideListParent.transform); - breakSlide.gameObject.SetActive(false); - ____breakArrowObjectList.Add(breakSlide); - } - - var noteGuide = Object.Instantiate(GameNotePrefabContainer.Guide, ____guideListParent.transform); - noteGuide.gameObject.SetActive(false); - noteGuide.ParentTransform = ____guideListParent.transform; - ____guideObjectList.Add(noteGuide); - - var barGuide = Object.Instantiate(GameNotePrefabContainer.BarGuide, ____barGuideListParent.transform); - barGuide.gameObject.SetActive(false); - barGuide.ParentTransform = ____barGuideListParent.transform; - ____barGuideObjectList.Add(barGuide); - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/FanJudgeFlip.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/FanJudgeFlip.cs deleted file mode 100644 index bad70ed8..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/FanJudgeFlip.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - 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. - The reason for this bug is that SEGA forgot to assign EndButtonId to WiFi. - """, - zh: """ - 这个 Patch 让 WiFi Slide 的判定显示有上下的区别 (原本所有 WiFi 的判定显示都是朝向圆心的), 就像 majdata 里那样 - 这个 bug 产生的原因是 SBGA 忘记给 WiFi 的 EndButtonId 赋值了 - """)] -public class FanJudgeFlip -{ - /* - * 这个 Patch 让 WiFi Slide 的判定显示有上下的区别 (原本所有 WiFi 的判定显示都是朝向圆心的), 就像 majdata 里那样 - * 这个 bug 产生的原因是 SBGA 忘记给 WiFi 的 EndButtonId 赋值了 - * 不过需要注意的是, 考虑到圆弧形 Slide 的判定显示就是永远朝向圆心的, 我个人会觉得这个 Patch 关掉更好看一点 - */ - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideFan), "Initialize")] - private static void FixFanJudgeFilp( - int[] ___GoalButtonId, SlideJudge ___JudgeObj - ) - { - if (null != ___JudgeObj) - { - if (2 <= ___GoalButtonId[1] && ___GoalButtonId[1] <= 5) - { - ___JudgeObj.Flip(false); - ___JudgeObj.transform.Rotate(0.0f, 0.0f, 180f); - } - else - { - ___JudgeObj.Flip(true); - } - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/HideHanabi.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/HideHanabi.cs deleted file mode 100644 index e84e02d0..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/HideHanabi.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AquaMai.Config.Attributes; -using Fx; -using HarmonyLib; -using Monitor; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: "Hide hanabi completely.", - zh: "完全隐藏烟花")] -public class HideHanabi -{ - [HarmonyPatch(typeof(TapCEffect), "SetUpParticle")] - [HarmonyPostfix] - public static void FixZeroSize(TapCEffect __instance, FX_Mai2_Note_Color ____particleControler) - { - var entities = ____particleControler.GetComponentsInChildren(true); - foreach (var entity in entities) - { - entity.maxParticleSize = 0f; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/JudgeDisplay4B.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/JudgeDisplay4B.cs deleted file mode 100644 index 29c64edd..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/JudgeDisplay4B.cs +++ /dev/null @@ -1,86 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - More detailed judgment display. - Requires CustomSkins to be enabled and the resource file to be downloaded. - https://github.com/hykilpikonna/AquaDX/releases/download/nightly/JudgeDisplay4B.7z - """, - zh: """ - 更精细的判定表示 - 需开启 CustomSkins 并下载资源文件 - https://github.com/hykilpikonna/AquaDX/releases/download/nightly/JudgeDisplay4B.7z - """)] -public class JudgeDisplay4B -{ - // 精确到子判定的自定义判定显示, 需要启用自定义皮肤功能 (理论上不启用自定义皮肤不会崩游戏, 只不过此时这个功能显然不会生效) - - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideJudge), "Initialize")] - private static void SlideJudgeDisplay4B( - SpriteRenderer ___SpriteRenderAdd, SpriteRenderer ___SpriteRender, - SlideJudge.SlideJudgeType ____judgeType, SlideJudge.SlideAngle ____angle, - NoteJudge.ETiming judge, float msec, bool isBreak - ) - { - var i = isBreak ? 1 : 0; - Sprite sprite = CustomSkins.CustomJudgeSlide[i, (int)____judgeType, (int)____angle, (int)judge]; - if (sprite != null) { - ___SpriteRender.sprite = sprite; - } - - if (isBreak && judge == NoteJudge.ETiming.Critical) - { - sprite = CustomSkins.CustomJudgeSlide[i, (int)____judgeType, (int)____angle, (int) NoteJudge.ETiming.End]; - if (sprite != null) - { - ___SpriteRenderAdd.sprite = sprite; - } - } - } - - - [HarmonyPostfix] - [HarmonyPatch(typeof(JudgeGrade), "Initialize")] - private static void JudgeGradeDisplay4B( - SpriteRenderer ___SpriteRender, - NoteJudge.ETiming judge, float msec, NoteJudge.EJudgeType type - ) - { - var i = (type == NoteJudge.EJudgeType.Break) ? 1 : 0; - Sprite sprite = CustomSkins.CustomJudge[i, (int)judge]; - if (sprite != null) { - ___SpriteRender.sprite = sprite; - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(JudgeGrade), "InitializeBreak")] - private static void JudgeGradeBreakDisplay4B( - SpriteRenderer ___SpriteRenderAdd, - NoteJudge.ETiming judge, float msec, NoteJudge.EJudgeType type - ) - { - if (judge == NoteJudge.ETiming.Critical) - { - var sprite = CustomSkins.CustomJudge[1, (int) NoteJudge.ETiming.End]; - if (sprite != null) - { - ___SpriteRenderAdd.sprite = sprite; - } - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(JudgeGrade), "InitializeBreak")] - private static void InitializeBreakFix(ref NoteJudge.EJudgeType type) - { - type = NoteJudge.EJudgeType.Break; - } - -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/RealisticRandomJudge.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/RealisticRandomJudge.cs deleted file mode 100644 index a73ab94e..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/RealisticRandomJudge.cs +++ /dev/null @@ -1,39 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - Make the AutoPlay random judgment mode really randomize all judgments (down to sub-judgments). - The original random judgment will only produce all 15 judgment results from Miss(TooFast) ~ Critical ~ Miss(TooLate). - Here, it is changed to a triangular distribution to produce all 15 judgment results from Miss(TooFast) ~ Critical ~ Miss(TooLate). - Of course, it will not consider whether the original Note really has a corresponding judgment (such as Slide should not have non-Critical Prefect). - """, - zh: """ - 让 AutoPlay 的随机判定模式真的会随机产生所有的判定 (精确到子判定) - 原本的随机判定只会等概率产生 Critical, LateGreat1st, LateGood, Miss(TooLate) - 这里改成三角分布产生从 Miss(TooFast) ~ Critical ~ Miss(TooLate) 的所有 15 种判定结果 - 当然, 此处并不会考虑原本那个 Note 是不是真的有对应的判定 (比如 Slide 实际上不应该有小 p 之类的) - """)] -public class RealisticRandomJudge -{ - // 让 AutoPlay 的随机判定模式真的会随机产生所有的判定 (精确到子判定) - // 原本的随机判定只会等概率产生 Critical, LateGreat1st, LateGood, Miss(TooLate) - // 这里改成三角分布产生从 Miss(TooFast) ~ Critical ~ Miss(TooLate) 的所有 15 种判定结果 - // 当然, 此处并不会考虑原本那个 Note 是不是真的有对应的判定 (比如 Slide 实际上不应该有小 p 之类的) - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameManager), "AutoJudge")] - private static NoteJudge.ETiming RealAutoJudgeRandom(NoteJudge.ETiming retval) - { - if (GameManager.AutoPlay == GameManager.AutoPlayMode.Random) - { - var x = UnityEngine.Random.Range(0, 8); - x += UnityEngine.Random.Range(0, 8); - return (NoteJudge.ETiming) x; - } - return retval; - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideArrowAnimation.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideArrowAnimation.cs deleted file mode 100644 index 16db4308..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideArrowAnimation.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; -using Monitor; -using Monitor.Game; -using Process; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: "Make the Slide Track disappear with an inward-shrinking animation, similar to AstroDX.", - zh: "使 Slide Track 消失时有类似 AstroDX 一样的向内缩入的动画")] -public class SlideArrowAnimation -{ - private static List _animatingSpriteRenderers = []; - - [HarmonyTranspiler] - [HarmonyPatch(typeof(SlideRoot), "NoteCheck")] - private static IEnumerable GetUnVisibleColorHook(IEnumerable instructions) - { - var methodGetUnVisibleColor = AccessTools.Method(typeof(SlideRoot), "GetUnVisibleColor"); - - var oldInstList = new List(instructions); - var newInstList = new List(); - - for (var i = 0; i < oldInstList.Count; i++) - { - var inst = oldInstList[i]; - if (inst.Calls(methodGetUnVisibleColor)) - { - // 现在栈上应该有: SpriteRenderer, SlideRoot(this) - // 这一条 IL 会消耗 this, 调用 GetUnVisibleColor(), 推一个 Color 到栈上 - // 然后接下来的一条 IL 是调用 SpriteRenderer.color 的 setter 把 SpriteRenderer 和 Color 一起消耗掉 - // 我们现在直接用一个 static method 消耗掉 SpriteRenderer 和 this - // 所以要忽略当前 IL, 再忽略下一条 IL, 然后构造一个 Call - - // ReSharper disable once ConvertClosureToMethodGroup - var redirect = CodeInstruction.Call((SpriteRenderer r, SlideRoot s) => OnSlideArrowDisable(r, s)); - newInstList.Add(redirect); - i++; // 跳过下一条 IL - } - else - { - newInstList.Add(inst); - } - } - return newInstList; - } - - public static void OnSlideArrowDisable(SpriteRenderer renderer, SlideRoot slideRoot) - { - _animatingSpriteRenderers.Add(renderer); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideRoot), "SetArrowObject")] - private static void RemoveArrowAnimation(GameObject arrowobj) - { - var spriteRenderer = arrowobj.GetComponent(); - spriteRenderer.transform.localScale = Vector3.one; - if (_animatingSpriteRenderers.Contains(spriteRenderer)) - { - _animatingSpriteRenderers.Remove(spriteRenderer); - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideRoot), "SetBreakArrowObject")] - private static void RemoveBreakArrowAnimation(GameObject breakArrowobj) - { - var breakSlideObj = breakArrowobj.GetComponent(); - breakSlideObj.SpriteRender.transform.localScale = Vector3.one; - if (_animatingSpriteRenderers.Contains(breakSlideObj.SpriteRender)) - { - _animatingSpriteRenderers.Remove(breakSlideObj.SpriteRender); - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameCtrl), "UpdateNotes")] - private static void OnGameCtrlUpdateNotesLast() - { - for (var num = _animatingSpriteRenderers.Count - 1; num >= 0; num--) - { - var spriteRenderer = _animatingSpriteRenderers[num]; - if (spriteRenderer == null || !spriteRenderer.gameObject.activeSelf) - { - _animatingSpriteRenderers.RemoveAt(num); - } - else - { - var localScale = spriteRenderer.transform.localScale; - var scale = localScale.y - NotesManager.GetAddMSec() / 150f; - if (scale <= 0) - { - spriteRenderer.transform.localScale = new Vector3(1f, 0f, 1f); - spriteRenderer.color = new Color(1f, 1f, 1f, 0f); - _animatingSpriteRenderers.RemoveAt(num); - } - else - { - localScale.y = scale; - spriteRenderer.color = new Color(1f, 1f, 1f, Mathf.Sqrt(scale)); - spriteRenderer.transform.localScale = localScale; - } - } - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(GameProcess), "SetRelease")] - private static void OnBeforeGameProcessSetRelease() - { - _animatingSpriteRenderers.Clear(); - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideFadeInTweak.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideFadeInTweak.cs deleted file mode 100644 index 1a597c52..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideFadeInTweak.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Monitor; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - zh: "让星星在启动拍等待期间从 50% 透明度渐入为 100%,取代原本在击打星星头时就完成渐入", - en: "Slides will fade in instead of instantly appearing.")] -public class SlideFadeInTweak -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(SlideRoot), "UpdateAlpha")] - private static bool UpdateAlphaOverwrite( - SlideRoot __instance, - ref bool ___UpdateAlphaFlag, - float ___StartMsec, float ___AppearMsec, float ___StarLaunchMsec, float ___DefaultMsec, - int ____dispLaneNum, bool ___BreakFlag, - List ____spriteRenders, List ____breakSpriteRenders - ) - { - if (!___UpdateAlphaFlag) - return false; - - var currentMsec = NotesManager.GetCurrentMsec(); - var slideSpeed = (int) Singleton.Instance.GetGameScore(__instance.MonitorId).UserOption.SlideSpeed; - var defaultFadeInLength = (21 - slideSpeed) / 10.5f * ___DefaultMsec; - var fadeInFirstMsec = Math.Max(___StartMsec, ___AppearMsec - defaultFadeInLength); - var fadeInSecondMsec = Math.Max(___AppearMsec, ___StarLaunchMsec - defaultFadeInLength); - // var fadeInSecondMsec = ___AppearMsec; - var color = new Color(1f, 1f, 1f, 1f); - - if (currentMsec >= ___StarLaunchMsec) - { - ___UpdateAlphaFlag = false; - } - else if (currentMsec < fadeInFirstMsec) - { - color.a = 0.0f; - } - else if (fadeInFirstMsec <= currentMsec && currentMsec < ___AppearMsec) - { - var fadeInLength = Math.Min(200.0f, ___AppearMsec - fadeInFirstMsec); - color.a = 0.5f * Math.Min(1f, (currentMsec - fadeInFirstMsec) / fadeInLength); - } - else if (___AppearMsec <= currentMsec && currentMsec < fadeInSecondMsec) - { - color.a = 0.5f; - } - else if (fadeInSecondMsec <= currentMsec && currentMsec < ___StarLaunchMsec) - { - var fadeInLength = Math.Min(200.0f, ___StarLaunchMsec - fadeInSecondMsec); - // var fadeInLength = ___StarLaunchMsec - fadeInSecondMsec; - color.a = 0.5f + 0.5f * Math.Min(1f, (currentMsec - fadeInSecondMsec) / fadeInLength); - } - - if (!___BreakFlag) - { - for (var index = 0; index < ____dispLaneNum; ++index) - { - if (index >= ____spriteRenders.Count) break; - ____spriteRenders[index].color = color; - } - } - else - { - for (var index = 0; index < ____dispLaneNum; ++index) - { - if (index >= ____breakSpriteRenders.Count) break; - ____breakSpriteRenders[index].SpriteRender.color = color; - } - } - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(SlideFan), "UpdateAlpha")] - private static bool UpdateFanAlphaOverwrite( - SlideRoot __instance, - float ___StartMsec, float ___AppearMsec, float ___StarLaunchMsec, float ___DefaultMsec, - Color ____defaultColor, SpriteRenderer[] ____spriteLines - ) - { - var currentMsec = NotesManager.GetCurrentMsec(); - - var slideSpeed = (int) Singleton.Instance.GetGameScore(__instance.MonitorId).UserOption.SlideSpeed; - var defaultFadeInLength = (21 - slideSpeed) / 10.5f * ___DefaultMsec; - var fadeInFirstMsec = Math.Max(___StartMsec, ___AppearMsec - defaultFadeInLength); - var fadeInSecondMsec = Math.Max(___AppearMsec, ___StarLaunchMsec - defaultFadeInLength); - // var fadeInSecondMsec = ___AppearMsec; - var color = ____defaultColor; - - if (currentMsec < fadeInFirstMsec) - { - color.a = 0.0f; - } - else if (fadeInFirstMsec <= currentMsec && currentMsec < ___AppearMsec) - { - var fadeInLength = Math.Min(200.0f, ___AppearMsec - fadeInFirstMsec); - color.a = 0.3f * Math.Min(1f, (currentMsec - fadeInFirstMsec) / fadeInLength); - } - else if (___AppearMsec <= currentMsec && currentMsec < fadeInSecondMsec) - { - color.a = 0.3f; - } - else if (fadeInSecondMsec <= currentMsec && currentMsec < ___StarLaunchMsec) - { - var fadeInLength = Math.Min(200.0f, ___StarLaunchMsec - fadeInSecondMsec); - // var fadeInLength = ___StarLaunchMsec - fadeInSecondMsec; - color.a = 0.3f + 0.3f * Math.Min(1f, (currentMsec - fadeInSecondMsec) / fadeInLength); - } - else - { - color.a = 0.6f; - } - - foreach (SpriteRenderer spriteLine in ____spriteLines) - spriteLine.color = color; - - return false; - } - -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideLayerReverse.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideLayerReverse.cs deleted file mode 100644 index 689b32a6..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/SlideLayerReverse.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - Invert the Slide hierarchy, so that the new Slide appears on top like Maimai classic. - Enable to support color changing effects achieved by overlaying multiple stars. - """, - zh: """ - 反转 Slide 层级, 使新出现的 Slide 像旧框一样显示在上层 - 启用以支持通过叠加多个星星达成的变色效果 - """)] -public class SlideLayerReverse -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideRoot), "Initialize")] - private static void CalcArrowLayer( - bool ___BreakFlag, List ____spriteRenders, List ____breakSpriteRenders, - int ___SlideIndex, int ____baseArrowSortingOrder - ) - { - // 原本的 sortingOrder 是 -(SlideIndex + _baseArrowSortingOrder + index) - // 令 orderBase = SlideIndex + _baseArrowSortingOrder - // 分配给这条 slide 的 sortingOrder 范围是 -(orderBase + count - 1) ~ -(orderBase) - // 现在要保留 slide 内部箭头顺序, 但使得 slide 间次序反转 - // 范围会变成 orderBase ~ orderBase + count - 1 - // 其中原本是 -(orderBase) 的箭头应该调整为 orderBase + count - 1 - - var orderBase = ___SlideIndex + ____baseArrowSortingOrder; // SlideIndex + _baseArrowSortingOrder - if (!___BreakFlag) - { - var lastIdx = ____spriteRenders.Count - 1; - for (var index = 0; index < ____spriteRenders.Count; index++) - { - var renderer = ____spriteRenders[index]; - renderer.sortingOrder = -32700 + orderBase + lastIdx - index; - } - } - else - { - var lastIdx = ____breakSpriteRenders.Count - 1; - for (var index = 0; index < ____breakSpriteRenders.Count; index++) - { - var breakSlide = ____breakSpriteRenders[index]; - breakSlide.SetSortingOrder(-32700 + orderBase + lastIdx - index); - } - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideFan), "Initialize")] - private static void CalcFanArrowLayer( - SpriteRenderer[] ____spriteLines, SpriteRenderer[] ____effectSprites, - int ___SlideIndex, int ____baseArrowSortingOrder - ) - { - var orderBase = ___SlideIndex + ____baseArrowSortingOrder; // SlideIndex + _baseArrowSortingOrder - var lastIdx = ____spriteLines.Length - 1; - for (var index = 0; index < ____spriteLines.Length; index++) - { - var renderer = ____spriteLines[index]; - renderer.sortingOrder = -32700 + orderBase + lastIdx - index; - } - lastIdx = ____effectSprites.Length - 1; - for (var index = 0; index < ____effectSprites.Length; index++) - { - var renderer = ____effectSprites[index]; - renderer.sortingOrder = 1000 + orderBase + lastIdx - index; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/GamePlay/TrackStartProcessTweak.cs b/AquaMai/AquaMai.Mods/Fancy/GamePlay/TrackStartProcessTweak.cs deleted file mode 100644 index 0774d9df..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/GamePlay/TrackStartProcessTweak.cs +++ /dev/null @@ -1,77 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Process; -using UnityEngine; - -namespace AquaMai.Mods.Fancy.GamePlay; - -[ConfigSection( - en: """ - Delayed the animation of the song start screen. - For recording chart confirmation. - """, - zh: """ - 推迟了歌曲开始界面的动画 - 录制谱面确认用 - """)] -public class TrackStartProcessTweak -{ - // 总之这个 Patch 没啥用, 是我个人用 sinmai 录谱面确认时用得到, 顺手也写进来了 - // 具体而言就是推迟了歌曲开始界面的动画便于后期剪辑 - - [HarmonyPrefix] - [HarmonyPatch(typeof(TrackStartProcess), "OnUpdate")] - private static bool DelayAnimation( - TrackStartProcess.TrackStartSequence ____state, - ref float ____timeCounter, - ProcessDataContainer ___container - ) - { - if (____state == TrackStartProcess.TrackStartSequence.Wait) - { - // 将开始动画(就是“噔噔, 噔 噔噔”)推迟 - float temp = ____timeCounter + Time.deltaTime; - if (____timeCounter < 1.0f && temp >= 1.0f) - { - // 这是用来让转场动画继续播放的, 原本就是这个时候 notify 的同时开始播放开始动画 - // 现在把开始动画往后延 - ___container.processManager.NotificationFadeIn(); - } - ____timeCounter = temp; - if (____timeCounter >= 3.0f) - { - return true; - // 原 method 的逻辑是这样 - // case TrackStartProcess.TrackStartSequence.Wait: - // this._timeCounter += Time.deltaTime; - // if ((double) this._timeCounter >= 1.0) - // { - // this._timeCounter = 0.0f; - // this._state = TrackStartProcess.TrackStartSequence.Disp; - // /* 一些开始播放开始动画的代码 */ - // this.container.processManager.NotificationFadeIn(); - // break; - // } - // break; - // 所以只要在 prefix 里面等到 timeCounter 达到我们想要的值以后再执行原 method 就好 - // 这里有个细节: NotificationFadeIn() 会被执行两遍, 这其实不好, 是个潜在 bug - // 不过由于此处把开始动画往后推了 2s, 转场动画已经结束把 Process 释放掉了, 所以第二遍会找不到 Process 就没效果 - } - return false; - } - else if (____state == TrackStartProcess.TrackStartSequence.DispEnd) - { - // 将开始动画结束以后的转场动画推迟 - ____timeCounter += Time.deltaTime; // timeCounter 会在先前由原本的 method 归零 - if (____timeCounter >= 1.0f) - { - return true; - } - return false; - } - return true; - } - - -} - diff --git a/AquaMai/AquaMai.Mods/Fancy/HideMask.cs b/AquaMai/AquaMai.Mods/Fancy/HideMask.cs deleted file mode 100644 index 013ee7ab..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/HideMask.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using UnityEngine; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: "Remove the circle mask of the game screen.", - zh: "移除游戏画面的圆形遮罩")] -public class HideMask -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(Main.GameMain), "LateInitialize", typeof(MonoBehaviour), typeof(Transform), typeof(Transform))] - public static void LateInitialize(MonoBehaviour gameMainObject) - { - GameObject.Find("Mask").SetActive(false); - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/README.md b/AquaMai/AquaMai.Mods/Fancy/README.md deleted file mode 100644 index 2f4f6dbc..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Fancy - -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. - -These patches may not well-tested by the project maintainers and could be enabled only if you know what you're doing. - -Patches affect the gameplay should go to the GamePlay subcategory. diff --git a/AquaMai/AquaMai.Mods/Fancy/RandomBgm.cs b/AquaMai/AquaMai.Mods/Fancy/RandomBgm.cs deleted file mode 100644 index bec48456..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/RandomBgm.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using HarmonyLib; -using Mai2.Mai2Cue; -using MAI2.Util; -using Manager; -using MelonLoader; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: """ - Random BGM. - Put Mai2Cue.{acb,awb} of old version of the game in the configured directory and rename them. - Won't work with 2P mode. - """, - zh: """ - 在配置的目录下放置了旧版游戏的 Mai2Cue.{acb,awb} 并重命名的话,可以在播放游戏 BGM 的时候随机播放这里面的旧版游戏 BGM - 无法在 2P 模式下工作 - """)] -public class RandomBgm -{ - [ConfigEntry] - private static readonly string mai2CueDir = "LocalAssets/Mai2Cue"; - - private static List _acbs = new List(); - - [HarmonyPostfix] - [HarmonyPatch(typeof(SoundManager), "Initialize")] - public static void Init() - { - var resolvedDir = FileSystem.ResolvePath(mai2CueDir); - if (!Directory.Exists(resolvedDir)) return; - var files = Directory.EnumerateFiles(resolvedDir); - foreach (var file in files) - { - if (!file.EndsWith(".acb")) continue; - // Seems there's limit for max opened ACB files - _acbs.Add(Path.ChangeExtension(file, null)); - } - - MelonLogger.Msg($"Random BGM loaded {_acbs.Count} files"); - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(SoundManager), "Play")] - public static void PrePlay(ref SoundManager.AcbID acbID, int cueID) - { - if (acbID != SoundManager.AcbID.Default) return; - if (_acbs.Count == 0) return; - var cueIndex = (Cue)cueID; - switch (cueIndex) - { - case Cue.BGM_ENTRY: - case Cue.BGM_COLLECTION: - case Cue.BGM_RESULT_CLEAR: - case Cue.BGM_RESULT: - var acb = _acbs[UnityEngine.Random.Range(0, _acbs.Count)]; - acbID = SoundManager.AcbID.Max; - var result = Singleton.Instance.LoadCueSheet((int)acbID, acb); - MelonLogger.Msg($"Picked {acb} for {cueIndex}, result: {result}"); - return; - default: - return; - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(SoundManager), "PlayBGM")] - public static bool PrePlayBGM(ref int target) - { - switch (target) - { - case 0: - return true; - case 1: - return false; - case 2: - target = 0; - return true; - default: - return false; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fancy/Triggers.cs b/AquaMai/AquaMai.Mods/Fancy/Triggers.cs deleted file mode 100644 index 7b4ceaf8..00000000 --- a/AquaMai/AquaMai.Mods/Fancy/Triggers.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Diagnostics; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Process; - -namespace AquaMai.Mods.Fancy; - -[ConfigSection( - en: "Triggers for executing commands at certain events.", - zh: "在一定时机执行命令的触发器")] -public class Triggers -{ - [ConfigEntry( - en: "Execute some command on game idle.", - zh: """ - 在游戏闲置的时候执行指定的命令脚本 - 比如说可以在游戏闲置是降低显示器的亮度 - """)] - private static readonly string execOnIdle = ""; - - [ConfigEntry( - en: "Execute some command on game start.", - zh: "在玩家登录的时候执行指定的命令脚本")] - private static readonly string execOnEntry = ""; - - [HarmonyPrefix] - [HarmonyPatch(typeof(AdvertiseProcess), "OnStart")] - public static void AdvertiseProcessPreStart() - { - if (!string.IsNullOrWhiteSpace(execOnIdle)) - { - Exec(execOnIdle); - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(EntryProcess), "OnStart")] - public static void EntryProcessPreStart() - { - if (!string.IsNullOrWhiteSpace(execOnEntry)) - { - Exec(execOnEntry); - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] - public static void MusicSelectProcessPreStart() - { - if (!string.IsNullOrWhiteSpace(execOnEntry)) - { - Exec(execOnEntry); - } - } - - private static void Exec(string command) - { - var process = new System.Diagnostics.Process(); - process.StartInfo.FileName = "cmd.exe"; - process.StartInfo.Arguments = "/c " + command; - process.StartInfo.UseShellExecute = true; - process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - process.StartInfo.WorkingDirectory = Environment.CurrentDirectory; - - process.Start(); - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/Common.cs b/AquaMai/AquaMai.Mods/Fix/Common.cs deleted file mode 100644 index f4e1c54e..00000000 --- a/AquaMai/AquaMai.Mods/Fix/Common.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.Net; -using HarmonyLib; -using Manager; -using Net; -using UnityEngine; -using AquaMai.Config.Attributes; -using AquaMai.Core.Attributes; - -namespace AquaMai.Mods.Fix; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class Common -{ - [ConfigEntry] - private readonly static bool preventIniFileClear = true; - - [EnableIf(nameof(preventIniFileClear))] - [HarmonyPrefix] - [HarmonyPatch(typeof(MAI2System.IniFile), "clear")] - private static bool PreIniFileClear() - { - return false; - } - - [ConfigEntry] - private readonly static bool fixDebugInput = true; - - [EnableIf(nameof(fixDebugInput))] - [HarmonyPrefix] - [HarmonyPatch(typeof(DebugInput), "GetKey")] - private static bool GetKey(ref bool __result, KeyCode name) - { - __result = UnityEngine.Input.GetKey(name); - return false; - } - - [EnableIf(nameof(fixDebugInput))] - [HarmonyPrefix] - [HarmonyPatch(typeof(DebugInput), "GetKeyDown")] - private static bool GetKeyDown(ref bool __result, KeyCode name) - { - __result = UnityEngine.Input.GetKeyDown(name); - return false; - } - - [EnableIf(nameof(fixDebugInput))] - [HarmonyPrefix] - [HarmonyPatch(typeof(DebugInput), "GetMouseButton")] - private static bool GetMouseButton(ref bool __result, int button) - { - __result = UnityEngine.Input.GetMouseButton(button); - return false; - } - - [EnableIf(nameof(fixDebugInput))] - [HarmonyPrefix] - [HarmonyPatch(typeof(DebugInput), "GetMouseButtonDown")] - private static bool GetMouseButtonDown(ref bool __result, int button) - { - __result = UnityEngine.Input.GetMouseButtonDown(button); - return false; - } - - [ConfigEntry] - private readonly static bool bypassCakeHashCheck = true; - - [EnableIf(nameof(bypassCakeHashCheck))] - [HarmonyPostfix] - [HarmonyPatch(typeof(NetHttpClient), MethodType.Constructor)] - private static void OnNetHttpClientConstructor(NetHttpClient __instance) - { - // Bypass Cake.dll hash check - var tInstance = Traverse.Create(__instance).Field("isTrueDll"); - if (tInstance.FieldExists()) - { - tInstance.SetValue(true); - } - } - - [ConfigEntry] - private readonly static bool restoreCertificateValidation = true; - - [EnableIf(nameof(restoreCertificateValidation))] - [HarmonyPostfix] - [HarmonyPatch(typeof(NetHttpClient), "Create")] - private static void OnNetHttpClientCreate() - { - // Unset the certificate validation callback (SSL pinning) to restore the default behavior - ServicePointManager.ServerCertificateValidationCallback = null; - } - - [ConfigEntry] - private readonly static bool forceNonTarget = true; - - [EnableIf(nameof(forceNonTarget))] - [HarmonyPrefix] - [HarmonyPatch(typeof(MAI2System.Config), "IsTarget", MethodType.Getter)] - private static bool PreIsTarget(ref bool __result) - { - // Who is teaching others to set `Target = 1`?! - __result = false; - return false; - } - - [ConfigEntry] - private readonly static bool forceIgnoreError = true; - - [EnableIf(nameof(forceIgnoreError))] - [HarmonyPrefix] - [HarmonyPatch(typeof(MAI2System.Config), "IsIgnoreError", MethodType.Getter)] - private static bool PreIsIgnoreError(ref bool __result) - { - __result = true; - return false; - } - - [ConfigEntry] - private readonly static bool bypassSpecialNumCheck = true; - - public static void OnAfterPatch(HarmonyLib.Harmony h) - { - if (bypassSpecialNumCheck) - { - if (typeof(GameManager).GetMethod("CalcSpecialNum") is null) return; - h.PatchAll(typeof(CalcSpecialNumPatch)); - } - } - - private class CalcSpecialNumPatch - { - [HarmonyPrefix] - [HarmonyPatch(typeof(GameManager), "CalcSpecialNum")] - private static bool CalcSpecialNum(ref int __result) - { - __result = 1024; - return false; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/DebugFeature.cs b/AquaMai/AquaMai.Mods/Fix/DebugFeature.cs deleted file mode 100644 index fb4b10aa..00000000 --- a/AquaMai/AquaMai.Mods/Fix/DebugFeature.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Reflection; -using AquaMai.Config.Attributes; -using HarmonyLib; -using MAI2.Util; -using Manager; -using MelonLoader; -using Monitor; -using Process; -using UnityEngine; - -namespace AquaMai.Mods.Fix; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class DebugFeature -{ - public static bool IsPolyfill { get; private set; } - private static GameProcess _gameProcess; - private static MovieController _gameMovie; - private static GameMonitor[] _monitors; - private static object _debugFeatureOriginal; - private static System.Type _debugFeatureType; - - [HarmonyPatch(typeof(GameProcess), "OnStart")] - [HarmonyPostfix] - public static void Init(GameProcess __instance, MovieController ____gameMovie, GameMonitor[] ____monitors) - { - _gameProcess = __instance; - _gameMovie = ____gameMovie; - _monitors = ____monitors; - PolyFill.timer = 0; - } - - public static void OnBeforePatch(HarmonyLib.Harmony h) - { - var original = typeof(GameProcess).GetField("debugFeature", BindingFlags.NonPublic | BindingFlags.Instance); - if (original is null) - { - MelonLogger.Msg(" > [DebugFeature] Running Polyfill"); - IsPolyfill = true; - h.PatchAll(typeof(PolyFill)); - } - else - { - MelonLogger.Msg(" > [DebugFeature] Already included"); - _debugFeatureType = typeof(GameProcess).GetNestedType("DebugFeature", BindingFlags.Instance | BindingFlags.NonPublic); - h.PatchAll(typeof(GetOriginal)); - } - } - - public static bool Pause - { - get - { - if (IsPolyfill) - { - return PolyFill.isPause; - } - - return (bool)_debugFeatureType.GetField("_debugPause", BindingFlags.Instance | BindingFlags.Public).GetValue(_debugFeatureOriginal); - } - - set - { - if (IsPolyfill) - { - PolyFill.isPause = value; - } - else - { - _debugFeatureType.GetField("_debugPause", BindingFlags.Instance | BindingFlags.Public).SetValue(_debugFeatureOriginal, value); - } - - SoundManager.PauseMusic(value); - _gameMovie.Pause(value); - NotesManager.Pause(value); - } - } - - public static void Seek(int msec) - { - Singleton.Instance.Initialize(); - if (IsPolyfill) - { - PolyFill.DebugTimeSkip(msec); - } - else - { - _debugFeatureType.GetMethod("DebugTimeSkip", BindingFlags.Instance | BindingFlags.Public).Invoke(_debugFeatureOriginal, new object[] { msec }); - } - } - - public static double CurrentPlayMsec - { - [Obsolete("不要用它,它有问题。用 PracticeMode.CurrentPlayMsec")] - get - { - if (IsPolyfill) - { - return PolyFill.timer; - } - - return (double)_debugFeatureType.GetField("_debugTimer", BindingFlags.Instance | BindingFlags.Public).GetValue(_debugFeatureOriginal); - } - set - { - if (IsPolyfill) - { - PolyFill.timer = value; - } - else - { - _debugFeatureType.GetField("_debugTimer", BindingFlags.Instance | BindingFlags.Public).SetValue(_debugFeatureOriginal, value); - } - - Seek(0); - } - } - - private static class GetOriginal - { - [HarmonyPatch(typeof(GameProcess), "OnStart")] - [HarmonyPostfix] - public static void Postfix(object ___debugFeature) - { - _debugFeatureOriginal = ___debugFeature; - } - } - - private static class PolyFill - { - public static bool isPause; - public static double timer; - - public static void DebugTimeSkip(int addMsec) - { - _gameMovie.Pause(pauseFlag: true); - NotesManager.Pause(true); - if (addMsec >= 0) - { - timer += addMsec; - } - else - { - timer = timer + addMsec >= 0.0 ? timer + addMsec : 0.0; - } - - _gameMovie.SetSeekFrame(timer); - SoundManager.SeekMusic((int)timer); - for (int i = 0; i < _monitors.Length; i++) - { - _monitors[i].Seek((int)timer); - } - - // magic number, dont know why - NotesManager.StartPlay((int)timer + 91); - NotesManager.Pause(isPause); - if (!isPause) - { - SoundManager.PauseMusic(pause: false); - _gameMovie.Pause(pauseFlag: false); - } - else - { - _gameMovie.Pause(pauseFlag: true); - } - - _gameProcess.UpdateNotes(); - } - - [HarmonyPatch(typeof(GameProcess), "OnUpdate")] - [HarmonyPostfix] - public static void Postfix(byte ____sequence) - { - if (____sequence != 4) return; - // GameSequence.Play - if (!isPause) - { - timer += GameManager.GetGameMSecAddD(); - } - - if (Input.GetKeyDown(KeyCode.Home)) - { - GameManager.AutoPlay = (GameManager.AutoPlayMode)((int)(GameManager.AutoPlay + 1) % Enum.GetNames(typeof(GameManager.AutoPlayMode)).Length); - } - else if (Input.GetKeyDown(KeyCode.Return)) - { - isPause = !isPause; - SoundManager.PauseMusic(isPause); - _gameMovie.Pause(isPause); - NotesManager.Pause(isPause); - } - else if (DebugInput.GetKeyDown(KeyCode.LeftArrow) || DebugInput.GetKeyDown(KeyCode.RightArrow)) - { - var num23 = 0; - if (DebugInput.GetKeyDown(KeyCode.LeftArrow)) - { - num23 = -1000; - } - - if (DebugInput.GetKeyDown(KeyCode.RightArrow)) - { - num23 = 1000; - } - - int addMsec = ((!DebugInput.GetKey(KeyCode.LeftShift) && !DebugInput.GetKey(KeyCode.RightShift)) ? ((!DebugInput.GetKey(KeyCode.LeftControl) && !DebugInput.GetKey(KeyCode.RightControl)) ? num23 : (num23 * 10)) : (num23 * 5)); - Singleton.Instance.Initialize(); - DebugTimeSkip(addMsec); - } - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/DisableReboot.cs b/AquaMai/AquaMai.Mods/Fix/DisableReboot.cs deleted file mode 100644 index 04effc6f..00000000 --- a/AquaMai/AquaMai.Mods/Fix/DisableReboot.cs +++ /dev/null @@ -1,87 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager.Operation; - -namespace AquaMai.Mods.Fix; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class DisableReboot -{ - // IsAutoRebootNeeded - [HarmonyPrefix] - [HarmonyPatch(typeof(MaintenanceTimer), "IsAutoRebootNeeded")] - public static bool IsAutoRebootNeeded(ref bool __result) - { - __result = false; - return false; - } - - // IsUnderServerMaintenance - [HarmonyPrefix] - [HarmonyPatch(typeof(MaintenanceTimer), "IsUnderServerMaintenance")] - public static bool IsUnderServerMaintenance(ref bool __result) - { - __result = false; - return false; - } - - // RemainingMinutes - // Original: private int RemainingMinutes => (this._secServerMaintenance + 59) / 60; - [HarmonyPrefix] - [HarmonyPatch(typeof(MaintenanceTimer), "RemainingMinutes", MethodType.Getter)] - public static bool RemainingMinutes(ref int __result) - { - __result = 600; - return false; - } - - // GetAutoRebootSec - [HarmonyPrefix] - [HarmonyPatch(typeof(MaintenanceTimer), "GetAutoRebootSec")] - public static bool GetAutoRebootSec(ref int __result) - { - __result = 60 * 60 * 10; - return false; - } - - // GetServerMaintenanceSec - [HarmonyPrefix] - [HarmonyPatch(typeof(MaintenanceTimer), "GetServerMaintenanceSec")] - public static bool GetServerMaintenanceSec(ref int __result) - { - __result = 60 * 60 * 10; - return false; - } - - // Execute - [HarmonyPrefix] - [HarmonyPatch(typeof(MaintenanceTimer), "Execute")] - public static bool Execute(MaintenanceTimer __instance) - { - return false; - } - - // UpdateTimes - [HarmonyPrefix] - [HarmonyPatch(typeof(MaintenanceTimer), "UpdateTimes")] - public static bool UpdateTimes(MaintenanceTimer __instance) - { - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(ClosingTimer), "IsShowRemainingMinutes")] - public static bool IsShowRemainingMinutes(ref bool __result) - { - __result = false; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(ClosingTimer), "IsClosed")] - public static bool IsClosed(ref bool __result) - { - __result = false; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/FixCheckAuth.cs b/AquaMai/AquaMai.Mods/Fix/FixCheckAuth.cs deleted file mode 100644 index 136424c1..00000000 --- a/AquaMai/AquaMai.Mods/Fix/FixCheckAuth.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AMDaemon.Allnet; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; -using Manager.Operation; - -namespace AquaMai.Mods.Fix; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class FixCheckAuth -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(OperationManager), "CheckAuth_Proc")] - private static void PostCheckAuthProc(ref OperationData ____operationData) - { - if (Auth.GameServerUri.StartsWith("http://") || Auth.GameServerUri.StartsWith("https://")) - { - ____operationData.ServerUri = Auth.GameServerUri; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/FixConnSlide.cs b/AquaMai/AquaMai.Mods/Fix/FixConnSlide.cs deleted file mode 100644 index 59b38572..00000000 --- a/AquaMai/AquaMai.Mods/Fix/FixConnSlide.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; - -namespace AquaMai.Mods.Fix.Legacy; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -[EnableGameVersion(23000)] -public class FixConnSlide -{ - /* 这个 Patch 用于修复以下 bug: - * 非 ConnSlide 被错误解析为 ConnSlide (Fes 首日刹那旅程爆机 bug) - * 原 method 逻辑如下: - * - * if (this.IsSlideAll(noteData1.type) && (index1 + 1 < this._note._noteData.Count ? 1 : 0) != 0) - * { - * int targetNote = noteData1.slideData.targetNote; - * if (noteData1.slideData != null) - * targetNote = noteData1.slideData.targetNote; - * for (int index3 = index1; index3 < this._note._noteData.Count; ++index3) - * { - * NoteData noteData3 = this._note._noteData[index3]; - * if (this.IsSlideAll(noteData3.type) && noteData3.time == noteData1.end && noteData3.startButtonPos == targetNote && noteData3.parent == null) - * { - * noteData3.parent = noteData1.parent; - * noteData1.child.Add(noteData3); - * noteData3.isUsed = true; - * noteData3.isJudged = true; - * break; - * } - * } - * } - * - * 修复 bug 需要把第二次调用 this.IsSlideAll() 更改为 this.IsConnectNote(), 这里使用 Transpiler 解决 - */ - [HarmonyTranspiler] - [HarmonyPatch(typeof(NotesReader), "calcSlide")] - private static IEnumerable Fix(IEnumerable instructions) - { - List instList = new List(instructions); - bool found = false; - MethodInfo methodIsSlideAll = AccessTools.Method(typeof(NotesReader), "IsSlideAll"); - MethodInfo methodIsConnectNote = AccessTools.Method(typeof(NotesReader), "IsConnectNote"); - - for (int i = 0; i < instList.Count; i++) - { - CodeInstruction inst = instList[i]; - if (!found && inst.Calls(methodIsSlideAll)) - { - found = true; - continue; - } - - if (found && inst.Calls(methodIsSlideAll)) - { - inst.operand = methodIsConnectNote; - // MelonLogger.Msg($"[FixConnSlide] Successfully patched NotesReader::calcSlide"); - break; - } - } - return instList; - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/FixLevelDisplay.cs b/AquaMai/AquaMai.Mods/Fix/FixLevelDisplay.cs deleted file mode 100644 index 7eab928a..00000000 --- a/AquaMai/AquaMai.Mods/Fix/FixLevelDisplay.cs +++ /dev/null @@ -1,80 +0,0 @@ -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Monitor; -using Monitor.MusicSelect.ChainList; -using UnityEngine; - -namespace AquaMai.Mods.Fix; - -[EnableGameVersion(24000)] -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class FixLevelDisplay -{ - // Fix wrong position of level number's display for music levels with non-consistant display level and rate level (difficuly constant) - // Stock game charts have no such inconsistency, but custom charts may have (e.g. 10+ but unrated) - - [HarmonyPostfix] - [HarmonyPatch(typeof(MusicChainCardObejct), "SetLevel")] - private static void FixLevelShiftMusicChainCardObejct(MusicLevelID levelID, SpriteCounter ____digitLevel, SpriteCounter ____doubleDigitLevel, bool utage, GameObject ____difficultyUtageQuesionMarkSingleDigit, GameObject ____difficultyUtageQuesionMarkDoubleDigit) - { - switch (levelID) - { - case > MusicLevelID.Level9P: - ____digitLevel.gameObject.SetActive(value: false); - ____doubleDigitLevel.gameObject.SetActive(value: true); - ____doubleDigitLevel.ChangeText(levelID.GetLevelNum().PadRight(3)); - break; - case >= MusicLevelID.None: - ____digitLevel.gameObject.SetActive(value: true); - ____doubleDigitLevel.gameObject.SetActive(value: false); - ____digitLevel.ChangeText(levelID.GetLevelNum().PadRight(2)); - break; - } - - if (!utage) return; - switch (levelID) - { - case > MusicLevelID.Level9P: - ____difficultyUtageQuesionMarkSingleDigit.SetActive(value: false); - ____difficultyUtageQuesionMarkDoubleDigit.SetActive(value: true); - break; - case >= MusicLevelID.None: - ____difficultyUtageQuesionMarkSingleDigit.SetActive(value: true); - ____difficultyUtageQuesionMarkDoubleDigit.SetActive(value: false); - break; - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(SingleResultCardController), "SetLevel")] - private static void FixLevelShiftSingleResultCardController(MusicLevelID levelID, bool isUtage, ref SpriteCounter ____difficultySingle, ref SpriteCounter ____difficultyDouble, GameObject ____utageQuestionMarkSingleDigit, GameObject ____utageQuestionMarkDoubleDigit) - { - FixLevelShiftMusicChainCardObejct(levelID, ____difficultySingle, ____difficultyDouble, isUtage, ____utageQuestionMarkSingleDigit, ____utageQuestionMarkDoubleDigit); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(TotalResultPlayer), "SetLevel")] - private static void FixLevelShiftTotalResultPlayer(MusicLevelID levelID, bool isUtage, ref SpriteCounter ____difficultySingle, ref SpriteCounter ____difficultyDouble, GameObject ____utageQuestionMarkSingleDigit, GameObject ____utageQuestionMarkDoubleDigit) - { - FixLevelShiftMusicChainCardObejct(levelID, ____difficultySingle, ____difficultyDouble, isUtage, ____utageQuestionMarkSingleDigit, ____utageQuestionMarkDoubleDigit); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(ResultMonitor), "SetLevel")] - private static void FixLevelShiftTotalResultPlayer(MusicLevelID levelID, ref SpriteCounter ____difficultySingle, ref SpriteCounter ____difficultyDouble) - { - FixLevelShiftMusicChainCardObejct(levelID, ____difficultySingle, ____difficultyDouble, false, null, null); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(TrackStartMonitor), "SetTrackStart")] - private static void FixLevelShiftTrackStartMonitor(int ___monitorIndex, ref SpriteCounter ____difficultySingle, ref SpriteCounter ____difficultyDouble, GameObject ____utageQuestionSingleDigit, GameObject ____utageQuestionDoubleDigit) - { - var music = Singleton.Instance.GetMusic(GameManager.SelectMusicID[___monitorIndex]); - var levelID = (MusicLevelID)music.notesData[GameManager.SelectDifficultyID[___monitorIndex]].musicLevelID; - FixLevelShiftMusicChainCardObejct(levelID, ____difficultySingle, ____difficultyDouble, music.name.id >= 100000, ____utageQuestionSingleDigit, ____utageQuestionDoubleDigit); - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/FixSlideAutoPlay.cs b/AquaMai/AquaMai.Mods/Fix/FixSlideAutoPlay.cs deleted file mode 100644 index 75c28c96..00000000 --- a/AquaMai/AquaMai.Mods/Fix/FixSlideAutoPlay.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; -using Monitor; - -namespace AquaMai.Mods.Fix; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class FixSlideAutoPlay -{ - /* 这个 Patch 用于修复以下 bug: - * SlideFan 在 AutoPlay 时, 只有第一个箭头会消失 - * 原 method 逻辑如下: - * - * if (this.IsNoteCheckTimeStartIgnoreJudgeWait()) - * { - * // do something ... - * if (!GameManager.IsAutoPlay()) - * { - * // do something ... - * for (int index = 0; index < this._arrowPrefubs.Length && (double) index < (double) num2 * 11.0; ++index) - * { - * // do something about displaying arrows ... - * } - * } - * else - * { - * float num4 = (currentMsec - this.StarLaunchMsec) / (this.StarArriveMsec - this.StarLaunchMsec - this.lastWaitTime); - * for (int index = 0; index < this._arrowPrefubs.Length && (double) index < (double) num4 * 1.0; ++index) - * { - * // do something about displaying arrows ... - * } - * if ((double) num4 > 1.0) - * num1 = 3; - * } - * // do something ... - * } - * - * 导致这个 bug 的原因是 else 分支的 for 循环终止条件写错了, 应该是 11.0 (因为有 11 个箭头), SBGA 写成了 1.0 - * 这个 method 中一共只有 5 处 ldc.r4 的 IL Code, 依次为 10.0, 11.0, 1.0, 1.0, 0.0 - * 修复 bug 需要把第三处的 1.0 更改为 11.0, 这里使用 Transpiler 解决 - */ - [HarmonyTranspiler] - [HarmonyPatch(typeof(SlideFan), "NoteCheck")] - private static IEnumerable FixFanAutoPlayArrow(IEnumerable instructions) - { - List instList = new List(instructions); - bool found = false; - for (int i = 0; i < instList.Count; i++) - { - CodeInstruction inst = instList[i]; - if (inst.LoadsConstant(11.0)) - { - found = true; - } - - if (found && inst.LoadsConstant(1.0)) - { - inst.operand = 11.0f; - break; - } - } - return instList; - } - - /* 这个 Patch 让 Slide 在 AutoPlay 的时候, 每个区仍然会分按下和松开两段进行推进 (加上 this._hitIn 的变化) - * 原 method 逻辑如下: - * - * if (!GameManager.IsAutoPlay()) - * { - * // do somethings ... - * } - * else - * { - * float num1 = (currentMsec - this.StarLaunchMsec) / (this.StarArriveMsec - this.StarLaunchMsec - this.lastWaitTime); - * this._hitIndex = (int) ((double) this._hitAreaList.Count * (double) num1); - * if (this._hitIndex >= this._hitAreaList.Count) - * this._hitIndex = this._hitAreaList.Count - 1; - * if (this._hitIndex < 0) - * this._hitIndex = 0; - * int num2 = (int) ((double) this._dispLaneNum * this.GetDeleteArrowDistance()); - * // do somethings ... - * } - * - * 现在要在 this.GetDeleteArrowDistance() 之前插入 - * this._hitIn = ((float)this._hitAreaList.Count * num1 > (float)this._hitIndex + 0.5f); - * 这段代码, 可以采用 Prefix, GetDeleteArrowDistance() 只在两个地方调用过, 另一处就在上面的 if 分支中 (即非 AutoPlay 情况) - */ - [HarmonyPrefix] - [HarmonyPatch(typeof(SlideRoot), "GetDeleteArrowDistance")] - private static void FixSlideAutoPlayArrow( - SlideRoot __instance, ref bool ____hitIn, int ____hitIndex, List ____hitAreaList, - float ___StarLaunchMsec, float ___StarArriveMsec, float ___lastWaitTime - ) - { - if (GameManager.IsAutoPlay()) - { - float prop = (NotesManager.GetCurrentMsec() - ___StarLaunchMsec) / (___StarArriveMsec - ___StarLaunchMsec - ___lastWaitTime); - ____hitIn = ____hitAreaList.Count * prop > ____hitIndex + 0.5f; - } - } - -} diff --git a/AquaMai/AquaMai.Mods/Fix/Legacy/FixQuickRetry130.cs b/AquaMai/AquaMai.Mods/Fix/Legacy/FixQuickRetry130.cs deleted file mode 100644 index ea67cdc2..00000000 --- a/AquaMai/AquaMai.Mods/Fix/Legacy/FixQuickRetry130.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; - -namespace AquaMai.Mods.Fix.Legacy; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -[EnableGameVersion(23000, 23499, noWarn: true)] -public class FixQuickRetry130 -{ - // Fix for the game not resetting Fast and Late counts when quick retrying - // For game version < 1.35.0 - [HarmonyPostfix] - [HarmonyPatch(typeof(GamePlayManager), "SetQuickRetryFrag")] - public static void PostGamePlayManagerSetQuickRetryFrag(GamePlayManager __instance, bool flag) - { - // Since 1.35.0, `GameScoreList.Initialize()` resets the Fast and Late counts - if (flag && !Traverse.Create(typeof(GameScoreList)).Methods().Contains("Initialize")) - { - for (int i = 0; i < 4; i++) - { - var gameScoreList = __instance.GetGameScore(i); - var traverse = Traverse.Create(gameScoreList); - traverse.Property("Fast").SetValue((uint)0); - traverse.Property("Late").SetValue((uint)0); - } - } - } -} diff --git a/AquaMai/AquaMai.Mods/Fix/README.md b/AquaMai/AquaMai.Mods/Fix/README.md deleted file mode 100644 index 047dd121..00000000 --- a/AquaMai/AquaMai.Mods/Fix/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Fix - -Fix of the game's bugs or removal of the game's annoying unuseful "features". - -Non-removal "Fix" patches should have no (negative, or any visual changing) side-effects on the original game. - -All patches under "Fix" should enabled by default and hide in example. They could be still turned off manually in the config. diff --git a/AquaMai/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs b/AquaMai/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs deleted file mode 100644 index 6baca63f..00000000 --- a/AquaMai/AquaMai.Mods/Fix/Stability/FixMissingCharaCrash.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Process; -using Util; - -namespace AquaMai.Mods.Fix.Stability; - -/** - * Fix character selection crashing due to missing character data - */ -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class FixMissingCharaCrash -{ - // Check if the return is null. If it is, make up a color - [HarmonyPostfix] - [HarmonyPatch(typeof(CharacterSelectProces), "GetMapColorData")] - public static void GetMapColorData(ref CharacterSelectProces __instance, ref CharacterMapColorData __result) - { - if (__result != null) return; - - // 1 is a color that definitely exists - if (MapMaster.GetSlotData(1) == null) - { - MapMaster.GetSlotData(1).Load(); - } - __result = MapMaster.GetSlotData(1); - } - - // This is called when loading the music selection screen, to display characters on the top screen - [HarmonyPrefix] - [HarmonyPatch(typeof(Monitor.CommonMonitor), "SetCharacterSlot", new Type[] { typeof(MessageCharactorInfomationData) })] - public static bool SetCharacterSlot(ref MessageCharactorInfomationData data, Dictionary ____characterSlotData) - { - // Some characters are not found in this dictionary. We simply skip loading those characters - if (!____characterSlotData.ContainsKey(data.MapKey)) - { - Console.Log($"Could not get CharacterSlotData for character [Index={data.Index}, MapKey={data.MapKey}], ignoring..."); - return false; - } - - return true; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSettings/CreditConfig.cs b/AquaMai/AquaMai.Mods/GameSettings/CreditConfig.cs deleted file mode 100644 index 52b472f4..00000000 --- a/AquaMai/AquaMai.Mods/GameSettings/CreditConfig.cs +++ /dev/null @@ -1,49 +0,0 @@ -using AquaMai.Config.Attributes; -using AquaMai.Core.Attributes; -using HarmonyLib; - -namespace AquaMai.Mods.GameSettings; - -[ConfigSection( - en: "Set the game to Paid Play (lock credits) or Free Play.", - zh: "设置游戏为付费游玩(锁定可用点数)或免费游玩")] -public class CreditConfig -{ - [ConfigEntry( - en: "Set to Free Play (set to false for Paid Play).", - zh: "是否免费游玩(设为 false 时为付费游玩)")] - private static readonly bool isFreePlay = true; - - [HarmonyPrefix] - [HarmonyPatch(typeof(Manager.Credit), "IsFreePlay")] - private static bool PreIsFreePlay(ref bool __result) - { - __result = isFreePlay; - return false; - } - - [ConfigEntry( - en: "Lock credits amount (only valid in Paid Play). Set to 0 to disable.", - zh: "锁定可用点数数量(仅在付费游玩时有效),设为 0 以禁用")] - private static readonly uint lockCredits = 24u; - - private static bool ShouldLockCredits => !isFreePlay && lockCredits > 0; - - [EnableIf(nameof(ShouldLockCredits))] - [HarmonyPrefix] - [HarmonyPatch(typeof(Manager.Credit), "IsGameCostEnough")] - private static bool PreIsGameCostEnough(ref bool __result) - { - __result = true; - return false; - } - - [EnableIf(nameof(ShouldLockCredits))] - [HarmonyPrefix] - [HarmonyPatch(typeof(AMDaemon.CreditUnit), "Credit", MethodType.Getter)] - private static bool PreCredit(ref uint __result) - { - __result = lockCredits; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSettings/ForceAsServer.cs b/AquaMai/AquaMai.Mods/GameSettings/ForceAsServer.cs deleted file mode 100644 index eebcaef9..00000000 --- a/AquaMai/AquaMai.Mods/GameSettings/ForceAsServer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using AMDaemon; -using AquaMai.Config.Attributes; -using HarmonyLib; - -namespace AquaMai.Mods.GameSettings; - -[ConfigSection( - en: "If you want to configure in-shop party-link, you should turn this off.", - zh: "如果要配置店内招募的话,应该要把这个关闭", - defaultOn: true)] -public class ForceAsServer -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(LanInstall), "IsServer", MethodType.Getter)] - private static bool PreIsServer(ref bool __result) - { - __result = true; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(Network), "IsLanAvailable", MethodType.Getter)] - private static bool PreIsLanAvailable(ref bool __result) - { - __result = false; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSettings/JudgeAdjust.cs b/AquaMai/AquaMai.Mods/GameSettings/JudgeAdjust.cs deleted file mode 100644 index 82092816..00000000 --- a/AquaMai/AquaMai.Mods/GameSettings/JudgeAdjust.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Threading; -using AquaMai.Config.Attributes; -using HarmonyLib; -using IO; -using Manager.UserDatas; - -namespace AquaMai.Mods.GameSettings; - -[ConfigSection( - en: "Globally adjust A/B judgment (unit same as in-game options) or increase touch delay.", - zh: "全局调整 A/B 判(单位和游戏里一样)或增加触摸延迟")] -public class JudgeAdjust -{ - [ConfigEntry( - en: "Adjust A judgment.", - zh: "调整 A 判")] - private static readonly double a = 0; - - [ConfigEntry( - en: "Adjust B judgment.", - zh: "调整 B 判")] - private static readonly double b = 0; - - [ConfigEntry( - en: "Increase touch delay.", - zh: "增加触摸延迟")] - private static readonly uint touchDelay = 0; - - [HarmonyPostfix] - [HarmonyPatch(typeof(UserOption), "GetAdjustMSec")] - public static void GetAdjustMSec(ref float __result) - { - __result += (float)(a * 16.666666d); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(UserOption), "GetJudgeTimingFrame")] - public static void GetJudgeTimingFrame(ref float __result) - { - __result += (float)b; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(NewTouchPanel), "Recv")] - public static void NewTouchPanelRecv() - { - if (touchDelay <= 0) return; - Thread.Sleep((int)touchDelay); - } -} diff --git a/AquaMai/AquaMai.Mods/GameSettings/README.md b/AquaMai/AquaMai.Mods/GameSettings/README.md deleted file mode 100644 index 8aa1d897..00000000 --- a/AquaMai/AquaMai.Mods/GameSettings/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# GameSettings - -Overriding or adjusting the game settings that're originally configurable / modifiable, but made into patches for unification, flexibility or convenience. - -Patches changing the way the game running / behaving which are not possible in the stock game may need to go to the GameSystem category. diff --git a/AquaMai/AquaMai.Mods/GameSettings/TouchSensitivity.cs b/AquaMai/AquaMai.Mods/GameSettings/TouchSensitivity.cs deleted file mode 100644 index 181901cf..00000000 --- a/AquaMai/AquaMai.Mods/GameSettings/TouchSensitivity.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using AquaMai.Config.Attributes; -using HarmonyLib; -using IO; -using Manager; -using MelonLoader; - -namespace AquaMai.Mods.GameSettings; - -[ConfigSection( - en: """ - Use custom touch sensitivity. - When enabled, the settings in Test mode will not take effect. - When disabled, the settings in Test mode is used. - - Sensitivity adjustments in Test mode are not linear. - Default sensitivity in area A: 90, 80, 70, 60, 50, 40, 30, 26, 23, 20, 10. - Default sensitivity in other areas: 70, 60, 50, 40, 30, 20, 15, 10, 5, 1, 1. - A setting of 0 in Test mode corresponds to 40, 20 here, -5 corresponds to 90, 70, +5 corresponds to 10, 1. - The higher the number in Test mode, the lower the number here, resulting in higher sensitivity for official machines. - For ADX, the sensitivity is reversed, so the higher the number here, the higher the sensitivity. - """, - zh: """ - 使用自定义触摸灵敏度 - 这里启用之后 Test 里的就不再起作用了 - 这里禁用之后就还是用 Test 里的调 - - 在 Test 模式下调整的灵敏度不是线性的 - A 区默认灵敏度 90, 80, 70, 60, 50, 40, 30, 26, 23, 20, 10 - 其他区域默认灵敏度 70, 60, 50, 40, 30, 20, 15, 10, 5, 1, 1 - Test 里设置的 0 对应的是 40, 20 这一档,-5 是 90, 70,+5 是 10, 1 - Test 里的数字越大,这里的数字越小,对于官机来说,灵敏度更大 - 而 ADX 的灵敏度是反的,所以对于 ADX,这里的数字越大,灵敏度越大 - """)] -public class TouchSensitivity -{ - [ConfigEntry] - private static readonly byte A1 = 40; - - [ConfigEntry] - private static readonly byte A2 = 40; - - [ConfigEntry] - private static readonly byte A3 = 40; - - [ConfigEntry] - private static readonly byte A4 = 40; - - [ConfigEntry] - private static readonly byte A5 = 40; - - [ConfigEntry] - private static readonly byte A6 = 40; - - [ConfigEntry] - private static readonly byte A7 = 40; - - [ConfigEntry] - private static readonly byte A8 = 40; - - [ConfigEntry] - private static readonly byte B1 = 20; - - [ConfigEntry] - private static readonly byte B2 = 20; - - [ConfigEntry] - private static readonly byte B3 = 20; - - [ConfigEntry] - private static readonly byte B4 = 20; - - [ConfigEntry] - private static readonly byte B5 = 20; - - [ConfigEntry] - private static readonly byte B6 = 20; - - [ConfigEntry] - private static readonly byte B7 = 20; - - [ConfigEntry] - private static readonly byte B8 = 20; - - [ConfigEntry] - private static readonly byte C1 = 20; - - [ConfigEntry] - private static readonly byte C2 = 20; - - [ConfigEntry] - private static readonly byte D1 = 20; - - [ConfigEntry] - private static readonly byte D2 = 20; - - [ConfigEntry] - private static readonly byte D3 = 20; - - [ConfigEntry] - private static readonly byte D4 = 20; - - [ConfigEntry] - private static readonly byte D5 = 20; - - [ConfigEntry] - private static readonly byte D6 = 20; - - [ConfigEntry] - private static readonly byte D7 = 20; - - [ConfigEntry] - private static readonly byte D8 = 20; - - [ConfigEntry] - private static readonly byte E1 = 20; - - [ConfigEntry] - private static readonly byte E2 = 20; - - [ConfigEntry] - private static readonly byte E3 = 20; - - [ConfigEntry] - private static readonly byte E4 = 20; - - [ConfigEntry] - private static readonly byte E5 = 20; - - [ConfigEntry] - private static readonly byte E6 = 20; - - [ConfigEntry] - private static readonly byte E7 = 20; - - [ConfigEntry] - private static readonly byte E8 = 20; - - [HarmonyPrefix] - [HarmonyPatch(typeof(NewTouchPanel), "SetTouchPanelSensitivity")] - public static void SetTouchPanelSensitivityPrefix(List sensitivity) - { - var configType = typeof(TouchSensitivity); - for (var i = 0; i < 34; i++) - { - var area = (InputManager.TouchPanelArea)i; - var field = configType.GetField(area.ToString(), BindingFlags.NonPublic | BindingFlags.Static); - var value = (byte)field.GetValue(null); - sensitivity[i] = value; - } - MelonLogger.Msg("[TouchSensitivity] Applied"); - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/Assets/Fonts.cs b/AquaMai/AquaMai.Mods/GameSystem/Assets/Fonts.cs deleted file mode 100644 index e05f6218..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/Assets/Fonts.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using HarmonyLib; -using MelonLoader; -using TMPro; -using UnityEngine; -using UnityEngine.TextCore.LowLevel; - -namespace AquaMai.Mods.GameSystem.Assets; - -[ConfigSection( - en: "Use custom font(s) as fallback or fully replace the original game font.", - zh: "使用自定义字体作为回退(解决中文字形缺失问题),或完全替换游戏原字体")] -public class Fonts -{ - [ConfigEntry( - en: """ - Font path(s). - Use semicolon to separate multiple paths for a fallback chain. - Microsoft YaHei Bold by default. - """, - zh: """ - 字体路径 - 使用分号分隔多个路径以构成 Fallback 链 - 默认为微软雅黑 Bold - """)] - private static readonly string paths = "%SYSTEMROOT%/Fonts/msyhbd.ttc"; - - [ConfigEntry( - en: "Add custom font(s) as fallback, use original game font when possible.", - zh: "将自定义字体作为游戏原字体的回退,尽可能使用游戏原字体")] - private static readonly bool addAsFallback = true; - - private static List fontAssets = []; - private static readonly List processedFonts = []; - - private static TMP_FontAsset replacementFontAsset; - private static List fallbackFontAssets = []; - - public static void OnBeforePatch() - { - var paths = Fonts.paths - .Split(';') - .Where(p => !string.IsNullOrWhiteSpace(p)) - .Select(FileSystem.ResolvePath); - var fonts = paths - .Select(p => - { - var font = new Font(p); - if (font == null) - { - MelonLogger.Warning($"[Fonts] Font not found: {p}"); - } - return font; - }) - .Where(f => f != null); - fontAssets = fonts - .Select(f => TMP_FontAsset.CreateFontAsset(f, 90, 9, GlyphRenderMode.SDFAA, 8192, 8192)) - .ToList(); - - if (fontAssets.Count == 0) - { - MelonLogger.Warning("[Fonts] No font loaded."); - } - else if (addAsFallback) - { - fallbackFontAssets = fontAssets; - } - else - { - replacementFontAsset = fontAssets[0]; - fallbackFontAssets = fontAssets.Skip(1).ToList(); - } - } - - [HarmonyPatch(typeof(TextMeshProUGUI), "Awake")] - [HarmonyPostfix] - public static void PostAwake(TextMeshProUGUI __instance) - { - if (fontAssets.Count == 0) return; - if (processedFonts.Contains(__instance.font)) return; - - if (replacementFontAsset != null) - { - ProcessReplacement(__instance); - } - if (fallbackFontAssets.Count > 0) - { - ProcessFallback(__instance); - } - - processedFonts.Add(__instance.font); - } - - private static void ProcessReplacement(TextMeshProUGUI __instance) - { -# if DEBUG - MelonLogger.Msg($"{__instance.font.name} {__instance.text}"); -# endif - - var materialOrigin = __instance.fontMaterial; - var materialSharedOrigin = __instance.fontSharedMaterial; - __instance.font = replacementFontAsset; - -# if DEBUG - MelonLogger.Msg($"shaderKeywords {materialOrigin.shaderKeywords.Join()} {__instance.fontMaterial.shaderKeywords.Join()}"); -# endif - // __instance.fontSharedMaterial = materialSharedOrigin; - - // 这样之后该有描边的地方整个字后面都是阴影,它不知道哪里是边 - // materialOrigin.mainTexture = __instance.fontMaterial.mainTexture; - // materialOrigin.mainTextureOffset = __instance.fontMaterial.mainTextureOffset; - // materialOrigin.mainTextureScale = __instance.fontMaterial.mainTextureScale; - // __instance.fontMaterial.CopyPropertiesFromMaterial(materialOrigin); - - // 这样了之后有描边了,但是描边很细 - // __instance.fontMaterial.shader = materialOrigin.shader; - foreach (var keyword in materialOrigin.shaderKeywords) - { - __instance.fontMaterial.EnableKeyword(keyword); - } - // __instance.fontMaterial.globalIlluminationFlags = materialOrigin.globalIlluminationFlags; - - // 原来是 underlay,但是复制这三个属性之后就又变成整个字后面都是阴影了 - // __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetY, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetY)); - // __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetX, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetX)); - // __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayDilate, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayDilate)); - - // if(materialOrigin.shaderKeywords.Contains(ShaderUtilities.Keyword_Underlay)) - // { - // __instance.fontMaterial.EnableKeyword(ShaderUtilities.Keyword_Glow); - // __instance.fontMaterial.SetFloat(ShaderUtilities.ID_GlowOuter, .5f); - // // __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetX, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetX)); - // } - } - - private static void ProcessFallback(TextMeshProUGUI __instance) - { - foreach (var fontAsset in fallbackFontAssets) - { - __instance.font.fallbackFontAssetTable.Add(fontAsset); - } - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/Assets/LoadAssetBundleWithoutManifest.cs b/AquaMai/AquaMai.Mods/GameSystem/Assets/LoadAssetBundleWithoutManifest.cs deleted file mode 100644 index 02b7f7ac..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/Assets/LoadAssetBundleWithoutManifest.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using UnityEngine; -using Manager; -using Util; -using AquaMai.Config.Attributes; - -namespace AquaMai.Mods.GameSystem.Assets; - -[ConfigSection( - en: "Load all existing \".ab\" image resources regardless of the AssetBundleImages manifest.", - zh: """ - 加载所有存在的 .ab 图片资源(无视 AssetBundleImages.manifest) - 导入了删除曲包之类的话,应该需要开启这个 - """)] -public class LoadAssetBundleWithoutManifest -{ - private static HashSet abFiles = new HashSet(); - - [HarmonyPostfix] - [HarmonyPatch(typeof(OptionDataManager), "CheckAssetBundle")] - public static void PostCheckAssetBundle(ref Safe.ReadonlySortedDictionary abs) - { - foreach (var ab in abs) - { - abFiles.Add(ab.Key); - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(AssetBundleManifest), "GetAllAssetBundles")] - public static bool PreGetAllAssetBundles(AssetBundleManifest __instance, ref string[] __result) - { - __result = abFiles.ToArray(); - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs b/AquaMai/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs deleted file mode 100644 index 72152de9..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs +++ /dev/null @@ -1,577 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using HarmonyLib; -using UnityEngine; -using System.Text.RegularExpressions; -using MAI2.Util; -using Manager; -using MelonLoader; -using Monitor; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; - -namespace AquaMai.Mods.GameSystem.Assets; - -[ConfigSection( - en: "Load asset images from the configured directory (for self-made charts).", - zh: "从指定目录下加载资源图片(自制谱用)")] -public class LoadLocalImages -{ - [ConfigEntry] - private static readonly string localAssetsDir = "LocalAssets"; - - private static readonly string[] imageExts = [".jpg", ".png", ".jpeg"]; - private static readonly Dictionary jacketPaths = []; - private static readonly Dictionary framePaths = []; - private static readonly Dictionary platePaths = []; - private static readonly Dictionary framemaskPaths = []; - private static readonly Dictionary framepatternPaths = []; - private static readonly Dictionary iconPaths = []; - private static readonly Dictionary charaPaths = []; - private static readonly Dictionary partnerPaths = []; - //private static readonly Dictionary navicharaPaths = []; - private static readonly Dictionary tabTitlePaths = []; - private static readonly Dictionary localAssetsContents = []; - - [HarmonyPrefix] - [HarmonyPatch(typeof(DataManager), "LoadMusicBase")] - public static void LoadMusicPostfix(List ____targetDirs) - { - foreach (var aDir in ____targetDirs) - { - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\jacket"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\jacket"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_jacket_".Length, 6); - jacketPaths[idStr] = file; - } - - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\frame"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\frame"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_frame_".Length, 6); - framePaths[idStr] = file; - } - - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\nameplate"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\nameplate"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_plate_".Length, 6); - platePaths[idStr] = file; - } - - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\framemask"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\framemask"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_framemask_".Length, 6); - framemaskPaths[idStr] = file; - } - - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\framepattern"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\framepattern"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_framepattern_".Length, 6); - framepatternPaths[idStr] = file; - } - - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\icon"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\icon"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_icon_".Length, 6); - iconPaths[idStr] = file; - } - - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\chara"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\chara"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_chara_".Length, 6); - charaPaths[idStr] = file; - } - - if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\partner"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\partner"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_Partner_".Length, 6); - partnerPaths[idStr] = file; - } - //if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\navichara\sprite\parts\ui_navichara_21"))) - // foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\navichara\sprite\parts\ui_navichara_", charaid))) - //{ - // if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - //var idStr = Path.GetFileName(file).Substring("ui_navichara_".Length, 6); - // navicharaPaths[idStr] = file; - // } - - if (Directory.Exists(Path.Combine(aDir, @"Common\Sprites\Tab\Title"))) - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"Common\Sprites\Tab\Title"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - tabTitlePaths[Path.GetFileNameWithoutExtension(file).ToLowerInvariant()] = file; - } - } - - MelonLogger.Msg($"[LoadLocalImages] Loaded {jacketPaths.Count} Jacket, {platePaths.Count} NamePlate, {framePaths.Count} Frame, {framemaskPaths.Count} FrameMask, {framepatternPaths.Count} FramePattern, {iconPaths.Count} Icon, {charaPaths.Count} Chara, {partnerPaths.Count} PartnerLogo, {tabTitlePaths.Count} Tab Titles from AssetBundleImages."); - - var resolvedDir = FileSystem.ResolvePath(localAssetsDir); - if (Directory.Exists(resolvedDir)) - foreach (var laFile in Directory.EnumerateFiles(resolvedDir)) - { - if (!imageExts.Contains(Path.GetExtension(laFile).ToLowerInvariant())) continue; - localAssetsContents[Path.GetFileNameWithoutExtension(laFile).ToLowerInvariant()] = laFile; - } - - MelonLogger.Msg($"[LoadLocalImages] Loaded {localAssetsContents.Count} LocalAssets."); - } - - private static string GetJacketPath(string id) - { - return localAssetsContents.TryGetValue(id, out var laPath) ? laPath : jacketPaths.GetValueOrDefault(id); - } - - public static Texture2D GetJacketTexture2D(string id) - { - var path = GetJacketPath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - public static Texture2D GetJacketTexture2D(int id) - { - return GetJacketTexture2D($"{id:000000}"); - } - - private static string GetFramePath(string id) - { - return framePaths.GetValueOrDefault(id); - } - - public static Texture2D GetFrameTexture2D(string id) - { - var path = GetFramePath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - private static string GetPlatePath(string id) - { - return platePaths.GetValueOrDefault(id); - } - - public static Texture2D GetPlateTexture2D(string id) - { - var path = GetPlatePath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - private static string GetFrameMaskPath(string id) - { - return framemaskPaths.GetValueOrDefault(id); - } - - public static Texture2D GetFrameMaskTexture2D(string id) - { - var path = GetFrameMaskPath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - private static string GetFramePatternPath(string id) - { - return framepatternPaths.GetValueOrDefault(id); - } - - public static Texture2D GetFramePatternTexture2D(string id) - { - var path = GetFramePatternPath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - private static string GetIconPath(string id) - { - return iconPaths.GetValueOrDefault(id); - } - - public static Texture2D GetIconTexture2D(string id) - { - var path = GetIconPath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - private static string GetCharaPath(string id) - { - return charaPaths.GetValueOrDefault(id); - } - - public static Texture2D GetCharaTexture2D(string id) - { - var path = GetCharaPath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - private static string GetPartnerPath(string id) - { - return partnerPaths.GetValueOrDefault(id); - } - - public static Texture2D GetPartnerTexture2D(string id) - { - var path = GetPartnerPath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - /* - [HarmonyPatch] - public static class TabTitleLoader - { - public static IEnumerable TargetMethods() - { - // Fxxk unity - // game load tab title by call Resources.Load directly - // patching Resources.Load need this stuff - // var method = typeof(Resources).GetMethods(BindingFlags.Public | BindingFlags.Static).First(it => it.Name == "Load" && it.IsGenericMethod).MakeGenericMethod(typeof(Sprite)); - // return [method]; - // but it not work, game will blackscreen if add prefix or postfix - // - // patching AssetBundleManager.LoadAsset will lead game memory error - // return [AccessTools.Method(typeof(AssetBundleManager), "LoadAsset", [typeof(string)], [typeof(Object)])]; - // and this is not work because game not using this - // - // we load them manually after game load and no need to hook the load progress - } - - public static bool Prefix(string path, ref Object __result) - { - if (!path.StartsWith("Common/Sprites/Tab/Title/")) return true; - var filename = Path.GetFileNameWithoutExtension(path).ToLowerInvariant(); - var locPath = localAssetsContents.TryGetValue(filename, out var laPath) ? laPath : tabTitlePaths.GetValueOrDefault(filename); - if (locPath is null) return true; - - var texture = new Texture2D(1, 1); - texture.LoadImage(File.ReadAllBytes(locPath)); - __result = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); - MelonLogger.Msg($"GetTabTitleSpritePrefix {locPath} {__result}"); - return false; - } - } - */ - - [HarmonyPostfix] - [HarmonyPatch(typeof(MusicSelectMonitor), "Initialize")] - public static void TabTitleLoader(MusicSelectMonitor __instance, Dictionary ____genreSprite, Dictionary ____versionSprite) - { - var genres = Singleton.Instance.GetMusicGenres(); - foreach (var (id, genre) in genres) - { - if (____genreSprite.GetValueOrDefault(id) is not null) continue; - var filename = genre.FileName.ToLowerInvariant(); - var locPath = localAssetsContents.TryGetValue(filename, out var laPath) ? laPath : tabTitlePaths.GetValueOrDefault(filename); - if (locPath is null) continue; - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(locPath)); - ____genreSprite[id] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); - } - - var versions = Singleton.Instance.GetMusicVersions(); - foreach (var (id, version) in versions) - { - if (____versionSprite.GetValueOrDefault(id) is not null) continue; - var filename = version.FileName.ToLowerInvariant(); - var locPath = localAssetsContents.TryGetValue(filename, out var laPath) ? laPath : tabTitlePaths.GetValueOrDefault(filename); - if (locPath is null) continue; - var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); - texture.LoadImage(File.ReadAllBytes(locPath)); - ____versionSprite[id] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); - } - } - - [HarmonyPatch] - public static class JacketLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetJacketThumbTexture2D", [typeof(string)]), AM.GetMethod("GetJacketTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Jacket_(\d+)(_s)?\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetJacketTexture2D(id); - __result = texture ?? __instance.LoadAsset($"Jacket/UI_Jacket_{id}.png"); - - return false; - } - } - - [HarmonyPatch] - public static class FrameLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetFrameThumbTexture2D", [typeof(string)]), AM.GetMethod("GetFrameTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Frame_(\d+)(_s)?\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetFrameTexture2D(id); - __result = texture ?? __instance.LoadAsset($"Frame/UI_Frame_{id}.png"); - - return false; - } - } - - [HarmonyPatch] - public static class PlateLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetPlateTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Plate_(\d+)\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetPlateTexture2D(id); - __result = texture ?? __instance.LoadAsset($"NamePlate/UI_Plate_{id}.png"); - - return false; - } - } - - [HarmonyPatch] - public static class FrameMaskLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetFrameMaskTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_FrameMask_(\d+)\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetFrameMaskTexture2D(id); - __result = texture ?? __instance.LoadAsset($"FrameMask/UI_FrameMask_{id}.png"); - - return false; - } - } - - [HarmonyPatch] - public static class FramePatternLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetFramePatternTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_FramePattern_(\d+)\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetFramePatternTexture2D(id); - __result = texture ?? __instance.LoadAsset($"FramePattern/UI_FramePattern_{id}.png"); - - return false; - } - } - - // Private | Instance - [HarmonyPrefix] - [HarmonyPatch(typeof(AssetManager), "GetIconTexture2D", typeof(string))] - public static bool IconLoader(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Icon_(\d+)\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetIconTexture2D(id); - __result = texture ?? __instance.LoadAsset($"Icon/UI_Icon_{id}.png"); - - return false; - } - - [HarmonyPatch] - public static class CharaLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetCharacterTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Chara_(\d+)\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetCharaTexture2D(id); - __result = texture ?? __instance.LoadAsset($"Chara/UI_Chara_{id}.png"); - - return false; - } - } - - [HarmonyPatch] - public static class PartnerLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetPartnerTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Partner_(\d+)\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetPartnerTexture2D(id); - __result = texture ?? __instance.LoadAsset($"Partner/UI_Partner_{id}.png"); - - return false; - } - } - /* - [HarmonyPatch] - public static class FrameLoader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return [AM.GetMethod("GetFrameThumbTexture2D", [typeof(string)]), AM.GetMethod("GetFrameTexture2D", [typeof(string)])]; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Frame_(\d+)\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetFrameTexture2D(id); - __result = texture ?? __instance.LoadAsset($"Frame/UI_Frame_{id}.png"); - - return false; - } - } - */ -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/Assets/UseJacketAsDummyMovie.cs b/AquaMai/AquaMai.Mods/GameSystem/Assets/UseJacketAsDummyMovie.cs deleted file mode 100644 index a39fb265..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/Assets/UseJacketAsDummyMovie.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Linq; -using AquaMai.Config.Attributes; -using HarmonyLib; -using MAI2.Util; -using Manager; -using MelonLoader; -using Monitor.Game; -using UnityEngine; - -namespace AquaMai.Mods.GameSystem.Assets; - -[ConfigSection( - en: """ - Use the png jacket above as MV if no .dat found in the movie folder. - Use together with `LoadLocalImages`. - """, - zh: """ - 如果 movie 文件夹中没有 dat 格式的 MV 的话,就用歌曲的封面做背景,而不是显示迪拉熊的笑脸 - 请和 `LoadLocalImages` 一起用 - """)] -public class UseJacketAsDummyMovie -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(GameCtrl), "IsReady")] - public static void LoadLocalBgaAwake(GameObject ____movieMaskObj) - { - var music = Singleton.Instance.GetMusic(GameManager.SelectMusicID[0]); - if (music is null) return; - - var moviePath = Singleton.Instance.GetMovieDataPath($"{music.movieName.id:000000}") + ".dat"; - if (!moviePath.Contains("dummy")) return; - - var jacket = LoadLocalImages.GetJacketTexture2D(music.movieName.id); - if (jacket is null) - { - MelonLogger.Msg("No jacket found for music " + music); - return; - } - - var components = ____movieMaskObj.GetComponentsInChildren(false); - var movies = components.Where(it => it.name == "Movie"); - - foreach (var movie in movies) - { - // If I create a new RawImage component, the jacket will be not be displayed - // I think it will be difficult to make it work with RawImage - // So I change the material that plays video to default sprite material - // The original player is actually a sprite renderer and plays video with a custom material - var sprite = movie.GetComponent(); - sprite.sprite = Sprite.Create(jacket, new Rect(0, 0, jacket.width, jacket.height), new Vector2(0.5f, 0.5f)); - sprite.material = new Material(Shader.Find("Sprites/Default")); - } - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/CustomCameraId.cs b/AquaMai/AquaMai.Mods/GameSystem/CustomCameraId.cs deleted file mode 100644 index f659d644..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/CustomCameraId.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections; -using System.Linq; -using HarmonyLib; -using Manager; -using MelonLoader; -using UnityEngine; -using AquaMai.Config.Attributes; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: """ - Use custom CameraId rather than the default ones. - If enabled, you can customize the game to use the specified camera. - """, - zh: """ - 使用自定义的摄像头 ID 而不是默认的 - 启用后可以指定游戏使用的摄像头 - """)] -public class CustomCameraId -{ - [ConfigEntry( - en: "Print the camera list to the log when starting, can be used as a basis for modification.", - zh: "启动时打印摄像头列表到日志中,可以作为修改的依据")] - public static bool printCameraList; - - [ConfigEntry( - en: "DX Pass 1P.", - zh: "DX Pass 1P")] - public static int leftQrCamera; - - [ConfigEntry( - en: "DX Pass 2P.", - zh: "DX Pass 2P")] - public static int rightQrCamera; - - [ConfigEntry( - en: "Player Camera.", - zh: "玩家摄像头")] - public static int photoCamera; - - [ConfigEntry( - en: "WeChat QRCode Camera.", - zh: "二维码扫描摄像头")] - public static int chimeCamera; - - private static readonly Dictionary cameraTypeMap = new() - { - ["LeftQrCamera"] = "QRLeft", - ["RightQrCamera"] = "QRRight", - ["PhotoCamera"] = "Photo", - ["ChimeCamera"] = "Chime", - }; - - [HarmonyPrefix] - [HarmonyPatch(typeof(CameraManager), "CameraInitialize")] - public static bool CameraInitialize(CameraManager __instance, ref IEnumerator __result) - { - __result = CameraInitialize(__instance); - return false; - } - - private static IEnumerator CameraInitialize(CameraManager __instance) - { - var textureCache = new WebCamTexture[WebCamTexture.devices.Length]; - SortedDictionary webCamTextures = []; - foreach (var (configEntry, cameraTypeName) in cameraTypeMap) - { - int deviceId = Traverse.Create(typeof(CustomCameraId)).Field(configEntry).GetValue(); - if (deviceId < 0 || deviceId >= WebCamTexture.devices.Length) - { - MelonLogger.Warning($"[CustomCameraId] Ignoring custom camera {configEntry}: camera ID {deviceId} out of range"); - continue; - } - - if (!Enum.TryParse(cameraTypeName, out var cameraType)) - { - MelonLogger.Warning($"[CustomCameraId] Ignoring custom camera {configEntry}: camera type {cameraTypeName} not present"); - continue; - } - - if (textureCache[deviceId] != null) - { - webCamTextures[cameraType] = textureCache[deviceId]; - } - else - { - var webCamTexture = new WebCamTexture(WebCamTexture.devices[deviceId].name); - webCamTextures[cameraType] = webCamTexture; - textureCache[deviceId] = webCamTexture; - } - } - - int textureCount = webCamTextures.Count; - __instance.isAvailableCamera = new bool[textureCount]; - __instance.cameraProcMode = new CameraManager.CameraProcEnum[textureCount]; - - int textureIndex = 0; - foreach (var (cameraType, webCamTexture) in webCamTextures) - { - __instance.isAvailableCamera[textureIndex] = true; - __instance.cameraProcMode[textureIndex] = CameraManager.CameraProcEnum.Good; - CameraManager.DeviceId[(int)cameraType] = textureIndex; - textureIndex++; - } - Traverse.Create(__instance).Field("_webcamtex").SetValue(webCamTextures.Values.ToArray()); - - CameraManager.IsReady = true; - yield break; - } - - public static void OnBeforePatch() - { - if (!printCameraList) - { - return; - } - - WebCamDevice[] devices = WebCamTexture.devices; - string cameraList = "Connected Web Cameras:\n"; - for (int i = 0; i < devices.Length; i++) - { - WebCamDevice webCamDevice = devices[i]; - WebCamTexture webCamTexture = new WebCamTexture(webCamDevice.name); - webCamTexture.Play(); - cameraList += "==================================================\n"; - cameraList += "Name: " + webCamDevice.name + "\n"; - cameraList += $"ID: {i}\n"; - cameraList += $"Resolution: {webCamTexture.width} * {webCamTexture.height}\n"; - cameraList += $"FPS: {webCamTexture.requestedFPS}\n"; - webCamTexture.Stop(); - } - cameraList += "=================================================="; - - foreach (var line in cameraList.Split('\n')) - { - MelonLogger.Msg($"[CustomCameraId] {line}"); - } - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/DisableTimeout.cs b/AquaMai/AquaMai.Mods/GameSystem/DisableTimeout.cs deleted file mode 100644 index d3a21038..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/DisableTimeout.cs +++ /dev/null @@ -1,65 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; -using Monitor; -using Process; -using Process.Entry.State; -using Process.ModeSelect; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: """ - Disable timers (hidden and set to 65535 seconds). - Not recommand to enable when SinglePlayer is off. - """, - zh: """ - 去除并隐藏游戏中的倒计时 - 没有开启单人模式时,不建议启用 - """)] -public class DisableTimeout -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(TimerController), "PrepareTimer")] - public static void PrePrepareTimer(ref int second) - { - second = 65535; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(CommonTimer), "SetVisible")] - public static void CommonTimerSetVisible(ref bool isVisible) - { - isVisible = false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(EntryProcess), "DecrementTimerSecond")] - public static bool EntryProcessDecrementTimerSecond(ContextEntry ____context) - { - SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_SYS_SKIP, 0); - ____context.SetState(StateType.DoneEntry); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(ModeSelectProcess), "UpdateInput")] - public static bool ModeSelectProcessUpdateInput(ModeSelectProcess __instance) - { - if (!InputManager.GetButtonDown(0, InputManager.ButtonSetting.Button05)) return true; - __instance.TimeSkipButtonAnim(InputManager.ButtonSetting.Button05); - SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_SYS_SKIP, 0); - Traverse.Create(__instance).Method("TimeUp").GetValue(); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(PhotoEditProcess), "MainMenuUpdate")] - public static void PhotoEditProcess(PhotoEditMonitor[] ____monitors, PhotoEditProcess __instance) - { - if (!InputManager.GetButtonDown(0, InputManager.ButtonSetting.Button04)) return; - SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_SYS_SKIP, 0); - ____monitors[0].SetButtonPressed(InputManager.ButtonSetting.Button04); - Traverse.Create(__instance).Method("OnTimeUp").GetValue(); - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/KeyMap.cs b/AquaMai/AquaMai.Mods/GameSystem/KeyMap.cs deleted file mode 100644 index 49971604..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/KeyMap.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Reflection; -using AquaMai.Config.Attributes; -using AquaMai.Config.Types; -using HarmonyLib; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: "These settings will work regardless of whether you have enabled segatools' io4 emulation.", - zh: "这里的设置无论你是否启用了 segatools 的 io4 模拟都会工作")] -public class KeyMap -{ - [ConfigEntry] - public static readonly KeyCodeID Test = (KeyCodeID)115; - - [ConfigEntry] - private static readonly KeyCodeID Service = (KeyCodeID)5; - - [ConfigEntry] - private static readonly KeyCodeID Button1_1P = (KeyCodeID)67; - - [ConfigEntry] - private static readonly KeyCodeID Button2_1P = (KeyCodeID)49; - - [ConfigEntry] - private static readonly KeyCodeID Button3_1P = (KeyCodeID)48; - - [ConfigEntry] - private static readonly KeyCodeID Button4_1P = (KeyCodeID)47; - - [ConfigEntry] - private static readonly KeyCodeID Button5_1P = (KeyCodeID)68; - - [ConfigEntry] - private static readonly KeyCodeID Button6_1P = (KeyCodeID)70; - - [ConfigEntry] - private static readonly KeyCodeID Button7_1P = (KeyCodeID)45; - - [ConfigEntry] - private static readonly KeyCodeID Button8_1P = (KeyCodeID)61; - - [ConfigEntry] - private static readonly KeyCodeID Select_1P = (KeyCodeID)25; - - [ConfigEntry] - private static readonly KeyCodeID Button1_2P = (KeyCodeID)80; - - [ConfigEntry] - private static readonly KeyCodeID Button2_2P = (KeyCodeID)81; - - [ConfigEntry] - private static readonly KeyCodeID Button3_2P = (KeyCodeID)78; - - [ConfigEntry] - private static readonly KeyCodeID Button4_2P = (KeyCodeID)75; - - [ConfigEntry] - private static readonly KeyCodeID Button5_2P = (KeyCodeID)74; - - [ConfigEntry] - private static readonly KeyCodeID Button6_2P = (KeyCodeID)73; - - [ConfigEntry] - private static readonly KeyCodeID Button7_2P = (KeyCodeID)76; - - [ConfigEntry] - private static readonly KeyCodeID Button8_2P = (KeyCodeID)79; - - [ConfigEntry] - private static readonly KeyCodeID Select_2P = (KeyCodeID)84; - - [HarmonyPatch(typeof(DB.JvsButtonTableRecord), MethodType.Constructor, typeof(int), typeof(string), typeof(string), typeof(int), typeof(string), typeof(int), typeof(int), typeof(int))] - [HarmonyPostfix] - public static void JvsButtonTableRecordConstructor(DB.JvsButtonTableRecord __instance, string Name) - { - var prop = (DB.KeyCodeID)typeof(KeyMap).GetField(Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).GetValue(null); - __instance.SubstituteKey = prop; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/QuickRetry.cs b/AquaMai/AquaMai.Mods/GameSystem/QuickRetry.cs deleted file mode 100644 index e84f9f65..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/QuickRetry.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: "Hold the bottom four buttons (3456) for quick retry (like in Freedom Mode, default non-utage only).", - zh: "按住下方四个按钮(3456)快速重开本局游戏(像在 Freedom Mode 中一样,默认仅对非宴谱有效)")] -[EnableGameVersion(23000)] -public class QuickRetry -{ - [ConfigEntry( - en: "Force enable in Utage.", - zh: "在宴谱中强制启用")] - private static readonly bool enableInUtage = false; - - [HarmonyPrefix] - [HarmonyPatch(typeof(Monitor.QuickRetry), "IsQuickRetryEnable")] - public static bool OnQuickRetryIsQuickRetryEnable(ref bool __result) - { - if (enableInUtage) - { - __result = true; - } - else - { - var isUtageProperty = Traverse.Create(typeof(GameManager)).Property("IsUtage"); - __result = !isUtageProperty.PropertyExists() || !isUtageProperty.GetValue(); - } - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/README.md b/AquaMai/AquaMai.Mods/GameSystem/README.md deleted file mode 100644 index e9b0be73..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# GameSystem - -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. - -Game asset related patches should go to the Assets subcategory (or the Fancy category if they're too fancy). diff --git a/AquaMai/AquaMai.Mods/GameSystem/RemoveEncryption.cs b/AquaMai/AquaMai.Mods/GameSystem/RemoveEncryption.cs deleted file mode 100644 index 9265afc2..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/RemoveEncryption.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using HarmonyLib; -using Net.Packet; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: """ - 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. - 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. - Please keep this option enabled normally. - """, - zh: """ - 如果你在用未经修改的客户端,会默认加密到服务器的连接,而连接私服的时候不应该加密 - 开了这个选项之后就不会加密连接了,同时也会移除不同版本的客户端可能会对 API 接口加的后缀 - 正常情况下,请保持这个选项开启 - """, - defaultOn: true)] -public class RemoveEncryption -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(Packet), "Obfuscator", typeof(string))] - public static bool PreObfuscator(string srcStr, ref string __result) - { - __result = Shim.RemoveApiSuffix(srcStr); - return false; - } - - [HarmonyPatch] - public class EncryptDecrypt - { - public static IEnumerable TargetMethods() - { - var methods = AccessTools.TypeByName("Net.CipherAES").GetMethods(); - return - [ - methods.FirstOrDefault(it => it.Name == "Encrypt" && it.IsPublic), - methods.FirstOrDefault(it => it.Name == "Decrypt" && it.IsPublic) - ]; - } - - public static bool Prefix(object[] __args, ref object __result) - { - if (__args.Length == 1) - { - // public static byte[] Encrypt(byte[] data) - // public static byte[] Decrypt(byte[] encryptData) - __result = __args[0]; - } - else if (__args.Length == 2) - { - // public static bool Encrypt(byte[] data, out byte[] encryptData) - // public static bool Decrypt(byte[] encryptData, out byte[] plainData) - __args[1] = __args[0]; - __result = true; - } - return false; - } - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/SinglePlayer.cs b/AquaMai/AquaMai.Mods/GameSystem/SinglePlayer.cs deleted file mode 100644 index be6c1edc..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/SinglePlayer.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using AquaMai.Core; -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using AquaMai.Mods.Fancy.GamePlay; -using HarmonyLib; -using MAI2.Util; -using Manager; -using MelonLoader; -using Monitor; -using Monitor.Common; -using Monitor.Entry; -using Monitor.Entry.Parts.Screens; -using UnityEngine; -using Fx; -using Type = System.Type; - -namespace AquaMai.Mods.GameSystem; - -// Hides the 2p (right hand side) UI. -// Note: this is not my original work. I simply interpreted the code and rewrote it as a mod. -[ConfigSection( - en: "Single player: Show 1P only, at the center of the screen.", - zh: "单人模式,不显示 2P")] -public class SinglePlayer -{ - [HarmonyPatch] - public class WhateverInitialize - { - public static IEnumerable TargetMethods() - { - var lateInitialize = AccessTools.Method(typeof(Main.GameMain), "LateInitialize", [typeof(MonoBehaviour), typeof(Transform), typeof(Transform)]); - if (lateInitialize is not null) return [lateInitialize]; - return [AccessTools.Method(typeof(Main.GameMain), "Initialize", [typeof(MonoBehaviour), typeof(Transform), typeof(Transform)])]; - } - - public static void Prefix(MonoBehaviour gameMainObject, ref Transform left, ref Transform right) - { - left.transform.position = Vector3.zero; - right.localScale = Vector3.zero; - GameObject.Find("Mask").transform.position = new Vector3(540f, 0f, 0f); - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(MeshButton), "IsPointInPolygon", new Type[] { typeof(Vector2[]), typeof(Vector2) })] - public static bool IsPointInPolygon(Vector2[] polygon, ref Vector2 point, MeshButton __instance, ref bool __result) - { - __result = RectTransformUtility.RectangleContainsScreenPoint(__instance.GetComponent(), point, Camera.main); - return false; - } - - [EnableGameVersion(21500, noWarn: true)] - public class SkipTimer - { - [HarmonyPostfix] - [HarmonyPatch(typeof(EntryMonitor), "DecideEntry")] - public static void PostDecideEntry(EntryMonitor __instance) - { -# if DEBUG - MelonLogger.Msg("Confirm Entry"); -# endif - TimeManager.MarkGameStartTime(); - Singleton.Instance.UpdateEvent(); - Singleton.Instance.UpdateData(); - __instance.Process.CreateDownloadProcess(); - __instance.ProcessManager.SendMessage(new Message(ProcessType.CommonProcess, 30001)); - __instance.ProcessManager.SendMessage(new Message(ProcessType.CommonProcess, 40000, 0, OperationInformationController.InformationType.Hide)); - __instance.Process.SetNextProcess(); - } - - // To prevent the "長押受付終了" overlay from appearing - [HarmonyPrefix] - [HarmonyPatch(typeof(WaitPartner), "Open")] - public static bool WaitPartnerPreOpen() - { - return false; - } - } - - [ConfigEntry( - en: "Fix hanabi effect under single-player mode (disabled automatically if HideHanabi is enabled).", - zh: "修复单人模式下的烟花效果(如果启用了 HideHanabi,则会自动禁用)")] - public static bool fixHanabi = true; - - private static bool fixHanabiDisableImplied = false; - private static bool FixHanabiEnabled => fixHanabi && !fixHanabiDisableImplied; - - [EnableIf(nameof(FixHanabiEnabled))] - [HarmonyPatch(typeof(TapCEffect), "SetUpParticle")] - [HarmonyPostfix] - public static void PostSetUpParticle(TapCEffect __instance, FX_Mai2_Note_Color ____particleControler) - { - var entities = ____particleControler.GetComponentsInChildren(true); - foreach (var entity in entities) - { - entity.maxParticleSize = 1f; - } - } - - public static void OnBeforePatch() - { - if (ConfigLoader.Config.GetSectionState(typeof(HideHanabi)).Enabled) - { - fixHanabiDisableImplied = true; - } - } - - public static void OnAfterPatch() - { - Core.Helpers.GuiSizes.SinglePlayer = true; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/TestProof.cs b/AquaMai/AquaMai.Mods/GameSystem/TestProof.cs deleted file mode 100644 index 387955c4..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/TestProof.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Diagnostics; -using System.Linq; -using AquaMai.Config.Attributes; -using AquaMai.Config.Types; -using AquaMai.Core; -using AquaMai.Core.Attributes; -using AquaMai.Core.Helpers; -using AquaMai.Mods.Tweaks; -using AquaMai.Mods.UX; -using AquaMai.Mods.UX.PracticeMode; -using HarmonyLib; -using Manager; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: """ - When enabled, test button must be long pressed to enter game test mode. - When test button is bound to other features, this option is enabled automatically. - """, - zh: """ - 启用后,测试键必须长按才能进入游戏测试模式 - 当测试键被绑定到其它功能时,此选项自动开启 - """)] -[EnableImplicitlyIf(nameof(ShouldEnableImplicitly))] -public class TestProof -{ - public static bool ShouldEnableImplicitly - { - get - { - (System.Type section, KeyCodeOrName key)[] featureKeys = - [ - (typeof(OneKeyEntryEnd), OneKeyEntryEnd.key), - (typeof(OneKeyRetrySkip), OneKeyRetrySkip.retryKey), - (typeof(OneKeyRetrySkip), OneKeyRetrySkip.skipKey), - (typeof(HideSelfMadeCharts), HideSelfMadeCharts.key), - (typeof(PracticeMode), PracticeMode.key), - (typeof(ResetTouch), ResetTouch.key), - ]; - var keyMapEnabled = ConfigLoader.Config.GetSectionState(typeof(KeyMap)).Enabled; - return featureKeys.Any(it => - // The feature is enabled and... - ConfigLoader.Config.GetSectionState(it.section).Enabled && - ( - // and the key is test, or... - it.key == KeyCodeOrName.Test || - // or the key have been mapped to the same key as test. - (keyMapEnabled && it.key.ToString() == KeyMap.Test.ToString()))); - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(InputManager), "GetSystemInputDown")] - public static bool GetSystemInputDown(ref bool __result, InputManager.SystemButtonSetting button, bool[] ___SystemButtonDown) - { - __result = ___SystemButtonDown[(int)button]; - if (button != InputManager.SystemButtonSetting.ButtonTest) - return false; - - var stackTrace = new StackTrace(); // get call stack - var stackFrames = stackTrace.GetFrames(); // get method calls (frames) - - if (stackFrames.Any(it => it.GetMethod().Name == "DMD")) - { - __result = KeyListener.GetKeyDownOrLongPress(KeyCodeOrName.Test, true); - } - - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/TouchPanelBaudRate.cs b/AquaMai/AquaMai.Mods/GameSystem/TouchPanelBaudRate.cs deleted file mode 100644 index e9294249..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/TouchPanelBaudRate.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using IO; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: """ - Adjust the baud rate of the touch screen serial port, default value is 9600. - Requires hardware support. If you are unsure, don't use it. - """, - zh: """ - 调整触摸屏串口波特率,默认值 9600 - 需要硬件配合,如果你不清楚你是否可以使用,请不要使用 - """)] -public class TouchPanelBaudRate -{ - [ConfigEntry( - en: "Baud rate.", - zh: "波特率")] - private static readonly int baudRate = 9600; - - [HarmonyPatch(typeof(NewTouchPanel), "Open")] - [HarmonyPrefix] - private static void OpenPrefix(ref int ___BaudRate) - { - if (baudRate <= 0) return; - ___BaudRate = baudRate; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/TouchToButtonInput.cs b/AquaMai/AquaMai.Mods/GameSystem/TouchToButtonInput.cs deleted file mode 100644 index ed8ef966..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/TouchToButtonInput.cs +++ /dev/null @@ -1,59 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Process; -using static Manager.InputManager; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: "Map touch actions to buttons.", - zh: "映射触摸操作至实体按键")] -public class TouchToButtonInput -{ - private static bool _isPlaying = false; - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameProcess), "OnStart")] - public static void OnGameProcessStart(GameProcess __instance) - { - _isPlaying = true; - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameProcess), "OnRelease")] - public static void OnGameProcessRelease(GameProcess __instance) - { - _isPlaying = false; - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(Manager.InputManager), "GetButtonDown")] - public static void GetButtonDown(ref bool __result, int monitorId, ButtonSetting button) - { - if (_isPlaying || __result) return; - if (button.ToString().StartsWith("Button")) - { - __result = GetTouchPanelAreaDown(monitorId, (TouchPanelArea)button); - } - else if (button.ToString().Equals("Select")) - { - __result = GetTouchPanelAreaLongPush(monitorId, TouchPanelArea.C1, 500L) || GetTouchPanelAreaLongPush(monitorId, TouchPanelArea.C2, 500L); - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(Manager.InputManager), "GetButtonPush")] - public static void GetButtonPush(ref bool __result, int monitorId, ButtonSetting button) - { - if (_isPlaying || __result) return; - if (button.ToString().StartsWith("Button")) __result = GetTouchPanelAreaPush(monitorId, (TouchPanelArea)button); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(Manager.InputManager), "GetButtonLongPush")] - public static void GetButtonLongPush(ref bool __result, int monitorId, ButtonSetting button, long msec) - { - if (_isPlaying || __result) return; - if (button.ToString().StartsWith("Button")) __result = GetTouchPanelAreaLongPush(monitorId, (TouchPanelArea)button, msec); - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/Unlock.cs b/AquaMai/AquaMai.Mods/GameSystem/Unlock.cs deleted file mode 100644 index 0e867813..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/Unlock.cs +++ /dev/null @@ -1,90 +0,0 @@ -using AquaMai.Config.Attributes; -using AquaMai.Core.Attributes; -using MAI2System; -using Manager; -using Manager.MaiStudio; -using HarmonyLib; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: "Unlock normally locked (including normally non-unlockable) game content.", - zh: "解锁原本锁定(包括正常途径无法解锁)的游戏内容")] -public class Unlock -{ - [ConfigEntry( - en: "Unlock maps that are not in this version.", - zh: "解锁游戏里所有的区域,包括非当前版本的(并不会帮你跑完)")] - private static readonly bool maps = true; - - [EnableIf(nameof(maps))] - [HarmonyPrefix] - [HarmonyPatch(typeof(MapData), "get_OpenEventId")] - public static bool get_OpenEventId(ref StringID __result) - { - // For any map, return the event ID 1 to unlock it - var id = new Manager.MaiStudio.Serialize.StringID - { - id = 1, - str = "無期限常時解放" - }; - - var sid = new StringID(); - sid.Init(id); - - __result = sid; - return false; - } - - [ConfigEntry( - en: "Unlock normally event-only tickets.", - zh: "解锁游戏里所有可能的跑图券")] - private static readonly bool tickets = true; - - [EnableIf(nameof(tickets))] - [HarmonyPrefix] - [HarmonyPatch(typeof(TicketData), "get_ticketEvent")] - public static bool get_ticketEvent(ref StringID __result) - { - // For any ticket, return the event ID 1 to unlock it - var id = new Manager.MaiStudio.Serialize.StringID - { - id = 1, - str = "無期限常時解放" - }; - - var sid = new StringID(); - sid.Init(id); - - __result = sid; - return false; - } - - [EnableIf(nameof(tickets))] - [HarmonyPrefix] - [HarmonyPatch(typeof(TicketData), "get_maxCount")] - public static bool get_maxCount(ref int __result) - { - // Modify the maxTicketNum to 0 - // this is because TicketManager.GetTicketData adds the ticket to the list if either - // the player owns at least one ticket or the maxTicketNum = 0 - __result = 0; - return false; - } - - [ConfigEntry( - en: "Unlock Utage without the need of DXRating 10000.", - zh: "不需要万分也可以进宴会场")] - private static readonly bool utage = true; - - [EnableIf(nameof(utage))] - [EnableGameVersion(24000)] - [HarmonyPrefix] - [HarmonyPatch(typeof(GameManager), "CanUnlockUtageTotalJudgement")] - public static bool CanUnlockUtageTotalJudgement(out ConstParameter.ResultOfUnlockUtageJudgement result1P, out ConstParameter.ResultOfUnlockUtageJudgement result2P) - { - result1P = ConstParameter.ResultOfUnlockUtageJudgement.Unlocked; - result2P = ConstParameter.ResultOfUnlockUtageJudgement.Unlocked; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/GameSystem/Window.cs b/AquaMai/AquaMai.Mods/GameSystem/Window.cs deleted file mode 100644 index e203f45b..00000000 --- a/AquaMai/AquaMai.Mods/GameSystem/Window.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using AquaMai.Config.Attributes; -using UnityEngine; - -namespace AquaMai.Mods.GameSystem; - -[ConfigSection( - en: "Windowed Mode / Window Settings.", - zh: "窗口化/窗口设置")] -public class Window -{ - [ConfigEntry( - en: "Window the game.", - zh: "窗口化游戏")] - private static readonly bool windowed = false; - - [ConfigEntry( - en: """ - Window width (and height) for windowed mode, rendering resolution for fullscreen mode. - If set to 0, windowed mode will remember the user-set size, fullscreen mode will use the current display resolution. - """, - zh: """ - 宽度(和高度)窗口化时为游戏窗口大小,全屏时为渲染分辨率 - 如果设为 0,窗口化将记住用户设定的大小,全屏时将使用当前显示器分辨率 - """)] - private static readonly int width = 0; - - [ConfigEntry( - en: "Height, as above.", - zh: "高度,同上")] - private static readonly int height = 0; - - private const int GWL_STYLE = -16; - private const int WS_WHATEVER = 0x14CF0000; - - private static IntPtr hwnd = IntPtr.Zero; - - public static void OnBeforePatch() - { - if (windowed) - { - var alreadyWindowed = Screen.fullScreenMode == FullScreenMode.Windowed; - if (width == 0 || height == 0) - { - Screen.fullScreenMode = FullScreenMode.Windowed; - } - else - { - alreadyWindowed = false; - Screen.SetResolution(width, height, FullScreenMode.Windowed); - } - - hwnd = GetWindowHandle(); - if (alreadyWindowed) - { - SetResizeable(); - } - else - { - Task.Run(async () => - { - await Task.Delay(3000); - // Screen.SetResolution has delay - SetResizeable(); - }); - } - } - else - { - var width = Window.width == 0 ? Display.main.systemWidth : Window.width; - var height = Window.height == 0 ? Display.main.systemHeight : Window.height; - Screen.SetResolution(width, height, FullScreenMode.FullScreenWindow); - } - } - - public static void SetResizeable() - { - if (hwnd == IntPtr.Zero) return; - SetWindowLongPtr(hwnd, GWL_STYLE, WS_WHATEVER); - } - - private delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam); - - [DllImport("user32.dll")] - static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam); - - [DllImport("Kernel32.dll")] - static extern int GetCurrentThreadId(); - - static IntPtr GetWindowHandle() - { - IntPtr returnHwnd = IntPtr.Zero; - var threadId = GetCurrentThreadId(); - EnumThreadWindows(threadId, - (hWnd, lParam) => - { - if (returnHwnd == IntPtr.Zero) returnHwnd = hWnd; - return true; - }, IntPtr.Zero); - return returnHwnd; - } - - [DllImport("user32.dll")] - static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, int dwNewLong); -} diff --git a/AquaMai/AquaMai.Mods/General.cs b/AquaMai/AquaMai.Mods/General.cs deleted file mode 100644 index 75c44b59..00000000 --- a/AquaMai/AquaMai.Mods/General.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AquaMai.Config.Attributes; - -namespace AquaMai.Mods; - -// This class is for settings only. Don't patch anything here. - -[ConfigSection( - en: "AquaMai's general settings.", - zh: "AquaMai 的通用设置", - alwaysEnabled: true)] -public class General -{ - [ConfigEntry( - en: """ - Language for mod UI (en and zh supported). - If empty, the system language will be used. - The config file will also be saved in this language. - """, - zh: """ - Mod 界面的语言,支持 en 和 zh - 如果为空,将使用系统语言 - 配置文件也将以此语言保存 - """, - specialConfigEntry: SpecialConfigEntry.Locale)] - public static readonly string locale = ""; -} - -// Please add/remove corresponding entries in SectionNameOrder enum when adding/removing sections. -public enum SectionNameOrder -{ - DeprecationWarning, - General, - Fix, - GameSystem_Assets, - GameSystem, - GameSettings, - Tweaks, - Tweaks_TimeSaving, - UX, - Utils, - Fancy -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/IgnoreAimeServerError.cs b/AquaMai/AquaMai.Mods/Tweaks/IgnoreAimeServerError.cs deleted file mode 100644 index 99e4fcb3..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/IgnoreAimeServerError.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; - -namespace AquaMai.Mods.Tweaks; - -[ConfigSection( - en: "Prevent gray network caused by mistakenly thinking it's an AimeDB server issue.", - zh: "防止因错误认为 AimeDB 服务器问题引起的灰网,建议开启")] -public class IgnoreAimeServerError -{ - [HarmonyPatch(typeof(OperationManager), "IsAliveAimeServer", MethodType.Getter)] - [HarmonyPrefix] - public static bool Prefix(ref bool __result) - { - __result = true; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/LockFrameRate.cs b/AquaMai/AquaMai.Mods/Tweaks/LockFrameRate.cs deleted file mode 100644 index f80eabbb..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/LockFrameRate.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AquaMai.Config.Attributes; -using UnityEngine; - -namespace AquaMai.Mods.Tweaks; - -[ConfigSection( - en: """ - Force the frame rate limit to 60 FPS and disable vSync. - Do not use if your game has no issues. - """, - zh: """ - 强制设置帧率上限为 60 帧并关闭垂直同步 - 如果你的游戏没有问题,请不要使用 - """)] -public class LockFrameRate -{ - public static void OnBeforePatch() - { - Application.targetFrameRate = 60; - QualitySettings.vSyncCount = 0; - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/README.md b/AquaMai/AquaMai.Mods/Tweaks/README.md deleted file mode 100644 index 64233662..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tweaks - -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. - -These patches don't change the way the game behaving, otherwise they may go to the GameSystem category. diff --git a/AquaMai/AquaMai.Mods/Tweaks/ResetTouch.cs b/AquaMai/AquaMai.Mods/Tweaks/ResetTouch.cs deleted file mode 100644 index 64c79d32..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/ResetTouch.cs +++ /dev/null @@ -1,43 +0,0 @@ -using AquaMai.Config.Attributes; -using AquaMai.Config.Types; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using HarmonyLib; -using MAI2.Util; -using Main; -using Manager; -using Process; - -namespace AquaMai.Mods.Tweaks; - -[ConfigSection( - en: "Reset touch panel manually or after playing track.", - zh: "重置触摸面板")] -public class ResetTouch -{ - [ConfigEntry(en: "Reset touch panel after playing track.", zh: "玩完一首歌自动重置")] - private static bool afterTrack = false; - - [ConfigEntry(en: "Reset manually.", zh: "按键重置")] - public static readonly KeyCodeOrName key = KeyCodeOrName.None; - - [ConfigEntry] private static readonly bool longPress = false; - - [HarmonyPostfix] - [HarmonyPatch(typeof(ResultProcess), "OnStart")] - public static void ResultProcessOnStart() - { - if (!afterTrack) return; - SingletonStateMachine.Instance.StartTouchPanel(); - MelonLoader.MelonLogger.Msg("[TouchResetAfterTrack] Touch panel reset"); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameMainObject), "Update")] - public static void OnGameMainObjectUpdate() - { - if (!KeyListener.GetKeyDownOrLongPress(key, longPress)) return; - SingletonStateMachine.Instance.StartTouchPanel(); - MessageHelper.ShowMessage(Locale.TouchPanelReset); - } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Mods/Tweaks/SkipUserVersionCheck.cs b/AquaMai/AquaMai.Mods/Tweaks/SkipUserVersionCheck.cs deleted file mode 100644 index 54a7d04e..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/SkipUserVersionCheck.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Process.Entry.State; - -namespace AquaMai.Mods.Tweaks; - -[ConfigSection( - en: "Allow login with higher data version.", - zh: """ - 原先如果你的账号版本比当前游戏设定的版本高的话,就会不能登录 - 开了这个选项之后就可以登录了,不过你的账号版本还是会被设定为当前游戏的版本 - """)] -public class SkipUserVersionCheck -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(ConfirmPlay), "IsValidVersion")] - public static bool IsValidVersion(ref bool __result) - { - __result = true; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/EntryToMusicSelection.cs b/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/EntryToMusicSelection.cs deleted file mode 100644 index 4b00be95..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/EntryToMusicSelection.cs +++ /dev/null @@ -1,52 +0,0 @@ -using AquaMai.Config.Attributes; -using DB; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Monitor; -using Process; -using Process.Information; - -namespace AquaMai.Mods.Tweaks.TimeSaving; - -[ConfigSection( - en: "Directly enter the song selection screen after login.", - zh: "登录完成后直接进入选歌界面")] -public class EntryToMusicSelection -{ - /* - * Highly experimental, may well break some stuff - * Works by overriding the info screen (where it shows new events and stuff) - * to directly exit to the music selection screen, skipping character and - * event selection, among others - */ - [HarmonyPrefix] - [HarmonyPatch(typeof(InformationProcess), "OnUpdate")] - public static bool OnUpdate(InformationProcess __instance, ProcessDataContainer ___container) - { - GameManager.SetMaxTrack(); - // Set headphone volume - for (var i = 0; i < 2; i++) - { - var userData = UserDataManager.Instance.GetUserData(i); - if (userData.IsEntry) - { - OptionHeadphonevolumeID headPhoneVolume = userData.Option.HeadPhoneVolume; - SoundManager.SetHeadPhoneVolume(i, headPhoneVolume.GetValue()); - } - } - ___container.processManager.AddProcess(new MusicSelectProcess(___container)); - ___container.processManager.ReleaseProcess(__instance); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(MapResultMonitor), "Initialize")] - public static void MapResultMonitorPreInitialize(int monIndex) - { - var userData = Singleton.Instance.GetUserData(monIndex); - var index = userData.MapList.FindIndex((m) => m.ID == userData.Detail.SelectMapID); - if (index >= 0) return; - userData.MapList.Clear(); - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/IWontTapOrSlideVigorously.cs b/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/IWontTapOrSlideVigorously.cs deleted file mode 100644 index 8ec9f44c..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/IWontTapOrSlideVigorously.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; - -namespace AquaMai.Mods.Tweaks.TimeSaving; - -[ConfigSection( - en: "Skip the \"Do not tap or slide vigorously\" screen, immediately proceed to the next screen once data is loaded.", - zh: "跳过“不要大力拍打或滑动哦”这个界面,数据一旦加载完就立马进入下一个界面")] -public class IWontTapOrSlideVigorously -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(PlInformationMonitor), "IsPlayPlInfoEnd")] - public static bool Patch(ref bool __result) - { - __result = true; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipEventInfo.cs b/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipEventInfo.cs deleted file mode 100644 index 70400ed0..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipEventInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Process; -using Process.Information; - -namespace AquaMai.Mods.Tweaks.TimeSaving; - -[ConfigSection( - en: "Skip possible prompts like \"New area discovered\", \"New songs added\", \"There are events\" during game login/registration.", - zh: "跳过登录 / 注册游戏时候可能的 “发现了新的区域哟” “乐曲增加” “有活动哟” 之类的提示")] -public class SkipEventInfo -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(InformationProcess), "OnStart")] - public static void InformationProcessPostStart(ref uint ____state) - { - ____state = 3; - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(RegionalSelectProcess), "OnStart")] - public static void RegionalSelectProcessPreStart(ref Queue[] ____discoverList) - { - ____discoverList = new Queue[] { new Queue(), new Queue() }; - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipGoodbyeScreen.cs b/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipGoodbyeScreen.cs deleted file mode 100644 index e6597d61..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipGoodbyeScreen.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; -using Process; - -namespace AquaMai.Mods.Tweaks.TimeSaving; - -[ConfigSection( - en: "Skip the \"Goodbye\" screen at the end of the game.", - zh: "跳过游戏结束的「再见」界面")] -public class SkipGoodbyeScreen -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(GameOverMonitor), "IsPlayEnd")] - public static bool GameOverMonitorPlayEnd(ref bool __result) - { - __result = true; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(GameOverProcess), "OnUpdate")] - public static void GameOverProcessOnUpdate(ref GameOverProcess.GameOverSequence ____state) - { - if (____state == GameOverProcess.GameOverSequence.SkyChange) - { - ____state = GameOverProcess.GameOverSequence.Disp; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupDelays.cs b/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupDelays.cs deleted file mode 100644 index 9517fc9b..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupDelays.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Diagnostics; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Process; - -namespace AquaMai.Mods.Tweaks.TimeSaving; - -[ConfigSection( - en: "Skip useless 2s delays to speed up the game boot process.", - zh: """ - 在自检界面,每个屏幕结束的时候都会等两秒才进入下一个屏幕,很浪费时间 - 开了这个选项之后就不会等了 - """)] -public class SkipStartupDelays -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(PowerOnProcess), "OnStart")] - public static void PrePowerOnStart(ref float ____waitTime) - { - ____waitTime = 0f; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StartupProcess), "OnUpdate")] - public static void PreStartupUpdate(byte ____state, ref Stopwatch ___timer) - { - if (____state == 8) - { - Traverse.Create(___timer).Field("elapsed").SetValue(2 * 10000000L); - } - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupWarning.cs b/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupWarning.cs deleted file mode 100644 index 5b816681..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipStartupWarning.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; - -namespace AquaMai.Mods.Tweaks.TimeSaving; - -[ConfigSection( - en: "Skip SDEZ's warning screen and logo shown after the POST sequence.", - zh: "跳过 SDEZ 启动时的 WARNING 界面")] -public class SkipStartupWarning -{ - /* - * Patch PlayLogo to disable the warning screen - */ - [HarmonyPrefix] - [HarmonyPatch(typeof (WarningMonitor), "PlayLogo")] - public static bool PlayLogo() - { - // Return false to block the original method - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof (WarningMonitor), "IsLogoAnimationEnd")] - public static bool IsLogoAnimationEnd(ref bool __result) - { - // Always return true to indicate the animation has ended - __result = true; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipTrackStart.cs b/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipTrackStart.cs deleted file mode 100644 index 625cfe32..00000000 --- a/AquaMai/AquaMai.Mods/Tweaks/TimeSaving/SkipTrackStart.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AquaMai.Config.Attributes; -using HarmonyLib; -using Monitor; - -namespace AquaMai.Mods.Tweaks.TimeSaving; - -[ConfigSection( - en: "Skip TrackStart screen.", - zh: "跳过乐曲开始界面")] -public class SkipTrackStart -{ - [HarmonyPrefix] - [HarmonyPatch(typeof (TrackStartMonitor), "IsEnd")] - public static bool IsEnd(ref bool __result) - { - __result = true; - return false; - } -} diff --git a/AquaMai/AquaMai.Mods/UX/CiBuildAlert.cs b/AquaMai/AquaMai.Mods/UX/CiBuildAlert.cs deleted file mode 100644 index 27caa92f..00000000 --- a/AquaMai/AquaMai.Mods/UX/CiBuildAlert.cs +++ /dev/null @@ -1,26 +0,0 @@ -using AquaMai.Config.Attributes; -using AquaMai.Core.Attributes; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using HarmonyLib; -using Process; - -namespace AquaMai.Mods.UX; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -[EnableIf(nameof(isCiBuild))] -public class CiBuildAlert -{ -# if CI - private static readonly bool isCiBuild = true; -# else - private static readonly bool isCiBuild = false; -# endif - - [HarmonyPatch(typeof(AdvertiseProcess), "OnStart")] - [HarmonyPostfix] - public static void OnStart(AdvertiseProcess __instance) - { - MessageHelper.ShowMessage(Locale.CiBuildAlertContent, title: Locale.CiBuildAlertTitle); - } -} diff --git a/AquaMai/AquaMai.Mods/UX/HideSelfMadeCharts.cs b/AquaMai/AquaMai.Mods/UX/HideSelfMadeCharts.cs deleted file mode 100644 index 6a244382..00000000 --- a/AquaMai/AquaMai.Mods/UX/HideSelfMadeCharts.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using AquaMai.Config.Attributes; -using AquaMai.Config.Types; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using HarmonyLib; -using MAI2.Util; -using Manager; -using MelonLoader; -using Process; -using Util; - -namespace AquaMai.Mods.UX; - -[ConfigSection( - en: "One key to hide all self-made charts in the music select process. Or hide for some users.", - zh: "在选曲界面一键隐藏所有自制谱,或对一部分用户进行隐藏")] -public class HideSelfMadeCharts -{ - [ConfigEntry( - en: "Key to toggle self-made charts.", - zh: "切换自制谱显示的按键")] - public static readonly KeyCodeOrName key = KeyCodeOrName.Test; - - [ConfigEntry] public static readonly bool longPress = false; - - [ConfigEntry( - en: "One user ID per line in the file. Hide self-made charts when these users login.", - zh: "该文件中每行一个用户 ID,当这些用户登录时隐藏自制谱")] - private static readonly string selfMadeChartsDenyUsersFile = "LocalAssets/SelfMadeChartsDenyUsers.txt"; - - [ConfigEntry( - en: "One user ID per line in the file. Only show self-made charts when these users login.", - zh: "该文件中每行一个用户 ID,只有这些用户登录时才显示自制谱")] - private static readonly string selfMadeChartsWhiteListUsersFile = "LocalAssets/SelfMadeChartsWhiteListUsers.txt"; - - private static Safe.ReadonlySortedDictionary _musics; - private static Safe.ReadonlySortedDictionary _musicsNoneSelfMade; - - private static bool isShowSelfMadeCharts = true; - private static bool isForceDisable; - - [HarmonyPostfix] - [HarmonyPatch(typeof(DataManager), "GetMusics")] - public static void GetMusics(ref Safe.ReadonlySortedDictionary __result, List ____targetDirs) - { - if (_musics is null) - { - // init musics for the first time - if (__result.Count == 0) return; - _musics = __result; - var nonSelfMadeList = new SortedDictionary(); - var officialDirs = ____targetDirs.Where(it => File.Exists(Path.Combine(it, "DataConfig.xml")) || File.Exists(Path.Combine(it, "OfficialChartsMark.txt"))); - foreach (var music in __result) - { - if (officialDirs.Any(it => MusicDirHelper.LookupPath(music.Value).StartsWith(it))) - { - nonSelfMadeList.Add(music.Key, music.Value); - } - } - - _musicsNoneSelfMade = new Safe.ReadonlySortedDictionary(nonSelfMadeList); - MelonLogger.Msg($"[HideSelfMadeCharts] All music count: {__result.Count}, Official music count: {_musicsNoneSelfMade.Count}"); - } - - var stackTrace = new StackTrace(); // get call stack - var stackFrames = stackTrace.GetFrames(); // get method calls (frames) - if (stackFrames.All(it => it.GetMethod().DeclaringType.Name != "MusicSelectProcess")) return; - if (isShowSelfMadeCharts && !isForceDisable) return; - __result = _musicsNoneSelfMade; - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(MusicSelectProcess), "OnUpdate")] - public static void MusicSelectProcessOnUpdate(ref MusicSelectProcess __instance) - { - if (isForceDisable) return; - if (!KeyListener.GetKeyDownOrLongPress(key, longPress)) return; - isShowSelfMadeCharts = !isShowSelfMadeCharts; - MelonLogger.Msg($"[HideSelfMadeCharts] isShowSelfMadeCharts: {isShowSelfMadeCharts}"); - SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, __instance, new MusicSelectProcess(SharedInstances.ProcessDataContainer))); - Task.Run(async () => - { - await Task.Delay(1000); - MessageHelper.ShowMessage(isShowSelfMadeCharts ? Locale.SelfMadeChartsShow : Locale.SelfMadeChartsHide); - }); - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(MusicSelectProcess), "OnStart")] - public static void MusicSelectProcessOnStart(ref MusicSelectProcess __instance) - { - var denyPath = FileSystem.ResolvePath(selfMadeChartsDenyUsersFile); - if (File.Exists(denyPath)) - { - var userIds = File.ReadAllLines(denyPath); - for (var i = 0; i < 2; i++) - { - var user = Singleton.Instance.GetUserData(i); - if (!user.IsEntry) continue; - if (!userIds.Contains(user.Detail.UserID.ToString())) continue; - isForceDisable = true; - return; - } - } - - var whiteListPath = FileSystem.ResolvePath(selfMadeChartsWhiteListUsersFile); - if (File.Exists(whiteListPath)) - { - var userIds = File.ReadAllLines(whiteListPath); - for (var i = 0; i < 2; i++) - { - var user = Singleton.Instance.GetUserData(i); - if (!user.IsEntry) continue; - if (userIds.Contains(user.Detail.UserID.ToString())) continue; - isForceDisable = true; - return; - } - } - - isForceDisable = false; - } - - - [HarmonyPostfix] - [HarmonyPatch(typeof(EntryProcess), "OnStart")] - public static void EntryProcessOnStart(ref EntryProcess __instance) - { - // reset status on login - isShowSelfMadeCharts = true; - } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Mods/UX/ImmediateSave.cs b/AquaMai/AquaMai.Mods/UX/ImmediateSave.cs deleted file mode 100644 index c4b918b4..00000000 --- a/AquaMai/AquaMai.Mods/UX/ImmediateSave.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using DB; -using HarmonyLib; -using MAI2.Util; -using MAI2System; -using Manager; -using Manager.MaiStudio; -using Manager.UserDatas; -using MelonLoader; -using Monitor.Entry.Parts.Screens; -using Net.Packet; -using Net.Packet.Helper; -using Process; -using Process.UserDataNet.State.UserDataULState; -using UnityEngine; - -namespace AquaMai.Mods.UX; - -[ConfigSection( - en: "Save immediate after playing a song.", - zh: "打完一首歌的时候立即向服务器保存成绩")] -public class ImmediateSave -{ - [HarmonyPrefix] - [HarmonyPatch(typeof(StateULUserAime), "RequestUploadUserPlayLogData")] - public static bool PreRequestUploadUserPlayLogData(StateULUserAime __instance) - { - Traverse.Create(__instance).Method("RequestUploadUserPortraitData").GetValue(); - return false; - } - - private static SavingUi ui; - - [HarmonyPostfix] - [HarmonyPatch(typeof(ResultProcess), "OnStart")] - public static void ResultProcessOnStart() - { - var doneCount = 0; - - void CheckSaveDone() - { - doneCount++; - if (doneCount != 4) return; - if (ui == null) return; - UnityEngine.Object.Destroy(ui); - ui = null; - } - - for (int i = 0; i < 2; i++) - { - var j = i; - var userData = Singleton.Instance.GetUserData(i); - if (!userData.IsEntry || userData.IsGuest()) - { - doneCount += 2; - continue; - } - - if (ui == null) - { - ui = SharedInstances.GameMainObject.gameObject.AddComponent(); - } - - SaveDataFix(userData); - PacketHelper.StartPacket(Shim.CreatePacketUploadUserPlaylog(i, userData, (int)GameManager.MusicTrackNumber - 1, - delegate - { - MelonLogger.Msg("[ImmediateSave] Playlog saved"); - CheckSaveDone(); - }, - delegate(PacketStatus err) - { - SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_ENTRY_AIME_ERROR, j); - MelonLogger.Error("[ImmediateSave] Playlog save error"); - MelonLogger.Error(err); - MessageHelper.ShowMessage(Locale.PlaylogSaveError); - CheckSaveDone(); - })); - PacketHelper.StartPacket(Shim.CreatePacketUpsertUserAll(i, userData, delegate(int code) - { - if (code == 1) - { - MelonLogger.Msg("[ImmediateSave] UserAll saved"); - CheckSaveDone(); - } - else - { - SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_ENTRY_AIME_ERROR, j); - MelonLogger.Error("[ImmediateSave] UserAll upsert error"); - MelonLogger.Error(code); - MessageHelper.ShowMessage(Locale.UserAllUpsertError); - CheckSaveDone(); - } - }, delegate(PacketStatus err) - { - SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_ENTRY_AIME_ERROR, j); - MelonLogger.Error("[ImmediateSave] UserAll upsert error"); - MelonLogger.Error(err); - MessageHelper.ShowMessage(Locale.UserAllUpsertError); - CheckSaveDone(); - })); - } - } - - - private static void SaveDataFix(UserData userData) - { - UserDetail detail = userData.Detail; - detail.EventWatchedDate = TimeManager.GetDateString(TimeManager.PlayBaseTime); - userData.CalcTotalValue(); - float num = 0f; - try - { - if (userData.RatingList.RatingList.Any()) - { - num = userData.RatingList.RatingList.Last().SingleRate; - } - } - catch - { - } - - num = (float)Math.Ceiling((double)((num + 1f) / GameManager.TheoryRateBorderNum) * 10.0); - float num2 = 0f; - try - { - if (userData.RatingList.NextRatingList.Any()) - { - num2 = userData.RatingList.NewRatingList.Last().SingleRate; - } - } - catch - { - } - - num2 = (float)Math.Ceiling((double)((num2 + 1f) / GameManager.TheoryRateBorderNum) * 10.0); - string logDateString = TimeManager.GetLogDateString(TimeManager.PlayBaseTime); - string timeJp = (string.IsNullOrEmpty(userData.Detail.DailyBonusDate) ? TimeManager.GetLogDateString(0L) : userData.Detail.DailyBonusDate); - if (userData.IsEntry && userData.Detail.IsNetMember >= 2 && !GameManager.IsEventMode && TimeManager.GetUnixTime(logDateString) > TimeManager.GetUnixTime(timeJp) && Singleton.Instance.IsSingleUser() && !GameManager.IsFreedomMode && !GameManager.IsCourseMode && !DoneEntry.IsWeekdayBonus(userData)) - { - userData.Detail.DailyBonusDate = logDateString; - } - - List list = new List(); - List list2 = new List(); - IEnumerable[] scoreList = Shim.GetUserScoreList(userData); - List ratingList = userData.RatingList.RatingList; - List newRatingList = userData.RatingList.NewRatingList; - int achive = RatingTableID.Rate_22.GetAchive(); - for (int j = 0; j < scoreList.Length; j++) - { - if (scoreList[j] == null) - { - continue; - } - - foreach (UserScore item2 in scoreList[j]) - { - if (achive <= item2.achivement) - { - continue; - } - - MusicData music = Singleton.Instance.GetMusic(item2.id); - if (music == null) - { - continue; - } - - UserRate item = new UserRate(item2.id, j, item2.achivement, (uint)music.version); - if (item.OldFlag) - { - if (num <= (float)item.Level && !ratingList.Contains(item)) - { - list.Add(item); - } - } - else if (num2 <= (float)item.Level && !newRatingList.Contains(item)) - { - list2.Add(item); - } - } - } - - list.Sort(); - list.Reverse(); - if (list.Count > 10) - { - list.RemoveRange(10, list.Count - 10); - } - - userData.RatingList.NextRatingList = list; - list2.Sort(); - list2.Reverse(); - if (list2.Count > 10) - { - list2.RemoveRange(10, list2.Count - 10); - } - - userData.RatingList.NextNewRatingList = list2; - - userData.Detail.LastPlayCredit = 0; - userData.Detail.LastPlayMode = 0; - if (GameManager.IsFreedomMode) - { - userData.Detail.LastPlayMode = 1; - } - - if (GameManager.IsCourseMode) - { - userData.Detail.LastPlayMode = 2; - } - - userData.Detail.LastGameId = ConstParameter.GameIDStr; - userData.Detail.LastRomVersion = Singleton.Instance.config.romVersionInfo.versionNo.versionString; - userData.Detail.LastDataVersion = Singleton.Instance.config.dataVersionInfo.versionNo.versionString; - } - - private class SavingUi : MonoBehaviour - { - public void OnGUI() - { - var y = Screen.height * .075f; - var width = GuiSizes.FontSize * 20f; - var x = GuiSizes.PlayerCenter + GuiSizes.PlayerWidth / 2f - width; - var rect = new Rect(x, y, width, GuiSizes.LabelHeight * 2.5f); - - var labelStyle = GUI.skin.GetStyle("label"); - labelStyle.fontSize = (int)(GuiSizes.FontSize * 1.2); - labelStyle.alignment = TextAnchor.MiddleCenter; - - GUI.Box(rect, ""); - GUI.Label(rect, Locale.SavingDontExit); - } - } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Mods/UX/JudgeAccuracyInfo.cs b/AquaMai/AquaMai.Mods/UX/JudgeAccuracyInfo.cs deleted file mode 100644 index 439aa6da..00000000 --- a/AquaMai/AquaMai.Mods/UX/JudgeAccuracyInfo.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using AquaMai.Config.Attributes; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Monitor; -using Monitor.Result; -using Process; -using TMPro; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace AquaMai.Mods.UX; - -[ConfigSection( - zh: "在游戏总结的计分板中显示击打误差的详细信息(以帧为单位)", - en: "Show detailed accuracy info in the score board.")] -public class JudgeAccuracyInfo -{ - public class AccuracyEntryList - { - public List[] DiffList = new List[TableRowNames.Length]; - public List[] RawDiffList = new List[TableRowNames.Length]; - public HashSet NoteIndices = new(); - - public AccuracyEntryList() - { - for (int i = 0; i < TableRowNames.Length; i++) - { - DiffList[i] = new List(); - RawDiffList[i] = new List(); - } - } - } - - public static AccuracyEntryList[] EntryList = new AccuracyEntryList[2]; - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameProcess), "OnStart")] - private static void OnGameProcessStartFinish() - { - for (int i = 0; i < EntryList.Length; i++) - { - EntryList[i] = new AccuracyEntryList(); - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(ResultProcess), "OnRelease")] - private static void OnResultProcessReleaseFinish() - { - for (int i = 0; i < EntryList.Length; i++) - { - EntryList[i] = null; - Controllers[i] = null; - } - } - - [HarmonyPatch] - public static class NoteBaseJudgePatch - { - public static IEnumerable TargetMethods() - { - return - [ - AccessTools.Method(typeof(NoteBase), "Judge"), - AccessTools.Method(typeof(HoldNote), "JudgeHoldHead"), - AccessTools.Method(typeof(BreakHoldNote), "JudgeHoldHead"), - AccessTools.Method(typeof(TouchNoteB), "Judge"), - AccessTools.Method(typeof(TouchHoldC), "JudgeHoldHead"), - ]; - } - - public static void Postfix( - NoteBase __instance, bool __result, - float ___JudgeTimingDiffMsec, float ___AppearMsec, NoteJudge.EJudgeType ___JudgeType, int ___NoteIndex - ) - { - var monitor = __instance.MonitorId; - if (!__result || EntryList[monitor].NoteIndices.Contains(___NoteIndex)) return; - - EntryList[monitor].NoteIndices.Add(___NoteIndex); - - var raw = (NotesManager.GetCurrentMsec() - ___AppearMsec) - NoteJudge.JudgeAdjustMs; - switch (___JudgeType) - { - case NoteJudge.EJudgeType.Tap: - case NoteJudge.EJudgeType.Break: - { - EntryList[monitor].DiffList[0].Add(___JudgeTimingDiffMsec); - EntryList[monitor].RawDiffList[0].Add(raw); - break; - } - case NoteJudge.EJudgeType.Touch: - { - EntryList[monitor].DiffList[2].Add(___JudgeTimingDiffMsec); - EntryList[monitor].RawDiffList[2].Add(raw); - break; - } - case NoteJudge.EJudgeType.ExTap: - { - EntryList[monitor].DiffList[3].Add(___JudgeTimingDiffMsec); - EntryList[monitor].RawDiffList[3].Add(raw); - break; - } - } - - // MelonLogger.Msg($"{___JudgeType}: {___JudgeTimingDiffMsec}, {raw}"); - } - } - - - [HarmonyPostfix] - [HarmonyPatch(typeof(SlideRoot), "Judge")] - private static void SlideRootJudgePatch( - SlideRoot __instance, bool __result, - float ___JudgeTimingDiffMsec, float ___TailMsec, float ___lastWaitTimeForJudge, - NoteJudge.EJudgeType ___JudgeType, int ___NoteIndex - ) - { - var monitor = __instance.MonitorId; - var userData = Singleton.Instance.GetUserData(monitor); - if(!userData.IsEntry) return; - - if (!__result || EntryList[monitor].NoteIndices.Contains(___NoteIndex)) return; - - EntryList[monitor].NoteIndices.Add(___NoteIndex); - - var raw = (NotesManager.GetCurrentMsec() - ___TailMsec + ___lastWaitTimeForJudge) - NoteJudge.JudgeAdjustMs; - EntryList[monitor].DiffList[1].Add(___JudgeTimingDiffMsec - NoteJudge.JudgeAdjustMs); - EntryList[monitor].RawDiffList[1].Add(raw); - - // MelonLogger.Msg($"{___JudgeType}: {___JudgeTimingDiffMsec}, {raw}"); - } - - - [HarmonyPostfix] - [HarmonyPatch(typeof(ResultProcess), "OnStart")] - private static void OnResultProcessStartFinish( - ResultMonitor[] ____monitors, ResultProcess.ResultScoreViewType[] ____resultScoreViewType, UserData[] ____userData - ) - { - foreach (var monitor in ____monitors) - { - var idx = monitor.MonitorIndex; - if (!____userData[idx].IsEntry) continue; - - var fileName = $"Acc_Track_{GameManager.MusicTrackNumber}_Player_{idx}.txt"; - var filePath = Path.Combine(Environment.CurrentDirectory, fileName); - - using (var writer = new StreamWriter(filePath)) - { - for (int i = 0; i < TableRowNames.Length; i++) - { - writer.WriteLine($"Row: {TableRowNames[i]}"); - writer.WriteLine(" DiffList:"); - writer.WriteLine($" {string.Join(", ", EntryList[idx].DiffList[i])}"); - writer.WriteLine(" RawDiffList:"); - writer.WriteLine($" {string.Join(", ", EntryList[idx].RawDiffList[i])}"); - writer.WriteLine(); - } - } - - var controller = Traverse.Create(monitor).Field("_scoreBoardController").Value; - var newController = Object.Instantiate(controller, controller.transform); - newController.gameObject.GetComponent().enabled = false; - newController.transform.localPosition = Vector3.zero; - var table = ExtractTextObjs(newController); - for (var i = 0; i < TableHead.Length; i++) - { - table[0, i].text = TableHead[i]; - } - - for (var i = 0; i < TableRowNames.Length; i++) - { - table[i + 1, 0].text = TableRowNames[i]; - var num = EntryList[idx].DiffList[i].Count; - table[i + 1, 1].text = num.ToString(); - if (num <= 0) - { - table[i + 1, 2].text = "——"; - table[i + 1, 3].text = "——"; - table[i + 1, 4].text = "——"; - continue; - } - - var average = EntryList[idx].DiffList[i].Average(); - var averageFrame = average * 0.06f; - table[i + 1, 2].text = averageFrame.ToString("+0.00;-0.00;0.00", CultureInfo.InvariantCulture); - var averageRawFrame = EntryList[idx].RawDiffList[i].Average() * 0.06f; - table[i + 1, 3].text = averageRawFrame.ToString("+0.00;-0.00;0.00", CultureInfo.InvariantCulture); - - if (num <= 1) - { - table[i + 1, 4].text = "——"; - } - else - { - var deviSqr = EntryList[idx].DiffList[i].Sum(x => (x - average) * (x - average)) / (num - 1); - var devi = Mathf.Sqrt(deviSqr) * 0.06f; - table[i + 1, 4].text = devi.ToString("0.00", CultureInfo.InvariantCulture); - } - } - - newController.gameObject.SetActive(____resultScoreViewType[idx] == ResultProcess.ResultScoreViewType.VSResult); - Controllers[idx] = newController; - } - } - - private static readonly ScoreBoardController[] Controllers = new ScoreBoardController[2]; - - [HarmonyPostfix] - [HarmonyPatch(typeof(ResultMonitor), "ChangeScoreBoard")] - private static void OnChangeScoreBoard( - ResultMonitor __instance, ResultProcess.ResultScoreViewType resultScoreType - ) - { - Controllers[__instance.MonitorIndex].gameObject.SetActive(resultScoreType == ResultProcess.ResultScoreViewType.VSResult); - } - - - private static readonly string[] RowNames = ["_tap", "_hold", "_slide", "_touch", "_break"]; - private static readonly string[] ColumnNames = ["_critical", "_perfect", "_great", "_good", "_miss"]; - private static readonly string[] TableHead = ["", "NUM", "AVG", "RAW", "S.D."]; - private static readonly string[] TableRowNames = ["TAP", "SLD", "TCH", "EX"]; - - private static TextMeshProUGUI[,] ExtractTextObjs(ScoreBoardController controller) - { - var result = new TextMeshProUGUI[RowNames.Length, ColumnNames.Length]; - for (var i = 0; i < RowNames.Length; i++) - { - for (int j = 0; j < ColumnNames.Length; j++) - { - var trav = Traverse.Create(controller) - .Field(RowNames[i]) - .Field(ColumnNames[j]); - var text = trav.Field("_numberText").Value; - text.color = Color.black; - result[i, j] = text; - trav.GetValue().SetVisibleCloseBox(false); - } - } - return result; - } - -} diff --git a/AquaMai/AquaMai.Mods/UX/OneKeyEntryEnd.cs b/AquaMai/AquaMai.Mods/UX/OneKeyEntryEnd.cs deleted file mode 100644 index 5e2dee77..00000000 --- a/AquaMai/AquaMai.Mods/UX/OneKeyEntryEnd.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using AquaMai.Config.Types; -using AquaMai.Core.Helpers; -using HarmonyLib; -using Mai2.Mai2Cue; -using Main; -using Manager; -using MelonLoader; -using Process; - -namespace AquaMai.Mods.UX; - -[ConfigSection( - en: "One key to proceed to music select (during entry) or end current PC (during music select).", - zh: "一键跳过登录过程直接进入选歌界面,或在选歌界面直接结束本局游戏")] -public class OneKeyEntryEnd -{ - [ConfigEntry] - public static readonly KeyCodeOrName key = KeyCodeOrName.Service; - - [ConfigEntry] - public static readonly bool longPress = true; - - [HarmonyPrefix] - [HarmonyPatch(typeof(GameMainObject), "Update")] - public static void OnGameMainObjectUpdate() - { - if (!KeyListener.GetKeyDownOrLongPress(key, longPress)) return; - MelonLogger.Msg("[QuickSkip] Activated"); - - var traverse = Traverse.Create(SharedInstances.ProcessDataContainer.processManager); - var processList = traverse.Field("_processList").GetValue>(); - - ProcessBase processToRelease = null; - - foreach (ProcessManager.ProcessControle process in processList) - { - switch (process.Process.ToString()) - { - // After login - case "Process.ModeSelect.ModeSelectProcess": - case "Process.LoginBonus.LoginBonusProcess": - case "Process.RegionalSelectProcess": - case "Process.CharacterSelectProcess": - case "Process.TicketSelect.TicketSelectProcess": - processToRelease = process.Process; - break; - - case "Process.MusicSelectProcess": - // Skip to save - SoundManager.PreviewEnd(); - SoundManager.PlayBGM(Cue.BGM_COLLECTION, 2); - SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, process.Process, new UnlockMusicProcess(SharedInstances.ProcessDataContainer))); - break; - } - } - - if (processToRelease != null) - { - GameManager.SetMaxTrack(); - SharedInstances.ProcessDataContainer.processManager.AddProcess(new FadeProcess(SharedInstances.ProcessDataContainer, processToRelease, new MusicSelectProcess(SharedInstances.ProcessDataContainer))); - } - } -} diff --git a/AquaMai/AquaMai.Mods/UX/OneKeyRetrySkip.cs b/AquaMai/AquaMai.Mods/UX/OneKeyRetrySkip.cs deleted file mode 100644 index 2b7c1305..00000000 --- a/AquaMai/AquaMai.Mods/UX/OneKeyRetrySkip.cs +++ /dev/null @@ -1,46 +0,0 @@ -using AquaMai.Config.Attributes; -using AquaMai.Config.Types; -using AquaMai.Core.Helpers; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Process; - -namespace AquaMai.Mods.UX; - -[ConfigSection( - en: "One key to retry (1.30+) or skip current chart in gameplay.", - zh: "在游戏中途一键重试(1.30+)或跳过当前谱面")] -public class OneKeyRetrySkip -{ - [ConfigEntry] - public static readonly KeyCodeOrName retryKey = KeyCodeOrName.Service; - - [ConfigEntry] - public static readonly bool retryLongPress = false; - - [ConfigEntry] - public static readonly KeyCodeOrName skipKey = KeyCodeOrName.Service; - - [ConfigEntry] - public static readonly bool skipLongPress = true; - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameProcess), "OnUpdate")] - public static void PostGameProcessUpdate(GameProcess __instance, Message[] ____message, ProcessDataContainer ___container) - { - if (KeyListener.GetKeyDownOrLongPress(skipKey, skipLongPress)) - { - var traverse = Traverse.Create(__instance); - ___container.processManager.SendMessage(____message[0]); - Singleton.Instance.SetSyncResult(0); - traverse.Method("SetRelease").GetValue(); - } - - if (KeyListener.GetKeyDownOrLongPress(retryKey, retryLongPress) && GameInfo.GameVersion >= 23000) - { - // This is original typo in Assembly-CSharp - Singleton.Instance.SetQuickRetryFrag(flag: true); - } - } -} diff --git a/AquaMai/AquaMai.Mods/UX/PracticeMode/Libs/PractiseModeUI.cs b/AquaMai/AquaMai.Mods/UX/PracticeMode/Libs/PractiseModeUI.cs deleted file mode 100644 index 8bd17b29..00000000 --- a/AquaMai/AquaMai.Mods/UX/PracticeMode/Libs/PractiseModeUI.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using AquaMai.Mods.Fix; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using Manager; -using UnityEngine; - -namespace AquaMai.Mods.UX.PracticeMode.Libs; - -public class PracticeModeUI : MonoBehaviour -{ - private static float windowTop => Screen.height - GuiSizes.PlayerWidth + GuiSizes.PlayerWidth * .22f; - private static float controlHeight => GuiSizes.PlayerWidth * .13f; - private static float sideButtonWidth => GuiSizes.PlayerWidth * .1f; - private static float centerButtonWidth => GuiSizes.PlayerWidth * .28f; - private static int fontSize => (int)(GuiSizes.PlayerWidth * .02f); - - private static Rect GetButtonRect(int pos, int row) - { - float x; - float width; - switch (pos) - { - case 0: - x = GuiSizes.PlayerCenter - centerButtonWidth / 2 - sideButtonWidth - GuiSizes.Margin; - width = sideButtonWidth; - break; - case 1: - x = GuiSizes.PlayerCenter - centerButtonWidth / 2; - width = centerButtonWidth; - break; - case 2: - x = GuiSizes.PlayerCenter + centerButtonWidth / 2 + GuiSizes.Margin; - width = sideButtonWidth; - break; - default: - throw new ArgumentOutOfRangeException(nameof(pos), pos, null); - } - - return new Rect(x, windowTop + (GuiSizes.Margin + controlHeight) * row + GuiSizes.Margin, width, controlHeight); - } - - public void OnGUI() - { - var labelStyle = GUI.skin.GetStyle("label"); - labelStyle.fontSize = fontSize; - labelStyle.alignment = TextAnchor.MiddleCenter; - - var buttonStyle = GUI.skin.GetStyle("button"); - buttonStyle.fontSize = fontSize; - - GUI.Box(new Rect( - GuiSizes.PlayerCenter - centerButtonWidth / 2 - sideButtonWidth - GuiSizes.Margin * 2, - windowTop, - centerButtonWidth + sideButtonWidth * 2 + GuiSizes.Margin * 4, - controlHeight * 4 + GuiSizes.Margin * 5 - ), ""); - - GUI.Button(GetButtonRect(0, 0), Locale.SeekBackward); - GUI.Button(GetButtonRect(1, 0), Locale.Pause); - GUI.Button(GetButtonRect(2, 0), Locale.SeekForward); - - if (PracticeMode.repeatStart == -1) - { - GUI.Button(GetButtonRect(0, 1), Locale.MarkRepeatStart); - GUI.Label(GetButtonRect(1, 1), Locale.RepeatNotSet); - } - else if (PracticeMode.repeatEnd == -1) - { - GUI.Button(GetButtonRect(0, 1), Locale.MarkRepeatEnd); - GUI.Label(GetButtonRect(1, 1), Locale.RepeatStartSet); - GUI.Button(GetButtonRect(2, 1), Locale.RepeatReset); - } - else - { - GUI.Label(GetButtonRect(1, 1), Locale.RepeatStartEndSet); - GUI.Button(GetButtonRect(2, 1), Locale.RepeatReset); - } - - GUI.Button(GetButtonRect(0, 2), Locale.SpeedDown); - GUI.Label(GetButtonRect(1, 2), $"{Locale.Speed} {PracticeMode.speed * 100:000}%"); - GUI.Button(GetButtonRect(2, 2), Locale.SpeedUp); - GUI.Button(GetButtonRect(1, 3), Locale.SpeedReset); - - GUI.Label(GetButtonRect(0, 3), $"{TimeSpan.FromMilliseconds(PracticeMode.CurrentPlayMsec):mm\\:ss\\.fff}\n{TimeSpan.FromMilliseconds(NotesManager.Instance().getPlayFinalMsec()):mm\\:ss\\.fff}"); - GUI.Button(GetButtonRect(2, 3), $"保持流速\n{(PracticeMode.keepNoteSpeed ? "ON" : "OFF")}"); - } - - public void Update() - { - if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E8)) - { - PracticeMode.Seek(-1000); - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E2)) - { - PracticeMode.Seek(1000); - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B8) || InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B1)) - { - DebugFeature.Pause = !DebugFeature.Pause; - if (!DebugFeature.Pause) - { - PracticeMode.Seek(0); - } - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B7) && PracticeMode.repeatStart == -1) - { - PracticeMode.repeatStart = PracticeMode.CurrentPlayMsec; - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B7) && PracticeMode.repeatEnd == -1) - { - PracticeMode.SetRepeatEnd(PracticeMode.CurrentPlayMsec); - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B2)) - { - PracticeMode.ClearRepeat(); - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B6)) - { - PracticeMode.SpeedDown(); - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B3)) - { - PracticeMode.SpeedUp(); - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B5) || InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B4)) - { - PracticeMode.SpeedReset(); - } - else if (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E4)) - { - PracticeMode.keepNoteSpeed = !PracticeMode.keepNoteSpeed; - PracticeMode.gameCtrl?.ResetOptionSpeed(); - } - else if ( - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A1) || - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A2) || - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A3) || - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A4) || - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A5) || - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A6) || - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A7) || - InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.A8) - ) - { - PracticeMode.ui = null; - Destroy(this); - } - } -} diff --git a/AquaMai/AquaMai.Mods/UX/PracticeMode/PracticeMode.cs b/AquaMai/AquaMai.Mods/UX/PracticeMode/PracticeMode.cs deleted file mode 100644 index a0878ebc..00000000 --- a/AquaMai/AquaMai.Mods/UX/PracticeMode/PracticeMode.cs +++ /dev/null @@ -1,282 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using AquaMai.Mods.Fix; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using AquaMai.Mods.UX.PracticeMode.Libs; -using HarmonyLib; -using Manager; -using Monitor; -using Monitor.Game; -using Process; -using UnityEngine; -using AquaMai.Config.Attributes; -using AquaMai.Config.Types; - -namespace AquaMai.Mods.UX.PracticeMode; - -[ConfigCollapseNamespace] -[ConfigSection( - en: "Practice Mode.", - zh: "练习模式")] -public class PracticeMode -{ - [ConfigEntry( - en: "Key to show Practice Mode UI.", - zh: "显示练习模式 UI 的按键")] - public static readonly KeyCodeOrName key = KeyCodeOrName.Test; - - [ConfigEntry] - public static readonly bool longPress = false; - - public static double repeatStart = -1; - public static double repeatEnd = -1; - public static float speed = 1; - private static CriAtomExPlayer player; - private static MovieMaterialMai2 movie; - public static GameCtrl gameCtrl; - public static bool keepNoteSpeed = false; - - public static void SetRepeatEnd(double time) - { - if (repeatStart == -1) - { - MessageHelper.ShowMessage(Locale.RepeatStartTimeNotSet); - return; - } - - if (time < repeatStart) - { - MessageHelper.ShowMessage(Locale.RepeatEndTimeLessThenStartTime); - return; - } - - repeatEnd = time; - } - - public static void ClearRepeat() - { - repeatStart = -1; - repeatEnd = -1; - } - - public static void SetSpeed() - { - player.SetPitch((float)(1200 * Math.Log(speed, 2))); - // player.SetDspTimeStretchRatio(1 / speed); - player.UpdateAll(); - - movie.player.SetSpeed(speed); - gameCtrl?.ResetOptionSpeed(); - } - - private static IEnumerator SetSpeedCoroutineInner() - { - yield return null; - SetSpeed(); - } - - public static void SetSpeedCoroutine() - { - SharedInstances.GameMainObject.StartCoroutine(SetSpeedCoroutineInner()); - } - - public static void SpeedUp() - { - speed += .05f; - if (speed > 2) - { - speed = 2; - } - - SetSpeed(); - } - - public static void SpeedDown() - { - speed -= .05f; - if (speed < 0.05) - { - speed = 0.05f; - } - - SetSpeed(); - } - - public static void SpeedReset() - { - speed = 1; - SetSpeed(); - } - - public static void Seek(int addMsec) - { - // Debug feature 里面那个 timer 不能感知变速 - // 为了和魔改版本统一,polyfill 里面不修这个 - // 这里重新实现一个能感知变速的 Seek - var msec = CurrentPlayMsec + addMsec; - if (msec < 0) - { - msec = 0; - } - - CurrentPlayMsec = msec; - } - - public static double CurrentPlayMsec - { - get => NotesManager.GetCurrentMsec() - 91; - set - { - DebugFeature.CurrentPlayMsec = value; - SetSpeedCoroutine(); - } - } - - public static PracticeModeUI ui; - - [HarmonyPatch] - public class PatchNoteSpeed - { - public static IEnumerable TargetMethods() - { - yield return AccessTools.Method(typeof(GameManager), "GetNoteSpeed"); - yield return AccessTools.Method(typeof(GameManager), "GetTouchSpeed"); - } - - public static void Postfix(ref float __result) - { - if (!keepNoteSpeed) return; - __result /= speed; - } - } - - [HarmonyPatch(typeof(GameProcess), "OnStart")] - [HarmonyPostfix] - public static void GameProcessPostStart() - { - repeatStart = -1; - repeatEnd = -1; - speed = 1; - ui = null; - } - - [HarmonyPatch(typeof(GameProcess), "OnRelease")] - [HarmonyPostfix] - public static void GameProcessPostRelease() - { - repeatStart = -1; - repeatEnd = -1; - speed = 1; - ui = null; - } - - [HarmonyPatch(typeof(GameCtrl), "Initialize")] - [HarmonyPostfix] - public static void GameCtrlPostInitialize(GameCtrl __instance) - { - gameCtrl = __instance; - } - -# if DEBUG - [HarmonyPrefix] - [HarmonyPatch(typeof(GenericProcess), "OnUpdate")] - public static void OnGenericProcessUpdate(GenericMonitor[] ____monitors) - { - if (Input.GetKeyDown(KeyCode.F11)) - { - ____monitors[0].gameObject.AddComponent(); - } - } -# endif - - [HarmonyPatch(typeof(GameProcess), "OnUpdate")] - [HarmonyPostfix] - public static void GameProcessPostUpdate(GameProcess __instance, GameMonitor[] ____monitors) - { - if (KeyListener.GetKeyDownOrLongPress(key, longPress) && ui is null) - { - ui = ____monitors[0].gameObject.AddComponent(); - } - - if (repeatStart >= 0 && repeatEnd >= 0) - { - if (CurrentPlayMsec >= repeatEnd) - { - CurrentPlayMsec = repeatStart; - } - } - } - - private static float startGap = -1f; - - [HarmonyPatch(typeof(NotesManager), "StartPlay")] - [HarmonyPostfix] - public static void NotesManagerPostUpdateTimer(float msecStartGap) - { - startGap = msecStartGap; - } - - [HarmonyPatch(typeof(NotesManager), "UpdateTimer")] - [HarmonyPrefix] - public static bool NotesManagerPostUpdateTimer(bool ____isPlaying, Stopwatch ____stopwatch, ref float ____curMSec, ref float ____curMSecPre, float ____msecStartGap) - { - var stackTrace = new StackTrace(); // get call stack - var stackFrames = stackTrace.GetFrames(); // get method calls (frames) - if(stackFrames.Select(it => it.GetMethod().DeclaringType.Name).Contains("AdvDemoProcess")) - { - return true; - } - - if (startGap != -1f) - { - ____curMSec = startGap; - ____curMSecPre = startGap; - ____stopwatch?.Reset(); - startGap = -1f; - } - else - { - ____curMSecPre = ____curMSec; - if (____isPlaying && ____stopwatch != null && !DebugFeature.Pause) - { - var num = (double)____stopwatch.ElapsedTicks / Stopwatch.Frequency * 1000.0 * speed; - ____curMSec += (float)num; - ____stopwatch.Reset(); - ____stopwatch.Start(); - } - } - - return false; - } - - [HarmonyPatch(typeof(SoundCtrl), "Initialize")] - [HarmonyPostfix] - public static void SoundCtrlPostInitialize(SoundCtrl.InitParam param, Dictionary ____players) - { - var wrapper = ____players[2]; - player = (CriAtomExPlayer)wrapper.GetType().GetField("Player").GetValue(wrapper); - // var pool = new CriAtomExStandardVoicePool(1, 8, 96000, true, 2); - // pool.AttachDspTimeStretch(); - // player.SetVoicePoolIdentifier(pool.identifier); - - // debug - // var wrapper1 = ____players[7]; - // var player1 = (CriAtomExPlayer)wrapper1.GetType().GetField("Player").GetValue(wrapper1); - // var pool = new CriAtomExStandardVoicePool(1, 8, 96000, true, 2); - // pool.AttachDspTimeStretch(); - // player1.SetVoicePoolIdentifier(pool.identifier); - // player1.SetDspTimeStretchRatio(2); - } - - [HarmonyPatch(typeof(MovieController), "Awake")] - [HarmonyPostfix] - public static void MovieControllerPostAwake(MovieMaterialMai2 ____moviePlayers) - { - movie = ____moviePlayers; - } -} diff --git a/AquaMai/AquaMai.Mods/UX/QuickEndPlay.cs b/AquaMai/AquaMai.Mods/UX/QuickEndPlay.cs deleted file mode 100644 index a8469bc5..00000000 --- a/AquaMai/AquaMai.Mods/UX/QuickEndPlay.cs +++ /dev/null @@ -1,70 +0,0 @@ -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Monitor; -using Process; -using UnityEngine; - -namespace AquaMai.Mods.UX; - -[ConfigSection( - en: "Show a \"skip\" button like AstroDX after the notes end.", - zh: "音符结束之后显示像 AstroDX 一样的「跳过」按钮")] -public class QuickEndPlay -{ - private static int _timer; - - [HarmonyPatch(typeof(GameProcess), "OnStart")] - [HarmonyPostfix] - public static void GameProcessPostStart(GameMonitor[] ____monitors) - { - _timer = 0; - ____monitors[0].gameObject.AddComponent(); - } - - [HarmonyPatch(typeof(GameProcess), "OnUpdate")] - [HarmonyPostfix] - public static void GameProcessPostUpdate(GameProcess __instance, Message[] ____message, ProcessDataContainer ___container, byte ____sequence) - { - switch (____sequence) - { - case 9: - _timer = 0; - break; - case > 4: - _timer++; - break; - default: - _timer = 0; - break; - } - - if (_timer > 60 && (InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.B4) || InputManager.GetTouchPanelAreaDown(InputManager.TouchPanelArea.E4))) - { - var traverse = Traverse.Create(__instance); - ___container.processManager.SendMessage(____message[0]); - Singleton.Instance.SetSyncResult(0); - traverse.Method("SetRelease").GetValue(); - } - } - - private class Ui : MonoBehaviour - { - public void OnGUI() - { - if (_timer < 60) return; - - // 这里重新 setup 一下 style 也可以 - var x = GuiSizes.PlayerCenter; - var y = Screen.height - GuiSizes.PlayerWidth * .37f; - var width = GuiSizes.PlayerWidth * .25f; - var height = GuiSizes.PlayerWidth * .13f; - - GUI.Box(new Rect(x, y, width, height), ""); - GUI.Button(new Rect(x, y, width, height), Locale.Skip); - } - } -} diff --git a/AquaMai/AquaMai.Mods/UX/README.md b/AquaMai/AquaMai.Mods/UX/README.md deleted file mode 100644 index 80cc09ec..00000000 --- a/AquaMai/AquaMai.Mods/UX/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# UX - -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. diff --git a/AquaMai/AquaMai.Mods/UX/SelectionDetail.cs b/AquaMai/AquaMai.Mods/UX/SelectionDetail.cs deleted file mode 100644 index 325600fc..00000000 --- a/AquaMai/AquaMai.Mods/UX/SelectionDetail.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Manager.MaiStudio; -using Manager.UserDatas; -using Monitor; -using Process; -using UnityEngine; - -namespace AquaMai.Mods.UX; - -[EnableGameVersion(23500)] -[ConfigSection( - en: "Show detail of selected song in music selection screen.", - zh: "选歌界面显示选择的歌曲的详情")] -public class SelectionDetail -{ - private static readonly Window[] window = new Window[2]; - private static MusicSelectProcess.MusicSelectData SelectData { get; set; } - private static readonly int[] difficulty = new int[2]; - - [HarmonyPostfix] - [HarmonyPatch(typeof(MusicSelectMonitor), "UpdateRivalScore")] - public static void ScrollUpdate(MusicSelectProcess ____musicSelect, MusicSelectMonitor __instance) - { - int player; - if (__instance == ____musicSelect.MonitorArray[0]) - { - player = 0; - } - else if (__instance == ____musicSelect.MonitorArray[1]) - { - player = 1; - } - else - { - return; - } - - if (window[player] != null) - { - window[player].Close(); - } - - var userData = Singleton.Instance.GetUserData(player); - if (!userData.IsEntry) return; - - if (____musicSelect.IsRandomIndex()) return; - - SelectData = ____musicSelect.GetMusic(0); - if (SelectData == null) return; - difficulty[player] = ____musicSelect.GetDifficulty(player); - - window[player] = player == 0 ? __instance.gameObject.AddComponent() : __instance.gameObject.AddComponent(); - } - - private class P1Window : Window - { - protected override int player => 0; - } - - private class P2Window : Window - { - protected override int player => 1; - } - - private abstract class Window : MonoBehaviour - { - protected abstract int player { get; } - private UserData userData => Singleton.Instance.GetUserData(player); - - public void OnGUI() - { - var dataToShow = new List(); - dataToShow.Add($"ID: {SelectData.MusicData.name.id}"); - dataToShow.Add(MusicDirHelper.LookupPath(SelectData.MusicData.name.id).Split('/').Reverse().ToArray()[3]); - if (SelectData.MusicData.genreName is not null) // SelectData.MusicData.genreName.str may not correct - dataToShow.Add(Singleton.Instance.GetMusicGenre(SelectData.MusicData.genreName.id)?.genreName); - if (SelectData.MusicData.AddVersion is not null) - dataToShow.Add(Singleton.Instance.GetMusicVersion(SelectData.MusicData.AddVersion.id)?.genreName); - var notesData = SelectData.MusicData.notesData[difficulty[player]]; - dataToShow.Add($"{notesData?.level}.{notesData?.levelDecimal}"); - - var rate = CalcB50(SelectData.MusicData, difficulty[player]); - if (rate > 0) - { - dataToShow.Add(string.Format(Locale.RatingUpWhenSSSp, rate)); - } - - var playCount = Shim.GetUserScoreList(userData)[difficulty[player]].FirstOrDefault(it => it.id == SelectData.MusicData.name.id)?.playcount ?? 0; - if (playCount > 0) - { - dataToShow.Add(string.Format(Locale.PlayCount, playCount)); - } - - - var width = GuiSizes.FontSize * 15f; - var x = GuiSizes.PlayerCenter - width / 2f + GuiSizes.PlayerWidth * player; - var y = Screen.height * 0.87f; - - var labelStyle = GUI.skin.GetStyle("label"); - labelStyle.fontSize = GuiSizes.FontSize; - labelStyle.alignment = TextAnchor.MiddleCenter; - - GUI.Box(new Rect(x, y, width, dataToShow.Count * GuiSizes.LabelHeight + 2 * GuiSizes.Margin), ""); - for (var i = 0; i < dataToShow.Count; i++) - { - GUI.Label(new Rect(x, y + GuiSizes.Margin + i * GuiSizes.LabelHeight, width, GuiSizes.LabelHeight), dataToShow[i]); - } - } - - private uint CalcB50(MusicData musicData, int difficulty) - { - var theory = new UserRate(musicData.name.id, difficulty, 1010000, (uint)musicData.version); - var list = theory.OldFlag ? userData.RatingList.RatingList : userData.RatingList.NewRatingList; - var userLowRate = list.Last(); - var userSongRate = list.FirstOrDefault(it => it.MusicId == musicData.name.id && it.Level == difficulty); - - if (!userSongRate.Equals(default(UserRate))) - { - return theory.SingleRate - userSongRate.SingleRate; - } - - if (theory.SingleRate > userLowRate.SingleRate) - { - return theory.SingleRate - userLowRate.SingleRate; - } - - return 0; - } - - public void Close() - { - Destroy(this); - } - } -} diff --git a/AquaMai/AquaMai.Mods/Utils/DisplayFrameRate.cs b/AquaMai/AquaMai.Mods/Utils/DisplayFrameRate.cs deleted file mode 100644 index 4e5a3f1b..00000000 --- a/AquaMai/AquaMai.Mods/Utils/DisplayFrameRate.cs +++ /dev/null @@ -1,53 +0,0 @@ -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using HarmonyLib; -using Main; -using UnityEngine; - -namespace AquaMai.Mods.Utils; - -[ConfigSection( - en: "Display framerate.", - zh: "显示帧率")] -public class DisplayFrameRate -{ - [HarmonyPatch(typeof(GameMainObject), "Awake")] - [HarmonyPostfix] - public static void ShowUi(GameMainObject __instance) - { - __instance.gameObject.AddComponent(); - } - - private class Ui : MonoBehaviour - { - private static float sampleTime = 1f; - private static int frame; - private static float time; - private static float fps; - - public void OnGUI() - { - var labelStyle = GUI.skin.GetStyle("label"); - labelStyle.fontSize = GuiSizes.FontSize; - labelStyle.alignment = TextAnchor.MiddleCenter; - - const float x = 10f; - const float y = 10f; - var width = GuiSizes.FontSize * 7f; - var height = GuiSizes.LabelHeight * 1.5f; - - frame += 1; - time += Time.deltaTime; - - if (time >= sampleTime) - { - fps = frame / time; - frame = 0; - time = 0; - } - - GUI.Box(new Rect(x, y, width, height), ""); - GUI.Label(new Rect(x, y, width, height), $"{fps:0.0} FPS"); - } - } -} diff --git a/AquaMai/AquaMai.Mods/Utils/LogNetworkErrors.cs b/AquaMai/AquaMai.Mods/Utils/LogNetworkErrors.cs deleted file mode 100644 index 73f59923..00000000 --- a/AquaMai/AquaMai.Mods/Utils/LogNetworkErrors.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Diagnostics; -using AquaMai.Config.Attributes; -using HarmonyLib; -using Manager; -using Manager.Operation; -using MelonLoader; -using Net.Packet; - -namespace AquaMai.Mods.Utils; - -[ConfigSection(exampleHidden: true, defaultOn: true)] -public class LogNetworkErrors -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(Packet), "ProcImpl")] - public static void Postfix(PacketState __result, Packet __instance) - { - if (__result == PacketState.Error) - { - MelonLogger.Msg($"[LogNetworkErrors] {__instance.Query.Api}: {__instance.Status}"); - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(DataDownloader), "NotifyOffline")] - public static void DataDownloader() - { - MelonLogger.Msg("[LogNetworkErrors] DataDownloader NotifyOffline"); - var stackTrace = new StackTrace(); - MelonLogger.Msg(stackTrace.ToString()); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(OnlineCheckInterval), "NotifyOffline")] - public static void OnlineCheckInterval() - { - MelonLogger.Msg("[LogNetworkErrors] OnlineCheckInterval NotifyOffline"); - var stackTrace = new StackTrace(); - MelonLogger.Msg(stackTrace.ToString()); - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(OperationManager), "IsAliveAimeServer", MethodType.Getter)] - public static void IsAliveAimeServer(bool __result) - { - if (__result == false) - MelonLogger.Msg($"[LogNetworkErrors] IsAliveAimeServer Is {__result}"); - } -} diff --git a/AquaMai/AquaMai.Mods/Utils/LogNetworkRequests.cs b/AquaMai/AquaMai.Mods/Utils/LogNetworkRequests.cs deleted file mode 100644 index 5cd60aeb..00000000 --- a/AquaMai/AquaMai.Mods/Utils/LogNetworkRequests.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Runtime.CompilerServices; -using System.Text; -using Net; -using Net.Packet; -using MelonLoader; -using MelonLoader.TinyJSON; -using HarmonyLib; -using AquaMai.Core.Attributes; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; - -namespace AquaMai.Mods.Utils; - -[ConfigSection( - en: "Log network requests to the MelonLoader console.", - zh: "将网络请求输出到 MelonLoader 控制台")] -public class LogNetworkRequests -{ - [ConfigEntry] - private static readonly bool url = true; - - [ConfigEntry] - private static readonly bool request = true; - [ConfigEntry] - private static readonly string requestOmittedApis = "UploadUserPhotoApi,UploadUserPortraitApi"; - - [ConfigEntry] - private static readonly bool response = true; - [ConfigEntry] - private static readonly string responseOmittedApis = "GetGameEventApi"; - [ConfigEntry( - en: "Only print error responses, without the successful ones.", - zh: "仅输出出错的响应,不输出成功的响应")] - private static readonly bool responseErrorOnly = false; - - private static HashSet requestOmittedApiList = []; - private static HashSet responseOmittedApiList = []; - - private static readonly ConditionalWeakTable errorResponse = new(); - - public static void OnBeforePatch() - { - requestOmittedApiList = [.. requestOmittedApis.Split(',')]; - responseOmittedApiList = [.. responseOmittedApis.Split(',')]; - - if (responseErrorOnly && !response) - { - MelonLogger.Warning("[LogNetworkRequests] `responseErrorOnly` is enabled but `response` is disabled. Will not print any response."); - } - } - - private static string GetApiName(INetQuery netQuery) - { - return Shim.RemoveApiSuffix(netQuery.Api); - } - - [EnableIf(nameof(url))] - [HarmonyPostfix] - [HarmonyPatch(typeof(Packet), "Create")] - public static void PostCreate(Packet __instance) - { - MelonLogger.Msg($"[LogNetworkRequests] {GetApiName(__instance.Query)} URL: {MaybeGetNetPacketUrl(__instance)}"); - } - - private static string MaybeGetNetPacketUrl(Packet __instance) - { - if (Traverse.Create(__instance).Field("Client").GetValue() is not NetHttpClient client) - { - return ""; - } - if (Traverse.Create(client).Field("_request").GetValue() is not HttpWebRequest request) - { - return ""; - } - return request.RequestUri.ToString(); - } - - // Record the error responses of NetHttpClient to display. These responses could not be acquired in other ways. - [HarmonyPrefix] - [HarmonyPatch(typeof(NetHttpClient), "SetError")] - public static void PreSetError(NetHttpClient __instance, HttpWebResponse response) - { - if (response != null) - { - errorResponse.Add(__instance, response); - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(Packet), "ProcImpl")] - public static void PreProcImpl(Packet __instance) - { - if (request && __instance.State == PacketState.Ready) - { - var netQuery = __instance.Query; - var api = GetApiName(netQuery); - var displayRequest = InspectRequest(api, netQuery.GetRequest()); - MelonLogger.Msg($"[LogNetworkRequests] {api} Request: {displayRequest}"); - } - else if ( - response && - __instance.State == PacketState.Process && - Traverse.Create(__instance).Field("Client").GetValue() is NetHttpClient client) - { - if (client.State == NetHttpClient.StateDone && !responseErrorOnly) - { - var netQuery = __instance.Query; - var api = GetApiName(netQuery); - var displayResponse = InspectResponse(api, client.GetResponse().ToArray()); - MelonLogger.Msg($"[LogNetworkRequests] {api} Response: {displayResponse}"); - } - else if (client.State == NetHttpClient.StateError) - { - var displayError = InspectError(client); - MelonLogger.Warning($"[LogNetworkRequests] {GetApiName(__instance.Query)} Error: {displayError}"); - } - } - } - - private static string InspectRequest(string api, string request) => - requestOmittedApiList.Contains(api) - ? $"<{request.Length} characters omitted>" - : (request == "" ? "" : request); - - private static string InspectResponse(string api, byte[] response) - { - try - { - var decoded = Encoding.UTF8.GetString(response); - if (responseOmittedApiList.Contains(api)) - { - return $"<{decoded.Length} characters omitted>"; - } - else if (decoded == "") - { - return ""; - } - else if (decoded.IndexOf("\n") != -1) - { - return JSON.Dump(decoded); - } - else - { - return decoded; - } - } - catch (Exception e) - { - // Always non-empty when decoding fails. - return $""; - } - } - - private static string InspectError(NetHttpClient client) => - "<" + - $"WebExceptionStatus.{client.WebException}: " + - $"HttpStatus = {client.HttpStatus}, " + - $"Error = {JSON.Dump(client.Error)}, " + - $"Response = " + - (errorResponse.TryGetValue(client, out var response) - ? InspectErrorResponse(response) - : "null") + - ">"; - - private static string InspectErrorResponse(HttpWebResponse response) - { - try - { - var webConnectionStream = response.GetResponseStream(); - var memoryStream = new MemoryStream(); - webConnectionStream.CopyTo(memoryStream); - return InspectResponse(null, memoryStream.ToArray()); - } - catch (Exception e) - { - // The stream has alraedy been consumed? - return $""; - } - } -} diff --git a/AquaMai/AquaMai.Mods/Utils/LogUnity.cs b/AquaMai/AquaMai.Mods/Utils/LogUnity.cs deleted file mode 100644 index 3826feba..00000000 --- a/AquaMai/AquaMai.Mods/Utils/LogUnity.cs +++ /dev/null @@ -1,23 +0,0 @@ -using AquaMai.Config.Attributes; -using MelonLoader; -using UnityEngine; - -namespace AquaMai.Mods.Utils; - -[ConfigSection( - en: "Output Unity logs (for debugging purposes)", - zh: "输出 Unity 日志(调试用)", - exampleHidden: true)] -public static class LogUnity -{ - public static void OnBeforePatch() - { - Application.logMessageReceived += Log; - } - - private static void Log(string msg, string stackTrace, LogType type) - { - MelonLogger.Msg("[Unity] " + msg); - MelonLogger.Msg("[Unity] " + stackTrace); - } -} \ No newline at end of file diff --git a/AquaMai/AquaMai.Mods/Utils/LogUserId.cs b/AquaMai/AquaMai.Mods/Utils/LogUserId.cs deleted file mode 100644 index ff1fc73b..00000000 --- a/AquaMai/AquaMai.Mods/Utils/LogUserId.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using AquaMai.Config.Attributes; -using HarmonyLib; -using MelonLoader; -using Net.Packet; -using Net.Packet.Mai2; -using Net.VO.Mai2; - -namespace AquaMai.Mods.Utils; - -[ConfigSection( - en: "Log user ID on login.", - zh: "登录时将 UserID 输出到日志")] -public class LogUserId -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(PacketGetUserPreview), MethodType.Constructor, typeof(ulong), typeof(string), typeof(Action), typeof(Action))] - public static void Postfix(ulong userId) - { - MelonLogger.Msg($"[LogUserId] UserLogin: {userId}"); - } -} diff --git a/AquaMai/AquaMai.Mods/Utils/README.md b/AquaMai/AquaMai.Mods/Utils/README.md deleted file mode 100644 index 59023a0e..00000000 --- a/AquaMai/AquaMai.Mods/Utils/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Utils - -Debugging or diagnostic purpose utilities goes here. They don't affect the game play at all, but may show UIs if needed. diff --git a/AquaMai/AquaMai.Mods/Utils/ShowErrorLog.cs b/AquaMai/AquaMai.Mods/Utils/ShowErrorLog.cs deleted file mode 100644 index 276a347c..00000000 --- a/AquaMai/AquaMai.Mods/Utils/ShowErrorLog.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using HarmonyLib; -using Main; -using MelonLoader; -using UnityEngine; - -namespace AquaMai.Mods.Utils; - -[ConfigSection( - en: "Show error log in the game.", - zh: "在游戏中显示错误日志窗口而不是关闭游戏进程")] -public class ShowErrorLog -{ - private static Ui _errorUi; - - [HarmonyPostfix] - [HarmonyPatch(typeof(GameMain), "ExceptionHandler")] - private static void ExceptionHandler(GameMain __instance, Exception e) - { - if (_errorUi == null) - { - _errorUi = new GameObject("ErrorUI").AddComponent(); - _errorUi.gameObject.SetActive(true); - } - string logFile = $"{MAI2System.Path.ErrorLogPath}{DateTime.Now:yyyyMMddHHmmss}.log"; - MelonLogger.Msg("Error Log:"); - if (File.Exists(logFile)) - { - MelonLogger.Error(File.ReadAllText(logFile)); - _errorUi.SetErrorLog(File.ReadAllText(logFile)); - } - else - { - MelonLogger.Error(e); - _errorUi.SetErrorLog(e.ToString()); - } - - Application.quitting += ApplicationOnQuitting; - _errorUi.StartCoroutine(_errorUi.Show()); - } - - private static void ApplicationOnQuitting() - { - Thread.Sleep(Timeout.Infinite); - } - - private class Ui : MonoBehaviour - { - private string _errorLog = ""; - - public void SetErrorLog(string text) - { - _errorLog = "Error Log:\n" + text; - } - - public void OnGUI() - { - var labelStyle = new GUIStyle(GUI.skin.label) - { - fontSize = GuiSizes.FontSize, - alignment = TextAnchor.MiddleLeft, - normal = new GUIStyleState(){textColor = Color.black} - }; - - var boxStyle = new GUIStyle(GUI.skin.box) - { - normal = new GUIStyleState() { background = Texture2D.whiteTexture } - }; - - int logLineCount = Regex.Matches(_errorLog, "\n").Count + 1; - float offset = GuiSizes.PlayerCenter * 0.12f; - var x = GuiSizes.PlayerCenter / 2f + offset / 2f; - var y = Screen.height / 1.8f; - var width = GuiSizes.PlayerCenter - offset; - var height = GuiSizes.LabelHeight * logLineCount + GuiSizes.Margin * 2; - - GUI.Box(new Rect(x, y, width, height), "", boxStyle); - GUI.Label(new Rect(x, y, width, height), _errorLog, labelStyle); - if (!GuiSizes.SinglePlayer) - { - GUI.Box(new Rect(x + GuiSizes.PlayerWidth, y, width, height), "", boxStyle); - GUI.Label(new Rect(x + GuiSizes.PlayerWidth, y, width, height), _errorLog, labelStyle); - } - } - - public IEnumerator Show() - { - while (true) - { - yield return null; // 让 Unity 处理一帧 - } - } - } -} diff --git a/AquaMai/AquaMai.Mods/Utils/ShowNetErrorDetail.cs b/AquaMai/AquaMai.Mods/Utils/ShowNetErrorDetail.cs deleted file mode 100644 index c852981b..00000000 --- a/AquaMai/AquaMai.Mods/Utils/ShowNetErrorDetail.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Collections.Generic; -using AquaMai.Config.Attributes; -using AquaMai.Core.Helpers; -using AquaMai.Core.Resources; -using HarmonyLib; -using MAI2.Util; -using Manager; -using Monitor; -using Process; -using UnityEngine; - -namespace AquaMai.Mods.Utils; - -[ConfigSection( - en: "Show Network error detail in the game when gray network icon appears.", - zh: "出现灰网时显示原因")] -public class ShowNetErrorDetail -{ - [HarmonyPatch(typeof(CommonProcess), "OnStart")] - [HarmonyPostfix] - public static void SetIconStatus(CommonMonitor[] ____monitors) - { - ____monitors[0].gameObject.AddComponent(); - } - - private class DetailUi : MonoBehaviour - { - public void OnGUI() - { - var errors = new List(); - if (!Singleton.Instance.IsAliveServer) - { - errors.Add(Locale.NetErrIsAliveServer); - } - - if (!Singleton.Instance.IsAliveAimeReader) - { - errors.Add(Locale.NetErrIsAliveAimeReader); - } - - if (!Singleton.Instance.IsAliveAimeServer) - { - errors.Add(Locale.NetErrIsAliveAimeServer); - } - - if (!Singleton.Instance.WasDownloadSuccessOnce) - { - errors.Add(Locale.NetErrWasDownloadSuccessOnce); - } - - if (errors.Count == 0) - { - return; - } - - var labelStyle = GUI.skin.GetStyle("label"); - labelStyle.fontSize = GuiSizes.FontSize; - labelStyle.alignment = TextAnchor.MiddleCenter; - - var x = GuiSizes.PlayerCenter + GuiSizes.PlayerWidth * .2f; - var y = Screen.height * .01f; - var width = GuiSizes.FontSize * 15f; - var height = GuiSizes.LabelHeight * errors.Count + GuiSizes.Margin * 2; - - GUI.Box(new Rect(x, y, width, height), ""); - for (var i = 0; i < errors.Count; i++) - { - GUI.Label(new Rect(x, y + GuiSizes.Margin + GuiSizes.LabelHeight * i, width, GuiSizes.LabelHeight), errors[i]); - } - } - } -} diff --git a/AquaMai/AquaMai.sln b/AquaMai/AquaMai.sln deleted file mode 100644 index 4145c5cc..00000000 --- a/AquaMai/AquaMai.sln +++ /dev/null @@ -1,82 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.6.33815.320 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AquaMai.Config.Interfaces", "AquaMai.Config.Interfaces/AquaMai.Config.Interfaces.csproj", "{50C59E41-2939-407E-9CB7-B186F33DDCF9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AquaMai.Config", "AquaMai.Config/AquaMai.Config.csproj", "{B9CA039A-A0F1-4C87-A33E-3F959974BB3A}" - ProjectSection(ProjectDependencies) = postProject - {50C59E41-2939-407E-9CB7-B186F33DDCF9} = {50C59E41-2939-407E-9CB7-B186F33DDCF9} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AquaMai.Config.HeadlessLoader", "AquaMai.Config.HeadlessLoader/AquaMai.Config.HeadlessLoader.csproj", "{6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66}" - ProjectSection(ProjectDependencies) = postProject - {50C59E41-2939-407E-9CB7-B186F33DDCF9} = {50C59E41-2939-407E-9CB7-B186F33DDCF9} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AquaMai.Build", "AquaMai.Build/AquaMai.Build.csproj", "{4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5}" - ProjectSection(ProjectDependencies) = postProject - {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66} = {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AquaMai.Core", "AquaMai.Core/AquaMai.Core.csproj", "{33C0D4ED-6A84-4659-9A05-12D43D75D0B3}" - ProjectSection(ProjectDependencies) = postProject - {B9CA039A-A0F1-4C87-A33E-3F959974BB3A} = {B9CA039A-A0F1-4C87-A33E-3F959974BB3A} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AquaMai.Mods", "AquaMai.Mods/AquaMai.Mods.csproj", "{8731C0E0-53BE-4B1B-9828-193E738C6865}" - ProjectSection(ProjectDependencies) = postProject - {B9CA039A-A0F1-4C87-A33E-3F959974BB3A} = {B9CA039A-A0F1-4C87-A33E-3F959974BB3A} - {33C0D4ED-6A84-4659-9A05-12D43D75D0B3} = {33C0D4ED-6A84-4659-9A05-12D43D75D0B3} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AquaMai", "AquaMai/AquaMai.csproj", "{788BC472-59F7-46F6-B760-65C18BA74389}" - ProjectSection(ProjectDependencies) = postProject - {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5} = {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5} - {33C0D4ED-6A84-4659-9A05-12D43D75D0B3} = {33C0D4ED-6A84-4659-9A05-12D43D75D0B3} - {8731C0E0-53BE-4B1B-9828-193E738C6865} = {8731C0E0-53BE-4B1B-9828-193E738C6865} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Release|Any CPU = Release|Any CPU - Debug|Any CPU = Debug|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {50C59E41-2939-407E-9CB7-B186F33DDCF9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {50C59E41-2939-407E-9CB7-B186F33DDCF9}.Release|Any CPU.Build.0 = Release|Any CPU - {50C59E41-2939-407E-9CB7-B186F33DDCF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {50C59E41-2939-407E-9CB7-B186F33DDCF9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9CA039A-A0F1-4C87-A33E-3F959974BB3A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9CA039A-A0F1-4C87-A33E-3F959974BB3A}.Release|Any CPU.Build.0 = Release|Any CPU - {B9CA039A-A0F1-4C87-A33E-3F959974BB3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9CA039A-A0F1-4C87-A33E-3F959974BB3A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66}.Release|Any CPU.Build.0 = Release|Any CPU - {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B5E1F3E-D012-4CFB-A2FA-26A6CE06BE66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5}.Release|Any CPU.Build.0 = Release|Any CPU - {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C0C68C3-8B2E-4CA8-A26D-AE87CF2A38A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33C0D4ED-6A84-4659-9A05-12D43D75D0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33C0D4ED-6A84-4659-9A05-12D43D75D0B3}.Release|Any CPU.Build.0 = Release|Any CPU - {33C0D4ED-6A84-4659-9A05-12D43D75D0B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33C0D4ED-6A84-4659-9A05-12D43D75D0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8731C0E0-53BE-4B1B-9828-193E738C6865}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8731C0E0-53BE-4B1B-9828-193E738C6865}.Release|Any CPU.Build.0 = Release|Any CPU - {8731C0E0-53BE-4B1B-9828-193E738C6865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8731C0E0-53BE-4B1B-9828-193E738C6865}.Debug|Any CPU.Build.0 = Debug|Any CPU - {788BC472-59F7-46F6-B760-65C18BA74389}.Release|Any CPU.ActiveCfg = Release|Any CPU - {788BC472-59F7-46F6-B760-65C18BA74389}.Release|Any CPU.Build.0 = Release|Any CPU - {788BC472-59F7-46F6-B760-65C18BA74389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {788BC472-59F7-46F6-B760-65C18BA74389}.Debug|Any CPU.Build.0 = Debug|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DDF15A6C-2A44-4EBE-BD85-F3EE61DCD8BF} - EndGlobalSection -EndGlobal diff --git a/AquaMai/AquaMai/AquaMai.csproj b/AquaMai/AquaMai/AquaMai.csproj deleted file mode 100644 index ee3d948d..00000000 --- a/AquaMai/AquaMai/AquaMai.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - Release - AnyCPU - {788BC472-59F7-46F6-B760-65C18BA74389} - Library - AquaMai - AquaMai - net472 - 512 - true - 12 - 414 - $(ProjectDir)../Libs/;$(AssemblySearchPaths) - $(ProjectDir)../Output/ - false - false - false - - - - false - None - true - prompt - 4 - true - false - - - - DEBUG - - - - - - - - - - - - - - - - - - - - - - - - - - - - AquaMai.Config.Interfaces.dll - - - AquaMai.Config.dll - - - AquaMai.Core.dll - - - AquaMai.Mods.dll - - - - diff --git a/AquaMai/AquaMai/AssemblyLoader.cs b/AquaMai/AquaMai/AssemblyLoader.cs deleted file mode 100644 index e5d21cd5..00000000 --- a/AquaMai/AquaMai/AssemblyLoader.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Reflection; - -namespace AquaMai; - -public static class AssemblyLoader -{ - public enum AssemblyName - { - ConfigInterfaces, - Config, - Core, - Mods, - } - - private static readonly Dictionary Assemblies = new() - { - [AssemblyName.ConfigInterfaces] = "AquaMai.Config.Interfaces.dll", - [AssemblyName.Config] = "AquaMai.Config.dll", - [AssemblyName.Core] = "AquaMai.Core.dll", - [AssemblyName.Mods] = "AquaMai.Mods.dll", - }; - - private static readonly Dictionary LoadedAssemblies = []; - - public static Assembly GetAssembly(AssemblyName assemblyName) => LoadedAssemblies[assemblyName]; - - public static void LoadAssemblies() - { - foreach (var (assemblyName, assemblyFileName) in Assemblies) - { -# if DEBUG - MelonLoader.MelonLogger.Msg($"Loading assembly \"{assemblyFileName}\"..."); -# endif - LoadedAssemblies[assemblyName] = LoadAssemblyFromResource(assemblyFileName); - } - } - - private static Assembly LoadAssemblyFromResource(string assemblyName) - { - var executingAssembly = Assembly.GetExecutingAssembly(); - using var decompressedStream = executingAssembly.GetManifestResourceStream(assemblyName); - if (decompressedStream != null) - { - return AppDomain.CurrentDomain.Load(StreamToBytes(decompressedStream)); - } - using var compressedStream = executingAssembly.GetManifestResourceStream($"{assemblyName}.compressed"); - if (compressedStream != null) - { - return AppDomain.CurrentDomain.Load(DecompressToBytes(compressedStream)); - } - throw new Exception($"Embedded assembly \"{assemblyName}\" not found."); - } - - private static byte[] StreamToBytes(Stream stream) - { - if (stream == null) - { - return []; - } - using var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - return memoryStream.ToArray(); - } - - private static byte[] DecompressToBytes(Stream stream) => StreamToBytes(new DeflateStream(stream, CompressionMode.Decompress)); -} diff --git a/AquaMai/AquaMai/BuildInfo.cs b/AquaMai/AquaMai/BuildInfo.cs deleted file mode 100644 index 7f2307b8..00000000 --- a/AquaMai/AquaMai/BuildInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace AquaMai; - -public static class BuildInfo -{ - public const string Name = "AquaMai"; - public const string Description = "Mod for Sinmai"; - public const string Author = "Aza ft. Clansty ft. Menci"; - public const string Company = null; - public const string Version = "1.3.0"; - public const string DownloadLink = null; -} - diff --git a/AquaMai/AquaMai/Main.cs b/AquaMai/AquaMai/Main.cs deleted file mode 100644 index 505f057f..00000000 --- a/AquaMai/AquaMai/Main.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using MelonLoader; - -namespace AquaMai; - -public class AquaMai : MelonMod -{ - public const string AQUAMAI_SAY = """ - 如果你在 dnSpy / ILSpy 里看到了这行字,请从 resources 中解包 DLLs。 - If you see this line in dnSpy / ILSpy, please unpack the DLLs from resources. - """; - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleOutputCP(uint wCodePageID); - - public override void OnInitializeMelon() - { - // Prevent Chinese characters from being garbled - SetConsoleOutputCP(65001); - - AssemblyLoader.LoadAssemblies(); - - var modsAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Mods); - var coreAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Core); - coreAssembly.GetType("AquaMai.Core.Startup") - .GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static) - .Invoke(null, [modsAssembly, HarmonyInstance]); - } - - public override void OnGUI() - { - var coreAssembly = AssemblyLoader.GetAssembly(AssemblyLoader.AssemblyName.Core); - coreAssembly.GetType("AquaMai.Core.Startup") - .GetMethod("OnGUI", BindingFlags.Public | BindingFlags.Static) - .Invoke(null, []); - base.OnGUI(); - } -} diff --git a/AquaMai/AquaMai/Properties/AssemblyInfo.cs b/AquaMai/AquaMai/Properties/AssemblyInfo.cs deleted file mode 100644 index e566a535..00000000 --- a/AquaMai/AquaMai/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using MelonLoader; - -[assembly: AssemblyTitle(AquaMai.BuildInfo.Description)] -[assembly: AssemblyDescription(AquaMai.BuildInfo.Description)] -[assembly: AssemblyCompany(AquaMai.BuildInfo.Company)] -[assembly: AssemblyProduct(AquaMai.BuildInfo.Name)] -[assembly: AssemblyCopyright("Created by " + AquaMai.BuildInfo.Author)] -[assembly: AssemblyTrademark(AquaMai.BuildInfo.Company)] -[assembly: AssemblyVersion(AquaMai.BuildInfo.Version)] -[assembly: AssemblyFileVersion(AquaMai.BuildInfo.Version)] -[assembly: MelonInfo(typeof(AquaMai.AquaMai), AquaMai.BuildInfo.Name, AquaMai.BuildInfo.Version, AquaMai.BuildInfo.Author, AquaMai.BuildInfo.DownloadLink)] -[assembly: MelonColor()] -[assembly: HarmonyDontPatchAll] - -// Create and Setup a MelonGame Attribute to mark a Melon as Universal or Compatible with specific Games. -// If no MelonGame Attribute is found or any of the Values for any MelonGame Attribute on the Melon is null or empty it will be assumed the Melon is Universal. -// Values for MelonGame Attribute can be found in the Game's app.info file or printed at the top of every log directly beneath the Unity version. -[assembly: MelonGame(null, null)] diff --git a/AquaMai/Libs/.gitignore b/AquaMai/Libs/.gitignore deleted file mode 100644 index 0f6e1c13..00000000 --- a/AquaMai/Libs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Assembly-CSharp.dll -/AMDaemon.NET.dll diff --git a/AquaMai/Libs/0Harmony.dll b/AquaMai/Libs/0Harmony.dll deleted file mode 100644 index 0b9bdf1d7f998a91cddd65ad7c4d1e241e6e7ff7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 263168 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 diff --git a/AquaMai/Libs/Assembly-CSharp-firstpass.dll b/AquaMai/Libs/Assembly-CSharp-firstpass.dll deleted file mode 100644 index 24a80f07ba5d9dc2fcefaa6d9954b3d79391c2d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 189952 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�jnXWntgB3_0yn_~5t;htt!P#)2>x#s@Ub@NdmjSV<3txErY=OJ@CeZ11ZEAL z$iFzKeFH&$^L~C|b16{h;tisSp!PK+6^5?R$gGjUCN*tFPs4WJlc3p^{}#Gnco&Uq zkU#8z=3tal?$^vcBL%bs<#4xA1L2XZ1eIWgvPu?;iApAtJ(yvvWX5Tm=um2#5&`1% zF+w8L!$o!dN-u?V{SD%IhkL-g_&)>x_s0Lyh|6c5;r$f;@AxSVD&pOB#2iI%G*XJT z%3JCamxk{3ZUfE_T_ar^x;OvJObJImXQXXXA4f0K9rNPBW?yJ1=jOa{1d%$7c22;q zm$UEy-oY~|QZARA@IvAK1dB+>nbtUpEu98C5aq%Hn&6be4JJ#NJODXNJ7bN60}~*m zT$%t;b`nJDvgT9)QP&*M1ANkJW=agGyrRagMLY8L^UENl-VSd1kdzv~9tnl@ygo4)#40l*t zui))%b-j99T^8m!AowxXKP<7Fb1dN^)To>lK(D6Lv1T>uvqo%?chx4D?&prPXt(+{ zPvyE~m_D z1iVi@78jcs*${3_?<%n}+y~_F4-^>tES%KB!NR~8mT3O2;47F7SKkGKh2u;K??-fi zPl8!}PpS`r=sR*KA9JxTISo<$z9StT?yTLsQ+q73 zHWoA}C0^6S=RW!30g3Fw>R>Gfhfhvqq1?)B6ni$x=ADir)Jc*qdO<(4W9YWVXO7*ErRYKAOa53g+F=UFm05cPFr@L~G-F7udQGkKa3n ztqQ+*FRv%dy5j`K?!b@vH{_8tw9N&G7shWRA?Hw7w%`IOeD~sOtmbTFtbg|cYo;ka zcF|UOwyuz-?_5AtTRUP^1(ly6E}zM$fAXTO5_HDt@z*Z6S75e4B)AQ+2lRRQlS*NL zV%;>(0qQJJmfOtPQU^(5MKD0Uft+Q4lJ7@M!?_W&9#rN+{BD|f;?JP}cLR>`H;Rg#N&KYn-_ zk`%|gYk%lE&Z)2!?`6F(8uJ094iS)TMLY?l9p)gVI187tr8uE0F<*Dl)VYc z*$GN!2IZUv<=h12yaXj#yIrY>#@DUygh+KnP%9%%az?s4O&Rqw?zd1(ib=_yHC%m7ebbvx$1{pq`oF){@Cn z>6z)_CsDx&6KK%ii(l%Q_p77NdM4!7Sa|pu(eq>ppl1>8 zdGsutRL{zZh=SJP1(gFm*D!V9mC^K(*iNc^czS&l!5#lh1I+`?G;6vNG?E4y3{pOx z3wseGYIb}@LCjAQ!*#SlP`;0px7JJib)6fY8R2cf>(TIFBUGTqK*M>6>m1R7ArfOt zJ>$jQbuok|#=vLmC~xo_-p0EhG_2$2XA#RE{0|x`Juys(%{VwGK~vc_12d9Gn1%UD z82(7f=?-a^Aq9i_=Mc?hm%>liIhB}-W^~-@qliY#rFoU*=YdpMWJwemeqr}XFGW)L zZ@W+bBGdnU_vw!@{Y#DXPA+tzS0gaNdwLMn`w^DC1@lKH18x0 zm)H^k0_#IXNCXHh0{!An?9efy-$%jATbLcl-jg5~Q_g%u95@jLQ^3uGVlfyjm7zpd zt5tW)0 zwRpWZdRgU3)V%s-2+5lvrqSJC>_j8Wudoh3$XwRoi4HGfQP(O8ep#t>H%^PlncFBE z{j%@C-O3-vj_Kg9HfQc2Q_-9Cc_`xjQqXJgYxadic#0*g5fGOTx1!tuD~Y)VsPHOa zV#IbLPD>zOPXs$>OmQO-8;GdA1NGot%`)et!gcJ!9FJ#AZ2vAFX9;etGYL;ev^+QF zC}Y9xK;gRGEOs+dK*`176ltnp2%n6Qwr;c)w^dy8Hqf!<^y4~r6T;hV_aMZxDmp9< z6!%)9#6WkdTjLPPH}DeJ^@=TwG^1g;=6&RWI?*nU(q_PW!uz5rq~cdV8Z6l&^m#Nn zfAR96Bo)-2EYdR03li2$O4yq3Y_~i$Q+dep-3Y$3NBS3V9-H`i{Hw>VsD1@4Yrcw~ zlFscC>0p*fPu^MnclOUU$do`LWAalKRnGZ%t8%vZF}E^rCNew$p!zlZXulI{s|+J0 z6vtJNUMW5NW2_JwM4M-XOcuB`VsYO$FJsR3wc%ri7vpC6qWL+A{*9s0e5{KZXnY9gE9hT~_S0J51yi^72-3|?iGG&RA0P?=zJs9pbqHX7 zC}|k({PJ{bPy10OY4+8VNQ?7wk_9S!3OS2AwD5HeLFA)Y?8{1MzDF{V{Yc2Yj=KCl zQ1J`Y=b|uV3Cwp9!7UlzW8wP~iBBSZ8-(U*{ydJK!S-}M{65p40Hi!mpfd31^6^Zi zkhdqW%3YP&h~wH}lG759`qD(|A&t}_rdBR&Akde1^Nf@ZS5%0o9edw8eaG=yq~rEY zk1%rc?gNa{cv2Z*oW^UX*WtI{@f~MF^cWHS+*ye*M#mSsV}t>A{P%yut@(`V2UkQ# zKZtot_lnSIeYdYaLcf&|olipaI+lmt#s~W7ZrF?d5AF$~$07O@qv&!zA2{Kr99XE2 z=f%(+g?*YqKLw0X9QX}KKNi$$QF!zdL7f-g!-QW*^t+7y zQlj5u^j8x7KBK>u=+lh;hEc8QpCQD566}qB%b*bbPUwC>y5CE5C!>Fm=nonFBcq!8 zL3=&EbX`@G^xr}x9k>v&2V2^Tbk_m5_8f3xnd;PjO#@>8#y6AXZ>$C1E#P-b5oo;L zpY(A^r+&0Ft=mEN8A%heJX0x;()WxHQA)g7^uH`SCA(E_?SBaLlu~9fwp+|Ferh-3 zhQYz#$v%=>`->9KgMb#mvgNNpn%@J3ng};+-Txi98kJSUvvS{@E*XS7GW?Ah{*cH3 z95a|dD()YM^DV1g#63n_sk?%Y;pw-7@_BK9C=8bCOQKJU^V4u+)O@g90x8^D1e~6Mh4si>sCYV)%S*Xntl=`q2!8>)~s1xQ54g zaQ3+3#|>fmcnHHiteTZs{d&c%Nk^_pKdwnPuCed-vzK5>A)U8|(HesVhHKySpm?{& z-aFWq?8<^NvydwL%5IH)T}KK$#V268kZ8Z<(&ZFfxC#2i>5y@G8z8qPo!xW-!LM3k zP4OhMxi#tYHRG6$E^CrH`>(9`6Sx1vHXVB_sM}v51@_XPLp22Kn+K(VgVI*65B|5r6BjRfnc@A`R07dOL zN7=IAlj(`v>N7}-=zoBrcX6vfA%+eH7&;@j`cq=)Ufk-ljMA&rzfWTR0u-hTt(X$9 z?|Je}@O0Qbk9=lj((T~_2J<8Md0DgNAVZwq_B`qB6ViK4M^iax*m61>bU$V}y=Gby z-IN+{g z@+mavewySHr{k*qxSV?WQt0e#bSlte9?O8NPJo^K;7X`vI2*2JK4gz_R>NDGaDD!;k(JY2=`Qf(8d)VPnHOxW_Rk*0EmXE`@ z>29|nkCqyERooR7gz#$Fo!JcT*VN+dx!b*+`Sxy2)^9n=x?qa>Y1t5Yc$%#n++4QM zv9mK-gFI>wKhMoLdzZBiSV4&3SgyhR52S)+Ol8}6aamS(+0rcGAE@rmqV7iU@@P<4 zm&LbXe=-yNXQN%WBkzTA-b6dNW(oLHweHR>$yUI#JwJrg&0f9==G@U*&a}!oh|=Sg zmV%Qco#A);+gR|fJRFpsqN3ZB)Y8x<7_u8wq?n71h^z1mTg;oV0 zo3&PzCTfW}7~;C&A^07@s@NPVks-4J;aG+w7f4|DaqPDeySOPQ{$g+F;Uegv`U^BM zKVEwd1RrZfMtrySm%vqjiy}jK_^g~WJ{0tus5g=%^yZnBC^0|WS@{iX%1Y+jcg_+r zFNE`l=`<=@DDrYveQuv|%JpyXsQql7g1;;L*)D#*!}869n2=B4JC*%hmec?~`Y39# zi&NSx>vZuHW#VXhPAZTIG2PF~rk@-`7i5U}es=hW$!aC)wCKRN|GjYjFNFqQEPr$e z+C=e)oK&ldl}C1*Z39_&UgdVw?Gn_@&7bV$PxkXCKZ&V!;)Fe#FxTA@n4@VoY24X^ zi;ou0@K5gY_4j0aH2r#JY0t!D9r+ol(#lL%>IUL#7r|g=1YJS>_+1u7cH@5u=UZ17 zMo%tvdY*)V?=$R56-H;Ix>BtNezJf~nX!I|EXO~v5ihH5+yRa$c@HW9XO9K1H|XU{ zEro~}cz{|zLvH;_1c>=DLLyMfW)AU4I4Mq$2oU&$p=Ol`5U0cli2!jl5l7&rGlcT3 zj2a#R5M#KxsF)*((fJ9+bNR#llFWd~@u3LI?mX0hn7-B#JbfW<_DC--uVW%Em0=iu zUl`6R_O%vgft*CW*d~<3L3dUW-=Y*G-~3;JMDu=RV72g&Tx%f_AWn-B5&>cj5eSdB z;b=@C5lGB+QeF0$5!t)dlfXY>b=~U8jIy3s)vskBhj(Lh%BV~Ol*3-XGmRH{7ns?NTvr73i5LJ9#D2P~Tzr5CdQIl0yMW`C|E2*NH z#Ahi5E`%SJD>TR;+=(cz`gsBMqVIqwl|O9U?|>lBdoGDY_FR(m&krK^rRblcd%XV~ zt^omF4xR;3@Tzbx123Ab&>RMZa4ks+;fZ8b! zbMO|0xr@xr+xXiXv1jNQzEaf(KSEsZ$aI|D;=>ZRuT`lgvYP1{J`tEmMtkPmihmQc z;ER`y(o1;5*mg`ltdX)*EUk~#SNJRonJqG4PaW3<`S=8|MpP#=hwxBPy5&bnsyxhF z8PBWel<9aC2{MVc&?QCHKLy!fS7itKk&Ko2QUx&R?_@Z0s0T|K7CE^9%la-&v7XU< zoTX|B)_-{6PslL#91j!VcyJu`1AFs$3DQS#-q2oxl=TK@`nvY(kPpH8@}2kTZfbCv zmW7Mc?gN*C6%t?I=-Jg`9w6hJ>qMA|zdJ#MLy07MABp~qy9p~jvryr=aQOLR3Qv%v zx+;fn-j&QKvT5%3F!#NkK39=c515+!le9eK&0^k5!ZppB`2=w%VyqA+ruog+h{Wko z;buP81U=LQeW?lhauf8GCg`h8&}RurZlWbQ^*Jm5C>AdJ=j7};?&rzfi$>?etht@kqo>J@pM5IO)`bkq;mID{gUVs8zJ?*!`aZYtaI+bK8AOeR`aZ^ z`6jXj{EnyWA*LMcpa>mqj0RioE*B!k>^V=qmM=~S<%>}c&4qMeEYfM3ri9-CXRI?W zLlD!haLxG?6aHR?N%#^D7L2=w=K`6+F*kmpL&skhWZp?yb0L29`Eq%`Aio%AAc%2o zO2NDZki7gjP{NZRrpbW_d5C09CKusQpsc0o%Tkd2GR|$KI_3#-6CKiJ$p8f29C6^X z2xf!BGf>feXpj`b0{)-Xf+r04{|)>n{%-tVdJlfAKbL$xD{ml13>s#>49_$Gv1Yj2 zm-YJ~OD;q-c(*3z&$Cz@iyLPD!bfUYJ)0?6!V4U(2kmgJV%bcMF}(C+td|HmQjfA|@wQ z+dBlamtM271dgRsCYLlrN+S2c1OG3SIC~|hT)4UnQs;+Zv-pssf0@FYcllUnv=^|# z@k^DYI@}uP!rgAT6WM0?U4CX`c~#5=<5H5zq7s^ch{9;Zz>+PV-%K{rh4p1ezN5>> zo9KC=Y#n?fvT_B|g^gi`n4!>h@LqUoY0gN7?vpmHh`D^plyi>a_I&lvnDODA;5TAH zh7Vs~#{f6zFi1r>-{j-1*1Q^N-ZIxl%bWAa;X${?-o-0eXxn(ydN0t`KZ2|IfRrem z`NO2e+|_rsL6O*>?Q+Ktrl>W1Ak)XE|FmbAvq6VFyAL6VC_7GDu$Y`B7DHL7wU{@A zd%a0BxCj!8jnp{(DdVw{cr4f*!|-Qdz~>uvuO;IUWUzjZ5_pUHt+(*x3o_;$WWw;m zG^mp@;9V1JhEYI#@c~cg@ZR7k_CBNn!#^wT)HGd~o{i-4-ipx=i6~yU=965DTgRve zRpRtU9NR`vA=Zl%)(hW8`OmvvAdy`!T-g ztQhTkWFzJX{m(($qx}wU*4rp+V7(gwuDFZOyrJ6JZm`4p`F2Fre+RjHRms~o4gavX zWNbYI_S_JA`rKD)aaMq92p*CU{uStKX+{uY11;NzEmCanXT$%2m_gWq=-{)-8vry9 zw*D~=>Yh@Y(^ri5UsCe!^%n5XWgFbvN_}bJ-i{4Va>vF;D*OjZz+oXj=-Anx){?#} z8$9-tLX}(Nxkj$O*V^Hfmd0f@tq1=GX3yKcBx$ZEa!hSnKyW$1j#gy429!3_-+UKu#OIP@|x|{!r{2 zMi)(hH%)*yPk=9Ozzfk2qe}$-6Lss?xYRcPiFkAd(SD7KZu2W5&LkogVU_@VcpOn1 z8Sk_QIiqtl<#?u?%Xp0#y?*;V#PAdwpOoH$<9l-6DIyNH6q2TZrIk0E`&uE9ec5OW zGvIOvMIcpny@-WS+yd}x+yOAtp$D|+0vQ;oiZm_bn;^b4=P%2vY#v z8g*m|dp0O(&xQuEB-e?lz&#L4a-EpkO!vwMU{I<+J1_^<;==%?-x;GZwrD}kXnd?h zS;lD55Jm?!+-_+A*ppzl81xFI4&1g@V=piz=&&Q$nBJJ#*t${1 z@eiO}{-K@D28D9t=P?MC=eWvms!lU2?ldt;qvzDlM`k-5VRlKVGK4OP`&adO%b`HUy$Xu1(3JuwC&x=y73rN$SYK zVRYpZX#H9lztWXauS*e+E+Y$#7e=LR&WM_?Nn%0S%iQY_*f0OI-3GHlUsx<3 zjiJ1r9v*)cNW3sBl@zTng4Xfr5^aO94GG>vb$FFYnvm|;=2dQqm0;ThsJdRvkHESx z0u&$Ti>lC&B1`2XqFQzy+`xn19bvna{Fva`7n<=;3!Zk~jQ_phIWUAB!k~K&Tav?r zAgpWM*Q>0Q1X_DDhm!?g=YTKi=w3Kx54^*u%ezeNeDet31RGyb>hKEdwqC5ri#KGk zR1nK=q~k3;rn5U-yaD$Tl6MewQsoj)pC3)D1K^1T=kz+9j=ETniG|MedQMLk%WNyx zPcD3>AJb{^N(UjRkyr@E>p~Dn3L%Li1p9ta|K8Uu+nLL5FQ@ck-Zgqp_yc-)wbLEu z_pt1<#QHgr>_%nC$8~Ok#8*(CgE(`tYvuq(-9Wx(29v{QY&{uuqZ_a)A;1wgy3qn0 z>7tt~zyUA9D?UOtmjI6E3owrW4oVBqM*s)t35eH=>2BrXP zjk0aMB#EJbTQAuVy?i1mh246|UbF{iCo?IV4bhdzgqe~SfTv%gTfIx@<&?9L zP(B3JVi_=!!MS+Jx*-z_f-PNQFweoHdgi3Wt(TmU4K`uB=~`S)1Qse2#M&=wqkFG} z(-UFtwGW>!XLad%dUZ`N1?BqiZuqDxDQ6A41@?qBu7(mw-X|>&B!F-u zT58@9Mix@*`=&(A9pNeG+2Fs76I@NcVg#}B;RygQIo^fsvG^S=EEM*GKjM`H`Yv2k z@|l%!_yK)NjjFN0EA~%vB+!*m%u;(iYL*=6hmUX8lK z2>40_9o?RRd1qxL%m9FS<2W#W$}1D~&UrOzHGZd}p%AaO6?lEILK#=l4RCD`u(-%E zsXIU12IX<%psvompb`jqnWZ3Ybu&cepQfr72eG4B&&=(;Iv{POUnr~NUXaiY7FL*v zwMZ5cq5_5DZ<|EI9IRgApp15PFyyY5yN$ZoHPfJ{7VyMIsBLxjl(#BnArr>#rPv*O z1$~#nW9(9Xv6tdu?plqi?}xhi%)C7ZZrg~N4-B@8PRDc!GxF?=RM3?Q_d;w*k+HoI z+rQuDUI?Ht;`g+bTE(Kv?yR=1RQt{)ah|a;uv~ZGv=}}hRUiR)%d+>*orspx0AbhJ zr_<$uOu0Q14uI_y>(F=|nR07ueAJ_I`#fur5Plqdhv zVIDK$EAfBnckq)gjP$szW-dD(?!3VTgAti5#U+YPx@Rbzp0)CWM7OpE8L98OR<+I^ z*@Nl-y~>Tc-u( zGn=W(etLqvk2qxU)3ZiSYn4tp11k$!%ZyBS#&*kmsk18$Nc!c^pyP7M&F!{=*t2h{ zUAnwzx10+}&U4XV%c{`0mGdG-ts3x^GRQene&L+3l+^0ZPq4vPSDo~-Y4~jo3sCiP zpNSK6**j#tU&Gz@wGLtJ*637p!LwKXJ1hlHJ{oAMJj{dd()S`MnZgXqZzHl-+vAOh z!&q*G!R@wAll8uNBF`5rD;(@@WOYp+jSkyAjyp$%!|5h!yB)_I#JYDfO||(=Ms1_u z1UBmMPc_PQ2{z-5C$vai4PNHXm9k6cql6P*go+0C(D(5D&)LcqUr1(G!}>w%qK*e= z$k~{7VRURls4|}P%twY9aeMPsx2{%oK)+E!ebFsxCzio&sE|DuSavqOS-$__;JV68 zTDGR|$6TcN6t;fMKIj2WB47ntw=TTG+W_>vHl3$?jc5GeVJ7l8B$2JWe*zF+GJ7OB zgvIaBH7lq11SWs`g0E_6Z{=aN6xWw82wLWRD-ip|o+;Mz;2pgteK)bdnpkRiH}i3x zya5(*mCiX!br#F?oXs%b3M<7g0+&03#48h}nR3cm-=C;FLr=c|?kYL_+5U$kR-PG* zdGqNH#T&p5qV`WaYd?4|NP@rLuNdbTM1Av|2ALbxaTiVWWF#B-Q zZny|ZR~!J_<`q%<0qAJO`8tFVhsf4+#IdurX}p|o$IFF{(Mu*hW(leh4kK?o=sndu z=w)ot^e35c1cWnEV^@Pe2HX7sl^4a(0f2C~A(C{8l~235a0!sP_R7P3c5E!27L!Zn zIWcq~=mvIn3{-CE2PJnycfv80p*l;UoWrG{+c_gihW%WUwPqqVbzqi(qS%57iHn`+ zp)u_fj`K@r+FzXx498GmTT8C5MdDaQF|(Ytd^>80XDnUS<=~K&V#Ajp+E&^ZTjB>H z#?9-OBDA%X0jnbb@?apt75D)mnlL-~1g`07*_Fq+OnXgN5Mm1<+fiDGS?HmN&G5Be zemGUlL39||d;@H}U3pBc>ugp5k9~KkUzfAwNT(BG6|z)w492CWL%p(fiPmfE0__WF=OX`6qa>FUl+D)h%?KvgC1ct z!{V2}%dQ6xFT{!w-hc?dMw!D~d6o=o<_5~V1Vxww@MBp;G5-*VfzPo1?LAw?R3jiK@*@qpK}=4oue>qmL`k_V8{(h;AE{rw6q) zcn|K9pmr>S4aIs(;2btw?f8o5b<&8k=P}g(Cv@ZSYk|Q_U<~ATtk~!}`)0|;!<&(i zH>)ACIfio6j&Eh2Yuy^ddKvYlx5K5kkgRsA2chZ_dDulZjQvjLCL4*U9!^Ak5@U+? zICD7Fc{F~?OZBCk#m&P@N04|+uPATT=o@>LzWH`^!CK6Ybi!wbIgacWN_!Q;lL0{3 zJ0OuAFfrN@3gO7~`DfaB3bW8xLf2eUal`m_rq72i+LpcxNoiR_wR?e?iDAYZhXk&f z*C3qX+l9`V@T5FHT)PE%2U|g#2W=@Oje<3fzib^ixi!o|b-k96Mc5*&juWW8!191T zj4{Iydl@_|_PfFJW@e(JSc(}6#H_suh~={ppOkx@wiY*UHghQ<%(=qDZupFhG9}1+y z2oE8XOC@DSs}$aa!wRQ&VLC@ba{?vAU;kq;lj@0p%t`nuFYm%FF0A>BD8H*!3Rknb zT zh=tJ!gFY_vL3+og6e;ulf=-l~plL3XpQ%hFLbp=p4}j)lY<)Joe9b?edoKBS7!Ku- zIUMKp0Q|#aTtZl%aIuG>)1$HuqnK70}AGee}zs5RKyiQN{o8@c;`xP^+{)T zbhOr5f$>ZkT$k;DPFicQ9os>=rErU3xj5WC1C#2uNfd&KDN;H5HB&{S;$*|YR*)b( z1Cs1q#x0>MUJac?bC1PZDCH~r?d@#oLLUQ>URi*NBi^*B+zY&Ho9g$p@HBrF9v{NF zYa5!DX$Q6}VjC{84JVV6;#R)&pe-5xj_nu&za_x)AE%+VSJ!PVJQD?HON%f!+K5=2 zSDt~1>{*D4#R!j5R}+eyKz=r81|QQqX|aiGXQ}bIkap*wqjVmMw@TEoWG02hPz=&_ zBtW_hehxggLiv7egPIOO=IUKEVf^8Q(dn?)=RNm9qWC=ko(sa%-9bCpz80Q?YJ}(F zCtErJwK@+mY)7-Vl!r77Sz(Rl!p|q-kJjUI5;I+UfVq&HVSIJ(`Bc+=s-^?$YSO+N z;t3Wklm;7Kh-i2be%f*SuWRt6&fflucWe-s+kf3%`DFdxB;UO(Uft{4p(v3++M`>NF%sA{Slv9iLj10Q!PrC56HF5~C2F9o7 z5X;m!Zcj*k1yhOFR9U{-9h0~1%(j+;R=Z(~yNy5^`We^*xuxFMwI_G~uhYG9# z4`Ds zp;c~z;XNJqhc3*d^gAHj_a1{UeQiA-CU5UU_V%>A*o7DEom=o9UDe5Fe}DnTac4@I zxXcY?g$NteknaJ+2lv9oYzXyr0xUKjk_{6<(Je)c;U+MW$k>0h&4>LPLCP!W`^WhN zCHWXD&B3YT`^lgn#TT+a;6%m>lc`OE7=us49a9pOL%HIrEfxrL5WSyiegClX6XMGS ztj8>=2Trk_j%&yOhbS|E`(|3Jq_y^7UtC~I0WAd)_T&9hf9tHFx_CWSyq)E^q|$p; z{rFafG{eqrRE34)vuv3Q*)p6ba}I%_O8%~ZqWPct=ig;@turIP#2`}gaR67xR7|nA z8p3?8YM90ch~;?c;xJNd8$?d9jV-&k4NMWOZ7?5-{V0NjZEOPH6lh=@MFfzNbhv&F zzp-t681Z_8e+0lF8eEXg4K65YaK@y;KZpn#oID#1PAJjfA0thTe3h$PdkAp@V+*2> z6N?5{p$M_TDO|F_iH{qcX~_mB?AC;0a)XO_JytwzaF)adr%u`645h)zLL2-t+u+Ie zUJd2(f3m$BLbJU^gt)y~Fpl+BdKNlcqK7G>&I>=us9sN_Yr>0ZJos}f`;+uj4bo3b zbd=c7Fk0P0Z~EB=!6S^C%ONbhjFjO=pCB$3Nio=*5q9`8tnlg#gpr;DI7>=pxmU7; z&k5VNF#2etgwH>J36c>dNG_BhS*kCis9!*Cd@pJD5+& zqsH>(87}5s@@FbgOYg`&+-I*MTD=@U@@8$+kG%|_dq)kNN;bj+Biw!ivaKY0`9!jL zNiy0|;CvY+$k>8RSCYkoI!6KbQV=->+*`rEb_*}G@N^3gSU6|leJtE#VPoMrqvjPX zZC?w6zraNc_glC`xN!Xv460EXaX50kb;}qoiRfD}+7ql-%D2jCP3sqbT4$9Wy>RNHiA zKYgneEm~d|h^-vGq%P2eMn6asSmuP3m)aCG2Q45vm~h{8)!zyLsz1i4zn+Qu>zh9O zV~EqYbktnM{3|WL<+eEvvG73_KGeboTlg>wuN=K8T4m#hAbz-wABy-9Hhvi5N80$R z(dsMVkD{Y2e9>riobb_rr4d9SyVcJGYmfC~wUFosv3_8VyU|Y@?Th+JLk<0-J?YrK zz?EP;{JyW5ZcvVJM39xOFBvo~Xk0SY5P4YY|sdVvMFVXSA1H zkzVMoL|2r)__gM~cy26X!jGMxkqi(N<-aR=SK5SjOvHm8sLp zY96#j9P*ebN}Qi1#Bp-a^MaTQId5qAr>~=WQ#9Az%-gq8{mMN6P(Pg2W(>yAJISrA z9Db%welN+}N^`L|MGW1nAL~MAQcqhsjK{EkzYlqA=XpEGi3*6iDSC$m(+a-Rf|*7J zyZ|T}vJKR`fSSrUCReCEu26=(rYD0Y;|TRuQElI(3U$O4YK4R_2vn#w&eB>K?Px_7 zsZc_`xI(SX6%ui8isA~jH>8g%)Y(8ug-{&Fme&J)ZKLfTn!4@oSFQ9yh40qrY(yVm zv`4=h{2s*7px9q$3=KMGG#I_WML^ z)#_=IYIjc6LRRc~4YB)Lr;@t^Z(NAnXo$Y-C}`dy`tKS3MYG^Iyeq5C@Un|g+LJcsdLrZt1>KW}_D9HF-%;5s(DYQwVKRr8j zn>+|9VA{QQmh^rF;i|;@%$ zc=DJE1?330FDV!=6246E$vYdmh1MF{B|iF!=BOLfS1laN_qc_-Z3#~Rp2|k8AaS!; zLF&yiNfH*g8&P5f$D%+rQn^@^d{UHa-6!ALs9%C5u@PR3=Y9|M>0tf9KdaBbAxe{& z(CDqaZL+Z}%qnQr7PS$4@@>Xlou<)z&<&;1pFy1se_6RQ0&3 zan<9h##N828dp88YFzcWs&Uohs>W51s~T55u4-KMxTW)*4dvUc;>GeUob>BBEhqAG;NGIe62~pq!-&X*Ui%{=r#K{nX~qFD zLL#TeoBZZZ?!w_Su?YB7DmnvnI4gA~$)&$jhuoh@;gzBrfvsJKpUsQ0gI$NF<6Msb z*E4hN2K=VN_krVd53{&_#RNO@zSv2y-fV-aQzpW%OzCXy?Z`L$jg_b_Ui9H4Mp?vf-=mH!& z#dY+5Kopm&|B-=iM=uA+`;aY&Q%%!wY9Qjx#ujxjGx8GSdX7TmHNdTI|0A%pC`D9% zKgPExXryj-E8pSna)u~eRNvZ25;+gG_JTezgrukwKZq}4e457LiF~2Quj3tSy$AF7bx*c67Qw)Lm1y% zP2g)f z!iWDk_uO;tiCD7SE&I(T2@#?8EeK-YCH6gbLL_3B5JAKe;wJVzp;bb)XcetgDQ!`d zR#CL}rKqK?_WwN3nM~sMzVH9@e%^dO9`l`N=9zuYoO9;PrK5G>`+i`UMTemGKzRl{ zA%NLtC=fa6)ZJ=jBW)1|!*>Xi;nFwXGg6+X_I(l|r^>J3f$Augu!#}wh| zEENrKiZ7kh0wXaSJYXbZ3>t~(FwDSU*61U!?G+rbt#Jk(=Tdeo5)Dy|S#7BPv9`~@ z5*|@WXDduL;8g-%CjX0@@_7I_QL`)n9lkI^j~U?dzi7&eT!=|fA4-HWFQYP}ACwul z^z$;uQJK--q<<_G3e%e{s=ldgGe2#_uL#Aq*E4GV@A(?6Iq)T)Gz)&r4oi=5sr2Ze z!l6R%2SzrW_g};0W$+$k)=rEwxih}rdmo*&!6694l!+wR6a{ZwP*1-n2`_F%P<|jJ zddRecLRZoOkkwlDXx~!8t2O*pgnv`&L`6rv7yVu>*y?~EOpOESD@!aGCV++TKZ6^= z;2tT-qbH1V?JG?vZA&qcgB5Iq_WIahZ9xyPo-wbRRbT~kUpK4F5^AFSdEM(~VXRp?mf{qJY1({G z&kC$)B!;{<4Q1G%*UdcG70=a`DzG}M(Ei_z&}M4~$~oaEt3{(sqr4@W(EYCg<*y3L z#C9nAlmDOOf0_Izf`0{;l!~bgZuz>Im8BT5#u7;$Sb?#Q$Dxd;kW*C*dBPQCm$|zZ zK&<>-3%prozA?s|MJ+YPRA32F=)a=(t_A+AbH=U(DtjO5%9{;OM(qwa4F6kO4DU;_ z9?1z{L1h_`ZUuHw+O;4~!aAwIevZSmYGq)lpNu`67$=Px1D^p>*us95 zq0X10`!v<(g67zc$*vgwK3G#(R4ufy!RK)I3an`oTD}Nv>cN8MPG3@iO`C&0(PVAZ zI?P*PZ;U%78~vwNMGKi37+0%|?yHM1=SfK@+ofa38hIGMBQ^3n1Nt%FQqpdZa4$s zO!@;v`P-3dva3W-Qgo^yVa{%tb#j%^N=}AO6-jq%j!w~}yA7XcyGb?KdqlrN>TXgr z>r7dK=n&AC?n=tR6c=^r|oF>ajOOpNv3i0FyF?JVRC*Gdodl zNZn0p$=VxSAfG-1VEqQaxIU6}qhL05lX|cvM2#TiVEE#z6U?t}%ySj$db7@Q2GF@- zNPSpe($xk^VMEz?qA8q`hz@d^M05i#B7qQcGEqxtqd=C(rV;I;@{MA1h!Ut?#uDPl;YY z$ZfF8?FG?linoBhk+J<6!A)f#%;J&}X$AGOY-WSk8*a>-`pZ%lLDZD;nai3GO`(uZ zmO!+U`qCPPCj!AwY(mHjYy+E4v;!^|7uaSthv*~N`ev6PH$=ywChXEDY%h^pOU%mw zc7XD-VJ6ZMc9L{~VAC!gW#1YlhZ`H!9d#w_CQ)%Wr0>`*qPks>9i{Ua{YeSlWFF7}6lUBwEDjHBlNTZv{iH<>X7W6UHXStEylR@rpuHq?)Abm4>8B zwMdsa7q5^esUd7xa$`+Fw}@#{Gor1AR>nY9L264BQ{*-jse>ZHNPHDj(=8RK6Vazc zRe-Q3CnnW&n+4ZuAh20HzNT9svvIObKnjz3QpmV97_zR^i*hzA8+A>AFfVgw*K~7} zno9AM&z=zyq~1rmNF)~`^_BjlkoDV1LnR3|6suShce7A8K$75Xmm5plAPwy=4Uh~( zKLE7_l2vS@sJT_#DoH7l2kCkzS8;O(@+4g$>1Ieiq)Q)A#jOR*7}ZoPUsm_;5@)e_ zQcVgudG2?Km81nyG=-d${9U5Ev|NfI-C@$@ORY#(ch+}_foy}+foREq?-E-`+oXXi z3~?6MhOwq>yEIspAqD6JED&fo(Fql)NE$)(vk9qK8V@I`y0OXBBM(U_M15d3EszdN zsYKtw(yT!GLYhKUn{;1EX_VgMm3S)2SJG6n`AR{Xr-3jpHG81$tTdZwY73+vIQldEBM17DK~HZHJe*5qoT zS<#DAWD=5tQbnig$qJ(yJZQ)z-8G7Lm{TsrJ7!oxA-{{k*8Iw_l0te?8=VBgy!=YF`@JEL zbh%_jk@HBW_heOGMLHZEvzQB~9h4WF>?9o?xj2jY$oWKiz4*xmMBh{SLOAKVP)?I5 zFX8g)a`9>a8DQ>t20b~D)sfdwUUv4w)~qLQEElq#yj6v1pA8^g6QHu%Z6X&@$P1L~ z=0Ih-)(lS{e&f5vU3Ep7EUKP%R|`q0N7YQaSxaG?i0!7p&`bh&rqQ1P zgelac6f!wk$jTxh8A1-F88e5+`+(Cfn$Hd~toaEUJV*K-*cKl zwR?fnDXPJ%Jl^qSjQ5NDEm^_e14?E0fzV0{&3uo!PQN<+!D%tgJ%7mO=(_S-SIpTT z@I`KFgiW76 z6IG`a492TOdr2o7e<3i3$w|M;_yJ*$ zJV$d+0N3erPY@@44hiR^&moaOI0B44(Pk8g|fH;i=MfiRzW)L(iV?@-8NoiXGfAY4y;Pvc>z@hmcSd8pOyf(+XQGBJy$A^B&t=eiI!eoo|LC(RCpcvRbh|kjO<30- z!5z^ow#=xRa5UD8Lz@MhLWwpLxv}Lcrcelk@hU=zX0e^d$|mgdsm+i+FjgV@WH{0; zAiQelIgxgA!u886Rt$t|Fr4`U**+k5_GFE(n>*{eJ^*BqUsaHQP2McIvsu+6z|3rSY>tONdCS2h570emF(yn9t}f4HJ4$baH7G87HpnF)d!|qG@O3k@*)wOu?;v(wwG`aG;BRINNKS?D5_{%I0sDI` zSgv29Fm)d)-Pl6Rd0bzVdVWyGr!RoA#HWuWSweCfNF(b&dE1+o0`5bZ==1Mll#W(t zVRD<5Aiod7diw+(tfEiB|M1aMxdQbpu`ftonS!-?lH6r^keD2_9^yU-#N0j!##%i< z^|mnx-FkbSZ@C4uRi_kzY)A4ND&yhQUEp)GBj##whrJ;6Sl;7LaOAb*qu2BXxilH&`{BKz z|F3oa4D#=Rn5!y-ZjjG!|HG#hje=aNy&r?`gVs^)350JB;X(GnI@CwR?c^8Wb3X#D z)t-d%<9R4wRYBQ%9LjM}Mu~kD{s!cVSah!pM)^nTzaaBx--mwQx)=8FtQ0I=m#J9N zx+LSF567n;8Y{cIvze>jKt1Sl$f!X$KdhL8QlA+n2jb|!xhOvUr#8Oee`+bp-$)Kw zhHk83mHpQTT@n*ZQNo#Uhjy$iGQ7^fxv}pOzck!9oPNUCrok zCU*yt7rI(OJ7^aAzvz#0Wmg~Y8Pe4OGOBARYP(be*|u9vkX9;Vs|CJpRau}f#p+TA z{D0}v5Tr}jW+2nIV(G5ZtZRYRflC9)4-?V7v0E&Jc}eag-P(eCm>c@XcIgD}Ju}f= zwR?APmqemluX(KVcUv&G0jAQykpQi>Y<+(Sue(#j6T#=q`Z4aRy0Z$l)cA3e-Q&}P zo1*+$LAk+Yve3GwOa-4qLs7O(Mp?69Dp;%4Dg{zMwispeg*eJ8(pdYV3i=G|i!!_b zWmj?=$=#+h`V>_}xtL;kG{?Aq#GucZB_Q3ZjF<)XCzIvT9nk*^l8eS+dp(Ip`F~@7 zSW~>xISWuuio{e(Vld~^AzgTP5REdZ_H1}7v6k$Y5AWI%Qwi#Y+P$Q;_fLTuPPXA} z@NCw7ur?Rm5_?J`D}3cVcerwqybSFQW6&REV#Gr5|9t!skRcORfZRz}#v>#z^~(pJ z_iE8d?10veuEBiHfNO`uyy(iOk_T z5{x7IZ^)H9d$hg~!lSz?+gF6)htZWHya3%_(){p4drbW`xd)`8+q3y(h&4ZTFGvT? zHf^hX0&ahjo2q;U?gWy9NuI5MJ}(x20rKK1w0wu;UYI8(c7)`ris)`jb#i74<_CMD z%7S4YQ`s!KvLA^(0Tx_Fp8)@uxu?K=eARdEGV7Re8RVFyzkobB@DB9e3{MQ{VS5NN zChn!XZq1#&zv^#rUxoRqDzq$0w|RRZt^HsbdL67BRMs6<4$v#w;p*t^T+CGntb8PP z3c^FH1;N7-);a;}VSZnXl>sXd>?bG>wZvA~0HpwHAt;B;g#(_LE96;bEhwLTD4(B( zRPm76{2aQ{1tz$6OAm%M0MtxFPY-uCCEO0N)Da!Q=OnGQd~5iDPiT!Wkee($!7|2G z*-dCKl`YT4kp1G&f@w`156o44kVD~$sj@S@G2}Ip7*b{5ZO8ogFg6Bj|7$Egq(X7^ z@HuV@ys9pXRk}XxVLf21<5p@R+NCFiUz6Qa*gRa&2YimVN4Y8k zWvl_^USprKFjWVFPeS29;qy)BVc?UKi*g>x-o4TN7x_OW*`voW5q^39QJ~ErIT2)O zewJFsgZ~F4LzBk~|LN6Xh3GQO;hB4NY>W1KzHL5l*yG*hK)qIvoT6@Bb2wBpqxVPdPC5iLq6M! z(nJnV<zbfEHYy zhq4x3mxnFf3%#gvKFZ~NQ68hK@Gk!pxU%VYBAsAWrn0YV;@G@J^1rI+ZXJfQ(n^$T z$-Orn-OYwaLHPM3N27Gx<)7v8uC+5T#!K7lH_vRyTbDs7=hZ~{bs|bz9?DuZP_`%S zf9dG1oRJOr*ZaR~%WQ7Bs#|Rj?Eh=A6mGv{WkHyWE0%#|i!nbBvr(>Gf^uKc3h;L! zd1MBq@T~y~gA&FvCE(Hi~k&qYwKN%f)0H)?Ry> zm#VKt3;s<|)*xAtq{m?N2_^Z@Aawr@*B6x~;I)`v{XM5{h4M~J+ba4)YO@{SbF1AB z;nP3mL-2`iiBgV5d8<9j-#UK?r8vOKpe#ACAE5ayz)o zgSmpr-i%~}6_^Lt5h$mxKyCfOSi_->P*UBxrQ~Eot;UdyM#*zqJoyCV_DnC7(_&B- z&p81pzFCPfXA4@}<&SneXodN@2vhl&llv=ipuTL71ggz5ly? zwg~!?+xsY4<|eF-28*z_xRJcR0)4*g0FsZFa$4viFngD`z{BX*y9G@#pNDBo{f1-8 z?F6N@meP8E99mvUsnlK!DMFrcbZ>5Y8fu_ZGnD5?oQ9mZwnXVQ1g+_@Hrp`nmT_1! zG=6!yC#mKsb#86QptE2nX4F|Iqdwj*ZN|7+G+ta=fCVtyi!4ke3uno~9I~*REbJ@7 zFjh+4cO<&&Q|ez)>Q@J#Pi{+;J13zGr!uZ289^aGrI1MjQQMK~A&%1en9|zrbCFsN zB(KT3EPjX;$C zAmN*@891waH5+ApTH{sghwf)oUVLs-I!nYRUxzyEn}Bkg|8>Zre&w3p9IqGpyp&60 zYL~y>hw%=pc=BxsADfJ_Zvx8v5R}DpZbK|fD6CJ|E2>HTzIY6!+qenlVKv-?m!^Jw z(mjY(yAjIo3h#;exmoYW;B&nI<$!9Bh5xH*&%x*PY?Mt~K8I9X>7M!nc;W=l{bJDm z29(f4FrPcOzlMiU{2g!16!RY$MvRhzU zv$vq!2hW{U_9=yI*WCcSu$ERs|5x-(;3KSOc78I-@^;pBH$g~!j{V&i^?lRtT9yc}VE*18%+N{58b^?)MC#LaX~H^sfd!rP`Vz_2S8O zAobVXQKrSAES^)RoVFqO3@b#L7KgHU4xIkQYo6sb6YkkDv7jB=8D&}=%JU&8xA~(i z4j?%v*0ZWxr$V^LaeGW-A%c2%VMnatKKZpk{$zps1-F+OxRx$|7qC3Ltw^hQ@)l_K z!yzcI`=Fc_gR*$e7SSt44D1By#ulQCs@2J}9vkD*-O~zRvm}(WGX%853Q?xTp)8&= zL}+Kni~yg|4JdDIM|piN%AJ)_P9KBPPVPjKhX##+8h*19EU=O}=+1$=NEi$7%uQvD z;d!9SQkFqU-KuQB_1MI9IM3Xn`;-qou?Ac#W8ba`V8LRp5Rfo>0-uww>!i<*w=6 z=l^MS!Q;}L$;;k%&~#}1=uy)l_5a=40R79a4a$cpca>pum0|i-rhNWC$-o)DeExNx z|F`_>arHT0cfY%m!M;~Mx3rRBJ%3=c24e?g?(_?zwah)T;gj#JoUQ09lAES`h%WG|F%AWJRSH?D+KJ_LWsB$koDcEo`nfeYBJ8*(ho|C2P~QKWXN>p^6YdD$i5bL-DSK+ldc1_3+~0z- zZvx7^o+SNH_9N|b@>vc(rL{3TY8T`%yd}!&TTpfzxa)t(?OxBS^meH#n{Sy2ssBxD z@=gwz16jBBIOh~K`vmfTeE4S|y{8@n=@|7D$g+8(s{>xmGAA3A@DetHbk|Ay*?$l0 zgYVCEb=H65X<(T|xy&!}0(|%A2!v=5FL&8xppl$XT_1UxSrVs|D$hJEEREB#%5UIW zkQ}sAFtpjuP|7qnkq7rriexqOtTLQa_@$}v2HITLv zm9Wa&k&Y6bU>o5i>nVIxonVWI&X6vP?H+W?JcVhn8v#?8H|U-@jiqzS9~1`diksQ$)Gi{Aojg&T(DyX(KHg*k!KkGi@Bu4PD1vriGd}vge#ah$_+q zpx5v|)Lrl}%}y`GA?n>tahH?oIBPF8b5vSlO7u8B3F zmuSm7CECJPkj@EXWu|2d+n^J32@f@IVFx+&B05K;_mEO6dak#y=cLPEnCnuV-b2a= zd&m~1MPptvSWN70WeaoX)E#K+J7w7RPCmD>78Ej5%bT5H*#`Sds6^VL*$XV&S$9s+ z>%!P})|XTBb-R`AY#1kx+LBVp#&Ytn9mEP*3a5!qNhxA8In8ngu_89VRM&I2vV$Gu z)URh4+sTe``ZRgBvXhl(QSS?NmYvurT;q3;t+75;H zm#jBYiPWeQQYxncqD?x{jIx*QCer)MUUr=8a7Nk7E)Zoh-?3pp;qPJ2N~BMgp>83k zx?_<%>!2=!^J3 zAG4-{4q6VdzNO^cXF0^u1Rb<|#_~9QG<=`sbGDb$RL^~uqwF)GJ7W2QT^70{mgDRu zr>nC|EMKuVob~~oU{Za`8O;n|vj#e8Jr{)=zGlsdGPHqgcbC?(X>U)NHaKfwa zx6F&v$7?TJzGXq27A(kj{hmc~IzRrJ<$E@hQ>z+xEazCJps&p5Sq`V3H6B`iV5kIi+NTnlG~tIc1K2Wx32g<}`8hf0irk2q*9QiuGsS>zry}1Y z>}#$YRACRh#=hb7VPz!z!&T7kXg6ya{pN1B-W0kOP%pm=`XxTlT1IQ2UhWCq2T(6% zGz;qGfzX{CA8sw9&h|T&M?#nX-dEPA{8&Wq!-<2>QnSFMGmiQQY_D|JZ9m zQC3Nk=$l+8bNX**vg9JDj@2l+b9ykTp;eK51U0v6QYfbde%s*}m}&^BW34FF<&+cq zy}6RqOi+}yiWJAGCZy*sbrDp@>LDd?`e=BZ)l(Y6X{u+O)hdk@y3STFX^PNww)#rx zoPuZcvIa=moc^0Wz#1s!ajNeevW#<7Bw5>ml{xRIz_AOD}0KC$BjL)_7^5AiKGbbcoYmvtJtf zNhN|p&Hbg@oE9W)wDy;x8|x`7NZMf?D9z+_ujwK4U}?UfqvoN~Qce#C$61F<1%f(T zM@m~bJ>9(5I!5|X(5Kc3(m_s_a*tb+r7t;cnDLD@Rl2}w@Qm};Y0@=Lr$et;XG*_w zdK!95h7(=j4|~tude^M@MY&ED=iITT3p!r!rE#vHN3)ZZd4d|%duq)TG(G91HA~Qs zFl*omgSySDO^=$h1>LR_XU!3GeQ;;%GC{Q)?6s~CbZhgcR;Qp-P+B-UpW7U@T(Pbf zlnZ6wD5wXNeXF1&Q1(JWx1j7rQu+1n4oP1nI9Xb)P;(hABzns0toOu!tUDxo6HH%U zC-0DY6P;jqjoTp&A<6)P^`Nv?52;nCS;??d+Nu-#99FnHrD9Gumm?kHlr^>m&lF;gwd`!^p<+tQ71SOBP+P)OD zz0hha5tKVRz;;4VpY%xEDM6p7McUw$ZJt8t8mkQs;pWs~NTdx;*ygkl=$zDBF9WSn zevk@?^qT)c+DxR^{14L4dPu7I3(_r}sOB$7PdQ=DUw{ouj4H(^mU6s`4dc3gdE!cI*%4t_vTXs_l<4Ha&<*qc4lNYSE?@8l0-Rl*`?n^U?^cK4>>g7_aMz%8AyY-g*Na&hDi#-;U z11 zEQOYq1+ixEi#~$B^oX><=L|fgDSougMbMAEu2@|SyPBgmi`uWEVLzuIYJ|Zq&yz$> z)+{>720z={0z*3CDZn&a6~jm(y-#=;@EcCN55UprVfdN|o@=bwWArp!6qIQ*8*X!| zIbo5_VrbG5<7L5p@d}&GkjKfod4~g@H40I6}gtWhMSyDt$k&yYj{I6560$OTYW==Hki8JTN{eDtrcywm(gUm z%62$78~$*9nc4D|+*;76MPBwcf)?BS?Qw$U*AB6_6ZFWtnjOxn=2jZkkG6Lbl;~B@ z4yRCaovCg!dk;a&z1!Fm1lj6!viBAArfw(u06{4ba2ehckM3%}-mo)1D{j*qAHURf6)PitSE=qb-JY zvcoy~uK9*=P7z5T+w%=mIeBL1yRJ5*bLuzhh<&x;ET>bWzP7J5INIq}ibkEauQyEO zlridxeWT$Rr(Ie3uA2>CbK2P-=`5#?n{V1T8yxgN+sT&Kdtl#cn8@jf_Y3=W!!b^A z^YUGb3@16A-1OF7WUzP8Q@>GH_1b9|#cAP$DqbHLE^|8A&c|!Fp<+ipWc-9MuYHF8 zoW|tlyMAOC&Z+L^nqD6n_H!CvuYuRc24g2Z-rET+ybc&rI7KeXcm32bgHsErrB4lW zIlW)IqV%b#>pf7rp9%6!3b&S#+k2h7;EY-L!_k;+@9qUh%yLq}CLA!!Dc-%By^OYO zO7McSWVvp?dw(xD>XlRHQNz5x5_EV}xbwgBuih}P%R;x;JJsta zL5-lLuL_dk4!Vqfs5{K-n$XRdFvIJHpby&3^|~df-GnRFJA!_9Yh%AFXaKbP13}@N z;T~PkV941cK~EutCxU82*`EmtfL`!IP&(+!s1CH>OQBm2ed48|{Ob3WA&2@4u4!|; zUKt({>GQ-JgRL{xeU{eK>SS*W4kD+P(%tFx#*jj!&l7J%3SKVjKv&vgr?)04e+xPo zzr*VvL84Swrw%zg;ANDb66rZpSH`_=o@tjvB z8QnNKp0v8`Raww&m-R+avGF(&&x+p+q|b<{(_zlf8!M>zfU=H(mT@-8NUZ+ELVHvA7KuW59vC(mkE_G zbHaNWIMuW(hIFzvSD6oHn}E1d8sC zsb{f`Zgst@$p<(MNo?vJDL>^DmDtL=hHUJihdhzj9;h9s-FZE{Ys&pOZJ5#5J6fK^ zY4D6;-tWotIbF&f>s?n~%_((x5AXV-<^wu~ni~k3^A6R3xu;CGxawr@GTKyirgtL| zZ<>FmcVj^%t#Z7Z2-*OBDMrva=u6E7nV>JV5VT;im%XK+?_<5}v4UPNaC)~Al;^hA zyS1SB#5Z1T1qCNw@a!PyhrF%cW%OCzF7GZvw>9s8cP~Nq@Xx&y1ntf}?%hYwcf1}i zz~hzGX?#c zd%7T#$1~RqLEEwqc+Zpb^qkRK%lUF45w?TPXTJQ2PONzHZY4`T&nYJs zzM7P8aQd;Cug?PcF(-Vdu~7b-Q~Qa*J_}`+K6;%!SXtd?k!h#yjhr;}kStWxP+m zpav5M_!J0=Ycj%Tji8Q`M)<51k76<%IX)Me<{vXap3={|Y+pRU}vLi)F&T1n)j&e?hrE@5@nw zs(ODYHxX3evs->aq>ui6^5}kKg=MwdvwTvhq&*9Fwba+Phd{U&swP{RNMmzm(f@ z3Lk#n=eYbCr+N#Va01CyowQMfNVmBzqSj@fuVlXgdU_?cUwux>T{$iDxbJgXP7(CX z=R3K8(~@>?e9pv3XJM$O*^hMY%609GjQq zk(_Ydb4gC+)EM3~|0vJnv=-hhUzT$@jSI`MT#+|&n%XqS@{{}_r=S*+@6YmQoGK2{ ze6PxA9>Z}Q)Q4qEQW6$a^LcnEJh@5x?*4q6__;he^K z2C+wSJx*PL9?Pvb;hWGWat}`UCiD+^fY2SWJQp<8^N8h7c_P<69_QoxO1{A9Vt9z} z8~G1Tm!?+p{Y$PqSkKv_CdICQ%f6hvV1D^WuEweB;Ar2sazjpo7Q>!axecfB+a;i$ zf`Z^gm!X^%!Fz4Vn8ay85u7S%oW*Gbkioc^)4h;-zOr#OCl%VkXe{DX*f__c7(eFZ z-YCbS8jo}OwMmY}WIV@dGU(u37*6lOlVBI)BTiAN@O8RT8N%Q%i+K=vaQYHn-&8O< zIGt$>Uz{4FI5o?MFRzVFI5poQu}a4FoIZj*Se1dw!ul| zoW5?h*cXl>=M><7#o9v9&ns8@wiGm^$r@icf1HP$lDE;fm7vXw`pSoa%Pf?BIG;1DT2P4UTmKtsPptEzG;HqgL~0wf~xn9w9OE-xMrknmZ1J&k+wO4 zMm3GJWeDpNf2(ZMD^IK-US;1RgLg~QA_U3HrLmI*ps|EAXpLEg)U z+ExjAx~#HafuQ)Jex~(;u7r5`Z4|W6E6{JVpx$+FdTkSwG~$3aoFvXuUjhWDesdb= zGvD_ELERe+G=C^)+V)%W9zki4da1^K6j`yCO~-*ce(OF_+nN(32FZ^88(8T^8Sxe-@P3 zqQ2i%L0x?3`(6`tM;UFqA;@IyXSyk9)pV2JZKHh{TE%r_jNcvOd7bFK@t*Oj&~@;; zXRJ3IL+UHX2gdP4@VsDFhUI}VozoGZhsKqhI_AO`D8@}h`b_)Ch&FJhJ>I&TUm1M? zGwoBM%bs<>`wu}ofu0MhpLBuW%WlnyrkgP;{`Cb{j>N-eaxZf*d9;bg3;9EB1Kb-!WF$Q$A zMvzT*60SLa88;E>rTxo@8Z2#h*U5fm)G+g@*ITZ0I#yP@CBt#|JmiCfnSPR>J2Mve zDS|$RS>HuDLn-L@#TAqrdg{=AEBq>mkk^K;^{b+68;Mr2ZCCht2wkUPYyE7>s8OZ5 z?S9@uw`5qcUw~3=H0iYYV=5Vfl-fiY+L|#{y@Qm-To*sHk|9`$<+?F5t9l139d#X~ z_o-iqGF;Fxzp9Elrp#u9Qkh7%8KKy?4sBLb0=N!sR#U=t9oej|G#7NruZD74kjkQ! zR%0;*z5SwYjbM@~bsQF|sdtX7$l~(_Lg7$R`@E;)P$w=luNYFoCuKptgwebk`AFtfi z^9l6{r@<@jCY04#veKJKud`%jKT#HIvpvdxvXYooR`xVyA*Tl`8~CRw2RZeGx0Y$j zLn0?j92sg(Q(h41HIXLr5Ti$ppDt(=)I_?VFIRT+ z&k)plMPL7UO2|aYCzWr$l1ZfZiTTP(BK|&3IjM(aLkAA=&r;kb>E(+l9OA!F>CI_- z^br4SWjUv|V@CVuC|8M`?0g73bx{&~twPV$8L z{;QM=f|mN{E0L2io|8S^l<&V-kXIke&A03ErQ-F-08nV(CX-&{<{S=8MEJizn~K#(|tY{v}@&;{znA0 z?Qq)vn4orEKlmRPq@ zmH!WlohVa_YV#H-kn2Xpgs}@s1W|@IIR@_7mD*hQ$s9?!s5Ij7j?D>T7nNA9`^69L z*p&`k_t0;*a!Kh)r1zH}m97p<{e*<${YNEPkH`AQ7z0XmC9v~$w{ls8#GOZ1lyu!D zn+CrIaYb2Al)<{kR0_DF>>|=z?22+%w@JPAitqgTRWEs@DYW z7ye0cA=38?|D?btCGdyyyhp&#N-Y6SPm~hSHHJgLzF+ z*$rg}r;TuDdP7M`!;l$l3;YWB4drK|EXvsp<(96aoZV0!a^0|+PmRASFSu@UP5*#j zmAASMauynJQwf<`milkX0U|x0zbQWvWoeIlOUm!cO`=TgZSNrVyYhhRX7!PjJIZse z%k2}y?kN9o-C&q|?kdJ6 zCem~ESaF}O=Nfy#6QwFq7Rxp?4tSz8&UKf70Tt9@ zLF)o4smBBr1-PkaIn5uM8(>zia~eAE;{cm_Ptef-FZDI2n8J?(d{xsdERmDN6dn!m zSFN13M}Hg;sD=tU8W613;gr(rrLn5olvCANX9B|1I8JZpyfj9rXc4a~CT%;bCg_dv zd_Z+U4PCdh8fsTP9=-94Qu}f%@FiqGl&a4KPS&Jil2TXD!GI*Co}foS_0=gn9)7jh zK>e5#ezn*@J5RZ}_Pw@Gc)Gq{z2~=wO;LO31m7j6IHssGiE`O~ z7%S7n_}g6}#W78lGRX>ax<;613f<6(5$0Kf?pB)Nn5|aiA#tRpt7ACfNKIE$IN?Z5 z7xBIvl%%AqrC*o4@X2sws9*7r%D_#Mu>2qH=PgH$Dq>rk_>OWkEU!g5lle5cP*-unz z#kd%CCG3A^r9bJiw3@KGSga1`)D%`1i`8^a_)TGsx{wooZY`rW(IQr%MrGaH9A?PIn@9 zE6dgCoUT_1W4Y>FPA{tLR&v!1oTkBh!xd^FQHGXVW4E$G-NSW@W^Z?_R1a`n-s}&6 zj&WV|pdCt{dXiJqK_3C(4=>_c>cF%xwo28AGPG}}?N(N)?nL;l9-OqPhH%0;%c-{E zgwNTXy!VtySo8UU`uZdFjMaoB8VUWmGS0FRXO()n`QdRi?}) zej{B*_yue}91?`_^j?s!R@O*Qh&4=hRO12~yXnM>zdT^n*_9qdwaq-hHl{wrZbats1cc z^XX(!y@J%WYBZ;$lzi81f%eL5X2vF@cWP;uy5k+4 zaTVsXG~}aqsJgSfE`dm|!BU%g4VDr1tc zFF?{q_%5{`k&}H3=bY?PJ8~+7uaS1C<2V&0UUqz_zTk8{@w#KTYF~?1oXp|&yJL?! zozvl&2OY&~CXrs+z34-Rgl~sQ8O{SQEHc`ndWJr@EW#1Rhs?*Xz3R z^_m8Lr8eR8c0!xLuSMB$hwwLoGA8y2ETeh#`Ujp7y1m|sf!_(j-L+>0tr;~Tu#9k5 z^?9N5O-c(aBivPeLFlsW>46so;m+bqg5>e@1Iy@#x=RBu3mxtd{z*{y=KR1i8eDHv z;8me}I$>wvH8q`={q(p`0)G{{Bjb(*-cq-7-QF3e0)JBv3c3(@S3NE0df)@~8mFzp z?*~3s{}A*%@DE{gr{SN#7lJy<#-LY%jB>@GzXauYnSR@hsh@6 z2EF}8PYE^4CUoQ4w2K+e{Sh?o9r8^puUi-z5>yuQn>la1R1vR2)eD|3f+mC_HKX+O zk)!Kim8al$d@iP*q|0StQPqQ7OecBHp3STsRKZkdqn^*dXEq6{Xv*Pq-mgheB~w19 z??%T4RW@xR%4JWRw-2gfy2^FILt}&7Oba)m75!@AZptIl^WttQA}W#alofZ=$DD9Z zcBe96KJn^XMmQ6>n~qROyi4vDLzF=$GMG(wIpI?rv#ISCjF-Xkh;|U^xi*`MiS!bgO(jH5wqwn>AhW3X z;p?Xa*-YhE_ui&aThXSIogCUk^?pa^W12&{ENw5WDt%1ZoQ}Y%(#N!#lhiyT$k()) zQ15u15oUkWaUKuXy8))toN&DxV7kER zY%x8t*1^ewO&@Vxb_e*aYSUq^ zn+hkQgqTV=rOewD6k1yhBpbMNHrG5Z(LnnGx8EWddo$?9P!7tPlQi!BKGY&KL zB+{QAgqemDWwFi?xjtd0z#@vr>P5h>DVv&d3WpI_%`}|Tt)585obc*f&1Bx8$HNtT zHB&22?P1+o&9spduIHd(V`*Zl_`aUr z^Y%8MrY3JrjbOJ{GgCOHf$+>K##E2fkpb|_DyCL~?gzCnjpXzp=vtam1>FydH5G7L z1G-kGLP7U~TANOB!o7ZNOh0n^26m3cnSSH+2~b4=5tCN=VaYXD>&g^s&1xDoPKKj%GTXmQ%_F64E!%B!L*Q*d5s#}+f+=XKSSzc z+Vmk>fp-O9rLSpku}=95D+Tv8UF39c37jKqdd}(eEF{NXJ>+$w9h~M(kwEtkr#0|y zVxXzcJ`9=5_O!PJ4>X}0uUs$WeAgj@8dfS#&(tKPO!uZjVDL~8a$2SE;6yk2^= zK(|tm#Ik}{2|D32B-knFjkZ9}7j#G)5?mmtT_wADwV)FDsCliRn~<~hf_`^7YThX5 zn9*+DENHOdsClcPcGBYD?Se)F?GV(CEe`%bkOnF27BtlLsJU3sHhD$xK2A=D(RkFn zUyzrp-Ta9l*NS%YK@sn^oTPjzsBVjb;Lij-YP>1yi|x3~I( z=^svAO+$h&nv{>Q2AyoRVMy>LQ)NzPU{w8RvT>RVAupT4iSpRRgdG59w@pP8-)-{tEUeZS2;(?Fh{PvPz0d!})mDlZ5% zKQNu-biMV%;0LBU`!UygOFuCUC(>K`iD@#C-qKGfz0$UQDhS)Qlujs}BmN40`c7V6 znDWSq-m_kqHWA@k5q_)3R7~V#cV~v0|1=H$81sTUE#yyrSCFeMS&{Gh#x${E5lz2!DfFzj`2O|Q8Qd(T_b#6x;W>^+jE z-;!`!U_AQtgYjO{ZrK*eug14*n3sXAtvp`VLcx9Ru}EP&yn?34F`*}+ktE3g^ z#F{m15mHHeL3Bd1k5*l(Xttwe=~dD65s<6(sJ4ey(VCMkgMAtq7g9xgcnm{ku&l@~ zA?{lC7dmZ=3}YVJ2~I1CUK5>Qa~sBocxWBJ)Fb{fJI5^Ok5Ai{^&34ecz@*3(us1}id7Rr99r+wsMD?(4~e1YEGTB5d+p* zLbVMLKB&^xTjL?cf>884KC6IHbDf{8leRF062)v|^$x`t~YHyUTU!J)I#nHQ(>bN>od$ zNu-ykme!akgNBf+&l<88s{9JuOMsQSYgvtWe;zBel^x92B$Em^eKSDZckwp63+F9#Qq|dFLwQ)rH+}c?yfA#I6&HNE< zo?zIofi6=D)DGP>bj#F}n@FW}Lb@0ES4dB;J0abU{4b=Jpr0bO(B4}4yWKuoCfS_F zp0diJeY71S1&Q_54ifz;U55Lye%ez`y*7ja75s$h!!98M>h=@;>au2aJ)pxnNxPio zbyrDuoOIY?aOSmkfrrEt`fC+`Mw`D%m_mPT;nlJf`fJ;W^c4EPlR}veQ|PblqL6x@ z=&zj-WDf1GwfY5ZqRt#TR4W$bA393=RuKGxD{lwA?Blenq|}3f_fB5Q zbeNZMS}}#h^p5z9)6CaPQ$OM}L3>z^CTinvlskh2# z+B>~qhG^+~kwI*hpxcp=p|b`36j?Vk^91 z)PoG!TF7subi`+|cH{RlTB4=gEu&>x@B3wxtL-mGE47pdWx7?`#)oB;ua%Ue)!Ny| zWxBQ6#3yC6Ub{==)Cvpwgl^P)pP~+F5YSknEUhVgwYX86Oa%KwYF72$sLkZMy5W@! zo3u=>YaL$Idy}?A*FpPz>bIHK1h!@qX{)H0dy$Eu+XdZ@ObXp82MnG0&$-XEmolXJ~LPEl-VyBaIl9nVL zx;sDwH4Gr2s5qjCqJp^Z3y#i+4vfyYkD?Chh$AS@=&0i^GV1uh=iI95>Lj4w|9{`} z=XvhA=bm%!e(t$zb^c<7DBi0{cH*@zp&eYZmExeda^!1CIqbDkp_096g{Wk2TOrC} ze^R^y7LQ8yz7?X9ePD$s-bYDg`B#_FxG$*RWy${KzJ)__{C%2~-)B~uAC3F*gfFbn z!EsNF{L%_hKYZi9{Yy%dQc=XWNtWS|`!VK8%W%l;{E8%{WjK^%84^6Q42RskSW?c@ zhmz`W$UTsGE5(iC*^!6b<2XdWukrH8?~>|ZhNRX1k5y{2`rlh2viko`iuaSnBRipZ zsLUGPjgp;Etq|4S=_%sesqVVaJ@&byYiRy2mTXFrT$U8NG%0jlQs}Xy(CacJnzsCP zr0yBIJ{altykdnu9qI9$^o=R`&Bzo_&fUQqucW%Cc@8sgrEnnkNmOl%yf2q2=*kuJMCnia2}&;W4}~QpI(b(^z}!Vtn)~9xoKa zDXs&=|6>N%C5{Jli|+tEq5v>O{Hat3uQ+bK5UFCqO+xe%kK-Ptw-|D%5Pigq1wy2W zGuH|H&+la?Q7)1G1ZUhxus8Bj#kZFco{k(EW3cXcY&^o1FMKT z`iicUjY!iWo`Q^1{0JFETmu~c;rmvKb;c~hpPWhX4nILhBhfo?lcDi{1W?2TmFWN1 zEwFK=5PikCQIyyJv>iUUJyHHzZppX$Q7yL5COYBC^_6ve8+1+l`z+#?4<$JBOoAt6 z5v1P_mQdwip4Y_MjI=a-R=oH~^t3c5{_AamyFQK5m$96-SjqI{SPK5%a3Y11M6%+!yB&_wRtoTv>N&jh^D#x%Rl5xF7p3qB2mgsiJqMiJQG9 z`!3i@+=GLt<)iaS<~-JF38l>c797s4{U08)5>NDkivOcSxl>=5xa===Q-Ut>Gr1pS zsqDDMaV6c3w_QXjNGQ3X2BOFwJdbPrQ#w(kCLhZ>P|KEN?=}_Xku5p-1d_R@fFR+j z7|Hri6cgV#m~5bwe`y=h9|Tmy$1L+2$Ca8XbDxIMg#WI6F7@xb(@2s^q6!c2WIB~i zWl7DDOfDyJcej~k``;+191BMuQ&Og;jPjM^PU6xUNGl;_K8H4H;%?YsQ_@c9E-$UC zOjF|BWvKTx!wSh<>=YT7G{KbVPLZ)*Tt=lDUr2o|aVfdsCKYi{Y>`}Q;qH{KcE+?k zl!95)MEjT)vTG};LbgVtwM|JoJ<$THG5g3|WG%Y363T9?d-wKHSG*g;(`(UR? znHRA_(8MQz4)G(8IGIj&x|DyCHRm5!QaSCE=B<>1hsSkSx+3QBY%8JUZmFUa?m`Jw zA#tKhikY&s22w`iGOnznq}%J;!ex{7mE6s?7LakRdJQ$}DkfE z!wji~O&fUP45HuB(Gyg~0}D;MD*kj^0$+C>mG;3{g7OE>ozg*FxC|0s07;p?D(F{K zO#EZUe|ZZ~j+g~F$L{U(#zf+N4XBB$*fxFz9aF`VqbbK;R}p?a;}3EU1L=w_Qp+}97f5qjOxU2=?=8Q~hA&u8U=9-GuKPE-CeDYc%VM+eeKY`ol?%_7e zW0y%-R7Fsls>lOW1g%m`JUK6-Q_7k#%S%Z+rK`M5O%dnvit+ffs4m-iB_JW~9aLlQ zFrD?`32vWimQQDSit7***U4)DT}b`k3`teITux~oNrqPMa-U-ER^+T9SM{<5Bz`oy zhFVh@qywi;M6LhJuX3yeRu8f)Dv35$0pem@ZH9>vt;C^&+ zc}y<#vL+6rm%DSNj0x>JFV{1&J?->gg4^Sk5AAtO*=1;*po-^sy|fj33cR*s$}9C$ za&HlpeCC<7Hl`hi!XwCBSKHHhBtq!~wbXgbsn7x_x{~ff9lu<;2A~n-o zN15%ANXMQg;bsbwJ_Kt6O)TJ9#x9vi-=0_Z5}KtY%`|a)I&wWGOLaF!iYjhpZU2x* zjie;E9+mK2<|fL3y#P~=lyWLo-Et3Nj-y1}qbfOt|!WirIcg;Mk02~|PsE6rG25?xZHj4I?> zRLYwpRPH6szUXPLKX44C|MO54G^c2yb*(9%TvJLx<@K#5j=hpbYhOT_a(8;Ng_Ln+ z$$Q2%qjm|dCZ2${JH!-TX?LZY`N}lqx>~mRwHH#Uq)k{|L->X0Q-y1%inHetKKGmi zlyM8Wk8T0fpn;U{NqtQnGfxKSd_cv2_NBPpahao}+i_V2dA1aECm9;crtZlT5}85*nx|MqQIRIeC%A+sEOvK#EAlmUKEb6` zjXfQE3JLtD`IO_KbtJiYDZvCqGbrCo=!xzSl0J$xP1qJni$2BiS$iMBDEt^5+yff%s!=Dq&2Hz z4IG|9l&?l7=;M8aleVbbQ>I;_nNbyg#GXhKi(tVV;>biSb)yZ#8T$?xMGRvJ#5P0bv$g3=7i(T9^mh5x&tA|B$_ zmN0iD(d&3eb0jFO{VkV~WOt~sbzQQ6xCt#aacVb3Y#dBbj**#sI`c8ZINXjXTv+MPt+^XlJ<6!$p{kr02 zNu(W++@HsjwMg0}8FvF_Oq^-+3S3g`)`0A!X4pyMnz>8cCFP|Blen}?5|`FY;-rn{ z3HHlt>D=`*o`2V#LArV)puL1WtrEvvO)><{6;4+iE3RW{m3#R~l>YhC)6z8YFs~IC zoIo?eo>jDFxOg?CQ&``fBI%Sq+12D8Asccex*~qRinu>v?$*RG%-s&5RnQ%uw1M5N z^#6DH4I7V^zFhN5c;k6gw$|lTJ6g-BSUpn9M5YtvmrKgEt6X)+8=pPbP))O@(RmJ8 zNOPSd`}#=S|95eRAs1D2@J#d>O70N<0 z5}ClIyeUa#c8H@%l050mVVn?}aZl@(&i`hPs#wvVG)hG-4sppAs>Kw}(cTyTfVI@b zA*^W~V)}GyjfSPvR)-f-ISDr@a*jTc=f2YcWv}6#F{f!tChFL*l<-Brp%i3}eb9QU zu{QX5T;uk1@LJa?f>^~V;(64_gw$5%T4);nb(?v@U|;!@yj<05;?*@+(-`L`iF8z~ z*{If3J3CYbl~Ar^9K6$aiuuqM-6(_9a@I5DsgCjl#*|M`NUP1(BN>N~x5h+I9{YAXP{{jn+2eM4qQ^Cn zRZ6M7hmmONhF#KLwj;?%d6}kNhbd(zG+CxhJ^OWlaFW5U4SFtf?ExGSV_skZSttxGly%&x|vFranUM(D&pU_@;hO=M<)mKVBIa{9Im=-(P zR#OVnswe1OE%Q|>mDZl-u)&mmA$kh!xrXb4-cdyzT3!?MOD3ot_m_mXVl=7ZL(Hva zugMXVj($e$Awr0?>T7<^!&ZlK?e-JLE84e92O^%_T_*WsVZclddVF61m7;Ga>cV7ZC|b zMxJoq$SY@g{x5Mke@R@*^kNzFM3my%&;OI@iFxoyba}r(Tut9IGus0q5i+Pg%(tsB8^zlAlH?Nd1>K z(>I@Hz-cT==k2PfoROeNnb!s+D0A6{(Ad<(!f|wCDC5dB&qf>K42{nN#3k2`udbmS-#DA#h+_%vyN+TlV0$>TH{lPZ z68*pJ1bw{TNX5R%Tx0BE`7glL#Bu!EQ?AhHm8>f6aVVfyK}HdaPcWgmvyirn=qBZA zn+$Qyb@IY-iMaBnqmo<39+%|JGbKBn(oCjzP1$Lj8_QUU^zE^d%P-&1F6O=SZ+Udf zxL@%4s=rk>dn}1#&A=-GDKGn7;!-OJHzm7LdP+@dg-qd1%#3E-|0!LbILR|xnc6R* zNd1xWQhO!-ae1O7Qj;ZJ>a)b<83^HKnVoDCBzGB3{4}wi^-xm&)IgMVgG_l-1Aje$ zC<%x+X{{vrWt8YrV~LAbH!Vbe?M%{#g{;qVrq1VmE4|*4Z=B4RPJ7FeDRwT_ihN2V z%ccR>#QFF~cnyx;K{ zk@E7^M&i=8OI)5~OWgiqR#L|EY$7!$QD#lZwz~9cs;N9NmR~wZOHM05O%PWRd$@lu zxrXqwrzBvknd0_>hG^m`+?6{-4Q4=`OJg0Pi|^-od#RCzdsmUR-_7l2Px%wJ zE!0XH?$)V(1>;`M)fQT~F-oBX|7- zleJOmp&X}D#vbeFW85y8*c*0DpVsPqZ~i5adX21sDjuCd{g}emOwPRayyS|JU~*oP zB5P!qrM8clCY|sDc9C{>qnc96F9@i%1Dq=E1jMf|viwZ( zsBeLoDfZr0C!SN+U9=GR$s^lPqrJB+7JJkI(F?^}>PO>NfZH^8A!1z-+CcESYXIlO z@xMmYFJs%pgGh6@@Nt@7sBheUt2nIAntQ)cw7s`IEWS|p-u4n;+Q!$A&T9O-Dn)w_ zbdPq%)gJ**yY(}Xu6=&`YruC6`V5lqL4Kh2XtP5}*IsB$RR(IMw+>PUBmGIhx8GEx zq$~b20?JrzRcWK*(S|pyRx-7l7GA3C5#!I>pnRmhRk%&;5f3$P2EAnDrGRh6h}-w( zZGhk1PVm2gh1zFpb|_z{4WoXKyq-i}>Dqglsi357+@%B+=jLaW5#pn9Zz|M2?<+p- z#>Ov|aY7&ARL6;b&h@BY2yt~U740^$pE_1MZ^mHI&s{ehkp6$o7b4t$Jm767>0%PM z&m`gbbuV=ibBmc$%#>oLOlQh;rc7tb9<;_9b-wmw`9k!_Uf{D>W){oLVwrQ8at>3@ zVM;wy>X}l{lxC(hGo_g+ai+wX5@*VCrYvX5a;B_e$||O;V#<1^tY^x4rfg)&My70J z%2=)A+%?)*ZQqLZ+7{++VeS^@-olhym~snK?qSM3Ou2_CJD9SADLafbFFyBuZKpWy z%+X?}*jAQ_-gp%@HONoK{8l7*||t7j7FRtTIJ-pGpI<-JYn-{XlY;QQl|FDHKd2{-PY;!DQlKq z2S_^UQ%EO$3hAUzS$-4gCyn2rLb1w}t>@n1EK}ykj7% zjp*yN=g~6rnVzX_MyWEj-6Q_&RJ0wXpCKI@^@`T3a5(5EZGOp_sok^ib-*3rzX9$J z?+5(vI2tLv#=nk|(}=!OtL{AH^l2Ae^b_EFr|bG2W%r2Q`b=>9>xb334TAycH`ouW zsSNL)H(XEGntydG+W*2Cqx2x;Pu9Os!)IM1wrP8vS^8M*^|K~{^6u?Ez#X%T^?uyW zcWP^Inx}`Ag*TlG`0XY2`h!~Qs3zn%bL6*yTH6F+corgIB804FJ_%J!QkJ5v>sOjW3VQx!@L5bc)A zlFw=Vt~=W`R$Exs;_3&<3jt5z=k%@jEbjh#ES^v3($!&ttUDaFb|*Jh$6-$P3la|(yGsn;KNm#Kl%bkB6u z`WVkFuJwHN+%39izIxLL-BYGEOc?=u+oqE|16BI9u7N83O4mS@f1OLEU*#I8(yuuw z+AEW%0H@#LItOK2in!kD^}t{Kb*5OM1~0!F>BI|$LqFG~UL)$2`1ZRz_2`iYAUUM^ zQQ-T|BntJyIb5%1Zu5GS?L$u-W&5vZIrqqN<@1{}MKkwooPQ0d88RcBtGN8D3<~wy z3T}aVPRHXQtylzYADXgAqu+^L&uzG#^E#}Jzj0p5dS%+RwSZLS!`i@WBPsLM&&TPW zurmI?>^KF!=0nfoGhH?n@d&TalWm;80E(J+)& z7yGz`2N~{B|8?dj?^wqV>h<0|Xc>I*vy2$vNqMnXUlC@!S=j?ho3a;_4p3Bafl}3{uec0&UvUFdwt?anBh{CAiWxx6zVjWa}1pE*XB~gC@{d4t2z%*@e@7}`g7}nd3|2dukJk5~{ ze4K+ylHnkk9LEe$3LMpdlbK%3^r;Ri+jPfXz^@&DM9MRrvrv*YeI6u_b5RO|U6jI5 z7fFtAk>nWH0&t6&JC(VWjL%|x9!s9Xc#Vs4Y+`ORQ{s#-WqdhHc5<4lIL);#D(8Av zE#O9$yp|=mu;eXFxq~V9Fl7f*9%ITb#-C>VS?0dTDZj!gzs@Cj)76NS_qmz@_q!GW z9&{}R{Lr-w@Do=j;ODN@fM2^V0sPK&1>g^^jex?v8PMUr5zy`44w&k`12D~fFJM3S zLx9J*9|auj-U&F=y&G_Z`_F*m+}sy#?h7}yWPzJna*~@`aPj$c0+aoF= z=@GLa;}P>9b-nnWs6mXF0VOIki_fwbwbdH#xO^EV&=4p^olX06RTYhE*Oa z!+ORyFn%rLTNvN!p|aiLp|aiKp|b5{?k;e#PfMxlql$Du=v4}(mXkuM6{JwCNhuVo zndxmzk28HK=pJzpbdUHDbdUH1bdNaBOO(N0qD*GInDMDz;!gKctV%D{eU_K%KF?cA z?ck;Msqwx7SnsU{Z1Oe&HhY@^+q{baVG;0k8FL2i)Sl18}SNUcg(t4*}lceH8E>?@qu6yt@H+c>fCcnD=jhJH3Af z+~s{2@M-TsKv)yN7rp-me8u|};OpM+0N?cf2)NIy_VI}QUKij&Zz|x2-gLlEyvG85 z?i~d9wRb4scixeJKX^|96sZ#d9jQ5h?$koS)YQ`f(^CDgBGKw*Z)N%|EO`g#!*do6Rf^rl*D?M=Dg(wj!p>n!;uOYURI{VaKqB|l;A=gj?@xsEM9}`cS5iVEULol;gNQlw(F8$}y)8jktn7G~$+WtmPc5lVh#oSZg`f zdXBY$V{PPE*K({aeaL2R?L(I8mOeD6+{5WS!0GJZblzmjKBnwvN@p6Cd{r8iygr@q zrgXxa)2SA1=~Rn&I+b>5I*q0ckb&ky2Aa<@cW|tGIMxG9*}<_M<5)Wp%Pn@LQyrg9 zr#e2HPIY`Sol5&U$9pK+|W^iAcTZi^72VT$gCXUt2vD!FRoMSEROJ!T$m&(@Jm&&%5<=3_@&V=pkH&1v9ezmxCf~FAtwF!8rAf_SyF8JAE#pnY@SkWZC4t}-xe8K@E9K>Ue zt02F^@V6QMZo@xd_=;1?4KsY7;Rg+W1$@ePzu^nr$Pd0>q)(*(m**F`6SuQ}?!*&a z6z-h38$QLO-`Vzy>nBdebXqO$A3Zchh_l7}qldvKehr6H$KY@Y^~xQy68?6vbK+JG z&mS|{YseXX9sIk+pC*$3u-MD~-;KjozT>~B7)69c|lXeS3xlJYAIf1Ba&Hv9vIubeF94or~ktxS~ufeay@ z5|btmj3ESXPId*4BIr3jXIG8Qz7<96nhQI$zD$m%HYo^gSTYmDc8Ir%> z@Wou>?>Kof`~Nw47yDDjiFrhC8@HlC%5O9LnpPR^G<^Ejo657x@Q30}6@@1oevRRG z8vd@OlE2^ZMTZOzHT=njUt{>4hQHMepHF(bZrpzOl#f_u#5erOroUX$YYe~B@V6TN z&KY26tqc(mkd~SEc7uOR#XMD|#lx|@BuDc|Ezv06uviwlP zpKSOwCjB1CpKSOwhTm!U`wd^*E9E*LrhZsCe)0~=_wn&VpQiAa-|)p-lE3Tk(%*0RL*JF*$%bEJ_~L-14>kN6!|ycw zt%hH!DrqNc1JknMCOaHy^Bj8GS@4&_oey`R!;`iZw3~t71()VbP3sHS4{oy4kyhmF zlU58@34D&TZ`wTIXThC=a06tTfUg3+0j{s^NSgqc2Uh@BsrOH-f;$WNEEnR#)xg~a zcOP7aJ2frK-6yRLeiXhNi-BMtEL3n;gXvvF)l|TkdK_R}9Rb*`jsaY(0*GbmIKbs9 z44SxrDfeLwBEPW0qJe){E=JUE&FxlD{S172o0%G+h~^%u&u(&R1?x?p6M- ze5@Q+^3*c*9JN*rtIO0C>SgLx>K658^$ztx^|$JC>Z|Gp>UXN5d9=aWM6F1htu<-O zwC&n&wdb@CwSQ|rYPw^HW4t5Vai*ivG0QQ}QR`@Ov^wIB^BtXziyfOCcRF@Be(!kF z@wDSv$J>tY9bV@^=QQU`=UnFkXPq+AKa@!vV` z(Ep;pr+=oqT)%Ritff7u8&;<+*%=315uk=3UecAh0?>_G*UUzDr)c&bMQt9RtFWhkl?Z8V2+}h!H zGu^nS!`>RdhV&ZO5}*q zqCkuhr+_<2j1y;w3Bm`7B9Vdn#Y{0p>m*KYY0&%OjNZc+u#U1$V^q28F#czn!xNW=`w~T9{ z0bl&8b(9d_Rz(5tyNKZ8F^d8BR1tk$$1=bL9i2>H4S4N^mjD)TA<5xcR{*ZKhG2Xa zQJ#zvy#4g60ITK^+&Pip2crnyF=-QE-D%eY9=_@(z_aF%d`dmR+I6=A_W$)=fW0Sn zg>Q@}nVMrMg{4*Z0m_m~$?~B)KskKXV}LUKjzyG<!f50n$GYGJ5 z1=aD?%TECQ?#)!T!Qqj>=bl9LB@4y^FP%&Di_apul$0`ALvfvDq;Srd9N;gpCg*NG z1$d0%k?@_@o(`Fv*A@d#XH6>`L#4ILFRj9*M-nB$rT5Nc3C6(=sA4=U0e+7Zc0j?n z^#CsvUcj?NFHp_~RK+>?al?9u{Xx=SD+sFh5`^T{ZJc4YyiZs*1!(oR|No7?D?_*^ORh` znMyw3BBc;8s+c4h56T&U$EiNV8VHDAXI1@xi`5cPmH?_^sX7I)LoEY5U!4Yc znOY8bxmp2uvpN%ShE@%Dwl*8^9BnS(0&PCvx!T!)0c`N3=%3KWkx7o&m&5cnz-##B+eEcwTD({sJI=e^QG8e+f_(Z)l5v zzX_;{x3n1WzX9SqEUg{*`+&GD(3SxI08kYlX&u1-1&H@8+A`pu0IK3s?E>Kc2E@0G z+C{+s1BiChRsjD2P!$6lD*?wkRs){oSOfaWfOuW+SO@$xKvkUXxD@yqfGTbmE(4tF zxB_sV<4V9)j;p|34G0}@Tn&5;Aaua73HUld=zwE0@Jj*F`;O~@Uj~TwciaH{3PAi` zf#W9NR|2ZyH;!$*X-39op?g4yJ_W~Z)djWp0_W|^} z(gAz9j#1LYuhr2aUF-tvD~4;M#WA85us?KiG*&JT!JHp2-gAz|%Hx+_wzYmjo6Vo8f%fiqz(jpC3O(s z>#0Kk_okiz_-5)btWx%Y8xns9HzeKzHzW>%8xsEjccJ(a+=Vz>9|ZUbh1lOf}4X#W17Tn%S-`)cO z`}H0Kcx>+>fXDSd0r2?V!^D)^9XD^NJz31a?f0w7f0cCAr_NGi>TdNN^>ekKHd;GH zE7xkYn0CE(pSD|jTl-q;=NRGG;CRiE?wsJvcka}CxsG+kUH7}5a1D1iyW8E*xl=rY zJrU1V&wHM)Jt-;uQpTrTlX6qa2Pt2q4ELVmE%jD-+q^5i>%2mlh&x0DONUJHTQB+9 zA}@vfTyds{{Cshli~K@S=p_GCafrjGiDOyr3=v_vPv9OMe7_h7?Gs9g=*x0b#9M9( zmx+NaH%*M@a5+|#M6VD7dyzj=e3D9jwTN=O*a z=8%7ravl5Il)JB|@OI^bHRRu_JljJ4?aGYf$iGwBluiEKN;8M=RUYN={YuMVqCcoK zuORxfpZzbC2?L4$m6E~XZb9#8!5l`jX7|D$qS9{E2hxy)D8&p4i@rnB!RwLYt6rE( z@p`H0%gFDeetJIn>FP*M{}}Z)=AWv5$myS^rd~w+Gt{Rz>{CC*e|JMasi$zbL_I%3 z;VJ4HoKKm$?=%WeQx9_fr>@>uuy;sa_c-|0;EU zC;3;aPb?vSlREM`@;9r~g5+PXu4VrzbuXv)C-uSYM1Mv-ox{(m>pA@w)LESVOX?a< z|7Ep})8C^`WB)a^Keyvw)ni9f{5RD65#+z6Ue5WyjfsT9@2G#oZ;1)zUDY**`~&J6 z_&QZ6BehJ{)6rTijlyHK^Z4a`4P8zB2pJIwSmv^dj!nuq(ruYJhzOSIz#QMyyKGuSWF(z(B;Y4c@2YFduhy>Ta)vaG`=?Pm%HMIJ-S=Fg!TAdO}~Wr_iJ-_{647Nyp_TaYdbjnh&H2%!jEb@+5eq( z9s7@K`+Y=zLi?86>krzO8z{V6i!=Wz&BO8kq}_Be(Vx*C;&y*d+j0(tU(i;xk^hpm zlH2cP?Llt0J=!TNiT;|V@i_mh_8PbA8``o~qQ9lxT}J-fS{aAm(f+~dzN;-@eLtX$ z;dXyttKoD%&^~9me`;A=o{zK>xW7NvIywBQmc#Y`OuK>W@uN1-L+Ssdy}<3PI38m? z(j4vVI~^BaPy8W{&scv?a2#a0VUG8hexhSSjQArRgIOO(J04v`;jxbExt~vV1U6E5 zykjc+6CGdGQ#j-}p`83i$3^Ui9pky(7CJ5+Mf4WOR`w%~j$cuDk>hF3KjyfR^KWzYlUM@i0CUFC$hiV@fw$BjpK3l*EzPc zf2rfRF(h}H<34WRD;%+96u#1t%l=i4(hU^;o#PhP&&M6xZlUlK4j0A%Zc^|i{)9K)L za5)!TMSPF5fc44ioHB{Ry`0mz-TOF?UrXV1=eImAj&Y7-{qFC4?b#fZedbK(@E6V?m-{Qj|HgUy0Fpc8T(gAy z!_IHGU%q!9$K&`%=Wn{TW#={RCMr{cTwWjHQdgU!Y&cen3BW5QT$!72DxD zz2jC2hx8?!PNV)Uw?|mN=2W6D)D>=*7JU!XBYGW&7wIPki67H{)lPo9e$FxEFVQEn z-=V8aU#36A@h;GJa(Wl(6E>0D3O(ay@>lBHtH@uizrgibqrVcT@H+kP&E#LIdsuHT z(mE+$BG=Su6kg#fXhmOIbgzcdO+TzQJA3{!Q)zZl7&#Ki7A=dlJ|8 zR`*bDpWEFtxqa?*AIEm`Zue3izxTRNVf}o`U8qocFT2NbdG@#ma5-Lczm-Dtzq$(r z`ER&AT#vWhA2I!Hx3Yxj@3?miA^%p0)vdB$=+k9#g>{u7=H+5dy*-)B?&&pdk~s1RP>|rSEn?y zKRabGx8K~9wIhi>Kjrz0a4n;posu$&`~@kk_&tSE<*JnW8#IKcc)ynEdJDO|rg^{Q z_AU1w=KiSgb})UWcLeKQwRbG{*KF_i?9cV~WBPpWN9>>NeUAHUf%k3Z2fUB6{sz6r zak_Qh`P@GtZmja=S#Z!3qFAQ!4< zhxgAMUgkZ_@h|ZHj{S?gWt`6n@6#M_rT0BfZ?*RyEWgIPi_5*vTf^;rske;FcbPZL z{uSObPX9`;o8w*Ot>t=M?Y)K5+vIg{{LS8hOuydi<$3Z3??L9@OhXxLf17(zAUVCAA)+PAInE$j^4}&-uNEgsbx5HeE(F(r z`>aN|COG=f&FA4}Yav{-ND(b?t#A>zHn>G_QMedf9IhR1G29ZkrEnc^=ff?7TMl;t z+=Xx#!F39+SONEIxRr3L;8w$347UbuExuG+Cwk$Ox))BTd*f`nH_oK{;B>kVPNmau zCY^@w-O|Nx;I6`%)ke6h@l6?hSGEc6TDZ+{*TG#6w*~G7xEtYag4+tW4en;R?Ktha z1@2aHthh}Kz#Z8D+>s3scjEl(F1Wkl?!mdqy>R#8H0*x32jCurdq@nzY4{+q1LtFp z;B@S_aF4=02KPI--@`o)w-fFOxF^L>@dvnFaJ$7Y@kh9);GTy2lNgQ*`{CjlxM$&> z!#UgYIAwbQ?nRuly#)6coV2|R_X^JK_u$^)Rh-SghI6;q;rrg!>TgBXNfKmzXR*hWiBW zQ@DS_eFpbA+!t_P!hHqzHQYCF-@+XdKJgvgVNoLf1NS{{D}NAE#gB0R#p&QraCD-i zz^QN=oCD4Yr^C76+{$d>flGn&D)aG84Q^53dc*ZmYDAi{8u!twMPImMlr{K<<{+zg zU-6AEhC7KRq7^Q#Y!WXiigGm$N&l)X5yg%rIK5sXwmUWnrxQ2X&KBinxZgWBiGRX< z3fj%^71tSfJUCx1_p~S%do}~V2@V^twQxI2>zh4tDsPgRxkSq!tDOnOVg5N5f^U@ldoi*gU(fJ{S*CRLSZZwP$KB zr81!|9*I&+;N`(+W60kgs%{E};s$L>C|=fDA6hDd+yWV*wmnuIYOT)@MG~1FS{!ON z+b7d(pQ57T0)JkX70%1?XOw2=SmE4~!VENs70%1a$jtWptgtU5BR4O9~W0fqWinIg*rD60~bB9&U!s5bGe|AQekyVyJ@k?WKL$zhCu{b0{(EvKZ zU**pxRvn-}6mAZ;HUgU)Ze?oN7mu`*ETuZv2J045gvwwv*b;(%lr~3#@vc%b(QY!c zBD0}W5S|s0KFV^Gq^rX$oFvpG<(@>k6>;{@r(=j1L$9DH$Cq80i>8o66a&6O zvwgYQ`MyLrqcG2(mtSDaA8(KVM%V074~K26%`dE!i5<*`I)7Di(isa zR9IpQ`-?J4GIL5SzQ42}C#R@1OO*S{vc#ldUG3?)7}vmJ9j$fKBfm_6mSkIDe`z6_Fy9InlDv}Kj2xr5IcDiH{H5sCEGt}G zkd>d4p9uT1OZ>iqL^wYqD?6_!Cr3=p%0nbMWPnK(X9OxvEU7|SxmlTozWf|9iAIr+ z`iYtc1XIUKa|%jw3aoHOK~Z*5UZE96Bj@L4`*K7zMr)+CA>8PzuMd;82{u~;Ef%P( zHma6mHb_oVgX9tcLh%Ngd6EBe zn`dT~my}gPIzPWCCo4POOt>gJzqqIvVY5++gYjT;qgb-prCE7UL@S(?>Cg4$W=ZptN{W({#bOx!&FzihRymEwN+Z!yvfVN6 z*~|j7^f?8&1^M~eR=BXFpr{}-*9zwpmS(|lS>eK>Vhn*qI4d(ZD+7bpOfRpbIHRP{ zZ-q1S3bRUbimfolO+M_J74~KN3krORa7IoM)V;Jo*04a#jyDvDb^=B}7W%SsGV(GC z%`loM)0dZRg$ql_G!+(NUbJY)FgLr%XN60${aM*rek+_;ke^jjR9YzJR2IsN!HR~< zgRQ|iZTOXpP`tg3kV)l{`u66~=~mwUqQabvET0+9DaP>ZaRVllOh zX}(z%<(0lFUwO%_lB%pgS#h9j#vI>tFpoY5_+}JWRdkc;QWsN*SKwF)w$_CZr45r) zC<b6+K!!4oNt>L&;;0z40f}G3}v%;lV#1!V_Sz)ZNOLBAa zt#D~hVL?_#L5V1@m|a~WW>*&bW|fG!zBwhLY)09vGSuxXOE?EcKR-L8)Jz<+Ge$@v zoP{-0VM!ufoSmJQo0DnD7eSW_V9m|=C8fETvGS~NUUpV~erY1?^W~Lb4^b+piXA1b zu$Z9M($+ ztEUT^IHn8ix(Lbz{&bl5hK5jdIxWVgiz?sTK&8Jt(LFP)?wKK~J7V!r%Y?EDF?H6g zN>LkL)E=y#DT>%HEo;WIeWs`?nNeI)#aUHxR)8|ADkG~Z$_f%Qs$os4Bkghv*#=Wm zR#DxhUR{S0Nx)8%3eQH@&JS3MOnN-MqM|b3ub4e!mhe@~qRN!bsGL2kxMWV5zhrt@ z^(@iC&D|7@v;>VI2BMt7xF-@smP`cWazM@&1~NzZiszA843y9NnKDl=n=yNyC@!g< zRaG(Hj6Yv2rUi5$AR0sQKvOX0Yi?@_ikLlsF7~yxHFwOS9d29-kaVyskz#ct201~@ zGP5wgu=5H{py@`GK@G#f=J5F;4jQu%ZQtewqdX2f0?4=uo4-&L`?ZqAq1JdsLj&@F za&nxol|+ogh%2{SSg-|*g&V1wzz%|aL!dld7pMva>zT%j1v|H7sjseq_JA|mTl_E# zjge?L6srtH&BbFWdJQ5y#sR&owIOmek)&jQMi3_iq1eyJ6vx`oPFR;V9#yhDxRg_? zXg~x$dHDG_iKJ)1N)~nSSw@e=L3=jh5Fiva8z$Jbf#kI8!*agkd}Ehe*TQXx(+BoZ z*C->>N}KIl8k0M|Kyj!p($a>bgqX=nY_5?a8bQXcL$|fjTpdMIquNX@fo1Dp49XCw zms-&v5fmU0!(IR*9}9de9wgaPr|f!IhiF}hvOs#i*2d>ak z;t3wgXb9oprWv~z8g|v8It=O1LK8>1a8flr)Y=$tA|4J^uvuOlB0icxiq{D$00s-q z5wa{4m=X=PHNkIztBf>vv^7Oqff`2HPtyl)ykirlgjz9lk$NOnu{acshU-IRm67`L z_85q@p18wN-l;cD<+!wgO^= z1%kYYs~62QT+u(ojKl!Lt(XbI^=0)u6V!%V35~&`8)$uKaTtrJdeMNA!b77cDJT>V z#U=*BCBelQlAq+K`SGXW<)XhW{dU6v?9+qTI4Rw#kO3^WZUt;GZsPwXwqT75 zo{3mJ1;q@MEJer1z^-j4Xz^q;GI3M^3`8*5XeNSGAIU&0%Pf;lVrsA@Wz3o#0a{iy zM?yztR|cVyM`o9|<2WxlZJL;;U^1E$jE3oex(gS@KQb3{6GgZ8k}Df$`$;-J@6$jQm1ge()FwA+8j*3e(-!ZCleuB4R)pqw|82)KJPc_4O~W!Qzv z^;^Bbmekn&%Iz_ApxHSlg(AnBsv;3g7iP+|$t$DN;1(>;+S}UrY6x}UIYqS4%p$6A zPl;PeVO~#KotxO4%DR@tx>CYP6xjfF87y}r(HRkFh`G(J7fkat(|E&HDnT!cO%K;b zaZzO_He!+sEH-AjK$sUxy!auU*B*g3EO97A^D>TY2yNpzpD;}C3TvLyG;@Ntr2X=jTnygfJdaz+B-%!I8c4vm^?z%@#kT1m+hj2dMLp)Zna%_-c z#Ck{-rzBmBT6ZB>xq2M@cWX>Mp$Nr`a7NY>6Jww5L%T)7j1j`tIMj%IyK`ghwefIV zu4cM(xec0|i}+NK7ChZWoB5fwmr_(fA=l)jdHxNwD^hlxlYg zoCCGHZ8*K7+}-5&T&WUb}QL-*UXbL%c8H}Oa2PsN%?&M2X1 z#gZPAFsoS^!KGqPdB}Qn&zp6xJGr(aj;pwC^=*qr8tEjb8^@aG?T0S7=EUfaU`Ny{ z$94nTfGRxAfM)qxYQr%)gOH~*6*#VgZK6IZ>X;P`w#I0gSJB{a3d3$xqJv9XrCr3) zq6jy(;^3#KnQ$C(lGhTT)waM(6P`@0Rp;zhvMA>KBHxS1WioF#X+;VvTpq$&w_Y&G zn8i>MgUWWh8+8hqT#kh&Clq;MsG~N5O)Xn*lzs`}a+QD=4@)8l!AQ~EZwAq9a~8K1 z^17bRTe-y=f^{J(Zu#QM=8k||R{LQ%>JnV6+W1zd1ZVGcn5@vwW-Mbt8;e?Jdq)+T z-5NEH0DBNK%a+K1?Vp)j41-ag1xEuo1q%hjRzX5sFdG38ZK0|V^bxhBsYGf&4xl2@ z>NX-a74wmW*<-XKKvN%q$mb6RuL8DL9+{w-*%EUGSS@GQTNIkIk|{WTu(-g{;32Gf zNf9k_kLq0>#)@WXPvS5sJ&~31x?P8d|Wf`o9bf#%m z9aRa`n*mNyb^uaH&=DaKpx!WNHyj?~fDHREqals58=Yb5imjDxGb1kx>|tX=VkJfy z@kBu#Mjc`W=7xd`t3tR?3K6R{LeMX7y1Up5L8me$xb2QsG<0PJlT8b)b4ta{HyMEdF-n$Z7svQ;PK1^Qvg-pz0F6#t6VpiJus{mQM(jpL?J%8$qL!So zikljbw^hRiwqtc)kAaXJr0%BsCqD0_qf1&%(0L)7K@QZ%;)wuK$6Y$pd7RnSAhL(- z1Y@y27rQ%|EP7>$8IToVx-2@Q_CqfwERM3r<)o5PdEv8K#Y*rjh=a6+5fUS<#e&}heRy2sGU?#hRps+=3#QGTi64s49QC}hF-XZ*SyVG zNX1ZrHsmOIQ|P25(&BGxx55@)XCu;%POj)JIf~bXR2s7rsHKf{q^RI4krLX^m?UD` z*P5&aNd&Xt0Xi(krHEPdKs&b%$_^84+I+krf_*S11HN8D`6}w#o7-bNIThjN08I*Y zpgjq;{Rm)Q$L8O{L8)t{lgm2T8Z7%l^{nB-*yqXsZGW*Lq@8erATd)hF)Db8AgOfX zky!CpwEC6=7ltqq!_d;cUld`+Yc+6c6FO?C#tKTRD(zsKWe^%HU$x3$u&#C%&ECd# zP2$x{=Q)$QT9 z5s&5(GNl~Eye|=1syX3UnDQ)+w3AnceF`4LOy%7SU;i7ZWdm?xN~cUN zzCocVO)_&*9RsEE`V#vvx#+QlNz}Ga!*LuoPiP0L&Rv!Ze36-0*~lOz!e&nfakfBn zH?Hnv7!$086A#uQLBJVrA|O`^LAor({RhzlGGIs!>Gqz+ zeX@XY9lE3kmR#ui3|7$aDMd3GYe`QFeUnH=v1F;W*u`Z5b7*y7;<(giTChFdgifSa z@mSn3iQf^;h~Sw32jdZ(j_|;wmjg_)>X+yRrk9X5FeZmhwXrQudZ=UTcFcWPtR!*C zfEwh4xiSd+U>;LQ57CP8ZjN3L8!S>fx(^YJ&5>H1?a&Q3hwFJ6&8X1=j6-dy*o-%r zNDsBd@ixm)`gI0QT4uxLm_gRHbj;1tICKp)GX^!IjTPf<{M3_CJSU>oH}KZ_c*H>I z{uyU)R1>Q-q*LMJ}Icjg9v8e)Qo+(?O@cnwlhBLkkRddSZlB?))a|Lj(I5x zmEp}stgUouj~s4ZOjuO4GF);c3t3kT=K1YW1k6jcBc@I8x#Hx zr)x7L1}kcMM5#n$99iVZH+1$wC4YuuSCS)_>}XO}{n5z3NDkd44QSE@TyhZ&<(`tT z6Y`R*s}$c!V)!40n|uw|B>|`yii3tXpAGe#5RbHKoHNq6KZ=Oi7CniWJmXHUdqL!c zV=GJ7{vaKYEq62_m<9BNY`N)V;xPXiCGVD?)R-gPB_n@fDPqBh=X;iI4bc5wfX>NUX?DQUKq8F@CGJ|7hsjCuct(;aPe~Fr<|G-2 z869Xuz{LwEfpd>KTKOR-Dwsr!$wG$ejPl8lyv^c81FigNn<4`^TQMIQ`HfR(URBV# zpI)C;8+%RO&nGyQ(MTPRSY;YGV2Q@96mOy+li=V z9gPj$k;Kd#a48)M#`rxO4u9++dEgtflajLIYf9QF8jlu^C`jjSJ&WNgOy=BHC(rXS z61c!v(bIKVG7+!ik_oU6$pj-B4s@bjIJJB`VkVEbG}sUucj8oh&Xn4aF2;VY$=D7Y zQK}SMrLtB|L+UA|k89zmnJgVM@YC`{pIL0<*aTO2I6vVCw5qlRS&1!XJAaTHGgFoe zb4r%`21BDe2c2;wI+~MPjw6xs@=)Lq4a-C*Ne!rXvA#vEVJ>^j4{TH0aPJxHIFb-F z7I$Ci!0JyvN-&6A!mcc;NSq?}Q0huL$&xE7d4gr&#>UI! zL_-Efm*ECVm0F6ob$srUpkYs9r&xI=);)$SI>Ep!8S{9Q+B2DFEx?k=$u}$X8IQo3 zs2LYIm{#A2hIG47k=XSe1x$Q7+PYKR7xOvih6 zF@u0Vg0o`yw$Cf<_ykf9di^eptu5V`W8a3-*@(2CN)TCX6G7|4Q$b(6(~$}G-^Otd zqf{Sx6)R>C3#Z;8<1B_xCa2>pFj)}cw-!s_c2VO zwvA*?T{0!(42T1Aqsu6&%sziU%ovxcxV>&6-Xpie7BLZDtQZ*W5-V!Y!y2=qfmz%h zTu=^9!O{ocX0X|%x}e46nhGkiP^M+>4HFg;Bb8ZDSW~12J6aB7DeY#IrD)tsEJ^SjrS-x9y2Z%=qT7k<;`gkCVM<#!COH>Y-OV@ zA}<9*sL{ld*kxD>;62VPbCDoo-6nNWiSs@fA%^D1M~U;w2@<9JVXATFZ-!-+s<48J z+6!fw7~a(;3Tvo^q~fp{4_u5BCP^~ZbKU5831h~QNthw)L6{`8x31VAC?_;b_Q!3|G-6pDwj&Ors>Kunx-qcg1gAt zJ$7ZYO_9dWEF0-~5cd_n)_Oy_qLmV-vw4Gn+Y^5M6Qt)q^q$mu3&fL_$!0FiA4vq- zL(&rzbHhW2Wd~p=BLxt3@%d zms?u!lP6SYsk#AEHSzuqMB`fp)SMRca^fKyQWVC*34CL@-ks6yuu9O!0GQ58%l#DIF%9;8Mn73BGZg zL}Fmo;>T#>9X4*48~su&lyHJ!KE6x{(xP2d7vXu8n2QSFdjMea27$~yS{6fyCQ9=7 zj)4&VlAPB(tU402jBWRGJwp@+4Th-vO$iyWpC?L+F*J;0ZAr5}QISE?*r~yo5lA|C zSxjG|TCZ7U$cl~g8RC}W8V&cZc+-n_3uezJjDz5N>K^Fk`2nAi^uRx|y%Bhg3=@KE z&BvFqKrydZ0*+T^?LoiQk>UYwBXG@0l8uUqUjDInScwy#>>vFdd9EngHm7{x3^H!h6OJZgJMDNTZ%K(;kzC)CMH7?}*6_%OyTjd$PB95Y>i zQ+w+|4w#o5b0bknz#}w?;B$gDDPm+H&n}pR)uJ&=*@%XYbZ3xegypX`*~F>z-Ko^n z;Z~bit@Tb+EP-K(#JB*)Juc}ZonkSKj_#}gR_u6$5tj_xM@>kdOge4mm{)9kpJt~~ z$5{e+FvEhJ`I1C>Nz-@Ya?V^xU}?b{Mm(1?--BE8nvG;%oBTiRoqvp-SDD|>y`CTU z+T&s7CRv8X4wofaoQ*R$i37Hc#RGUw``Tl98p}s z{LRPaPhMb{G7M%>EH&goj=I9Ta4k8ZggxH;tRfsQKgcD|)MY<77F^un2w6bg4O3!G zV+-7=h3J#xD13fm{rL3ag>Zb~A{Y1Oah+JX><%u^_?<+eT})y_;zCu z9;Fj%(I;(nbe?J)GzHAva}luG3DD??L(7-BG2)Rc(md!s2hv)EctgymU@S$3(kGlMAET|XisSKMM-?v+jpwTTlk3&FxI z);xRN-#+X&;j_4mD&36F{Ei@JNqJG6agCnB)>@wN(Rhc+roLPRi37@7g+!@LW13m7 zXeRe~dAcq%Es-?DHNM|q5kP|r3BgP|HYV2Q)!qc>_D=M{6>|@I4e>q2V%m;Z;iJqe zNudM6^)gLF^J_Em6u{cL23Y6K*a2#!Ph0*%3U0p$?0hCwa3mlPPAPZdN6oC*7%v4)yei zPsl687yhQV&rZGk3c*?J*qAuYEY`LU-c_Yn4oz85*=#GlJBwbSQ*%WNE22z8#iLw6 zNgLzp@V1?DBkIY9mcHUfLY>;$4iRz|8Y$eBHjsE&DzkVDz zU_cvGjK-bxiFT3kl)&8Jn4K{fB5i_Pj*$2UNfx3k(6gfuG_Sg@;8kcmGQM{85Y*(c zqK+raz*MeT`rOC7qGg|jXx=u5YInh01jj@=N^4FP$f0Y<&a@9PxEqFWA#qx^CTMWF z_6zaZc|*HZYuJG+B#4n8P|i zLBg9sB=(y_YYT_Kkse)`BN8BYTsE%~yCoZ|D${iIDYM+Si1~~dz-2G&-e9MMy&YJg z`=Yf>3B-ZfX>Gk>&a~x(U8{v~YJDwn-w95%VozmlYGvjlVRlAO9cCzuHs?Sh`c8F4 zFvBfL36YdG*_KBZaO4WgDULjMhonVZk04C|J;0g=dVr~2Z|IpzlIW6_H>=VVVn#1G zEFd?}L#AzN7~vqxLC5stAr`^g^f7_%YSa44mP`jArKLf|#YUQp=Mqoj{uWVk@5JI_ z7L+k8hEO`Q#GKt_=_@R6V*)J9_KY6a2h8%b^H^!cVq!y<^rG@0EB;V%BT)R;?h556 z|IAL$B^F~-tC2w$%CmDi8Qexriqz2PNgfK(TE{e$i7MSAtY>@t87OV>X8>D`L?1GM zyVem>A7?Agrac^X&>y=8wl^KUQ`kC~g2xf@1gCist#qiDyY)IQBYOv+MABF?7l9Ro zsCYk#+>>3=;QD|tCWUwoqvtYhn1ty=tB(h9uykqX%wnLUS7wj97#wuJC%Mn*J_a1$ zL}h1dcB?jBl}M{^SH(ve4n_Qoc2wJezCTn*sH+K?7rNsTDpy0M7c&@F=i32rA2<&R zLzfk;K1<1?p3FQtS->Gd-6ztG8TR(EX|TxdzP-E)Y|2JSjETJEvr=VKwoGy`pls2S z7)DfGc7czXp13_Kbi#5C#Yg*1{t4grD4GEPsoFKejsj;Zc(QT(&$nozjho zjMm%WJ&Nxgo*#k9GwqQ$4{W5P#=V-DP86MC`(k=w*^VUV@d_Jz>YR8d^XSU#M#Ezw zw%ZPL0hHCHGqKM8v(hL;owNG@?Xbp1V?#BWT4d$07*YYO$S5=e#=P2~hnxY8>nQ5N zVq!r?s1c;uh0Bf%*u06cw|#&qgoZwh*_N{_9+C&R@eV~Sa z(ydLp{3@QhcK2Q5b3i5BEX^!lz2gwG+}i3L7Z%nU>yj5OR2wrZ6S_owly4-o&G{Sa zDhGM(Wbbf?zazxO; z5!=D?S(ihKwzG)DAw&*rg$E(fdEP6lQ^)9c=Bu+zGr?B69OMdT=a(7s9Gh0vrY_lw zC;L@kLB`fAVNg(tX2iJPIpggMV5$33EzhuJCAng|-p!*sSuajasl z2DZz4F`@(rx7n&lg`-Puecp7-wnPmRFxP^YD|SUM*^8bs+q~!zpns`Vq+3J~(o~ic zL6qx>#iXqRc-N10ofxsSGx7X>F}AJdPNG2YN)S_8oWmQ@kHyfUR|Gtl!k7oRB5w0l zhv|WW6_dPKiJ~?3VSkb`8554q8ZsM2kCtrM-Lk8PaZk`= zMS?T+DT7qjC|nt83p;I`(=Uu*7L7$nI(88wKzb70qyf)}DV~MW%Q%&bD@ixc#$2>B z8_wxqKyG(d%H8D{Sq(zyZ!_FuN`@}rW3nitfu-*kbxxg;M6vMnSQUdxaJ|I188joq2?r)u=vq zwM+8&6-#wEqYGT>{)gI%;w|#zc|ON9JHKksiM5Ly&|ZckB*=;f__{>Q2p*t49_8Uc zjpQz%-_4opjBD01&}C&=q-7EIi*&(xU8WgWVm`jC_c4=-jhka97k>5dikpzqe0BxmS>-Hf-j5ToaB}6PAuo<@@2n>x%_~Z$*~(%jre^5a znsFA`on0cEq&Bh`rWwIZuX*UW=HV^dp_BNfj1uz2Zrszzgal^C|9zJ7FoJ z5O{dXv5oa7_;d`%-##0>65dVs>&46bWP1U{F`L|oE3&&u*HgpXQk8)@BRzxFUsW{gF)dB0HMN{T9U!yo z7JdZ1Fr&z?O2Uisdy|Zff(MbFTO{m8H`=qcC7d$mJfo?+J)Gkr1z(0BOFL&1amuDc zz9A=8JlLv9;ojY;2UL<;KMl|HZiq6WdE0uwI6D{6;7BfRMkak478fNvZjrF0i;C9HVjYXC;>fpR7 zMVy$t$sxG;ImuccQTfuji1qF8Rjj9J)?uJnGS(~XqMX(gZnCdfj{73dW!F&#cSu60St7ptfj9VxGu0%*~yB)cwNC!mIxndC@`3U`09>b`^;yRshJa{#Aoh{hVVqH?0$f(9FQtZN$tJ5FF;5%4zE0U+@KgzzP+vJej zzv3ctYg-c;Abz;z%o+!#u@@7N==Cx*KfEEvC+^H8)O-Uyg5B;=+*Hq_QlNb_3gHs7 zt6jmpz+~QzQ)WXGAiVrYeMhp(xNE6Tvn8qD$<~#^?l2b~varsM7O!O(Hr9`$Z6G~H&zC~kX9GzL^5Mz9e7ELop58;W{Y|W~Ep%lQ9-%K(P=V66x<}0nI zSBn6Rg${ruer@%+7;o0akmP~6j2ppg^B1Da>JZ(~4GoE&<@w9eh45t`Bk+uq(}<6c zmLXB1t6sB`>@cYB1hBkKT4CSA4*Az1Isy!NZKqfriR~a8FKI(uyWuh0MvN8@GOGK9 zy(v1%l{c9+gdU{Dfi(GR;-KfIiE9gHlk7 zPO{C$_9Q3a*E#u5G&LOyJ;|G;x;wvR1D+4Ff5RmLI}XFe9n#Hh2XXt|9*Ls@*v(oC zew{4L*&REzQCo(#Q+OOL6B&nw8*0Xqb_cI(56ReFI-NjLl4QgxsT0+pax}w!MAKJY z=4IqBtwA)g`ZTFo#1N(Q#f5A%RSp;)=dnOSjuMsW$SuIMqJ09QOG_{!Nk_!^>@wgA zC9>XJx!N<#h_P29jWxz~cWz3d1@sen*^1i%ignJa=jI0&qoULZq03CH4TEkdD=rY{ z%Y7W;NWu0YSd~}+MT@1`86^YF<_PnNIB7ESD9&i&LRg2J+7o(k79C{+B3q#Z^ry3~ z#m&b;7Q5GIU`orwEwz(r4$La;WlJ9Is70P{5;9#prYb$M$>KmvR$y10s1bJKm|dYL z!CbK4COj&Kw+Z#s6x|uFW@Y$xD+y^wWi^BQ0^T~gqhm6zlArytLpp>;22Wx> zQ42vWm{D_2RL*d&Jcn_+%akE}VHi^9YL9>K3Prd~0ZMzBf%4P;5B-bJUvm8M(j!eYXpLQiIT{2_LvADB~Xh%`+co=P&x`1zL zN%zTe;$+CBJIqt;4LcF{4))t*F-F{?@#}XH<^!}1v>`nvI-?lFMVhp4CjqUqK;OQY zh-=d*X(n+guUAJj&o_$}QI`2dA=9QRj>QSEYQl<9N_3J;vrpdBOiyMrM|X)*mEv5% z{56Y~BnPLaW$|VcU(?9+d^GwvIXx9r1 zEZXd{1IrIG;9b)K4x)pbiI5|rji`+lEU_Eo_W5uMOOp9Akf@qY!xnCi61Rp{(Dfr{#q8u(M6R zwj(;Viqpm)?{y>%`8oI!j-n|Fk4$D4)P+7tUpauYk!}8jfINl{RI(8`pF?6Z+W=BJ z06(b#hQR0l5WT(>#n&XDn;cb4D5GMRL4Asv2l67fDd$zIEQ147-nvfVh zS-5UM3vkZd8!tf|UYN#2J}SdmD_I9`8=UvKvaqzVlo+D}saFCd#+U_68CAnWIWG{u z0I5T|*4U#BNlPrwwahCtKbI&x80Hi8kwQVi=fuX$V<{RU-n77{qUQ>>nHxGh+72Kq znPQDeFMI`C|;${ z*7XkM2G7*emEK{CE0<+{Yx-(;Fm0QPmZ*zm=|Dx%&!OLG)vXAN4e&oxFfptgp-(-`!YBKB{4%P;mmOk41?q7p9PiElu<)B!RNl#gSFqw{ZpnpHKTX zjke1#iP1WHuqCsdtFdI-h)tC&ZtWP%1G~D?)gvrd&)9dt3aD36X|wBLh9TZOmRZnE zNoZpHrt2KXl?%{<)2%d6Jx&2Mo?h2+}e zv@vXNB+GM7o?)PXJnR{}F;0pg&bIC|?=tc)=7P{?GdDViVAjh#c4|VVW>v{ z)gAO_20^E!M4}k`po;NocNH=!*k5!KMq_AcY=_)C@CqO@LxbvHlfz35*%GC!NnM)t z@Z{82iX1p+BU0B^8)m18k>nm-Vx>KHrOx>4DM@}NEA(t83vR?FEflOqMQ@=kwl4>IM=!NiUrY+{`?Bf8W>XBTE4H<($tj3|kUj2y(Rh6tN76O*38d!`YNe3XV5$PT*} zs%oPI6ERYRCm}M%fpn5aGai7=(&e1BGbAmXBttU@vtSK5KRw%U^;zbdZmc=JGRTUV zkIB430zH)Z$%^yfb`sZ`gpqRYO<0;7o=icSZlPh9DWY?`UOI-#Kd#JTOm09$*COj~ z@V7K&l2OzLCeGD;h%m!bcEAW!v?rwz?SNOP*6Ae|3~euN3J-TdWsWlQPwT9}C49!KDLBMn{;q^XIHby<2*;3N|;6@@WcX-C1_u7y5Ajsz{;9uqMDhqv%MJ` zd%a*T;Y=;cARbobI0s@BkV&EvE`4vrvb9OL;Afu@S${1L^9>5~$8Cu`OzRrdNZOmo zfki-e@+FI+^Dgdz$l5BEX}@)_KqBAWYZ8ol501zpvL<+!+ z(j)Id+;P*GTpNo#tT2FLByuX6nY&I*q~TRI2N59M{$>DJtt+8^?U(B^P4F zqTp6(EP3V8IOb2qz-F!EP&&! z;m{*cCu!X}v4*im7GC=xoP8HQ^BDPOByF1a6i<`PYVtsjxd%M!=#-5R)2};M0Y_0X zgER@8znpHt(8hGPpHJQ+xyUZoS=)oOOo(SZ#XPwyoi<^%wr^@j1tQ&!Y@&>^Ys2x3 z`f1Do5Yn(?GT0I`9~<7b6@A8zz@QmN(u-~vC!@z%PV}^m$Rt8{hGm<*!do7gc6vLZ z&ciyPZOG3v@old-5c2HRDGhJP#Ny@Ys||ER+Mk(N*QrcaP2n^n9ZPN~%bD=knHcRA zI|fmlV?M-@m5k_#P4$xzl++7Mhqi-@LQM?0;Cb7yawd`}TSV_V+itk`VmF+91-?(j zRQf2F&t!mxQev!m`(m9gU$L$`wZK`_Xg4(15f;X_s39rkgO+K&t7~GrP9R0EIDm}S zMeo^_=s3Cj=)&TnZ^0E(N_WQYZ-lIv+0xh*Ca6lut^PiYttw7lM2BtkYaU0E;7lMh zEq&)-28xh)X)PZ|?A}aYE_e%9&UDl6;(QuovGmM)$k#uapW%pwK=d{u7avbq7={6R z2S5&4B$3{19LBah$~~9n=hqFPKBfc06|w35*}Lz;(D{1O9TcRR@yT2RJ#G_$Hn1== zbV2dkhM&&Ym;yh$;uT|=44T{=EQ(G)42>vcvf1`IYUgy43z0~IDH92`to*GG8hdsZ zMn`H~zby8??&nm02k5oAOflZZ2QezN4Q0w zM>yT)Q*?(|e0QRRUp+o=@mLQ;7oBDoV;=Q@Oa79+X2ot@-F+8ZZMuvwiGFus*4^AT zbhn9Yra;wBy?R3ORdLsz&H$8l>@5~7)xUrq);E+2`dVk%%R@+r*K;)!*+Thby09B2 z?Px#UkNpo=mA%Lw)e{)A$Q|YD|a)_NabEnoY?xOqMv5;<1p#oZ2c33ByFwsG0 z4Lx{f8mmUC0wjdyDGEd}jLFem$Kr5tpB-dzC%G4=^3(HtM#@8s+-EK8tO`iSr?upi zoOabklqA38z0fn1rHYR4r&uN;iYX;PIawUdDi@JpE!iA& zy`ro}u{enJtXhT90#$cA$A!gg z*Yz$k*oWqqHxM&)$S_yI%SOaw8&7n>T@8QrI>A@{6Ahd$?3ITH+V>lJ1;-*fV)0db zO&Ta$CGA10I^kguP&OrzPpTjTYWGa*HeVGVTDibFj+Wa?yJuRS33QR6@~{V?nYbJp zmvd25TXj;GI#swhJI`({N?rTHwYxGl5hZ$fu+f5I*#UQtMmL!Pvqz$|2#Hg~cF|l| zvd-TwiseKF)X#-D7tOAjX*#`lJ8kbyW>4@2y=B#OM{VQE{DMa(t66-OouHqo;5o|i|w2m*9q*T7+WEU2CCZ+0L zPeW1ht5V*MX}jbw9WNuY`>&F#l`=U4$l9AVN2H)*CqqN&-H~WmA?X+VBh$Yim3Y3GMCN^aN|8 zSwgtGb!r7U@hZkPr9g}4QUaV@H(uDg)sl^qDsjrqE;mR+l6!nU<{pFCvSKeZ;u1$E z9j@AVo6NtOngjV7dpu>MNpoHzpy`bBqlk(@-L?($^L3NU+sEKw{kEya^tlDxP$`e| zy8zDYoA$oio}XK!^o^yZtEuaJ>D$mD)C7_W%q1xVY(nFMy8pxZK}R9V?&zA!k=k*}U*xM^$f%ok8!SJhc4A>>jSCCn-0X9GjH%uI z;@6QuB`Av^34_>|?wqA9;8F3)h~*P+rr75!$dF{Owf zZZPZkW~$LQ_qUsKf&1CXI6-o7MhBi^+aoEscEZm;LA3EC)K$_V<}%3igx~mJG_Ou^ zu!S(qijk}`M`~|I)!^zJra3)Y9xB;qn`!refovp_0>Eq~(Z2lViI#<|8)9l>aF48eYUp9=^7PvLloqaqL7H<( z+uum`;k$ZYlp#Bp3#?2%eKuzil*9Sl-D-*ymzQY+$i9feVZo(UGMH@0{>jNT_f2ax zF?H#(l&@TXCwv30^Rck;u@G)O6xPB*mvNFl-D%x3zlYys{i9xTlH7Q!FYHr z#n!hDE~b;g`sf z-(w%7y@$gG!d(p94?@YJO{L2*)7^@O4RUsi4WFX9?h?Y=y@rf;< zbI#Ixn)inH069Rxbt9IK(#VC3Y(n!>KYb;*tjj<3`&xJ?($j)L77X?pt8z~}4IBwq z=t~YOz;;C?kB5EXHX6}EZ5Mmhzdfgite-TD#iK+sc@GQG8bhK^jgAFSn$wDNC!^V^ z*H=!?=C{fJh&nNiqB@sP23W`!WJnC&ut-AjbjAOB~D zZeIFp8m`C0RJ54ZY<)LkcTt2sY8-NcP;oPbh$YS9sw0s~CS3-d;{Ty_Q?Fi;)W+!~ zo-R`O!q+=ej8W6CmenGfB#LMqD%mb?kk*wVEHUgf+0>0Be7UWuL*WvNG$Bf);0!uG zO>Gi;ruh}i)^w7Dp;X>6VrdA774Ku_tLf7etH#OiWztyX1TIIQDxN7)TyQR)htAF-Yl=$JL6*Ul4?r-<1n|f1|yDymL+LSauU2T1+ ziZ-O-5OE9Dlz+8%`Mn0h&$#5~XJ1dz8VaHmjnFipUK`qch2YeuA*U562zhHa7pOyF z`Eq+36_1ZVmt!O-KSUCZ|DOjnZC_Rgbu~ZH8^^vBz8cO#WQj3NFsfE<6lz`*3wo_X zVp}4+y?qP|HO>`IgP#IPiE~GqKgkfc>h#St4$~BKykn4r=&{4u2hJG(Y z>{X(sq+Btq(&#&od5c=p@PF_^6U(;MPgcTiqETITvf)OxUA?#dNgTwokeaCE)tchBdU|!}5yD-_XHl`^ zQH1nfu{syl;fC&5VaEYOrqDA5awo&XOqfzGOO`zeM2h7zu;2;&)aQ}zJY2iyDEFna z=BvXDFo{c=R=qT5XmT=lTOeHlDdN$r|#e&j=t+{F~yqnY_2F-O&1U@l8Zg9<0bsJUcTs1hY z!J-0k;ng;^Hv-eh62YvX!Jm(1iG;M$tA`A!QOA&KuUT}1NkCIUr`+1TB#X8ETfJ&4 zErYm)OHrDQ=TU%l=GjXYb}|?F`@j)_h&Y_1FERR)JUpub@a|rvI|j6VEv}*S(xcoq z(JSp)%2nUFkmRJ2QVIw83Y*gJ>y?A1(_SshB_C4uEL-^(2Z@*o=KZMAEVxshd`OF7 zghr~Bgu9|vGzoV;k4gMfZDzC8}%bIn?KBXLV#<&mmbsKw0iK-<6O+y8me=k_wXJI``rCxiCluv~4<|)Fl zjX?ivQ55{q;b+8%?+oi%264AyoQ-vrQ8;W+Q`mOOTXO;OHC z)^3wxgf}SdJZlRryoBM$dcYp(0Xqj%S59e8Y|lk)I}1jNF%B_aL>m&rd&cgn3-!Of zm_t6-_e|gF??vjbWsTzlyS*FpCiYtA{3Tq|wBGQ)9}Uh!H4XkHM95?>d?X&Ksa+$! z&UnoiwQ5CLJ4+N+tW_rCfBVyAbqJDQ4sU0WN$gjFn!KIPlE^p-v|XR*3_eL?qD@~% zYwFfAQ~78kK+I7)HkvYAy(77ok4S&CKjjVs|=@qEKcoW5QB^yT>qT6dqlwfFBby$hs1NF@m=t!5CR zB`sLAlj2GU(-imKd%J$;?}L=vCpw8?ohT75rH63IH%ZgIC&br8JQHLpPANM4OHMpJ zm*U+B*8Jz&X!F*y(t*@%S2f>|OAACNYpv$;6iI8DcF&k|d(QN)wq#MYWDac?s}rT| zQHN_Y{}3H5`9t!Rbf=tPfvT3!S|r zO3K#|QoX62&JglZt&t-Z6MAm}eFM|J0{iOGw7hIA6DNO^Nf-*MrGaj#fS0 zLr|ruJ*!rpIEJY0_2IL9^H_siZA;wI>+=q%=TV~`U;mqK=FQp=U*aFNsGo` z{91j<=YGH?-=QYl=?LA&KRNj6zi8h6J4gS{@YEgudHxN57W(R?Qe{Uyl=^Xuj@t7k zJ!`j_`yNFYCPKOYrrLCAPhUOU5b*99Q2gef4%By)hK7dv>t$XCbFt8@aPdrf1w-fYftzZ#5N*f6-u@i}qgO%uSowx9sBRl={Wj%PoC@~#Q@OqUs@&B* zqpdIR94ss4XzTlBkYSDt^rHm&k#)g-I%#xsf2BHDvY49(JjLb#3%=K$_p9f|#?1Xd zMF}3@zj+Va^SA+zk8U0ZwPRyhVK1e^UIIy#y`!y{Dp}aeDeUD8^kM?N7^CwZ+%Yz~ zc`D0t^K6W>c{U~6{6GSIAOoFGpz|4ME`jDU(4_>rlz~`K8L} z=5q%Bu03D0r_;qt=Kj8(TY=cMt)bf1NTs}YNZD=m8E8*!t8UqE-DnYSfftLl)7EL#QV{gBPTJzIFiDz>bnjcn)&1cE1)_e-A z-KzIa=ykODSvjlqU``lQ0#%#^5UpNv_YJ|)gCmlB-kx8wrw~(XewF9w)(2E@r9Xtx z=0CJTpBHnq!97?RZGK^BXz!2@bq!nC>la2BEZ%Qhy}wNJOJyv1AcE{uy2e%%i|gA%v(X>56D+6Y|n@~+8~+`Mw`#@e>Zg0 z{AL8h1Bq5HrYw&R+7xBk)LJ9Fg_G7EN~~?Y6nnKb=0wmMV^A46w8qLQpBnB;xe!np z9vrMz_l|D;s9LFxZoNz^YORBy5fm_}cMbNBZv7Z1L?0qJpY{usC7a!&kf;* z(wO+^vtj|f-K;t4cw3At0s%~6duzAB(QB8EyTE(cWn5Pw5((V+baihSU$$ zl;K+QkL*3#>L-VxA@~Y4fY)5x&;Q6r%+Z>pxDUS}?6_$Z=M50rTNz$M(0pre1o;TG z#86mSBEoKPx_kHT8)#_rJGJI_>2lA%`Ap2b`OGg5?%X|k?O?5SN3As{WSHxY(Q5|^ zv3*9a*o;hxQ0Q(tzjd1;R~+ZegE6&6(kAi|lXR;}Gf6Poy3@V0S^J(g(R_A zB_97P^?s_!pN(Unn^pD2k@9fu_%3r$r_pQoi#=Sszt(!}4TFQV<1W<=591$%F@d++ zG>X(Rk8XHy=dJ=6H_deT7{hzCH649qZax)j9q=K;i>mOU7{#$T9pQOW)%QcHuP`(% z#~Yb^ysJN6Yw`a|qJRWN&Sa>MjBcK=4jPUu=Zhwq)?vEhausAQUR8hX`hN105axO<1}9l;HZ3VQIUdebK8! zGrB5r#k4u&;r!2|5I_1n9xn9CyuCgiHR;}@uaj!Q)nA6@}_=IBhlihI3!I>#j2Eu~`<23v5GNX2Gas z;}kvMZMhoynlJxZdH7D+ae#k%-bJQmMAei^+XGUHOwr4$^jsg4b?B?=@thv2R^Nag zJN4M5$B-Vo^%&OU4SI~|F{(#Rj~n#3QIDJS_ys-QsK=NdZ_?uz^?0)$d-QmV9)o&R z^w_J%&3e@Jc&i@2q{rLzxJ8d!_4s8y-mV8Ks?f*2Q0V&=J#N=yzaDq!!3JCCRVrK>zmF4`KEML#FRpp5MUE@#6masf`(K*K)h`ET24B ze$ULi-Zg#C>^<+i^S-+e&L6nCI z^8@9Z%RAn9EtG%dRC%B>zzPwet~^lg8yMZXREA9q?AkF+!T6zoEyk>+vl;o~>Aw;brpuwjN*B<9B(q4lA=cJw7bh*YtRS zs@{WRYh8~k3jDZSO+BvZq55q-sfUW$Qa!em`I z0k!tTdbP#~YwgoxfByi2!~vD~V?{Zx$3Z>r<#BC`q^`BV z2b5`)EkFTSVg9Cj>I61_Prjz5w|RKT z+zf(-_hf{dOT0x56ba_ z^1wiOpkL1K>G6F6XyNE)(4$We5Hh-Hs5vgz5RYpo)ym-YCu9)AuBwkU0{k>wTz)wag;*hA>nEqd(JW4|5; z2oS}MZXH&X9{YE&-HTrdOn8knq5gZ|mb)om9&e zbX_N0_p*F%Qqh!z8(C|*U{t!AWM)ixCH3LXMHQhB$BL>~fZ2}$l2Eu&$EDwy{^we&`7>5XzeV9p1sVMloc#)w7} zpcJB|+9l>GJxZk^mLez?k(Iv$Y;=y`(YaxJT0$-lz&$rK;Ds^!ysml+FY$rksrx5Y%WVVI7FP1#lcUUF};TE zrTVAP19Fgz?nKNX0NE7>5%$Ki(eMy3T$LAUs;ZJ>Df#}Ce7`l-+hLl8&3mi$0aJwx z4Nv5yn`xau=zDi*RpHLk=+1{ul8 z<@7_pGvexoD-@VGgrJuOP`OH>83SHU0WTB3GOPy_QLdOKxKbI0njNz*%7rpwuorgt^uB7d;6o}c1H_bSnw*Boy+rnwdtdE@k1P%D6LIv2+L0v~0pQHAeF`?jB84hd z<**vrU_K!h!s>-;iL76gL&S47LN-5;9Osi`E;%mAp~&W_CI|U!uBM>XT~MD?uK=%5 zN4bZXOQ2|&>*!`PWp*v4xaKLYDa9wLTvfx!sBc(2l2&VUm4{Vv)?;Kp=m@Pk(gxw% z;L-}Iin2DJ9NqkN@!8EMStN_(pXB%JqNWPDkppj*^CfAEnCdq_qrPOV$8)4IQW+ow z*(y@!XFSE24^2qbc(KJ3Wsgw_h-~vI#PgWlQy`jVKaDD;c0R(MhS=Disl(9grRrV{ z0iKLJE9;YVASFVcwaw3?5H{%M=Z)5QDfSQ{Rzu#*IY!`{Per7r_tUY?D4c~zh-*i6 z4CNC8y)s6=RZ*uo41?4j%vuxF5*zyTk)#42Cn<%rt+q773J zLuEA3zeJuxRDjf;MeAD(9{RrZg3;+$X-#=#=LoGaiuihb8}F}5#sPg*aERPN&C6v7 zt@Q%Ho%mpIN=gx>s@hmdF*d&*qi)`!{WR&T>*8+ZF%3~>JAXL}o9OKYWgH8vRX;B} zS}krpZ{$!0`(?#Q4&}I`Ok7lWI~cp>CF7{k`?h)u4c~W%suYa82IETn=11VLR6A_X z2b4RzPFB=&0%?kZSib>??Yi&q0twGKMnU#i*ef5Qi}cb-!2!(g7)Ul5U8+zQem3-xa+rbTH=nZH69r zQ>!(s#wtTQ5r2rL*2g%{S;!z>qEeuFhpD;M>+8GfbO7QczwZb&^tVqU-_^7L5xj_) zX$X2z7)IRmA;>@-f!mTNH1{JhUU$AkG6WDJm8&tQ?-S}E1k(uI3Jd{?qeA40n983y zNNxfQJ--umQFK@2R>$(tR;ZaG;lRtBFa}(C z7z3wt^4O<%BV)+GFnMJ&oG0BkV0jp?P!ifu)q9sF1_aOpRmRv(O=COL+{Z=@gA10o zbx^#w${dDT{U8;ss-go`O$n8e>h4gLlbqyJ9T;R=2cRbAn-Th6)6WQ74g{#fG@se4 z0zLHs6Y>H}OOYop>0O(jkbn*Jv0br^3a|*xPaAHjdj)~e+pj>VzfAGq9ocp3-rb}_ z{HKj-CIrIzbeN81N8)LPN2dazUP3<~LqDH`zKAHQ1iVS%PK6HdX1;8G(VPTSmHeyn zA#u1%9ufDjPd9^frl<%yf+7dZX|!pCCl&%*S#r=PBffjJ z^2itnZr=1|1v6Xbb{5K5mH^aTi@Tsw|S5|EA5??Ei((K%S$2wW^VnKF~J zqUmRJt~!F?EXVK&JZ%SqA$!vm=7&0Y6VcwpQ-3kKK_c4V5z0p0PTAi#thPpY(Mj@E zc&U3@dpzzKUBHc~~RV6C&yI=Z#bI{KhJ?^VFok2NHjdZ5-9 ztpA%YK!)`EXNFlMS8>A8a77>$00>F!63{ZeW)#I@+I@YN%e4{D?0H^f_GJ$nBfr{X zEOb~C_Z!`s z3bPsy=GQ`>xz47RN?g*W5C`tsrIv#Hdq>K!C%Fz%?H`h?N=(*!Y`n2XX87!7p-n6b zFb^^MtaNq@D`Fz8<0-KQckUC{OCg$Bs@2`~C}CdPPjTvv)|;5_Z1d}_I!aS#pedkS z*^M-zGdXj@wcd0G^FB+CJNLjUvLv)dX%;2i!0C3w{7bvx`K4NYw6$u9n$J+e>Oj4` zTWfp@Wjl&kyo|7#gYfE0_&|t&7D)mUaHzlej3gsos*v70;~Qbd;txxzQ2_d`I;so6 zwTZFX5iP@a)h2kBYluJtEHQPgW`I<<>{V4BwTWSmT&hh_q^jSk3cM@B8kXW-zP&t567L!|*=~AGomsVA2$lM6QbQC$@AOwun zCeSfiG7<7qDsSt&*KF%c@%@$fMs_v+_l@{|Hohg{QtPc3=_^BO^P3E02e$5y&1>CH zDc(Gs6SyEi(V%%9h5y zQg~a8fBXGrNO^Wf_L2{+U{)S`s`Kxio0>X!Pw$9_r`M<3Bfg76jBIwt9FJT%<7PPL z*KRjs;D=`PF=tklr7)Jo(FZ^DHTT<7=$TS@_|PH-a#Kq%@~Ya`7aH}Ina0W@=3>X| zlk1H-hGQ?S%r)wl`H(_=X1>0${KWJE#!wgM>kG^E^-J^h3me)7X{(i6$h`f2nc*`d z@oM6`*r&Ot6b=;d=keHq5f-x#1Vj|>uV28f;yM_7cPYH{x~cpt09|Q+xfI^oGp(5( zjdq|z_?1$)wGiz{`lR7iRjxhi=~8&8Eh=_DuTKd3>vVR#gIGY}g%uk3KqgKn#1x3hla%KR)}tLYBjN5StZg}bsL(r)JTcC#do718dLAsi`% z2ij2M)+ViBdTBDsqWrElk2vCg$l%;hB*1^i1DYp*l9gi3h$4FJc`ZDbCYK$#*ZwE706DwkRY~ccqgYU z3O%N9Z2sz_T^QWf2Vqjjva`$4+1q1$*#*X!KD;dTY%eCPu^JQDXSt8l(|p70cJpKj zjSGrX7OG7CmOY&Ku~j`M&7+&2NYBn}^XQmcq!P z)02-*oIdgn-gViuwJGsUozQwR>>9$cQh4xsbAoklnhG$qjKTRiy0UE^*i#B`>?+1N zeF*hZcyk+}wvW3wi=?VMb}?(ez-(z2`|0O6QNCgvzCZHFWC(99g*{nS3Yo#d*n^+O z*2^!J!c8fL(N8^6lUFqbMN7iSqguxXNEl_hu4|kOs6FBy7G$#D<4o^DXM-PwGI79Q)oT zhyGT7=b!(ZPT0$(UTY^N<{ogxJI?O|9JP8f?+e^Ig>^2R;VjxYF3Zb;$Rw9h zAI2{WBS&%T@0)%9?JsP#)i)l zs?%@{P73CiUoArTYTrvVs6?G~IZMa+F80XjIg6vScK+`m7rMYf*sn8pS^UFx;mc3p zpNYQI1GFfFhPl-cDm*@%%Ht~&7#VS5sol^-iMCryL#<^%`X3Q}Bsd}HF4i^bqB&AGvTr~__B#(83a35 z>eZ`PuU_3XN36ZxaxBYA@bBOMwygVcO`Ge=4KX~9#uO56uaOPPfU0vRSi1g71S=Qkl zj`h;KN4^&4_PjNJFx#=nvKm>-at-$2>wyP>Z^yN)=?dG1Z$SwE`u9BH;N{P;R$M7j z<^Pr&c?HGaAK#X>juHU6FNh!}|2>qoI>8-X^{n-i!(PrmXj$pD@HGBI+x#ON&boL5 z{`4hLxumQS-*GgOoH`QL!ZSc9Z&G$C=ooI>-wLX0Bs}Z9fQZ7Y1mNDa^~9pV$1SM%Z+P0btolDR_`L) zS{)^@2fT%Lv1AXNxzL_jvIl>G+!xxjfTr&(P#@6kS9JjO1Fcv&4QMvd*v;<*ngeA0 z_|M6O_FRD;zjd#L&OoL#;FTl~L?FE9Y<~%CIMr;3Z+&eyJe!=5vaSzy2QK(2RLis7 z0D-x67)Stj35!Kb%(=wWN1=$ zCtCI;O*R~Vv3C?8V1WN@L9-UyB6l8HH5d<>%w&myq z%O%31l;X<0#_#o5{$Akyy(Nuu(z_^WIf#j3KouJvc?1u5s8 z{4sXbE|l8j;<-jJVBM+8KSu@7)8QXXF(hpit0{%fLRZ=pq?p!%L@6ATB2_3QDy?Kq z0rVycy~$j{q?`#L!j6(Ar%XDj^lr?^#Nwx%R7OglOKJI!DVEmS&Jt;$6~__&QLsbE zs&u-b@mB2_B?5@x1idOT(Ph^<51ApsoFh7hLGh|-+n4rLPJwt8h(vpcw6=%9LdnYwZp)8t|1W|j%U~%7zvJ0z!2LWD`XAUltW1sb}hIA)n-b~ z?noiimYv2aH{&Q98pjQeB5G+5+u1;QZNF6yjx|U*)uuT93^MlIitUyRQAh1p!7qR> zjo2naVOs~b)3MGb9m2|)y49M?_H;OEBK@PuE~X?B(-0M1dx#WNLj;(RMvCVI@-3R& zMQ4)MQ{+iMiTyi9GH}$%x~WsN1yoWAQMO_!rBJanqhuGa`vE3?s4y_{-r!i$bUKqU zUFpr|W;UPeznV~OH04v$x#X?AE^2;jZz|{ZWc=eKES*dBWDV%$(mC&xN>5KFmsP%d zJ9C|tdAZI~E?t3zoL52NrIGl@OHv7SBs5uEO{=6`E`B%J_ICAjN!B70mFZcO57R0# zm4+nE%G8)=G{|&7OAauaW4fRvyW76V&vZgdb~ZZWs_^>!6ChpbHPvalBZDgyO5`Pd zAd-yw6lr|Yu&~|Uz`$CQNsL&n7|CcNMir61+A|SU=hR!ee5(G`R!t1nNQvj$reg(g z{0x*;#{>`^6Mgd(w#CF~gle`8S!|1vm?}yFRX8Esi2WK&py9Q(WI8527VX@Bjc|bb zTEmIs(=B_HFsBh=XiP$U zs<7;z(WG)_iwYIoqVnWKDo?hkNQj|AD~maJoq!^$a|BRM=_I_;=Y^@pcPA42Zi|?N z7-H+1311)6!U{*U-q51;)I?fOwP;C*p>=*tArRm`cxZfYVzTeGl1Ye3w!WF4vhcVO zXm_&l^u)wZw-QT;Nqm7Mrq04R?^+BnI5x6jM4(YFjNFSN_u|NXW8`j%+|kHoWTkS* z`}HQ&h@eChm+%z_3ztfm+9a5<(Ak_luv@-x-7pO}AB+*|xz${Fw#{J3zYK5faz2~F z3mg!^n}Gc*_}KZ{l{iDeRq}0I&DRay%(rn3pWrR>c`Ki-{bIJS1@f=svq8KxV6lnV zCX05Gvx~uIAkPloCecuKHzs>M2&FaDxB<`&Zsa>ThglPI5HB_CW=M8CNHd{{zY>6&w0Fy?4}mc zRCzoR-ZD{DwiZw=Re_>v*-)W{*`iux#-b}CzQRvTO8@gX{mkvt4;8l~hLSIVV+IWc z|5gAoXznE6Ra@u8;<1BwYx&+IQQymFg9J%yRVre)ZUb97!Dg2Aby%yt5APFrtfLn|2UrD zZa$44#(Dif^Lj?|`UC+(V-n&s+ARA?J|$9pvYF~f6H`6gN+ls}sSa+;76}B;5o5zn z!s+q`@=YMN;HTh9_l3VgrpWJ85cgej8j}>^A&C~2(OP*-Qc10Rns}wn%}ME8LyXR? zJK60PYWEUcACS0v*rVb)!Dl2z(?|1d^W7)D#U^GT-M?h@4B5(YCvSky$3b6+ zfiK3u0|J;n_rVtUB>}b1lL89$ha&gOQAojmxCwnlAliY9Zy1rtI!Dg#v9`@vWLf>L z|45YdtC9P)$o+ccJ}Ry-{#Xn=9s}Qqfp5mZw*-KnCnERTk-If=zZ1Dnii1%U1^B6{~o+r}Tezl`wd z4ufB8{G-%WLb9tX&|D4jQ7cJ5T8_gNXoY`H!jUODdsCe447ml zRgV)$J|;~o$+1plT9jML320L;be?u9iy%caQBto$dnS=ur6?iTA{C{g#b~NmZ=dS8 zDN=25DilvLuJ${DWMxX%=>(FAdSIzonix^2HzNw&R>YRN(nz(sutG-N6+o3}#Zdz4 zj<#+>6=HA^~S6RrRWc8d`TfY?1oC;(!Q z7@+`&J!6CdAeP1m1=QX;*2TC?fY2jp-7*J!08KM%s#M}nV8}MhouYw0?aj5L^-o36 z#y}*ccy3aqSn0CEuS;A;Fs$_0Nd;`5HthCT1?gVgO3HS{?6{0s(`JnxKIk4pra$ZZ zt}DRQP$7BkcX7826}uA=G_yO#&FYRSFwyOnle!AJ#s3XO=uUN~1<RW{!0>$NS4JSQJ_^1znAcq_s>!E<#BWcF1P(Z5l|@@cQXZ7gf6_4(YY3ZzeNQ0 ziU}xywfd_N=yEEo7zpSvlWt2_-&^9xN|8zenQ1@4!@Tr{vIX0Q=Sih`=l5O9zMlAlyuf{2w_YR{Ckj zIyRPT05oDpGe^JW%I7-=tUT!@x z9tXD(&${7~BpQaAl*jf(42|RXga?6(##E{B?ZzV`5&vJd<>vVdZpSw~p7L$vNN<`7 ze>y2vr#p}j{)bqNpT&c{7XY@ozm~z?UkDf)lMug1)&bS(b(Q-v!QTOT4HvmYx|LNX zVD#rz$H%=)#mxj&JkPYUSFYV~7gyO9PE+CTEzgeFZv2Cojh|2C?>EBVKM5EblMvAb zZv(cWe@tVeGhXArCnkAGlAtCekr0yv>(M`^u`vr?;};W?{8o~ngd~v=lLY(HKPIuM zI;#T%Wm&`4!sD&f@px@(i#7bHO!MULBukIo6xWxf@;br=!2sj#6U8GHr8%YVrIPqna!2{OySlI5~S1JF6O(V zlDGd5xuuK(JTUI1DtJ@@N*#>OY-2t6HB$44ZvruVDD`i$h@A zD?MmC^Vv7vPd_z8;0+~4(@7L3Z{U=`Gl2bq#D@phIsqQ!?-i$ox@GC7hWxyt{5kEN9ChcI4QP{)sW0$3MCstGr8}SKfJ+SKi5~CI+%)_P@PyQQ%CuV=k@M zsXBAY9kac%-RJ3;#I|lA9pjRmJ!{QD8Q{766Um;&zhVBtrZvJp%$$XBwP@XzJ@^>k zH3ud54)G65VqxAb`hKf?0Qv6^AN!EavN>LPcQW2}Amf!Q!p+VDS+6`_(|hH1{dVF#F6>w%oykspchpTd2aAcB2^LDk4zVQO@5!P#lr|4{_So3TyKds$#%{*7yHnU}pG3&>5;a zEM5A$0is!R#WD6_5E0R~nG;Dgb_VQxiT%wLc(N(+q=GXfw%GDd6PK}ID=y=PX4H;S zY9n1qm0~V6IaYuzHeCxvjn;|g2_!m& zC5lix#V9(g2!-jkNI_&1*%Cqniy@qFT;AN#7$?8zD$!G53?e}a+K z3V^h)5fj1w;wi4~OdA%0mH7921BxQuvT1E6Np}!?PHy*{((XA`JlKdgh}(;eTS39( z&)*f6D=B|>aXL!<*GUT*D)?6rkr`|FUgW=JXr{eoXjXWK!hOyb{{R^A59Gt7uHaut z+`&_sdKG-OJuw9nkh3DB=w!k|T`aml_N@j7P6-Zzgx5HhX6qjeh{Vf&Dt&8L#yU87E&`+lfpMCBz7YEtn@!bsdXbA%)@3Zp^{->n`bR?E^X~Sd9f0hWZlX zUjm1Acqc{Ct+2-2iP5`S(Iq5`{+VX2d*@8ySi|uc25TSV z*Wo}rofZ^+RWgtsNKY?mfR;KqjFeuviw@;*AU`0=FrFiX;EQI^d!2;_n5c~CMn7yZ(UUPx&Y#BI$UmM8f)n`oC-SMS7VjGQtQFis zO5rb-kA(WW3UrNB*swr7a8*hAJ4r>&7sUGndX2>VxIiCkdcP-GeO0({BK2WL1=6zyDhD z9rl+CA0LqHmI<^AHl}eQgn|KZjn~BxN4!KHQlFN!(aK+AbrIHacjKHmMq_8y?cQO; ze>r*8ur0J2!TQ(0N@>`?8b?+)q|KSRo=|ISj7j)Qg$07kjdPp0n_*r~V)<{8qAARG2KNiW{X|68!nM+h>Q!g>IdM`UyX@=Bo#A|Ol3@kM3zze4 zOY2WFQI9oo*5YKt)A^>Rq<;W1QodPsvFP4&{)@0uP5HmZsbqbuxEEl(y^_(l8Q~^q zHJIrnf_ssW5-ni_IDYC5yQ4c%L0NZn0ZwN~_qV>w9i5LyT7BP|gHq&z_UmXL<*<-= z^B*nHDxVWx1^uq_*{P}<-YsCNxWo+~RcPjtqr!0wK1725+6>Nf*0?J*lZfms0nJ)6 z9PTYpd2V{}cU0G&>F_x9cZciLb;B`nGtD!m=!xpl3S9XM89SR+p`#dGRto63JfGPO zn*KM$JoqRPrF-`L0c25OgBUnClaI>u2!a`NPLyd!DWn@%?`M&GI4DpLkmoke5;|w| zq3d(E-pCxvoOXB}s14#B6R8_MWu+t1vz3tW2CC+?UF{qqXB*EFF%`T678P_`puy7n zj-E?QU9C_6#JU)v0EpMe2nFsj^95PW?(J+_%|4G<^t*KCp6zgw>wHNivb3$U;8Fgj zN|`7uoE{E9D69&#fK;23f;)F24w8vA$%Pzrm1yfF8sjO=G+{DLnk-phxq#40pK)~y zmUA+oPP6@*IIM%7>}eJh!lr(|c?#Nx+&?U+rRHIC8w= zB*f$Y3(|#c36ax%J4_PEgT+T>=Z$0>XSZ&uM72#4<7I-5jQ95a3Pr0k+VVGtRth{${HVmZBNJSIRZ*}qJp z7({V(T@n;!Buoz%w;AR=vUZWP3mU9j_QIE|X-l3h|FgCzN$k8S6BKmr09ssDAm1be zLDTifaS_MAQlcm7SZT%62YGtp#DB8{Bru%C2` zMoHJ-8AW-QxY(79^i%rRqNf{Fw`zh+{oPT5_lVoO$kVkTNiZ>fcgBCOrts>wNlat^ zndIwVu{M_;a7>0Q5h!Dm2$bj=uWy0p0Tv8}H?+X>TVT)v*SEk66nr1tjD;<* z)&e&GMj3Bxfil91urin_eZF?PkhmDQ_I`oh2;}!kcU_jxGWi@NpTqfxOcT~4l$te= zTx|;pJpTiHYais3UHUFUQo$X-ooQXZUVb&0boc`1%uI?lr3*8I4-uiOA$ew)FMk+c zWjc!s9|7R#{hc`FV;jOqrY9MERDusk1s@Zay#S#ulUB1l&XPxqdUdv&4qqfMLRp6Q z3`LIL2X~RkkXZg&nTG}+2T>Rvrq0dTI;qHJlen8enw~h?(1Z6#Q)%1@ypy_e9xewy0eTh%-5TDNRRLcJ{z%%J+KV_cP zx0f7o7y1bcBPnaDtjj!1B!(%5sOKtCu^C?xQjhS-E@ez^)xHYYnYQa+!%efg^mW|& zqkQc2s5ounm+IIKsde%g5tsiM9-?5Wp33XPKcEwBm|$Pz8$3=}@C`m;Pj@qTVx60# zr{5%wPI}r%j9!C!rnkA@BCLk_a|^}b3E)Ckd4(xzCiph~-b9p0G=!ZgaTfMPF-%af z6`bvVhwu2|nd0iMj0p~&1he=aTBuHGtgkcYeza1JPn!EWXWqYu_IZjY=i$BN1%mK;~i#_7Hf#muNG&AzHM#xq%8mZC51 zbVMH~tBNdtMdg)x-0w8Z4ttPMJ;$Yw%3ZJ?WM7s7SgWu*()xn`;-jrs)|V3&##;6! za>(U#?tB5en{X@TFYJkk%7e|)t@o2C|~_q14|+6<9veW_(a-Ny_cn# ziK-MlPjI}S*<&4jx>28g#_Y`ft(_;7%ifGB(#xXuEt7}(jLTx_B?Tt_4(pJ zdna|1^#$s>^&xdr^l1oBuYd6Yp3{}o*FsEU)UkBCDa z3)M~5X-)Xu`mXAx>WkG)*O%Z*Uc2GecIP9b4zeJR4&9$zL>`Sj!Kt0DxC)niM92J0 zaa9`kwiz;zNR`|%iRzZt`b*;^W*0`=Si5dw|8tEi=;J*0T=dwIw}E|z{?1DUnNIl# zkQ`D=u=ue)PfD|#EOwM;q;Vx=g1v|$_N~KeAg{%er{ebp@0AZ@t!N*h(W@mceKvJlNfzXaT6)}AkE1thl3ko37k zDT4sazSV$LUS;f@&8>^oXBb$@c{qGBamv-s!8CYW*UXokO3u;YTb7%2so)5#6_aAU zSE7fq#L{{75c{Epqc+bKvvL5~;6OgQ?!G^C>8f*fwKF`O?nRAZzGV9c5o0<-(}QtQ z1;<|o{p@(5Gkk(*cG^FL024bqZl$CSjlC;cJ-b*1v3D}wll3K`??Asatx2E7$+Y7i z3W;)xGXk$NnIxl3=5&T1A};GHdj4<6c}=(Gj#zUfFPR_wlDu{o8J#UMLiaDA?6$uO z;#Jo8#HS0{(=FqHrQN4DS&D`9x(Px;wsxvyGp$&{5}27w%nt7)S-N0QtE3Z_nT4)1 zb~fIG(nOgZM$QJbYe?M9Y96WJaPSE^7laI5pNjLsj|;`{Ibml%2>MUcCz+6+f=dNx zraLJ(B_r{-keir*x&k2H8Y2_{aczuH0K|1MLIJJ=sG1)M7i^4;zM0VADDi0fT8&KQ z&#oN}t}apS+A#u3MB7Xt>oO?i9G3795BjCG#79WyvB)aTz_o2@Ca(WS`Ow7NrVi3C zFlK8uIIqn4CT(zBow$yO__Trj(zK&i1G9BRtc&+0_%!=3omT(OrtR!uhaEX6o6Dt(A23M^K32P&O)Wc# z(QzvHF^3SAK`jEe$}CNnT7%apXqmL(m+@LeL$Jw7yr?ShXrFZW_@jSqWT?&X@d@nIdyy_wjD4J!9C6)<76>~e25 z_R;pry`8a-8ZGy7+RDV-Q+(aAZ>jh=L2km9iEn!BTP{Av-X?4>@v$Xte0z(Jd$PvI z8JKc!K9@`dl;7pPt{1FUzz)!6L8Piqq09#RCDK!$*y z5s`q5URIErl7MWm)&QNsYYi|hIN1QxgVPO=3(hn^PjI#YdV_Ne&=s6&fbOc3hj_RD zL#j78AEcej=eIdN{gE4KO;XChngNWsJu}R)>FQx~qJ8G@9aJT|hNTfkr@4k!j_@g;gBHA7PK7XHPX}5gi&i>BPw+ljVFA*7t?M`fFRZS>fTZt z(*K(nvA> z;KcYeh)QP!_|Jlb2$J@y_t5SO+X@)hsAHuqOP^`6b3E(&)&l7RZ0jWF*iSg!f?Zpv zokF@XvBovT?~O?Z&mEf^a6~=SzNM@uh>JGndP zLj&hkyP`;G)he}fX1|fPMcHq|>@Z zszN#~1wgzrMkoM+1w9i(0T8#w2n9eehc+=30P&s}p#X@t3*trFsg#_?CG`Eu9xq8S zzdBF)_%|qf65_9fy($-r+`@GT%&u!vNZi;)g11=ONxVTwuwF(9CB#3VMFMMT*EK05 zZfYaZm`LJ$A+cUbNQi%6iv%Xvu4__Ayf-eH0w8XS5ek5KUyM)y#O*Oc0TAzx5ek6V z5+f7<@qrkj0EiF92n9gg5hD~}u(`wk%v^ImJq*@7SKB~AWjyvKO35VT3ULzkD{*QU zP<%QNY=*nJGGqWosvBeJ8o73+W(FZK&0>p>4I%cRtpKqvj0I^#m`csk*tU-mGZ6(K z;zbfs>fO|MD%BT7JdJR<&>?QOD>nhyU{1}BLTRrWDsFd5PW?-0e-ugsaw{`!X-G`1 zf&mugs+m+lL(6P%&_pz~xEP0=gr`zP4NnC%xZxbO!x;v(|UO< zv}XwTIP{INmlFrgWs@iEStf&{w#%TKbnO{PwRe%hKqhr-Zz9}8Fl*M|7rIOi4I4_A z*E$ye$nG*a9c!;qCPl_n-EZ+YM%57urCfm6r^q;b4?}+}NCeatA z3jPGK-V~V(=2EGfdeh-+T`imFmrYdPRwY9#k{}i>j^RJVx*w=Qx!(X5uHpU?EFMFWZjv zg%Yo+8d_6MAo?G4L$6g^6EyZOkYqlPphMBK&x`@FuRL_aHl^QG+z6D>j{jB(-n*&g z9WzCri*|rf(Xr7kikM~l?@>fwxKI$Sp^y;1j35sqkzq`8IWW&cU5}l!gNtZDwof1C zRu5&~g4Kw&e&!fY_z8%$F{b4$W6RoDWuoNPZo}vdW&QJjZJ+Lu8=BC27l6*tr=+9j zn}#}l`cV-xodh%cH|zHjzT?Mt{Vz)W=~k&_oYl7KiE;>OUg3K7W(Goba1}m%8cT*r zwhl#Yf3xH}`VKJWjMV0)e9UpAi;+}?`NEaNE6-6JvncWDEXwwoPP%5Bq9GfQL6jh9 zNP@YFvxl~&#JgPz9_Wz+C1!0Q-;ti(alDN6I7)E@(E}EHT<(;qTqD%Jf`21Ck&OAC zE$39_ynN0M>I98Xi>lQPK1R)qTe=ob6f2#!rU`8}yv4*~V-wbIC4j%i8>G-MiCVNX z8O;#Sk3zBLQ-rRMLb1G`N|o2Ns1R@x+m+%TCT;=IalKyKH8C8zf3xJr+zt( zj*mw z+0I4d&BI!{STzp4tD&d7*`k5ou+bhpITbYsj?S?-|2E>;qcn_2@82d8Q@ddHB{14n zy2mzMgpAW-vKg=6gLd9nlfNz^=P*ALx?K36kP`WNJ_@7aX21q z2(ub=0VRv{ob?b_52+e^sw+lqIm`M@%3|zhc#`ozJT^e;ym2B{GQ1jkt@QtQ+(x&@ z1{#c4qEmz)CM#!Bf2zZjh%NL>YlZbdR^iL$egi{Zi;jeI^h}+THLNuciUk;Iuf}wR zDa`(<;0l0fHP-B4S1mgoV1K;vox02_?c}|jL?y+>Kh}=5kd!EdSN&|FvWJ-$aT}tS z`v_-&)rnEE5L}C_Kh;AS&Vl6BBklw$?u zF!N6=xmTm*vDm!O4QQY2dK=aUEi^B9JJ}77=XqFB`k%CUjmzvoxdFYNSADE$6X#pm zs+Bi7=;-|*Pi&phwI8EH&cNQnKdV-Hp~2Pt%c^6SJMy}==0(H!o9)pLq2XB5s6k-& zz1mq&s0-eNmR?ybjw`8o7F#S=;+nR)>DjwIL{wSy)wY;M=^y6^n$sxY=By03IX8+| zAKxL|MJtigTFnw<>7GK37VM2lcNKxsf+J8md0o>Sldg`7AaWWQM^5CH9yDizSFyZN z%^LW6NDZ6wx)EB>?kbwP5ZlF8nzg^m9(%^(d9#cDf|#SBqJINuS+n_;`e$)Tpf7w* zU^66<0g(S4J_XMXe+k~dkp#MWku_H+7Rj5Ix4PBtqUWO{af2@Sdm=zNoKi{4U5djA zYCU4dVDeFIIH(g+_j*%HA40}GDc$54(nV;qD@rfI1A0qY*|l=%;2q-2^<&y_8@!FIu^H9uOX5uvC&uyhm^+}X|qKNg8)v0BQnG?xoZg_$CTSGMX z*a94_BYLb`iW+X|O7*DB=Dxk!_p11yPK>1>x)gU-wg zZsF4vK0t06UFK2-GP<~BMz~TDjSr(6)k15@XDfM_h1ew~hvJw-iSHeK$$IzNj!scv z+j_tC-3=Yx0?Yml!v||+n{I^9lC!O~ZwAUQSNXEkTw$qsM-{KnZKaqCNyRr;mY*n% z`0oTE&vur98F6~@Vy9g9$@_OplN+}4j{k{?kZenTQc!bw;6_~Ke*yf{HgmO$qkjIX zwhsP{%?|!n^3c6viGQHP-xS5aTH-^`V?Zz}@!ta+yth3*lZjl7x07`o@n(sCn~7hS z`0tbWx10E8Oh+%l9Zl7JY+DBxw^zcikEt!gLqhJ8SQ3Z#mx{9S2;E7sOx?_i;yDmo zHdVBda9TgD@%@rHaiy(eH016!V@lQ~-0@Yd(aC?Fg%lwcsraPPNg+GF4-aUOj}F2w zLbc+-vj1uFVRS7x#&qJFVyxV@52xeVRnjlK(y_A0>~oY${%l`n8;wwMV|@7;$?gO2 zWd`$w;Df-8S`>QUl%X4<(9cd8s+7064@?=TggN>(W#GDqF7~Ni1ZE`Owj_n#y83RW zmS^-MB&)8ZZ_~k*bd1}N$IOsRBbcF?g1;he?HDGmb)jJ^CD8+E*Q;@9U+g8_Nf_^& zs9UPtfqW3wYv56CX${E9J7!*wLn@=IBDKRH7<`Cm&9iks;wkD6gQyFsh!E>X9|0w+ zrNR9WQ{Zy~jD+`yQr;<{X0GxDDJXwY-MvnzPMM58$GIV2<_Ny-&I%N%* z`5%5Wf`GuIwdw^6$X@0f&d#T=NejQ|7U`S(h^KL0b~ zgtpIk*v$SVuC0ea1KKw8;Z;gLLeZeTVHvGUG)C)(iNS>tjJaF-P`m*wuR0g5+k`sS z?-qVpJQu{6AU+{7crc2YciJ{m>YtRjPW@Awq<)V$$@-@yK6cl!#*ynPeIsp)Rl0AGh!UQF5jnj z+Nk#XL`+n>x_yn$MqYLM8uv$Db+=5joNa|mWyFLD_P9~ua6E%)O$)WFohaE*rAtt% z>dKj!`P2b*o@?AoNOZ(f;Q)i}Wt&GVr8A26eSvVK=h2SWI!@W36kvF}=+Ij)(>po`q5Ix1f0{>?8 z+bQTPTfx`Kbi*5o!IM#BG2ZKiFAEQLkH;pp6EQDV`oZDoAwLN8WsHCX(4%9 zzSsh1MxddehyKw*p9kq68O>#tV(H1oXHZ0Dh6R|D(<0smhN>NANhPNTqT4yu9|JFFWJ6WD*r;vf-a7zc=}W68@k*cCp?ZQ z{&yt86|mZ}{m&t1sqZmpB5P>EwO8?aY;%k(U81%wMF+g`IbGpesANn{$_TR=sdM}w zx0+@3B0hR1NA#q{y8-0NSq_gR!zW1Gn(^rQV(#o8B0LE(Iqb#RdtLA7wKB8K`75dY z%xbd_FZ1|$@OhlpmyZZIu~$jBo6V6x$uCuM3m*``$jY`}AU?W9{rXRmQxqR9YH`96 ziBwmL2K?MJ=)L`|XTw3o5 z>!XC#ZhU*piC$f-?A0FB)gCfWh`dSk=`Rqe@W{N|nknntxP1z)=dd%na8H?KZ<*`;L&C-O zC9G4B(g_Hv#_z~rSDgB_(j}r%>|6v-laV|=fg*5)_Z+C$W{cupz;y6m5-i6x(*E-( zLOS>X2s!*m8@N*1zz-z?waD6CY%{v&S>+39jG_}~koSpF$JK23C={dD2K{ZY$KwS* zB6W@KkhQSdvpAjUfXo6 zhC;71JurVHpB~tIWLkP)-w`i8aLGtAJut|X*2S`3J>PD%Nk0kOEw$(ObK33*XT$YEa5o`HyXFcAM@0I=i$OZ(9MJuMy8L0DnU+E1 zi|~nCOUVES_gCVVHHqnhK!6&GXuyU2~(m4n3AP z+l-}l{EpD%$Bk~pNV9UXA6e_ruXLY2bQF#ZL=DVX_8A6y_yjC%cd=eV&zpMd(`OPG zy0t%*w06>aBDQ%YMD(LW->`g*)S>^96pB|7+<+ceo@W$+Y+{@`5f~NBt+n3~XY5|| zZ|laUYMu{o19|^i36kD2?xW5}Pvoyg-|UTjk&qhQqc`?|Kvz?_V-ErqYJU{ums;RM z68860wI|h6>>ae-((^aGeC;JlA`)m)*kI`1y%;4b5d<@FztH_l5IW0 z)SE1??@8(tOwvc0=a>E_-0Zpv$m=OZ?)K%sftm!z(O@bw0OXx0oYQgi;l%2K2wCap zaUyxm3co&ol?cwlR|sZEEjp$a<+1>?GuAF!w+N&bm#;Hx-MswB05{gRRz>c5N?R=^ z%S)+ztwm8btwnijs-<0{hlp~!T7x+Mpsor5lL3!(T0vhjO4F*KsMYnt>v)MXEcZ@m zb{wZ|wiA_9+*aD#-TAF{*PAIH!q4_d!;_tbK*=uX%_`ZPXWCx2MKk9`1apmtM5Rm& zqf!~u8b1lgnfBHgdh=qHvO1Xb*{n*heow3~{nmT$kKyO6gBBPav8*K>o%?l^jNV<> zk-51eI$wHu$8k@aI3+t@DEjlLx>AW}DI-wqc0%soSSPF9W>76Z{I&=lOb^GafcA8z zT;_o>{S7Av>$+U%h)rjBRF=!|iDbSzQONtW0$IyW>pM^rXx`qUX{G|%aQ6IWHt`E) zoar8bHUm9#>s?+!mY?-ugl5Jd%%f#Yn5$i-<3YQAoz8#c8>lmx-Au6)Hb70Wrlu&L^TE)Uf}XEw_WJ} z%cbVL#^gzTS*J;!DWXZ9X9Qm#Z&2W%R34 zp-M8{n;7pW?q9`Re}lHg9nDrxmp4aqqBWj**0QyxEeS8I*vZ=}?KXCi{?42+Kin)* z3n9Oq)n<0NW@T)yT3iZhEN(VsFG&Cdo#IaHH8tB-m9xTA2TXu&zL;Sa%}Pd z4V`{ssDb+)bTG6n16N>b8{NekvC3;=+Ynj{-yfIFv96*2I9uw-3>NnDbgaO;xR}$n z@yfGj63+}xOjz5ngr5PDam=&(6Q!+H?YkMVwdB!RK8In3^g&!I*5SeA(j1y;A4M#W zd^%1@GEPXEu8GbD`>x}JWNVzjaG6`&VDe>r02!?evEA(AwkpG^%n1L9@iEdfr&Q$Jp$;ev zQjNc~vyK&f_~Q)M?8|h#0K6bRi}5~d;K8^qPq#LHKZdti#~o|zxy+gJq{nX2_NwG! zHVd7gNIea6>_HoeHV)O?R>NsE80IUZU&WA!DX%L4;%hNN0T5r05ek5KG)5@!qFw8v zz1F`1HeO5jm%tDY9Pchp(%(}Yp4452tJ-1*oEO%;NqKu@x=Mn+F78J1^80Dhk8J-g zzH|stu&!CjNb4MAV9xK*cP-s1?o^VTke}{g-XglLHi_#_Rff7#33H{u zMtIdim{?$~HyB-%GH)bxrz>TfNBdV&ib^rOg*e^m@By4QYD94S{fIGKW6|6ja)QI~ z3?+jj#NoL0QR*gxSF7s=$BSzRCyJ8@rb`-QUtuaZ2`rbfq$#x-7t=IsZAosxJ7%7| zH)ifCpDX%c%ssZcD~`m&cHZ!Igm{vi44j}qsZmN93+zf=Or()=6IzBHrRHsAlU(aV z)+g7&L+#pVz2!gS7h36p8N1mS54s65Sj3Twi zlI~1;EXFcLPW9pwFcE?9AZ584SKEgQuu?T8kSBgbpsqhcvTD&CJrp|_%%%|9<+#BN z5ms~{Y`Np_D$#g?Q(KuPHH7$u6DMzGiHg0TADg5ek6V z8Y2`?9X*6H$Y1lq-20G?Tz!ypSLQ)h&VwJ0{N?b~7c{Wn)(4MuNlK0+{Xnp~Iau0c zkA8Z;<{sgA%Wq4N2h&>V7a-@lv8i6 zKSwfc+f$`mP6cn+QMw~J<;GLmUR`*KPPrk5S)gK?cS3uv@Z!IgGFG^jB5w4WibI>! zU0tRin#x?%Pn+Nh22Dtr8~uIuCE_tb+vGlXzy4ydKB*%i9(|m+JRBm9&(q!rsGlzu zaJG8p@Rb2`IR$F2pv=<%hNyowoQd`@L(wI^x&j~uV}t@+N|p?iEiqkM0-1X04|UDo zK1etrqV4YlU?Mi<%@~<_62{u<34!%fPlBbMm}YHXPm|MWJ?$vnNNbNC*-XLQKS)k>mE!mi?tVZY>aw8#?zBW(rE?$tX(k3|l>~3UOybj=U1xX-{wR-h zz8L+YRPBTCv&C!;UxT8LJu8Bf4=LW?N)i4Tm?sHogt`n_WE_~3{Z{?UYqGpby|xG3 z423zL-|&fEUw$XDs=bYoR>1h2u$>8O@N+~QLAg=JUlYT}qg(+tvW}G^aXuQdOmuOkFtn2Izm7kGz-X$~^%6t@puyJ8W1SMkR6j zD0~=JTt!6*J|qdu2@4H0YZ)}K=+uS0{_fvJBpL8ZTb>Wynv^lZbEJhHm_ul8sXI~0 zfKK|KAb!%U%msT(IX(sg9gfr6ZRuI1IF9tPy4OtqDNVl;TGJf!n{b9eoSy1!=1=-1 zw6_WAKi!^wpB4k6pBR#u|9ru}o0#U80bKuAQjDF$M@2-&(-(fT<>?CL=K@SfrC)zd zAd1%6mWGY2W-&gaY@9A4;jL#$&8nO4|99@{mnJ>V6t2T)bogV)HMVT_wGzBVex$vDo$Ja0$;Xk2met_Qp1A&d7fYdR!`8m)zbbQh8(;q;Z(oc3o;$>26Li8c2 z?@?-vOcDG)3->>bxwrK(P@{+D9X8HIuORO9ftm=G#5!e!be*^gPM;Z$%j5jN4(FQb@ZJP$fPuy&koY``9q=` zr#F5MHUB{Bq*!AG!5bk@6pNcA+-j37jO$8G$A##K}yFIcI_=FIC zZ3>;>7^w`NqmW}YO&k&-*G)hsv}&WEeN9lFn*z#Ulzt-7W~3<532~wcr1r>1+WUf( zbbj9U6TS0XuQo>KZ|22(2AwrBlBUpOmlF|%-=*AsM`97x<1ZNpEOW)jQ?pN zC3Y?;Yk2Y-pKM)AS610eju2qmDvMbm12X5~`h652f~V>1*!-}@AynIR&Y&DaOjhbK z_WB0B9ADFxFcI_LlB zvh;Z47ql6jBJ-+|ST;w$iDYmnbIQ*VpTO9MC>dvqo z)#1C*Qz@U}$mZQNla(^omQagoIBdh3EGNg+OqdmlD7NF-5ISm7Nv7uA5L)N&N&m&V zmVdOQk`MM)h90F=h9hAhMlp!#lj5z^8Vin*B)i1rOuSl&Sw(-f#L{8T6C@uK{&>AtmuH*_A56S6sKd8jReLU=QwwyspS zZI-d zFPbQ5$WOye6OvZm>C;Nga zBJZf?TZ_P~Z@vZwD?7QtYsKvkL*d_?fxQv1tq%DW*prB(i`?~-f!u-JWP5_8FL~oB zC3k@j$M^YK3+i{8uB(*=5f|xRFdlov&|!hiQr9y_+4A1v^#nXF=ymg z$T;27*XnOdbBju?)M)$;O1A8jaU;S%1E&b?Qz|k_1IgmyYg2%hd_RhkA+|_SSN7Ne|;04sZVnl{4D>xCcdu?-|xSniJz^# zfr&pyErWsQ>SG)R9?0n2HS%yfq?vjkt7at%>D0a?@^tBi^vKiQmdn62m5GLoiKuGl zLvKjSQwRcur98z5zLW*FnGr1InH9kow&A5b{SjVzw}I}+BYpXt2tI-Yb0b*h;scq; zBjp+JB9EBKfo$ZFZgHS9^1Pk&16`3vs&!zRdd8$a&E1tBQa@wpSM5vmw{kw~!z_-v zqw+OIPu6;kU)R`z^^nm>jlV%83b_Deb!OwQg4yOi1gTmG%4e0b%)lmVCgCZ67wOc` zOL4Mb1f_|x(Usk=VS@RgkH0{HcYPmVt*7yKL+#5Gs8xr;)2B#kA}ibaLm%U*wE5kG z)_u1dt^K}Z?wg5C^3l!9D7XG#S+zU<`LZ6^VAK)bE;=ndg)aysKe{#cNBC*TC;XG( zGK6}KI}p@RRex`8CnD883%5gKg4I*=*>L)ZwVuVp8sfMXGhl3^lJ2pd_cwq}`LiJ9 zZhia2pY9Ux*q_NxZzfi<+H}Vtktl`dK-4fL{_sNa3bzgQ-YQ;Uue0%?7JCbXy)P(x zf>rjqdThf(MtMkYy=fv3;vM@R$x_abVTY{C#l{w%AL+4`_m(~3CWbI;2ltaj$%OhF zMG5ASF|*uUc#rUPp`b3}GaZB7N|7C(IXw7xte!tebVF8Rag~KTpAor>K~SM)kK$EU z_lefb0wR?&0_d=XE!_cc(;89;ov?lJf5F~_MA`A)HV&27DVQ_79d6}LvU-I#nKR9j z{*%9&jBCb3#r4;6hQhBQ5LMTq@aY!#hZdMv*o?4S3p}+2UeN;I*#bY=0v~CCPqx5+ zw!o1^%`Dcnz)daiEiLeo7WjM%{8J0`cWn~8qy^sG0=KllZ??dfTHv-8=r3*(TiycK zw!qCT@FOko^DXe_Es%%WV^J??fy-Oq+87)YD^`0Wsx}mE5acGn;W4pxQMGM}qYj1l zw7^GO;CKuCQwz-R)?{Ht433GBGkv|?+|$yZOvEN}&8F>VZ3q7BmuMPe|0G32K84yS zoXxCV!guUnf|XCLE?yR(6Ak%9>wR0MqRBzz29Zk|(bj27>G7!|c1DY5?XhKD&m_5f zswA#|X_K_hhE3d)jTIC2^U8BxS>;AEhunDar_o^?TO4jptJ~LbBConD&6AvC&<`$? zOjhcwz`tA&Yh{P=&H#ZU3 z8|mjb7pQ;X{SqP!n|!`a_YgiKI3^kGIDQRey=LDQZ-LUV^Ed~yL~>#(C7=)Urn3)TlxT2*bs8@1~@VhYa9Qpo4C`d<8U z9k_@0kxT6gLRp^Ohs^Xh*Y_2^XYi}&`vVk%D@DT&0L+$KPx?Hh@4HBO?<2pf$j>=C z{7GCK3L9tcYUVO!kjy(%T|DtvuJaRLw{pQZy=R<#(qu0eW;Ofc0VZW14ih^Z;SaH|kx#vF{`frvx zsl7^7Avh8z+y2%=D!Ln(vNF@n-M}O5QAr=X1xg%jR9Yf$so!g$Rr{EbiXy$0NW)x> z1l5`5O{|$C>GbT8m&yrvrT&iw?>{4XDz_$LzZ~^wPwyf;VJx_tPx({iBl)eweT(F` z8SI4ocn4tTwmkbZ&lzPmUt-z0%jK6jjuL7&@JUslHtTSaoMP=3aSFyk z6Lp{9unx;&t{Sfi8iA`Ui`k`v1V-Yy7V*wl!kf8Ec{7>o?Z-h&uds4)^|d3NOW91Z z2z&`=yA-U*w^F=zcP_ooA2~QZuyAC5NxS#b&k>#;ID2<4!k#{|ClI(h6nFl}KIwsQ z%|Qu#3;BnJ$}84-qjo7nQaQ_c1nqLTe6c$>g3foD2N~oUE|Vn#u6CIa8Sn^~`IG@y z>QbmSNcmX>DU=C`n@Qi|Uqo-T4ovs2Lk8c8Ql{gaQ_FwIudkWo47K}2!mJ@3k6XJ{ zJP()op*PiHqo4*>X^!-U_tS=WkCKS~W)<*v<1xW33P&AtK28%$0?(Z>!f%Gj3l{9r zAKXUP;ItiM!rIn&%l)i7w@TGUYzDbMJSq2)C!L*cYuX& zmPB5&`v!Ml{+fe?<%9Wm_$&CglsK!};tBjp{vAp@ava&Ie2_bE(#TwQU=*1Q)JGEK z-HH>M^32k#3I4tbzS*TYrT)@fnut_Y)*PQOw*zOV%pnRET)b*eN*{_aA?b2vz$*=8 zN&{Ic;PUvCOr0m1`5~5vsh2v9gU2uGy<+y}^O(jOsF%)mYA50j*@fU_+);^StR=@O zC2{eTBIhplHJ+KL%ruvjqrkpXNzF)BX1oH%@J|YE?-(?oSKi$Oq zSd0|FKf@sZK=SIT&PJy29o5y&#_x)Iw6n3l1NTs8BXj4D>I!G$Z1FF3Hm1Z~g8V(N^s zi?vzX)JY&KD{9wE%Vvwx32p##^vQ|o9ZvP7+Ku?L{1l8U2RgigJ#^(UxCvN)pqqzX zUPzUkXhkyC38DYCS4RKl$@Fgl5xjGI`hpkw@@B;!(7EfOZ%&_iw!e;I@B>mjuLi&) zO4B)M`dYxO{Caa|f}08cS}7@p=v-Vo{2^9H)s3fz1!0z66;tS>FFXZg@Gi3KS}Ge> znc!AneO82J({~HZ6{=$J9)VH?u3)d==5_kk-b`Z}O^G|$?Ha0EPFKua%3yUZz4v zQN{lZWjIyJaGP-GE&Tw6;C9vf#K#rx;C6szsDM@I?M!h`J3B>Lx=mTSn`P-TWr-2R z-Y!#?7J~OUCzPcN$;V~M25*9~cA@98PF|lK%5_GCfkiFNF;be&oX6vyUQ{0c^%MsM zFn1Qtv_78e@Zg{RX_t=so-=Q3%Gu?U!QG&`aT5ueM(O7=P&^U_hB3G25mH3WA%}9$ zOa<>p98&Enb$-W7MaysQ2s72^+aHhM{pE-c21Vp;EjYrA6D99U$oodk`_t`t|GGVI zQB}$NUff^{`I^g@ADF;FyOf-C@C0#VrkHSikht;X%dgWSCP+xufNVx*%U)-7i+qGH z8Kk^j`MN`r#)f)IzCJ{ZU{D3KC>6{DXbGle`yy?Gw+|CPF;QTTj^|Pv&!eh%+Nr6J z&H9V@+k2hv<|uz>P0inwb!J$j&L*-piG}B2j$C7$yBTyM}$kZz? zkyD;gj}Qn(k9D)@TW}Np>!=U0MUrk4?oFgKTE&6fJBiFO)ZnAISz4=-TeGChM##Km zB7K{k=18p3G$$E+j96@gio%&Euy*}Xd1tq)JdjLeZnHD3s7#sonR3lqH)>M5KHJnq z;bT8+;yZ*7?Su#Bn<1hT+$HQVD9#e@rrzF}p1bBC7oO>LWj>*2^XW|VUCfJcWn8So z>Hg@4Y-OMj{-LSDOCKas)YJd9g{1?GvD;c$8bgdtER7pr`O+x4*Fv2ap{BP`Ya&#k zg*rIxf1KiUWd;Tp=Q9J1Mbk{2c>K+M73Fp|NivL;?gPI)L@%y3 z1G1k-vF1-5YmLT=x+znYbwj|_lcYV0}Z2^M4gi#*XT&|i4ek?{dCG3`8%FxH++zLF z_ks>yw!S}>w7)^-?md~{vn1GD&1CGnUXK`VzRdS1jOtWEUVqw1Z1eh4=%|pXh~rw! zPV4Y5BnC!O_>{>Q18?e%rH;l-#AWwb0Y|>|NV`5y7<>+2{4lPqS3B6Y={Aww;PZsK ztw#cXlt(9Ai^mA9cPU+uTl@NMU8e1QxJVk6B7>*aB;yN^G6$n0A@#Xib#iho@)_xJ zx6063RdY!fLxL~1qKmMikp4D`wFiiDt%Sv7Mdr)?MWNl`LBQgYg$>5JTBpu88FJbau|mkp@4-7jFMG4*BsAp}AyPcu zOp)W)3jHrjNUFiqp~*sy%P*3>j63sv(WRJNLLO^RalWjmPm}ReP`RXGYj}KGZzf!| zj9)0q)EdG|F4a1m-X^55x;*A~tIVoYkZ$lWc~yoR%x<^>^%XoXQ~0ek^GWHL1^wrA z_C}d-$%A4~!b5bJ;zZ8EnZp#1kn5)4lknb|UM3@=bTsJys_?cU?O#EVJUhen^x?gD z-Y#sVWqZ@xYIE zDL48XgISv)oirA+_Q)?Cb?kChhyLzNKkvMp?pW5X=y*%o-YY6-Sz7zjJ82=Q;Ons8 z$)k_{qX1X}#tC$y)%Ed75`OEd*DbH%Oo)4{tl~zKfpmcfd3I-F!r>CH!4E(2!j72? z^yRz_A3)d<$zT(rtMKcZUFGf^qZ9d&fSml6=~r@en4*;W3t8{WEPDpIXUqgD%gb3h zeex(~Sx4RWa{j#GEr?&YAM}=W^SN+09L7>3*V|A8ra|8bY}(mss{m z1kLpE8)Uoeg9@A6RGez-m$9m5O^_S$zo}wS%g_@EZr`@D_x56SdUHKfOD-RLi{wtO zw@q|qCaf*NzkEtF-&%h7zho+#lAlA?schVL_2xx83Daljm%jx<9l8NG))NRpp zYvD}ZecVdcT0^{Au{!BrOd-oNohU$aRa=k@wu+cwCFL&uoQ(fFtTUi5e|e2IocG^I zVjSjr;*1)H*}nMd#%UObf(UZVIF}jca^t+oIP6MF6gCIT!(3Kk)2{6QKeW9GfLv9T zH(q(K>ebqvu5@=*cPHs;0^K~Ss;jqv>98d1s|X0v1xPmuOVUanR3S5_s2DeN5HkiD zXNCb}90whB+(w=G7+2guXV_#Dg5oyrh>8ws_TE%Da=3u>0QU2h=)`!lBu9dK)CW(ooPLp&*Xmp`i7lAP&)? zpo>C5ocn}=t_cNkPEtJ0a`&LJtU3&NQ%&smM7goRuLrZ;)Q$fM;aCC!+g9R0TL~mu zx~dN*zbV{BufC}FFuhP*OLC9UGh*dz_g%$0C9QGP1+?jkwX`NyuTNu*s0|z}SkrEYg+xeNO$BY?W7V*yHm-3)hi*Gj% zV$pF}R=(%x7q>+7RAo{21JDS$Co%+@v7CU;fqYXXJ9hBo!CJ@tJHVO;Gby$eD2Vum zTGraJh75fah3K6+7P|mHLZ4I^omy!PK@1}9V+g=F_&7f0H3_j6D~5w?UI7k;T=GV$ zpxc5X?h_0;&e`!KzC3H4Dp$gBqwZ6L4PbZsJ8*zD{W~I&jo$cc^eaW@M$Wq1QeCEz z;&H{u$&{4U%%a)ecac!b_jH@@du_h&xA}h1=KEor??-LE|7!F7xXt&IHs4R%d_QaR z{k+Zh3;HhT^UnuWZX3=QF1u|ftESZqehH-JFhOVr{Cdv-a^wA`87zQ6)Cqyc=~qm= z<$ac3S%0ktyYHY3h~66pfggd0lrwn307Hjtyw6!k&A>o-cNA*MJa{Aq&3^*yDab|x`@;A4tI!haZG{>Wh7eqflV zS$1s?Q88~yY0B4HHV%ON0kq-n1o=ee<37uze_{E@tZ>IuzV^j+^^-_^jSigFOp-0d`i?iqIm5sH(5??h*o za^h2McRMYNCx-!FB;GLc!vqexXxu1+v~k;M-j3D>aZh36* z=HiP*A85p67Bu2si*PiU1bJ(NXm~+{aSlP8e?*)-^1)SR%qM5pvwk**2!6L_cjnr@4{NzUFcK^fr%g;r-;m@GQHIClE|J`Q}kbpm^X>aZhf0)VmHe*Cuvw zWXL>*Uo^9(IKYY%e1JFrU}*(I92lEfW^WW(a}=k?_A2xNm%ie~_Gl)=o+1(#IKs&% zNlP36NP(HXH~=`>#}EeqC5_M%GkK4G_y`J&ARupR};9mbu}&KOPw5ry}+ zIAt_6 zNkMALdn`!7ayyyiw*csj4mY3+=38Y40aia4RO_8^9XRIDlpr_C#JW3)G(@Bqz*g@_ zU|J0dNBLs?N~P#1PT?vk8A zp|DtKT|b@2m7fKoO1)vi82KP@W24JEsr_D$UfP^`%r~b%G+zH&&rfHxc_hEl&SZT8 z(VM7oKx6gOQG(_=@i^+47EfFx+Oj(nsMq>3R0<>jMG*;LrLrFB*B0mf0f{QW>jHA7 zR6{?Df?tCjPzw(OybpnHD~Alwm%v$_G~oj|Y^kUz@y z1ufZbtOY%zUDgndZQoAouO=a;DAR^{I7}WbmjTu8riIa9h$4A^LOIcMZUcu)E81|~ zD*-b}t}xuOy+8PT!Jb+oa+no~d<13XJb>-2uW}eJlfx2>#&-ma;zm0*yfQZ5`HgyC z#Hpqf-Xcpm)4xD*O$PZO12zrsc|R8w>FF;4Gb-JC+S+H~*&1!Ho2}2JxVP+4t*!QN)0oYntZWwE7&7Qc%CY73fS$q- z{6kfWZFCvmSKwdi3Y2CGiHSsUjQ>*Kw~W45_&$riQ+%II-+JE{rLXM!V)PB7iiHRm zfjF)pq-$F!S$6=i-EvppQ!d%wMf@IYdsTdUD+B7FrfLZD%gAhZ6i(Mcl6#;HHtfLD z-`a+t8s2yNIra=KFBFqf&L85N!ax3z53j;M%oQUTm;yMwVtewpfc**opvC4{ZJeiz zMjnFyqd>R4H-T0dOHqQ-PWcF|`dJ7_9y|NEcLM4(UFyaA?*!vAW(A)}FzzTsZs_V$ z@~T<}cb^)(XgpKO#N87i)Y4;Toau9rV?quutUnn}gNUPl?2V@(Vkn4mf|`hYDW1r? z>@sykl1_S>+Sr6X0;%z*(T*S?oJYwX%>S#fB#f}&OnItwx$jHRH{<(~^quDWQuH0= z`*3hHRjT;D41Gh1u&@Y1NA0tLu;fyEPKT8vn!a@4YYiWKDd3CJzxPXBDj1Fsy0mOK z+>pVP~9jRqD;uMODpZap3Z+F)saa?b31oOZRNZ}Zq2A_oZ#cebfxrlMrLNxdU z6=k3)VZb?+IGp*40|1DE0>q(ftJ6@YALCeHyX%n`tYqa;tlUJYl(p5JujaiwF%v+T zJk5oRTKQO2YQV#_4XM$`N@$-X;$(>FcDtzQVvjfSamxHO^!^dsYOuuaQIdsHQpUyS zXG*eKoHC52NV+XYZFqw(eNv`k0akSwwpjAZ?(ujpSv!$LQ`@?f`V-H(9@Sv+I&r?ora@wl*B8g1tkOVJxhtoXC zHW}@O8MR_#o7%(G5jZiC8+$Y%rjxijOUM`gO*r*F1aGHBkW{wXo>9KgXVUhpUvhQc zX-}nVJt&9XuPG*I60&RS9*zS0dX5c|NICtWh zz(1_T{}q{9gU!$J(>ve%BENc7N}HT$BN}?)g-B$gtF{B>Q6bvMHXAK>MGL91bnOn7 z^IFIfMz|}mq;R)_0u=fGFxA-o&41%JdVllN{9>&jxw^jq&F)#Gl{S;T?AP)4VzfQL zyiAwT_!JUs^2f3p%uI8%8+XyoAHe^eZvMdi8M^s{vjZIao1axU2~w);#uw=35AaE_ zQf)U_2rY@-AorAocH?fk`2&2iQF&=MFsX_*`2+Zu>E@5!_&k2GBcqO8TXlL#ph9w% zVC4L8_qAXvRsJmHz-$h;qVkSNJf2LM$lLA>bWLKen!v`9fR9j*34%a>1yQ9h6$b#M zRsiAvfOHE$8~~7R0f^%q&A!kz%Qv!F_+oGIVpOn5ksmlGH9@ThSv3TaiOk|vNB3e- z$dmj07E6goSl09q7Tat57t{tP93~n~r zd>rFnD_0|N3bGYtaMS&B3fH!MDe~r@C6&zi3M< z`eiJQT^MCgL2#wZayjGSiHDbJ7>*DQy027sx`-1(Y5C#z}l0yNLyk?NoC)wu<2uC*0u%sDa zcF#h>=v+xnA)V`x&io8Q*j#$It&PQdx4i^M)QHH*9lwRqy*#{8isBJ$tSpL~Eca4G z8-@8P<)e3-1h?w-0p$NF$)8d?I6G?R=D|9)xNc{K!#ld5c4+xm!Vdun!dJnc3HevU z4HrELQ;U!$2{dLjR@6 zSBReJj^%k9kh=y!cA;x?p)fzYl`p`i+=GdYb0{=6bdrg9u6_t0($%u^*^?D7K%0~< zP21C;X_5$L?vx6j9AS1P#Drz&or!QG_Z0UwuL z?Y7=+v&Dxk>3g>w#5MrM$qTc1y+&q#*UGSAU#Z=)iQl4|O-$$(2M!ZU!yHF9B!I$Z zmYvI2L0}?t=i?X&7O&LIR4`mAmo6%u%ZgU ziV-z}e_T|SdU8ckGZcqD4{uS_48__Wj8oQijHfqBzX>sP6{utJ3Bcu_U0$JDiVD6F z41$#vr9S9H7_*4Rt>1qA)>a)CxeE5YKchgjQe2}1K2Ss>BYz5A7Z%xXx!d8HDb`GL zU!iE|!`%h~fFW~4EAk}h_%|sh`Bnca0#lfe-G^^^lj&d_@0;)rV6@)^8q9jubQ)7Q zmg0RI!8+zjU(>s-1*N3eqbK{l#njP0I(v`OSh(vgj7~yTrF{`PJU^*)?SWRVj=KYS zao#$N7Ql{|9YHA$z=+=@R6Yuu(N*FG3IwZsSdn!o>;GX{J!pThEmS_l@%|OzH@+LH z*S!3?|A^ZRuqE$(4DkA)NV3SMxY4WOX>98BV1gv)Ozfd$e+!k8@aYn(FeOt3TNssc zR*$W!tv_Dw{9$#~w!p1>-*+I{?Y8$MK56}o*xnxaGV1HF;?1vuGgd-s-Ac;Tq*%KS zDJbJ1^PHg+-(#0ow@=a7rfMG`qNfc@rZUoR*>bJ~q+`ruFJRV6u$ZAQ1T!lYoW~yC zXV^uRq=i!*9OcmOW0aRMFl`1tU4CF(t0CNd3z0a-qs)JZymwtNRdlzgV#-{(0$ms# z;YMkuLXOUe{BUWWvYjGb*)i52NWp2sLPn+*tRIel{Il>#&Q7c<(JT%_hGndhcy&rj zO74~5!YZKl0JtT~Az1+}z4%G{Mml4ar!7pkyqBX)woJ#awV*yUfXin~3FDme&SkMb zt25%}J!Elj^PBw0Jlm>&0rTJH*9E|-a1-_uQ5qm|=M4S$z4U^y;$0sfs~!` z%vMs64!4p74)KuKTtABg01{BV1$_qx-=U9Vpv1${53}gf4R3^qVi7KMvP&hQ((j4` zfdB9T;s8L%We(y%-aUEuR#Hc2RGp{;_M_C<+$H@B$uswf*>-7JRJ2C*QxJ_ud6<%S z7Cbr3N0<+$yP2MjSUGO%D~QSIFCxjLS4J78(PKf9Sw1SxUR>d~iRa%~`&^xrX5p&@ zzO&&Q3w+1Jho!T@JRH7>z;`HoxBqSsU*5ePPSb|-;N`yuytF;)*A_F-$<0g7xpWVX z+=6{G=nY_N4^2QMq3pwEpJCsDXiGQI_%tZ?EPP?+CEA#Wjqm{TaA1a+m)H;U<^tv= z4uoBXSB0**5W6sM{tmQa6y1HBMxc>RLEa&|{|RPO+~rGQ7RLvuo4SkC=t485tF-%o zY@?&(Hy6`s0^-ukG~~-P#4#{*-np0!U@s;a`2kq{Y1A1uRCD#U;7!V@??RL3ljw)T z`#|%4q=DD0@SC449})Nd0ihX7nrS|t!(YCB1Q6j!wmeUN4o& z_qs^)yGnWg!~p9$lH3L$xh;T7V`gb8Te4Wa+^5lr%wOfxxGd5QUQ;iVPc4r~ zq9c1^BiN7NAjCax3>;{7kys8*9o0?hpwD+dvq%VSVK~K(^z92r!hpjS|mcLH?QZN)*4TgIs zgZbAyNUPs#6@sUSBxDy=Tdnj)40J1Hf%^9md_MqQ_F%YiWu?%sC|2W9lG=D2zy7Fz zNQ`qF;(Q))p`Z{yoyUT7y!Qgfk=4rUWVHgWGJVznb1VMPYA7}f zwQK7*3PC@Yb$<*Mxs~FKMy$%Z=O)mFz6hFsE&A4`Am*CP18Y_-5Me1?SCuXeS?+g1 z5E#TyDsERg5tUfZj35JoMX5$piu$XQ_r&cf>}xBUpVMB83aDkdVNT*DO!Iam@8b0w zu`fO`7_n`S+_F8~zv{RT;790(&L;8&&<~X$w3FjeJBM;#!&ujHu@o)Gz!On%sT_3G zu18?)7Gmgf%9Pug8Gy>n)F&Rmc4cimQ%%R05Fp!q90ZKSWfB2pA`6w%MKYZo8Jbeln15~k-J$R4T59@;=O3}Scos>K0*U+ z8BmKm7R+oUqMd_=#5)!AP`DY}e!z^s;u+kmUK)XIaUUUh)J>_AdSGaHwz`ihd}y;e zRsJ1$>jaZ_+DsaLT}X2Vk-93T`C9D(2g6LlTqiEGEW=>YLloEy8CM)LiN-_a|eyUm*`|1>wl2F%RS0~c7ud#950akDK(`RS$vjD1c z5S8Hx7zk;eKY4mU`3qKMQaPe zv3;J(a8GC3vzt$XPh4V$yf91LLnt~-3B_0ZoY#w^wRZTi!TW{z`u?97|If_#=jQuU zelI}vFaXuV4?0Z&GkHmeyDCfoS~mt@SZhjrB8Y?aHU>t1N!tChz$uSlhx(6j)PI0# zfXEOC4ck{?3WU#(0BkatWs)z`62}M3qXD>IKmgd&wO#Hhx=$udB%uC|af=$4ukz8p zEEd_s%ETYcd-pOwF_vn^EI8HnH%X^iXAO^LMnb#K&EGOcV1d~DJ-zwnfAiy3k^i0) z_O{%Y!?kw+`RSbFWs$`BSOUR|&Cs0t0?H+{s6OrhX<`3YKFJ>^Z0`(6!f03R2!Is6 z(I`TMCfj={!j)X^fb5$@L*wO<#I_KPG;B09;>e)<9RR;bW6K<^okoGT(b%G^L7HWP znwy`%qDVNos>MmmeHZ6Fe^74!37>$sTO*09mF~w6fWV~^;+fp6U&fqwE^86}b{Xt^ z0@(W)h#4BY)NGdJ0df~Dl;+` zp3X$^$GF$F;(9OB3ci6AEbxs|!EF`ag8P|}lWZ>(Nm-JC@otlCLnwvQ|uFbGA1AFMWRge@O`I=C+6KO z7)?tFi+7=#)LV2Lx^VC|@)giUVSS*rC%?E%ws#)8C1|834|RN6=Eqi=q#shs)vinp zTnpHppGO>}WS&tmq})R;=|JPSJ<_l^SB74N=PEG=TEri8wWQvD=oa?^7Af$0R7 zax+#=#P9^>Z{fB_?Y`2oe%b9@w;C~Hwlk+Qle0&(sX<*#V17&2- z1oj}kXNXZ=9dl||fmB$LJ&Gk>?Wr!aXCXNysE?h^?1?1o+3DM$hxBuyA>j0lA%JYs z?OoO#ibSf%<-G;* zV&y*EW|5l5triY0_WO0HSiJKu$BQn!bAUbEjoT1Ejce=TxVFyRU4RrPe2T9^OkAh@ zCZt(e<(EE#(!Up_4;R*tKz;w8dUxfLKg_ty>1?C$UVIFacDF9jD-^O|#b54{ylJhX@ zL%aIRyxRhJWk}&%+SHdxqW@jqr^hw0HK=xK;Zz zSh_*$3DJb^6u;U>+ur#gfOZW;gKkljIJjUL108bzs^L{9*6>o(QhW3 zmIY5Y)-|Lv+u_bbB(oWX!oEVFoeJ%go!Rc?+2v+$_RQ>Z*>=TEImUrY#0O`~=VVj8 z^OfE#taT|TcoKTqseb2~O(JL_o3y3Rs~lRJ4uX={e9dQ5SN2xWxUTGl0oz-Gu=07v zdi14g2#ikfsUR>3bCy)H&@-_TNsQ%c2c3pH-)>wD!+H1h7;Tk}ux*~h0MCv8zeczM zr;DiGWMRYjMYu_~az5-WD(4`f%1e`l-pVP-LVxA>WMO6H7^GX7P8KSawaLO{Wegcs zipj#-%5bu9bY&=6IHrQEH&k-9@za;iy6f|r^&JTJ>4B;pv3fx8_m_@U*f?7*<8IMe zl(v3`9}yJnC(4i{x)K;aLX0K2V9VYi>g42?`YGDpHK?X+48Yq$z&QYufOVYdBW9x{ zsSD-BfVd$99*PXHeBHQmDvCZYUHj)yAPjKOGuj^ep(~l*%YyV$qutr$=<;j=QDy_M z4!^QXnoVPk_t!xPY)?Ae-#~6O<{0; zCfkLfDZ2-ka$|cw^+YyX49;tHpbvWo`mWpXPbR?dPg)JGl7iEM^t4V3(ic80D1NNR zVUH)FAaqqtB?`*_S-g&PC*&sq{qd~&T7_QDj z_?|6RgA~&LGWwxs;skit=t)+CG>@Wkxa1K{K?x%)f!_DP5ipwLmF{iEbMySq@&2BP zbhq{jxhy;yuqVu(*y|(9-{J2=^he%!t2Pg8iBh<7pkzONww#x0oNMwH~H;5XelP2osJ_MS$z6z~{7q4=n?j$J0 zi4Fdm0=NV4*q#fyO*t_!slW-pI=s*e*S~?nxrYM7FaPmN=JlcpFO}k_7SL;|2k){5 z6ovACt{t1_@E#624O1jsa4B(d#di?}2*rMuv0I(Y{$fXS-eHV7IlP*()54q%k~}ye4188j)=+N z)UYGVkI}WT4dFV(^YBB1xwz3hA zB!}Fbnm^zNTn>-&%kj>DdvpNn%@!lR4Hn>$5Aw5w3u}dDADzkp$ak;ir=QXWN17~j zW4IufJOY*3wj>{8l8U7%!9zqpS(2(lmHQmB#j8?*vGe0CsrFt2G+91kZ7-A@n(!V3 z3>U9T)jXzd&Urm?=e>vEE+rCqPqr^%$MrP^UW*?KnfM%G!p0Zhh4KkE;yGZ*j-ttJ z6v^BNOf;tGxqe#iZAKx-8Pv<$7Cb`uNxaE_E0G}IoJgI=&Tw+Py6V6^`uq(N{m^GvHCMm zfcaa{A8!>9P@&jv$w7zhU53=l$0tP&;^hq1Wq~Nb{Ml#-L>g3^0!j$wY*?VUG*)E< z4v~?hh=^F$_yS^kJAsY^ASD!wPFYnd`*Pure-;|+6*(2Hji}!&CsQ;wX3tcLqkYj4 zkdy>#B!sLXZCk~g2!t__L-9puve8|<`+zb9b2j)CY-o&&8Aqv$k9DA`gxE{q&bV=h+uel~-#{7EoARc&hYu{=~SM?u|W6SMB zAkOPpb`b^SiE5euvMXC6oTi_6b*RC09aadsA{5P$EcGiP2%IoVU!G_BqT~lGB>dt(UKC) zNdhfhdpeLjCpUf%&s#-#hCFf~@{h)#S6cB3v_0%{r?!ha7-}&gq%)ey&37P|ZalA$ zjm?kY12-~*eIC`iH1x&k-cD*Q0gSdZ!yo|*LE11NW^}&V6@@Bnex`%?xbOuEgZC#B zl~uJ1kh9*XxM)$2RL$t7ieVY1g#SnBP=Yf9vMykkdn4?btwH&f;{v=!(gd@69gP;c zN>-sSWsgRkfohTU80FjNk^7~dS=gXL6HxvqlGRKsc1Ci3=Df}*e!HTmjgOMqcYvtviQM?7e0e}!L~x=3C6+R=V@gS7 zKsQP1GEN5+@li-P*2!6Cek|(1h0B3VmQc1kS|?~8xrvieC2_!Fp(^Q&ws2#nZU95Y zg1i_~5*PGd^Hn^yNDt~4X~ob%k6(``2`j6mzO<%rJ>_qp0}g3ng8GV!BEMB%ycdjf zH`-6=3X9uG-lgC*n_&oa89@GBT~=fi@nGYHc+oP<)8p6;KZXrNqr^1#A=a&QFX%r++m*EN+Q3DuY+E;-pqs;fQ6Kk<#!JU5*66HL{~3lo+&h7n=<@ad z27diwY#~9(##H3qqf}|ysec(Q-Uogi?o{cJ+S90BO&o@>OYbmoQcKhWG6&B0g%KCe9Er4T;g`%KyLoXY<9eqc0DD9@=;kEj?s4#8D+f>DLgY|K zsVBmbVj73%;;J&kG-ehsX+eAJW*SmelPpnBI4}+hJi{f)k7<`Jdvb-@|ybNy5Vat;Zunr4GUP<9*UW?n) zdafr#W{X#8jU9Ki%+Mpj7|eRa@fV{=KWqV)IHQw=p?1RbD)f!v3J?< zD6W#>fN0vaDGKf!31+qD<>vk3Fjiqej*Bw5wXMQk4cJxkW41D+Mibj+lG|od<=LqB z1j>q?2X;^DnK`crw-c{2!I|>}9&|-B=lN8%?cB(Cgnh**v0=W;kqn(l(G*H&ffKlA zOk?L_xa|Qi>_C!Ca*lffyxIpke4{n;XsiC(Q@JR_DUxWETUc(Gtt1xKY~*0qSoo2c z*=EdzEu7~jNCUztOZ~P7yT_8Ko1h`2NrBO+UJ$etwT}^Vj^cZaLNHx>FT-UF#i=GL z3H?i(+?O&n?jw(*&T-X(>61jfQa$L+WFB%EutuSQrbnstwg4pUWH>Lj_&^mYpxG3r zEOeL74n@yT4P(|3N?DDIH<;kNKDwOeb@6auN_%b8YW(xZ7_QYL%V0E{X37>ZK0h~L&@kvC#p`8arq#*mwbni>-o%S|C>xmO+sks_k&;DDabl&)UDCeB>iQB+gW zQd5_XF;OljISzgp$8rmBc|-V73#@V;K(XU$*(8hc7WSoETJHEd>NHoxRnd)yMaqqA zLRRQ}4?@<+7$D^`xDvjlS0?ZxBgWta?x8;&d6mu(pL+(~8`XUp-D|bb*x60YmNC1M zG9|T67~ee=SlDDXAyR7{po3cS^{?iYX`L(LZHjVX;!^2_li(#tq0*HKe$ntoNO=XX z%TMXZaTKygTQxb3KDsEo7g1sJEkP-h$S&XKQ5T&U9Lbnv@KNa1_NdNV1yrxI6^pQBC3MRt!Qk zhQXI5zT1i~3%hAX50YfQ9zmrL9)Vjmp8!#on*#Ieab$^l+_Po`V@nk2B}RMUVvkhD z>(b(I&&GDx-ho8fFJ)X>rq8_dpj`AjKkeqiJ;tI|8aehlncqcdjq!K*N816}Jb{~H z{bu;f_RA^0j9WAKTr9cJm8O0SR&1()ws}CRNDJMTy9!{atNxphND0>^elB>HpN9`{vPeBs%memH-PG_xJqOm=zwH1wXs*(`6o zkj3b5wwul?1-ybzE-Z$#ouc6$&US6{t`I=ldxd=aM3p|A&CB86?tyG4Y&Vu?2eR2h z-=zBTbgopRp@Hm5(b;APa$Xbhv$@>FnpRPA zS`_4j!sMhd-s^#;d%oH94cUSB;6++2%`p@vPBIy5{Ja+VC0nNzi=3=l)(2r%gG!(1(Irj(Xb>kT!>8Y@YmhHF(^Sj8_h> zOX@+7oh|h1``>K^XKo7>^k_lt`M1U?C#k+i^MJt57^!CMT$) zGXr9wL*~q-*+G0cXHbK;$or)_0dluObeeP-z(5~5a7OJZ0`D2gYmED4D7VPhakK8i{}9v5YDBBil?R;2b6MtV|-(LCx{QfRKxpzy9_S3N@il>hFTldgt-(M#^X8s3LdPH@=`f8OHv1_tqZ}p$z0FBEr-Wy z@E00)`L?kaL^4c{!_}`Mos4bLxI2^vZl#g|6#Gz&(6T``ck@aPEZ6j+auxOHx{W(r z{Y0Dv@wM1NL75l}Dx{Gil*SbRiusi|0Lc0PaRAUw0IrL};L#MfFWLy}@sY&=K(7xF zhmTpa+^bo4wqI>|pkYdyBQ~0u1~v&LO#*7ipxW|qau$T} zz%;LeV*=RI3J392Mu5lZKL}C0sHj(3(YS(V>z71uCpxzQ!hSini29+O5c?&{pJG#G z!B|?Pau+Y9%}J@S3B^>Tj|HP6-=WNkeipZep*>;}g6Ch~hlY>tfE}+(^#a`NCe@8C zVFS}$KO)D=Gr*ChnnbpBr?nd!RFal{*1ZK(0*{*AQM1XmwPf}Nw-z!pe#W}Z$C#z_ zaUVL$AvHm+dT%2=Il){Q^cM)yoUuaZesd`AQm2VzK8yAbJD@<3Nh|C=THD8=ZA0j2 zwNzKe6LaM)Sl>N}a>UE4@U~!U`;Z=4UveuMGnQ!YyzCf;9J+UiQP;WFqQl->$+Z?2XC!g6xyNXj zBm|p#{@i8*Y71*a>|0@%f@&$=n1&st`6|w$UxT{9RV@OuoZ&oB0`(xT@UW+zwnPO& zS+n>~ScGS0Vo_)lWxlSJQz*~7|3umfQ(C0LsD8DE{W`N1$$X;fi|L1{#Wgh($Yh%u2SaUlgyPrG*ta^Ngs zBYTTdZN8druls%=;##EWR!J0DBDVkJqzi7sKjBj6=5jrq=L7~!-R!EtJFg$;Lv!8m9Q!yP(# z`dXOv9HXfF)Wa zp2lKZHo_;3T-=x?&fv;(6YP^F;p$-`!JP3T(853Ereup*;&rHEtyiowh0WgeAm2t0 zV7c#w%X$VeO(#i8TPOSMSQybd6=e7t_$k0Ylt#ytYPOTaqndFUCG3tG!qNlFk; zq2V?P^>8@Aoh~`;cdWc7UuO!IzZ@WYiu8|h+RFOSz#lBEnL ziU3&5Qh>96nbkUF5~3dR^JfyGI%@UhB;Rs(iFi|@aTo@T{1a^CxF>)S>@qB1*&&EU z1Vqi$4JV})nA1r`(Sxm6Y$4)Qq0d)|^hT_1wRQ%luvU4B2~9A&9uazfP6X9rS_>p-3}mIx$$W17*&>*!Vjwvlha9U$l2}p0-gQ!B2|OOo z8Lue-sYQm(Yy63lej#xliRHwNU>5I38GbMm(zU*65d{^Jw*49aWCF>S@XOb_4fm* zKOsgZ12{Sf&4K_p7EQy58v`3hI}9!#oAnbjN+$_u-4$WF>Ucs`Ti#p1Eu$TRJpehJ z%oQ@;8vq``A-~-wqeZ$RC6_GnBRC1=??2M{ENfsGTfOsL_MY2!>TAbd`yNQA$FYSrFRO*tkUgYf`LIJa_~Ks}g2kydJ9( zIt-PZkO^5NT>|`Z`(l*n$86`qM|suY$m2~NlI5bB~JfTAV@&{Na}E2^%l^Nv->@38hM_Lv-ZEI;_H&w+pI33`F|Ozzz#1 z09vHx#xfxktdx8OC+qy1|C2hu47AERxm^AKTz@IlpZ|tUTg8%LKM2(t@?yU}hcExE z`n@2%xrX&u|1&VSd9XNnO-Az&I(wTV{0ue^`D76;4#+>*CIE2&Fh;;On~zZ@V^a!0i!reMyXPbk5eU|$u5|NkMfDqr*%7k^1w)K3LJTzmDXyuwE zvQkt&I00LU z`v+6Gbg8>7L5YaVcM1ll%g_^?1H+C6snJrD3$~scQ>7Nvww>+x=(=I`6V;<@jvnN6 zw1we0Bxz0}ZTU*KPyHBPL!sRDO`$eJ=f_kYbe}-wMfVAeTaO4Ck8Tz)MxuOruvtLA z(YA53KoZo?x*H%?zJ^WjO_=%gNBxAcz3awl+gpM5@__o1o%#J~#47Gz17;X~yI|f=to&y8$|9R)Yl%gL7 zi@JRvH5uAI@P$1=LxJz1DDC?RQcJR*K$q?(uxnVjpMb~LlwW;dxC#r=gzYnur`c5? zG`y<-bc$VJ5W5$8Xb9)W3GoSoQbRCok56=%K=fT~=T?K`fyYI<1G{$9H$D$a)fv~m z6RH4LQsgp@9g&#rPvF@<_xr#t22FxT&-~r~rx{`d(iIN`kA2bA&@+CQ<9LH4p!c~W z)G;t0OpZ7(4NhV4O+T7BpDbc8uATRvkaVhq`LuN9Bo|^OaYoT`)di7h8#)hG5ymaj zOstR+?Y`!2I+c>AUSq#7hGPpM_V7+8>3d42gwPD{7+4vveHvv5L1ZZzLSA^##02T0 zeGGGJ&5otjQx3<3Fq8|GQZ2HXnGh+QJ`>qT`qZ+sr_#jqxGW5AB{$KoIk>XuB&4bd zgaQ^Ns6vEWb}3s1QN#g5B#9rE^h^M6V?m2!zpY{|uPk7%RQgjHE=97jAf`OyO?v$L z`&=ds>XfuIG62~-I73?pR%hwjEg;-q@@X<`I17>?cibl+B4`F86SDI_fN0YKkjcs* zF_xb>lMc=$Xd0^DnC9)pX1_zawEa@Pb`b$SAWWjPO%cry~mbOt1dcQIhit)DEiA!3sc(u!p#%H=LXn#Hr zUeVsS597fvgjbYXQS`E4OUyy#poE!|Zikp=*4+rfges*04<4tGL)X$0ljhKM(j2;$ zVK^C@Q6)n&s-$d)ZE+1!Yer=yjqL@~#E}GYI+c`;&g?le0O#@*rVX@($9BCCuhp?A z1?Fk_=a3E$XSCKct1k6Bw`A*xb$2YOXRI&B@zn>B{G!Y{vOr-bdY*bA%@Q2gQ6%Mb zGe{ILk7B7!Ma&$-PU4L^{~Wd%@&VFWPE4&PK*3JL8#PCrafKrLMg_|C0kn#2m1=!Z zDom)L#1WQfsm@UsP}P+Sf0E`^>rODbisWTNS?W*f!_ab)TPlJlxwWcTmW8Y*Osfc4 zmshBiW4nrkh;)LE0PNr}st?931U@#-1cOAuju$G7IYYb|LT7M_4YGJ0WO4l#T!e^& zA~-#X(^CEgPSULO6bR1G@LdueOSxM04}v6c5CreyV4?X0x@|Uy;<0O`|Bb-@F!FB5 z`vdsrpDBPd@^k#;@r|M#W$@+SHZvW97M*R`6N4xiX0gFnT&xCDJK2HbOqUhe2z(@w z?cF&6P|n?fQ15m4!HaF!$nC3S@bZ1aDHJQVQ;H3J5~|moYk|3^Vn75yuNO>~S z!E+6l?_hek`cJ+^_XKESBwajVm7DBWqmOZ1kyPoh`tagbe0TkQ)IU18XYgV~ip23wuSvj1+N4bjngcmgAkhW)lT)flL zMd;{&p*b$!QcTj4$Vz{4lZ_`V-DeTW&PBti!J-a|Y%zC+$wfIE%GTqV|Dvc__!!06 z)FQy>ToMU~jaa=omq&HgOjKN`8`_|>Y#nHYYWGgXRzcU;W9KGgb|DG%`NeEl%CmX} zwcI=f9K#5}Pqf}a$f<-xn-3%^AXr3g9^Uag_m4v*r z8L7d9ycKX#nKNqCk zrD@BP0-<(P0CZmb| z39CO7PpsNBhah6({>WIoFf^Br7Yg|Ql~rChN~XlUDzXib)`j?v-7u+xZb2Y&61G1E zU%a9sn^i~wrT{`$W>zae(EoI|A1LrTIFzu_m|bS7Tk<+{ut35FC+Nf3vRsosr1fc^7N*RSW#D zrr*jBrZqb&3Z%>Cqj6Fh4Ki9^j>D~X;6^NuhKh#c zu7qajZbS!*N8X{8Tl3Kt3iSA4n|rbQe$onBrdFI zEbnVf4celHe48FWea&t7K>Lc0it-ZA-4@Sddu}MQ6Ze(=6xVYifP$0A-6mIVngG~O z8s=CdMadARi2{+EQN-odLC02h*vygNYxt#6oj6eUvB(YRr={+f8Z39rA%cB#^Uv|I zcCgXGbFFyT)ZqCu@vwox^BR11uvOVNtGpgL#?(c0@tQAYV38R9@v66X;@`TcgbI@z zYzz|<%2B`e2-7OEB)ppQM#yW0NsVZ=uDc7#g)xu$Cjqc}_LJD53lb~gIUd#u&xeMi zijY)|*@t(Sh>l2(l63%NFU$$j5UPI5Fs;cpF8xr9`-W04TB@Pv_d*z)VYRp=V7U zd=>fJEcuWyX?aRiPTa~At8t|tSZ&g2#8XhJeiuSfHV``Tlte`-aBk8G2&iTIceLe? zVU`(JXLZYM@~ni!p1cP=0%A&!U}E|mL{cy$raBQb)Mc1^q5cMYUB8wxQ7+KYSkeet zBQg-r=_kL}M>1a5(5d@fWFr zACKYRx^vhqn%KxrQrmbmfa}&R6@u01V)Ra;CzlGr0rkb`Pb~$!hs1n&sgRE@1^m0E zfY&btyc)oDuU;zT{H1_A^}mQ8qe}t%mIB%Uu0t=jI1P4nixPflDd6Xp0)AjA;9o5T z+zDW5$HJ_pUcCr%!6L{hiy%iVf?$VDWzCnCDV)S*}d_7AO<$x9USi2 zz}LNNDUx5m1aQh*nC#Rm7eP*61esn0!R-+3)eM`(hoX0)c;YH;#5eu9SgPacV>v{Gp1 z(5;Cz(J_=HL^f~{-XraZr8UN{`I@P=WZUOY3C!d;KDu$;t`O0oNrD>Uni*L;AxdK2 zr(P!NE(%vZNsfiGd}<1T@hv>pV@v#K7MJ@`!F=>?2Dq2wdipm zVA(>q(WTrI=sW%b2^G3TS229w*8=bj01pi!nrJO{zAf-#Uvk;b8{x%lb#MpaPGQ`c z8QkQS(2#E1!5|#MoJkGd>I}UD)jIijX^r9yVF40xW>zmkEhK@c!@xq`M;_V;}ZYjYoEy1cC1mf1Sw4VG`IPkK zSD+mH3-mvK2`tMJ1`RDkCWYm7yv6J^-y!hAVBQDpRzUZ_YDKt-{-MZ5Umf($mY;Mr zZ-yTS>T}J%VnD9>E`AzJ)=MJ;tHA_~lWB=l>i83+Px=;tFBfcys7{HfPN7~BZ9Ef0 zJ+lPWm7*`S8X8D!3Cy*8)1;)aIA5+=P2l1&cr zdd2F%h1?a)O{~tMBZ5^5X^i8(wnAZcQ@%QjCPckx!X3pg*DWk5wg#C(Hr97xzN*nj zG-hH^%o2J+W*V0$Qb?=8+S+ics8tyz!Ytu>e&93|h<}`vn1|vJ8F6VDW%(PEaR`*Gr%=nc;I;77M zBD~IkLDF4FUJ7)FTUDYd4YZ}?xQ7Tr2<;@57U=_YcB}fslPDijm(nMQNadiBU^BoF zMhV02;*4|*j?Kq*18&*j`gS>?5&4KlK8!R~C)rBVhF&9_Xa?#4Ni-4duRd@qexzw( z8W5!Kgwl70k`S=TL1zdu78%QdG*+SGH@+F^vheb_G%!?fP)qSpD)9k1Tf~@gwmFSu zDeX`1;OJu&me1}kWHza1U^Bggo95IzDBc0zTgYtQJ-0>m)`$9hlzHHk(pi)~E>%i8 z132}c-`s_JqsM;jSTh=@*Tg}umc*M_24g@mCedG`QMqT4uWPUm$k!!{dxNpyV;~kv za-;wMSCU@;1W=kE7w7xv{1-a$NF-Jnea-j7qxoKjeEA0^GL=v`Rl*--Bz!-@Z-c3M zA4g(VgQ=4V|1J-)VZ>ezg_`UF;~3*rt$@PN>}V2m zJC?Jw92-A$ScReH2k

VLQS;k#v-f)?5b{6EY}tfI0t!h@fV!NeBE)Xs{~jsV6sn z7Y}Xo?X2%WEPpTM6E^(=OSkTB0Ide;$Rt)*aY2fzc%aAQ`R&Ld2wwM2!C|DhcRS0E z`D02nVPjSvtPvJ#HJpjy;q*`h9sX?j-na~V5KSC#4$K{AWW3(6)Nn)&F`&xGjP@&c*G1aLzH|9NfTWt zYn1_KDH$EK{VfT(x%nY{tR1(g$EWAvMf4nJ6;ewRB!lO^NMh>PE7e>?ij_>O!4U5b zGF>51KChMxa5%`iyd`qfOCB{POR8n7P;rrWMBIOiWX@v)jg*(II%S!-Ip=)fbIj0#N%sNx~FPe80k8S zx%geVT}|2>9_0~^P|jICY&IJ?r(~q#Cvx+Tx=R|g4B#JUb>sL~!@s-m4=<9p{p$t} zUQ~{(dk#OAI|=T?vTaH~hs&qIj-))My%X%+0uoGj^buhB4cjWDg+4YF_ALF2+jGC) zo|&v84~5Q2U*&LnKiI|0fw05zuk_MzHvK9OXIVSGhS=KS?f5#KBGg7)CLsCDy?zo% z)6Q{~M$Ff)0dH{^HTkSpBM4tXuSPLl;c%w;&qyET}lnD3Eo{$KKzA)*7!>py#m~ z;y;B&m)^<%P9-43CCgn42sBl-`vTJZz58_xp?uwm8sOg&`@fHN_#3_p_kZ1E5FNIg zsVjhSA{dR6t~v}Y+OE}rG7bY1LANDM+ez1sVtf_*R(!LEDjf%>!|y>}W|#>~57v`> zWJ(zz&c6`HYH%o}RVTm2`Sv0iqlKdw6Xj*uVSyHjqwi=(Y@O}BoaD)Q-_{x6ziGkz z1G?WJ=qettis5MxSB~96J8=YZ8p#!Nm}0T%;<;5QR45T7_#Y97Zh7L!cE-h9+i7A9 z>clZiu^OBj#8EDO#ZojjiWkQM~O}aw9{jq$6RsSW?kA%PK=NT0H9zS9^u(5=mgmK*G44FLN39cu+D=I}xcL z@5Asy*7Th{%35Ky(NEkRC?Ys$2H`n_qQuI*jD{qBW z)#R@Y4*OY7P5zqYk#=cp7bB6kyhX>~;I2sbrL0!8BPYVGtLM>01JDp5#xo_QvZ0F6Hgd~i| zraKGCao6OwJllbMI+@S%JVID}zDF(8L)(T?Q>a=#1U^J&Z|d&KXS=X^B4T$o+cmKH zGQOHCI9=K0h2-YBeWaMpPCf!U5_y!qQ%iGuwgbHD$l~QH*355^NBO3S=ptZT zDI@=ID!1V;0n?nEn_!@~d^UsU0s9jeAbuAJlAVxQL406y5&tghfS)T8M4w!xag%tE zW%Jxkc!>uo1jx4c&p^Xfc+lv`5~dxQxY_7r+~lfhT>U>vV_)gRERZUQrOB6jWQ)6KaU}>+JY#XC zWsS`UQ#eA9N&dKdNkA*1xrtNvk%QIvCc;So@dhVgnzJM&iZ^&EFmp{t!Kam9EF$vB zdQe21M`b7*%ABGr0819aO=#ET#rw@}pL_{XqfT5a7tW^8RW;PHJ-t}nvxfIU2jvH2 zGG&QlIp;L7h$vM>=7eXHm|*OOXGq&8H%uZH-HZ*jDHFFEl1+J@vQj(-8wb+yCAvdgMiAM`-{w1>WCok1<=a_l=N5)#?9#m=P45C z{Ghd&z@FQE2*ArB2Ez#*BJx3U`of(3#KhJqe=y`hZ~V`s#Z0$Jt{i?a3F9s+X7Cyw zNGns_N1q-~OJeccydPc#${7r(KZwAVP4z#jdiDfTk*;Qb*Fc<@xfgJLSj|9yh}YjTBpsE*{$T=DQ0 zSX}sYQ?^1OKeXx4_TQ z;~|%HSl*mqOXcI*fO_BySHm8h0=|TWg(6l+=9PkY^BZHC1@)c#5_yUvg5v=%TE~99 zP*=+KXzsysayG9F&x?q0DZv79A!Cyu_cHwARDi9+=bzE{us?h@p9CseAhdB~n785s zD|)>KulWdzXf^n0K87F4)5GRVApMV;uOI$x`f122j=QY}zqyGK^hZR|2nvN|0|PxS z7ttk21eB{As@+@g<=PKmW~emaM${72AS74;N{v3g$K~3=AeQLy8Ht4#wb(eZAYv4` zxOw#RR^thH%7+4#4owIxTC*XE+7_BD7`B--M0{`)sD%1U_1BY|(nqqbP|q*R@o!SR zr1OcUV(Ak0V1y4iHsrpJ#Wh|i46uA-kl5*AK_omk)DT{^u}$_`EMJ|(Oe&s`-FLzh zRiPuE8-K6qqyKmEUsOp4n158x+kypXBU;pf_M)b1-zHD)z-czLxJAX>4el}?>_Yg# zfgAUYWR(13%YoO@zDWT5^{=IUGu*hAmLI*A7Wq?txaYz@$f4FUf2ZF_h{>AeoI2EM zJdQ7pp(=RSR-H`}2oOIa_;6D`R0_0s6K977$d@0IZp7M`cNHuNFrBQ)Y*ETGJdU9# z+D>nP3zva&c;u{Cyc`?Pm6D+(`Q{$dDc6>BZ2TShD`O*XC&<6r_uO5Rg%EuR_HnyC5{)3c>!W3nV82WCf=`n zK$Y6bj37gcA8f{)cP|Q%^NN+)Z-anJZRM4I>`JX3a(^*!@3I=-10ACSs8KpB_idns zqlM7I(ZcWdx1y}lFDf6X=)-={%58p$c&3XS!ii>&y0o;>Q=ua)W1$={ZxnU|;yIcr z;FjBW;9L4)eDw3R4NOd0Y0Rnm`JRP*Wlm%@{tL;=Dv1t=>dr_6=f^H%tP?r^vKl{R z8LY;Sd_+2Avs7VfbJ!#l^DQUBv6X4#b+x6VD%gg)rEA%4PKg;cl!~!p>{=FuSwfeU z8~;~Hmve3Q$A~~u?HMSQ=|~98IC~hTnMp>gnrKQ8>?-avAK$FG#m6qNqvNnlV%gAp zOA&<*+qX4m3igZ^tF0iix64rMmP>RLB(?iil&9LYEI0mO5ig@g-FKq`IF2}xzXIK? zW3+qlR*A35@L-FwPMlp>3ri>yuA-VJMlr&+$clR(LxL$MK6M!G43L0aG+q@b?yBx- zLyju4;jA+g;;msGP6kMIt31SlK8W2;lT|O>j?#)}hV`^rOc~8%K%`ZU0nwqyfJkf3 zEK6dc9E(SU%gr8%IU{4PL*5ec_tIJJ6qY~?q*|3<93a9-&JguiW2Pt=f+5T}yf^_{ zIA$zaB&v2)$JS_Krpv-*gQkLF2nbfnOc>Xi@}sHd#!UI*y0*`PCT$y3H!yccuyO21 zve6+$sz%>p0Y1m5-G02r&k@HSdyUojiDdFq{7&IMQ}F(00{Ah1HB?$2(R`yEtFgaO z44jrEughsk&H%b*n#@=8*p|16k+06rZq+)rBi!1Pa@nrvXmocv>cnQw#Q|JyaMG^L zj;vl9@2r@E^{bEg22&Vjb7?a~hg}si@T{6;W87%5mWy%9(hop4`-cVdHBEjs59z)$=+t17u+~QR0x{7n zWogc047TNnr8!BF!)&xl(!^k)gE9DXx-Aec3LYGvu;Y|FsYD?%BzmM7>u@E#Re&mv z+m=8_sN$uMAx6U3YAK5cg&NeD-);}`>ndmJKvZNTnJO|e*^%rN--*Z}Ch&;ue0t7r z=dJyglL_1BovUH4AJ|F67>YLKi;D8w6i7L_NQ?)K6x&VVMpT*QNKqg^+TieY@hlZ* zHSS?_j9^yf76Vs_F35x(ISl;yBgc%!FTjfwHi2ySPZ-s%`6b#y#=4HsMBM#r?2EE* zeadeuOT}Any;!`cEc}pukY728!|hS;E=vEk=5@Vik*9I~hE6ThJ^Kq~bzQK*uP#i2 ze~*YbD6h};Et%k=DF4kUYpfpkOC$z-A%8~k#YvH@g$y2S_4}`ZJ+*}?M$)N&0odsh zcBy-8_bYVA$?YC-;{t^x;-R7alu5}R9ZFxXqdFuVtrOzYFrbJCx@= znkNbWDx8`*ftvZk1d#blm}ZdqP0XALa;xfj@BkQB6XPsQ!?uH!za(GgmkMhx1exMiS+c6k($3kf><$#=){`8 zrmr*F6nV@yf5+UGCPaev!IJ$%V|M#UktFORh-B>}h?GKzyJ3rQEaZuQOBJ-ihQfVG ze!VxdU$7c%354`Ne&tvlxb{hj;FBH0ODc;C8cJEjC*>Q=2RJ7h`3Qwnj z@Vu}ePU2{r`9&jb^YRmDOxB{<>xECm)0Ht=LN2T2$EW>?8q?18-RbGUEE>1{AYc@D z=MMVRMaV91PDNwnF_p=L-%&X;mdxCxp3G7=)>ov@6itbOIj^q%$6hm4dojJzh%EM0 zh5_gSfa#-`sK0DS<)+TcgvW%0#iBYP`>G7afWAszD_n>Ww>X8m6%tl3?|>Pbv@>H9 z%jZ6mIQd_kc^cV#(wdFya@cYGF6r5VPCYkOT>4FNHqqG<>N9z_eQ>*7hu@jmvK=n_ zT5sj?#U3f!PG``pE$8RU;T{lVxLA-|G}A1FG+NNnrQnj%EiXuO+6N1=)xWF!fj{N@ zv7H^GJ!Pb8e0K80O^K^+cJImR?D`7A^{3Ija(!#;JiZ0OmrcnifD6fJ(0&W{TjDod zBIHYC>he4}G5M%OPWdR$ein_iBiCV~|0 zBjwni^{s7&^MFTXNc6h~7zs3pI#W5*S?fG5XoI!;so~Br>qZ!pu7H7*8qTMtbGnHl zr`hVxg`B+PwX10bISdxwlbgz9b5Wo8W1~|xdZYN}I@C<+oR`9zY;-bVg>12qtMq$8 zHpm@uH5s5at~bzw>}~t2-=g2WOdRQGy*zZ954ws$7bhKRygE8p^JQ7Cp-@rXCULI( zBX5sqczB$tI{zhSICq+{!adPw!{(cyLG6Wc(@VTGasFSrKIJ7|ka+yPP4|1(o%#KB z(ws|d^-9qWf`b;e<*tp-@;zT`lJFA7xardnpfLDOtc>Ps z52KrTwdeEeN1r3mvz<;EDexx&w{kGL_ zJ_YOZ6s*;Q4IULPX&;h?{b+TNX4^Bzkb$lUmpYxwb-GmWR~Q!E(2=WtYK57lIdpL0 z+mFY$Ok&qwH(pS!nBp=SVDp=f{q}tk3_k|L+0{~u4Z*O=YbY*WlU}`Mc~>3?|A|VR z{O8r-Sn}wry#xl;Uauj^whv1W0EqI=FUVkgppgLScryBlQXAHebsJcJ+``%pe{ie= z4y(5ct6R@XuBAWLbykk#n6|vTT;aU%d*tJ`@9R{`x~SuUp}|H{UbIDtZ+N&TIjFF( zqVuY~o+rGt5skf{Q9<3YB}U~Gj7s8H6=(}jwwGN=|Iif20d|+gAgVvm{<@7vOl;%9 z2H1)5cqhh_Y`t;niUG3VS>&(%5HdRz!j-4WE7AW-xd7PN25=BuQn1p}AJd6vQofI> z4;Whje^wj(m*F$gjk9!5$6Fz`;9Y*vzD>cJBI1nWeMl;C6?tQ2qn&q0SzHyM*k{#; zEq_O#%ZFRT8p;kJ3py~@>u2}tP-Xujr$@7&W8bMdBrp3&{9vW!t9EZfadUJ;2vUId zRhvpDZi6=ct*&>kt~L#q{%<#A>Jh?VqOI(ll<@YisSeUuOix@vnP(uuh>!`LQ z1KwKRJs9U0$VA)QL>Paft-YT=0d3sY@l#M#?KCvx>c<{FIlcC+4w6$wt{Xob<+~o5 za{AJLnR2arB_q~fi_(uQwcD;3>+tcAxw>q8(ynlKi0+!i>iVT`rOe=s`e+Mp`Gz)L zReo`fGop6Wj9V)epnUwr@a39dtTr49XR*-ctJO4PxhC|eG%x&jQff9b@@#@~uIS9szwF^-3HgkJGp#Zq*oUR0)@*IkHY9YlbY)O#VV$g z*(ReW5P8GTV6H=W&r*tjaj))^wxJ)~!m|c&+zYiiu;s_(B`%S{dEsfw?lCTC+=WFa zDNN(ps47xx=o_^nWL84$UWj~KE=Bi4B5JGE7(1{2b!D0D=vR@hJ4MaEvur=>5SUCD(c+Ms7qEwUD_G-s8vyqmTw!9CM{ukURx_% z^V>2u6>62+qGg*rEi$*uM@hu<7RY(4K+c!X&0QCWkNi1!ZBUiY79=z!abb#~&$waR zzcaBKXe{e^1!js%HO;#W+t;y>R=uoh9~!cz9?@zou0bFf3vZ?6$Irj;TA+NL+d|tE z#)5`CTCXKr<>vi3Z`%ZZFKP0q=t9br^sSN{`BP6$j@_Tc`tjL{DW4fVOy5X#YI#g; zbe@i&(qe?t5yh0^bjiEgASoe86AGc4xlzPe9{G>XOgrHS7(|-7B#D`Ro1^pIi^d88q>I^)& zTv?yV`q?949$a^uksu~8bhIlmt#Yp<@%@_=CL z1%40B-|U4mBCT$?Es!_KiL&>8?sjhGUkf@c2He5V1XGOCmliOuAxCz29@P({)i9Ng zIE+I_qE`XGssKexQ3-h&os?ke$w=pMreHqZfE1_Cixbsc_q+cq6LV5IT)f zD%0fE!xlIGsSmfb{KZ7KUwaz#=r5j?YQM|)9bP1+)_Cgo`tV|NN;DK|eD+LA6lyT= zmLCU}&>GwF~(~^=?aD7!Ea}aT%0*+^d z1rz;<6GvMx(HEUK+JcFGq@pRC){{H2wgFy;x|kPVx+J?QFms=BG^dCH@k{tB~d(0Qr~;Fo(`~$S_{# zzLUA^UeUE`++$CW^ff?UO(V08Y;A)ePU@2WX!vBp_gk~ji_PsIYl>@v)Fk7ea(8$o zK5K`V|F@Kc30rubel$%qBbvFW665ry5D;pTTT7pHhbDa+I|#RqMB*U9;2E%^#N7p)6E}hx!38}8n4r@^%V%Z!~G6kOS15p4v0|0;%j2=Nt#KO z^4Og=Ess`z=mNY@%d0z?b@+6WVl`7t`qbOgh=SNh8U$72XbrWfbX5`uXeqfWz*cVa zJnm?jl+WM=gzzx{b(Y|9tNfL$Bdd6)?7UX>B-}FqXsErpq2oX@Ga4Qs41tbuowc^u z7};9e_AV#E?=GzcZ`8gZ@1N60fE!?a8)idmYaII#Dvta1yF+K{p0V7I#P}V|&jg6) zex%gX|BWLP> z>kNLRF5!Zn|J}(rP{Ybyg?r+5_ za<=(#Z~Wk+DE~ptSjq8&kC9vRRBHU-qvh80l^#F%Sh+Q6O}=Tl1;5;aUT(oIw;>Z6 zV^fJazTKMFlZVC*;;xPs59GOd-&I+UbpS7!SvYjvp1h3JWm452aA3uvNkfktqmv2A zxO$X+^;mcyU*h451*zk0_Hu`&X80+O&1CeMozG{c2VFq1*yRuJv(Uu@GBdK!lgn2B zR!`g|Qv)`XYR*e1?k1n!bEPWQ4*fCI5gEO8pV&CB7Wef;gOxI zM2}GtT|=r@dO%eyA-u=}k|a!&@u1D@iV5(!^%I zJ88jPy=iysa~GcR+xEwC`C=;VY_?YBk@^+zEihUy*zV;XXYr^vwV4+b7aZ2=mn(Sz z(VfvJ@q1I%A3^oPBzF5#>4ESX5&r>IZJR_5l5G-QS&LRYSJpVGh8$iHc&MbEsahP; zSk|2XU)jNMZJZFM1o&dok}gu0ALuPsks)K3Rl5hAx{o+AKjm zCy$1t@f0HmNCIcQt_p?4*41og(}CUhEZlP^hH9|>C=K#zzh1lkDk53~pdu8y`A6vH zI6kCWvR>~bR^z(ymzbMSE)|}Qp(fE58pYg+OE~*|RC*5ra;Ffe8f}tsO%RhUnsFv< z9g{RW;v71#PvOcbmVz^fkXNdw(^t4VYJ0qgf$H#yV8#edR3BjK;_@DId^~OaWK4r= zkI>~nXB|35XJfzQGXXr~X2Mb0uB~A6T-ldraqQr$UY$5_rIf^UUM#(reqaqqyJ7)( z4rtfH(rfYMS8@{$$BJt=I#1Ex=x8GMF1BxP8N;ATqEssKxAZzv9MqcG7J)WwI+u-| zwA}K*K)9q9XzrAAztkx~vpBqxq_EV^9U2Sr3^bmsVfUdx(hdXxky1pGzDXz}Pcr%s z`G^u`yF<{vnPNfB%P8iF6@y=*uE1(Ne{(ZotQg#mn5q433kW6Qo0MHqIjye?rMf4gEJ@%K%YjZ&#P7X{HK1IP9U{N41&w6KM_i%yaB{b zi*g}5xD6sZxSRK}ZBc3nu5zj${tRNL2IGJZu!#z<6d6>Evzv+*$}LAds>=k@T^SIo zx0$>-6YV4?rk9_oLA!G%3@KS-%-GtmAtnrTXPkPCE{J1iXSO)k)NC9wH!~5tsxup7*O@a2=-1-#>T2TCp%>Tqk`H;-9CwFe-hrj*&do&I0kNKzd@dFGvT5F$%`v;7*V~K;! zK1hdaR+#RXaZbxI{KfFS@=Yh_I+-*dC(Wup%>v;7wpIP`Hz`&;Bx=RX;q@UxnzPp{ zx78`sFwZg-7+Yy5lhH}R@``ukPk_0o-}2tC7_ z$Y@QM^0cQ*k?W>Q;LKxpPMe9J+;f1eLq$`^Z=*@yQ z$8jsQdC2Z6C8_V>_n684{6t;j1MNY}@OySLB%Pssj8_-aru%ta2=wi zO;OOSyLQ_@QEfJ|j+Y;02NmPv?rh_|^n>=<#`xG9$kR?sP?`vYU$Rr&1-T`u!Yx&< zNMaq_Qt^r;{24c?tI6oc*efxn>)f|tC`^&@Ic`jlpsbV(TCREp!+dX^Vne=WjI!H2 zQM=d1CB?@t+$y}|En1uXxZ~i~aN}IpZC=)O`;@m(f9zG(>{CvpXOB!LwU|bm{yEQ~ zX|d+x#=ExIx|3JCXb)jdDs7ZwZOOIGxv=&L;M4JdxthAXCH9*Mja%DFP~@1C28XNS zDy`VLVzyC-VpiM+_$aL{sAP7+RilF9$2Hehc#FRdY0P2w&Zb`6WWk}-TO|7t!glq! z+d1r??8z?yaZkcNX+0x!6_dZix%re6B_!jHG^~wm>`FxmYi%y2uKub*voTreESPP5 z%3Y4vPOi8dRyY6STk6^(BerkdNx1Kq9(Ez+z77&>Nofw9pYLq8>rz8uahqy9OYSia z))Qvtr}1p(AB*R)#;?n7cSV^@B0Ow~Fo=zVrYN15l59+wKfZD?ImrGpo#6Z-QM9#W z4Pd=g{l~_p7vq!0l8lw#h?~Z66Cbt)&*8)B?Pfk&^EdRRMxLiHy?I$*hKF19W#@2E zU+lF#Utf^DKwq$l;sWSisIMMY$NEnJ(>%qwO3<6$oC!~Z07uPQo@@Y;Kf_y{BQ(S2D>c4@jykj%v_#-RFi|`j`DZ3(u_n|VkxX6OVyzVzh zU9~v8uh6&U4;Z-8(~sf}(1r@<1?h$g#~QDp!m-t8sBmamHdHt-$Td{BX8vB#)llKQ zpr@fiiJqV6RCU20h_> zC=tetO6*gDp5be-G}IjgadpbcYdy{~KNrK~=S?2x*M;h!$F5La6J&N5`U7&7jiKz| z{LY}?+EsO9?Xk~g{n_uVBu2R19_(%8dUMb}UdY~4K;~xGq=MX?b_;6D&9ulE!8{h& zpGzsGxWlF9Sr~7r8W)O=8GMRjMmbkVwM)#he^seZ!X#zTUntO0 zu2m8VDRckYy0ql4_3fgX;t)@BPn&v>+tH<;?MuF9{4z}CI9!bV9lgqKnD40$m+oK zcYUa6buTogY){_wH57;fiqazIwLeBB4Nopzvl|M>ZTm|!9Hm&a0jr^M#HE?j6@EoI z|AHZQ>ww><0WzEMU~5AIm^-qDI%U#c+ZUNqKSa}5vgY6y50%UFT(&$kb{9m=jMaWb z9HB4QSvbM=b^3Xh!1ITkbe;z!y@ndOLb$HhP`7oRx7;`P@C~N1FkY-ZPY2L$VvH2Hi+ibOV*^pXh#Q9zpj*dtEnVb>MgNi-!EpT%CkP zGr;konh>ZTeQw)tL$QrI-4!HHN|DXp!=aik=x%;MYj#ylr>yN{SE~nFaVNTfZ;5Q7 z|3A2Ze?2+c1$;TY@Ewe3lIj>v(-?R1PU)Ru$&a&?zS@O#R$NcwXK6jN=gmoiZbDJ2 z_WV{*!dv^Kur=e$sTijZxtrAda0{vs(U~ysZ6iZBOS2OYw z6E|hVJw1tBF_BpJ-&!@bEZuw(x}|zoT0$kmPdO7Bc1J}zx*Np5`t7QCjs*W6*lC`7 zTzSsQSvN{Ge|$PMQLLQ8`r|MlQ^o9!$EcI(uI!Cd3;_ejl9g1cvaZN00>^?0((N!w z{#|6m-k;&9{z{S|{WqJT(Tl0>4*Lt*gMLa}EzbJstv4}UbMc7{yUM+eRcP-;i8SR- zumHRRG`0>~W?3xb=tyUjb=$0^9(?CRj_Rk z{RU`wtrt!7&CjDrd;)q{;I~0CV_J9-ZZqwLNWJA@8NOiy*an9wwj;v2XYZ7~u$6@U$&VIWY z5vLpKNOnah37XX2C}uxRBUDF>Eu&XO!lm9le)K{LvENynr&>SLswgH;^%NP7*WOw} z(oM8Q_mB$>%Z!Qar1l1{)ANq1ka4rP5=T9ZsD-G(ZyYd(bF{m1nuGO6vy0j21%yf1 zFDos%jq zQ9smZ%H?3!d0$18mmDlD>9O=wxSN7c9%;Uce%xRj+w+Q20%LlVb+0Hb597nDmCFNY zd3=!!8uGa?)4t&{nOFZE!p68Qo!H%wQ+6fv9o^%+$GrM)W~c2jCjKhAwQu3G*UG0R%Nq^Q-y%myNZ_Z zS<1o5NF-|KEg{D2RUqao%|8z#)GntzB#C+SpKzB3`Aiz93SjL){w~*U$BUVJ?i?>= zWSeq)h*!4t9nO@8^c}%`#rOu_;x=M%N@1IPi(^js2dW@j$V3 zRAPs6TvX~SD*4h4MWxe1kigodv9Y3aj61`ICf8`}RwP?@CCn9dz~U0h?_^i2BOxx9Q{eAd})i%$O6mVbkr*q;yS8hb3>>c!eY|K#3XwL z2FPSd&$lFthxQzG(WRsfWXX*#dV@uew4#TS3;?b6XZL1f4UBg*2 zyiX&qX!R3=(E3sKr*09O*OJXIhTj4JwIC}`Dly9!{kI^Mube8y@ZMB!S|W`!9xwa~ zuzS(x$b?_T<#e#q3)Ro2C-bHD|J3T&lS;4Zeng&z+cp z{OIHbNiES{rbhBcn%d2iPbl5`hg#tl%X`Xuic4=I@NSNdFKGskjy40Az4~jYP3DH^ z_iO}_FS-cl4fnEwp8RVyNGU|Sj^AvM?``G9eS#KrLy0GE<^c=qvF2TRMjP#~;glU9 zi}0e~$Gh|madA!9_Z0?*SM>dYExY#%OS17=GQdj3>-1@_+#0XPpJv_p6_NE?jYgAC z_3CdRbPBQGn*?2FU8xfslSMdtw=B2OJ7DoFHB+v)`%@#9R}yF5PtRlHAKf0LjXn|` zudYm{x@m9Biyj~-YdN_F<`J}B(p`fKa01TA{JWOv-TW?gmq$>Ma0qlDp{T{&=Et%y z_UeKt_?1x?twU|)6ZnVj2#9Cpj5YVD%o>$|`#$V&MtBnXq;b;kXK^ekWk6Wna)r&pJqi1SBYHcLbcMF3$IE16Rz?SiY`j$&_(KNSc!xa8 zX`eMmI3fLjcPapuyqmApkCcQ@6(8yfnnr9S5SSR920Yq@Y;Htrxo`dvtSj_XGBqQT zGOv)tZv3Ls!ad#DCuFC-$=gN*yGSN0D|%h!jcp)v=p;Bf5nOy&yJSD=>I9JEa@q51L##z@{9ArdW>?8$^UYZb!s3>Wc?D+&I@ z1-lpy!J`O$4L_opFrcH5?fc_!`_@`z_yEDgeo&uM<4{X?yTMLkm*v13i*y(+y67m57E;&Hy~3>qZF?TozQ4jJj_HVskncx z;@H@sHN=Fa`v_r{>ZCXoLo=OR6c$?o^jup`BeT zO=xL8e0-Xz6Q_~B&FPlL@zTn*v6<0-vPGg??Tjv<5vYL7N8!VOV(*zuARJ0ef+HC| z0uede3KU*9^3DrzT%?RH0S;p%*@3$&Ua6~fqjCkM@NlQ7staRg_te9EGpunIc6g3R z3c;!FZflPZVC$01&ZjuIJxJ_uiV#s#QU zeNW3|k4#<7k15s{=PtupbQgS4o5xdX#&Pdg_sn`Kr?o+*9|R-?NKdXwm$ifp{JgzI zAw(g6)oP+~i=$C<@V5y;A{7%d5CyM^K@HNoQ+im0;Mm#&XEUbvRpI)btj4ic5;(R|NXbO~fV;;4)>|5XO@z1nerRa?jX6Ic=O^V% zhMG;Wad4$R>F_E(|LwH9UD~}$OPzS_b6~qm@jsz~rDJGld(&YXFSpwgo8bdwUruHi zp13q=SN*%Za2ElYNT$4I&+x)0w|vzJFTC1(bHs%^_(7x^?r8^YCy0H3TiyH>T)|jH z@c?S@buli<4#>90_7nii3xlAGDY6Brz$A;mRgeY&q?nFunAcr!gdY?I@lE3Gzs@k> zw7rs6H|o2B!qSH&ORfUn-3e}Buj;7gzaw3{LZ3nbsnDnKu{KnNceAT_H|}_6JcEJP ziX*eVUiefw>`CPoSN>KcHMydEj^m0!K)fPbkP1AGD+b}#VZRMcbI`Oj$x0f76uq#f zkzo^ou;LQN$l)p~oo>?DI^W|B#ZJ4u*T@3r*}Jmg5r$U(m|p?Q(ucqia*?$X-pS9> zhvl8*#}GF673Rsz6?Tr(OX!W$|0pScvWPS#e1>Yk?FFAfc|DC)h+NvnT~}ZBYMSk#F`;N3 zzd(^LRi?0^!MBpqr z1cUUv3(X0H;)&f0%YRSE)tFw6UD)zOtc@tgur9Ce46X~c-|V!0lul^&KI6gj!Wsi- z6!~txf6lk3a>>}|SfiK$&bef^pzBME-IWm^fWH7BhitefJ{jc3kiUTbnpDY9-u;pv z)0=VME>1Du?I_;kD4rSn5d}M_Qngw2T*s=x#=Q!j;4F*pQ?HwRi?@6aF6VEZAzYU{ zu=gIMRf_V5w>E#R8a$}t4IWgH#>RgA zeLpBU{s_0V*<|83sgpBlvrd6XCux}R%8U_uu(GbCEz>p0X+O(JO=VryHkcPnuVN}W zRmf#_muAwr(zTVe$M4>rN*_VfgzU4BeS3SF$`+&5zxT(qaST$svJ1JR&7Ezzulg?A zE?H#t4y_3?iF3*QENDvWRF2E(d+*89WaLE4bniV~X-==aBpRlB@99qC8kVbvM&V<$ zG`vj=fwvj6TVr^dNJg(6?@M-~ao!kHGxkL2z~?8v1YH_SQt2^X*Geqhfjn+ZTY-f; z_U0MW#}Yj*l(KePXcz5o1WnjPmqmEV4f5nI&L^&%lixRg9{)ypbLPdjNxp2;H!2_9 zhs-ic+9N;HHhyJOM#4RoNH?L&UGk<{-fnpjwgT<%6?m@#s1X9>UB7S$yn9XVaT(=2 zTjLn>h;-4~bEDMIoDU7O*T86LpxwpyzpR0_Zll&3rzS?WHTAbPB`UElnq)3dB=0T1 zEN@iEJ|1WOSA0GA-4|=4Ka~2I%X>UAvb~-6)Wyzyo$)e$arp0=N6802ub#q`bKfr& zX+XE;8}>tPEw%IP+i$jye!9m0GxI;;5pii6|GdWkFKzgH zh40_x-+1fS*yuL?qr;7Ws}=rp=Qj9bat}7vQ9x!)&wcvzF$I22bL|x%>nR3Bq4jM+ zYuvfps|)$+5M|LBV$$o4)sM0-`Gs;UHvY+deyC6Qc2!#V2lo3uezm<25he#V2Xbg7 z>{Qik1~u<=S-jRPywUd+t#)>@%F?xHi_nsbh|Mo-M$FWn-QoVL4tL=Z%3SBC3V1Olk2#@=T1y|LEA(Fy-&(-w6MhEW;0iouym&C;GB0`jEab^H2G@wQUkf zUEBtXji10oSB+|U7;v4!B)00Ht{A zsDFlkNRIB8Q%rf-tm)HTzF>a9FYlUPpD90mKA$bmRnyt>IdfSWoXS4wWy`aR;fIyH z*zgqsPW2sRcM0@9V%%%6I^;d>!_lJUg+23fX?{FYuFa2S%NNdX$d$LubCO#S?ZOnK)#r;3a7L9RS8-wott^RjQeIMWk;R1`I7e<=RC)WgrF&6(@#P4sUg{E%E>T=Jd!;dK; zruy)e_6_cKu5$U>J;sta#xv->j!Q?e3v5KC44eB^`X;uRcGE*;1hH{Q@-=ITYPk88 z>_qryWQ->4;C$Mgrup>GZt@T|UgXh-h3BX-^wa9^=0dLEf-pPd9D7dWvNo7W?4Wa;OFP+k z>(?3g(4O=w18hj#81;FGyE{I@_BU3)&EcR%ULGaSR0g>8+^)H^2N$YI&J2xuML~W_DAb%xepyvmN$j0@Kj&# zg>N&4scQ`_bbG^Ac8-GAn?o9_U6s>{h3*Qrs9 z(N0whX&xG{^m2T_)7=2Xk9g;*KPVP@D;LCxdk^uBI5(IM75Xaua_C=akpq8z^~88_ z_PeM^27L?)nsL&LS5N?=5EQc2uj;xGH;95<^EGvBF@nP%IWom5HF}2Lnff((U0X zv{-KI3^x5NOY&T0Dv`Dnl(N;&xVZ^vHzk=vGZ79;GZ+-J;h!7JHSYxj!QjzgfaC+g zK(6{y`fECYge-9$ZE4KrXwAv_vDdwb$8hUEAjvN7>XnDW_kg4Eef-=|YtG88)c}Ur z>0m~@e^^(NGLHG#%H_OoKY%diDVn@q4N*6{*1Wn~mR5JmlIxbcIpuLZl6xEKku=hM zK@L^sfO8|w_kn*T$Q+)1jr^MJoqv%h)YpVqpTm@Mn+&gRRRDt3X%Ba0a_3%6&&}{4 zM`;KBH!)b~E){w*(>X4T_0Zfa*jAdyGZggf!b>yW$33zC33nd;e}EZpdx(;qb2ifC zOzGTNK5^HzX%@xeLj`mStZnqZLQAmmOM>o&Cn7N7=`lFVOsfyC3fQl;hu%BEvFIK} z_?!dFZvRshQ^Jo>(OcewYi(+!%U06{;Io#XtGis?bWf*`GcWPRT1Q^3=sQWoxIjzo z<9~@6`WP7cin9-3Wn%rE|1Z?Sme+6_&8;QUiN9d}J74`;7maM;iZD{ze5#6~U$Xvg zT05mKW{*tF9`Ww*!V~TM!$9V~!4~*b$Fl4pl&Sue^nUJe_Nbq0v()ymuBPVh9gzBp zzJLk= zmQa;`z6wfyLeusEWsXDy!_c60xf6cwvF&8QHGee{1TB z+g_?Z*L405)s^Mp>hIXPd-mSbumG&@D(?|Ty=^Wgn`Lo@P(A35n*K2TE9knCZk}q^ zgH`U@S|w7juw0)O&eatWcMX>j_?6eMTTaSr6@NFZ=CAb*@;IKq)pH4UW2);2560Y0 z4r9y#+eb}$aW--9`xTWvMtb71xBIzgHh$@FK`}XtnsjcqzzS2Vky+OA`f{y~XO~+O zKbvfYCOKrbrAu-b)^DA*oiV_7*DBSl*b%=~>7K^Y>gJhR@0?W8y?*t(HV*ct=f0#@sJfD92ed_frRopu zMplv~&h4dz_TbqR5`y6Cye(51N(O!$i_6C2=6b$ds(wui`7TyMpk|ns9V0@2SVV)F zC44!P)QbBszlRsm)C;l<=tnc9ecXfwm<0~heX*AY$s&IkRTpm3lS65tv=7hW#iPx) zivatonbpaK5;fW0?kCMQS?-MmMTqHB;V{f>4B_&e%x6qa8{nPH=cKk@o0A6X+Gf1@ zPc^^Ee9S+-#y??xuQ`u3rsW@OaHcF7&Q^U+AiSYfY*AFPftdw;M%@P!&?sP58#8>> zlK6+1y|g)5!s11$fxT$Z{lG@zFWT>yRD9E;2%643MU4Lu z&xoq%v7~Q#F6{Kg5}m(P6Eum=Ol|nZZTB@D?)P@MbHCYyadYL9pjCZn^}!8CC#%md z@wVR_=N!{nK)FoF%mNCsll2lTFC4PhyJA_-58N4SJNX!ET@h+KIbJE581)kbNQ`R3#U-Ho(n@k-#oCOM<0le3 zb|gx)9Q#{bOeA%myE(v^{P&+3_H4W>Hmahad_nkC{0R@O`*|J3h$HOtBY6kLwzvJSt`2l@SDV%EL*Zg{dwh5jJ6hkFO0ui;_u_!*95W7 z>5AlX4!D4t;anXp!4%Vp zT^)}J=vxyoy2jrD4r0`m9b&E1dKjpAYo{$29g=PxQBz`jnyxD^(pvXoK1&6RAVvU*e58= z=JXH}oO9xk1?qBhdYBOIVJpShJ2{KooE{;BiNZqmaL~Cqy@8PAMhQC#bSFg=deM)3 z4JeU1_FP%mP%La@5!YSO3a&dq0b2{8?BRvN#$aQxVRME>U1_oUwQOc?0{Lh6%ou{B znGKRDEe>5(nh)AZ77H6?45V1tR5>l!l&J>6rtEAtciu~jM}keoyDD8xw-~UALT?H- z!GL=OQRpH6E@;{)TTwm5^RGhKx+fa${-Te(T;<%U=#$S!RW^7w>H{(vCy~i zR_?a1D+GwFw~WG~Vxe5=4*G&Ji`g>J<)9pNV_3N`rs_Bet=jd$*pc8QqE90Fq~Ij0 zjxpS0!5E-lpc;AeRd#p9y7OMg%$zA#7i99p1xaC0QC9Ag$t?&@F5VTK42qM3la*W0 zy>Z9uVRv!75RMm48!t?Z7skg6rxs4h6i%;jGBHW=;Ph7(CWBM_;*ntT_NOmAy>Lo! zs$Xh)P7TKWp{8d%nDA*t4s0Sg%^zucP76YRL(>xmr$fW(!Rgt#JA~#iZoEe~Jf*DfY$D5Zt zlG_)g+M5-xuH`v4R9QT^{YR#-8RHG;KO=@VPHlc=8$8$yw#~uj?A({tdbT_f*u=_PS01+58;G0sX;TFbA1)AjSpa?-vVI%-y;&RBta7iiLT==5H?yR)&Ik z;O2ut#N#eVpI`vNeK0uxwV)qSJj+MzMuKWE58f*9Rot2C+v9$K2>%TFK`}U^vmcxR z+!?_c^n+sI%u0l`KZne`SU9UP;|FIR3C_Ab$j{|sXCdDV%WZCj<$|-oaaM2^&Hs~J z+8c<=%WiFR%#b3OEG4F~f^7IBG3iVaof(`-!Cw|10~Jce!r7HGXlQ2_?<^L!R5rOO zJUCkiJzEwIP5Fu|-5G2l?UrB*=Qdx)zfd?^Ak~i-3+GhIfShxP*R0EL;QTlU9e{OIt=|3H8#l1BEy!w7`IOujW zM0q`D02=a(h4U&i#lra<0>@+o=ab<4;QY9b&bz((&F17CoJZ7o!Fg2GCyRw`m23TA z+mYh!h3(#kV0(;i2M0w%L*@y#1HL`jE~sF~?L2|d>kSPzqZT`&X20FaVh3q<1Usmq zj|bauZwt1;_*ZfM(HfpMKlMS!rc7aH-fF!0sH-i;u!QaZ=&g(z=}n{0j1uc z7-CT-`GxPal)UFIR!(SL$aDEz;lX_f5b9P!&bdG!7HaZk&(T#Wxt~{_X_idMw@xN0V zbO`+fVH$fKN|>83a0skz$j}R8v5TJj9=m@HKAy+QHs8>Ygt=B5pTH;#80ern8SHZ) z$psiqb9A}$`5!vW6#$#0jZwzL-2E|Ghgf^(^&Ew$Bj-c1IVqf@E7;IpJ!aVwM&;?#$yS*BoBN1Hio6LUWdx`(fnH(%M$ z4a38QF6Kq-tg?L01l^ht`R(F|32~dtI*utVjP5(UDg-}9I*q-I)9u(UMa;fc>AGC~ zJ9=IL)*a7dkA``SlQg80e>XeJmowvJNB3nG7>6x3s@M+LH6n;k|LQX++a*h=`$M!jVYJyX`|7UpO+P2L0#OKVq zfv|1AR0sTQ=}xDIb>8aWJFwCs z+AY>`kD|KNYV7fG{qd}+{too9z!%HtE3FQB9P{VC=z5%0#6Or8fz~FsQ3Z@@fi8Z# zvET&KJ|eEkOc(7OJ$u+)10U0pX5K4yYgUT8EGJ&hUPOjNat>Z&7LaiqZp_-Pa3@%A z4l1;Wj*;WAt#?w1--kAn%X@X54aU78{f6N?Kbceb#*_m`Ed(4|`FY^9>2I!_gR=29!2Q9wqhRMT>JnEpS!-W*L zvuAvM{yWV1e-jOBesO7Tee785TbE>|zb*UuYttX-W2U$ zkTS3!sHu1iobZPHT_)sfNEYPwIA38^iIL@a{iHMAycd4fx}#3r7_~F>DC%2+P0qF5 zpqrUP4#+k?OhJ?-o z|M%!@5*cJx)7UxrU@gBP0V}!{FV?xSDPyv?%_OjGn0UDAJw*d>65z~Oia_VicDN-F zLzc-Nvb4ba0*A5L%r7^vM)$Q8N>kOZq%a+T<^$s3tbTIE^0A{O6igq*0;Fru8cvYW zIw-@Ui)9siE>wMS=pUQIqxvN(8}uCJ#|0in!X=QNqfO1?)Z9BD6r#7kgdZEgx7>mY zf#+dRmB@po8*^I;Sv{J=Y{Cu~`cM_#CpF^C>^6Qjb85(ONw$+wj!Ks7S1-^753dth=o1WmEWo#pXyP8#WbrP^H}xtQa!zG_TWA2ss~+c zIDY=k3~j!(n^-i#5K%(!`fLPNyiky?^w1L0$e-dHcJdTA-|tB++eFp~wJ=1shs~%d zYzO_Rxjzs5sm&#{xyQ)3aCr0@dkSIbO4*(vAmGgHXj6GRRsG_YXKR6^`Fw_f!`4Un zR9EE`^d$R}+1oSoSug801{!;^yes-$zqHRiE}5SD{Nznuk~83BAJ1ndQ*(YY)wd4K zljK~|BS0t0+tit4bjch9Y;Ojbh`H#;tJ@wQ0t3u-Xa^^ zJ;umW_)`tF>F}Rz#66!dZW#N^A5F;Ng+C!U8qRZnB{QA%!hgey`Q%La?>Lq}1dtc~ zr4&>yVV)7f@G7nrHO>=F{)4;l2bn=5Kuwk~WS{43ESxuusli}bIjS9UPOD4gL@&0~Y& z5*nmyPzmuH6&0@|Ukr|?frH1EiJ6*q-#jMq{Q4vDMH}JL@<(8a9}U8U z39O#L=;^J=a86)VZgWiIu%r&rH1~T4N=tntv`|a!qM{-wFl^|2>2NbCHD7vmZ?jF4 z*-o)q`>zpY6LyIo6C%dlQrG3ea^#wQqHmzgA=5`#07o)F&?jr~bS!1&!<1rCv^M z8;%wA8+{V6B9E2mi>I9)hFn0EO|B%TrfMg>L{_;~ zg|%~Vnr)2mi_{X^BoxnB&$XI1Ww&(SW6$&mMTLJ)eht@1;vq$dU=CVpGG=E`-qHp& zZ=q59^%H+X&%Vgoo4{NjVRdI7Gs7WifcZWJ{*s;9VKOy;SM&txbs*1nbIbF=VOFIq z`PS?3=4$Vy0A^k%x`@70``~Kt8~x5$2JcjDsFJuo`ZSqLoq_Mez&jsSo-D2dnIhZX z{FeWu!hrf%GtD{o6Od~Wd%h!9vg6_rN8?{BMY}OTzi7Vw}bw2#>Zw9%&$%kbBx~kjy~% z+BV2|JF_(`90*_60<*~rD_+}gV#?(;Yim*_$59h-=2WOh*R;ZWnhiyLU48qpeD&wj zF|pLuRy3BC;$~+p$wr{)UQ&nuz>jMdDC4vni*%q|GonP3@Ak?khp&go>7e$PRE=l9 z)~F|IU%>#3XTL5$bCYxf_2*em$>=lWGTk5hmOhKmjd?AAeMh zNw*k(kQk{X(r&KAJ)1~tt`z>chsJN1lPAOs0;4@h42-vN4@@?3Clfb<`;pdvAAM3HOD~LO2!XALiCq-1jMaBdJ^k5HtP&`+0~hc z?SkP6iIkWq;P0h;FL|&$u3-3#s`^oGSpLFk?Ic=J?#WDaEg>sC6BMmQKs*20^V|8) z?r7(K*29|lrxM>J|MP9mi3i?GX-3Nz^Ftp@eCmVLqAdYye?u<_ze2X-++F_+o~3P! zjW>t?>7hS3SljSYqBY!?4l41NbtTwK+I$c`3BJV6$KYJLPgu&c(O2~?MPK97xP(Mr z-F}bxo9GHlUne;F20teV{$?vU`WDWW@qF#45a64QKDR(<)53B_cw?Q$J_t(`6L0=- zVqEJ3)q%zk@xY3$Aw=K|A@m`+qHniz`;KxOeW=`45^;XcMjmJrL#HQ3mDf?PF7DPf z9ZoS($>SzR<|%pN)l^}&JHh%faVhPhHv6)~^#?B_a_PI|&AU@iCm{Ns+@;!eP4~ek z0#Vz-G*r8RFgNKe;|gsCd!bz08O=w{Al<>sf!@@7$m*p;@{rYu66!^(*ID#%?QMM5 z;{8+M)q1QHBWt~m^*M)hS#%Yn`yp4j?sK>-v=oPq)WS;hd91r@-vDAbyql3X{9XOL zK|gQQ&kx8(k7@nXaBMgcJ^#zx!{=KT8#@S!`E`Z({{V&Ha@*QW9*l3tTv<;%TaZ_^1J7$gUfm8nzXiFw9k{Ipxre}| zpFqcWap|Y_SytZB&*Uz(-An&WK=dyb+5}YAO+X>Cr0s;i0K;^gAUs9MMoR}D1IEeGqWj*otP(6MEaGAS zE0T5mV&H!o9!}o0tn6K^XR%2aJLh6or!75X+EUb+wlJGZyo@n+Nn@;r9?$wrlmdT# zS$)f|G09vX2dEM9Iu4P3?JR<8=kYUgtF+F?i`I2g@z7Di^py{sz2(RFf;ZFl4&%+X zyf%K=Smm;@J#aEH3|&YH126CU<*n5ozr44aDnF^>m(Q+v*w1LLGq(Wuap2Y`!X@Ut zd?oFd4lL&V`z|V9KL1nP3->*0>}5RocVPZo{s_K{#_%NOKktV*XfQd>4gwyfxjuY0 zC@#X}hi*5{^!j7EaXRBKKZV(mU0I$4Ahd+ew;}e0Q>s_(KP8iRbPLy|`K9gD(spNA_6&kLt+Mq6IB*K|eO` zkgdKqW~eqe$pc8eiwBU>bHi*0IJ1Yiv7f&0B2;`1&5sA!Y3}fIgvpjcH_Wp0m@LQ` zm!cj@pj1hI*qML`e1Tv6fFJc*fmPnWr@W7)ylYE}AmS26{-G9Plib_kaXBquZ8>97Gu8Js%gJ_8roH(+Xet& z-!B6+000Byn%iUThHv_FqKtvCpSaZ-7`O=x2Eu}3i^~Ii92yIc2Jxdu-w@*CB8vn{ zzEaZnp~_(Z01gRIvP*L`B{k5fJ>i8$E&bZ$H>f|bJ-Ovh;xrOk-cwsn` zlK&+(X`Bd$$=dd(;^`_JFAxq}6+1?+iu`Oxq*5Z@xi7qdzUg2?UfD&tsi6Y0+12M$ zk|i-N(runbmA+Bl2WKCqKt@Q^9ItL%MX$;B9+KV@kyIB+MMHaBjk)T4o@(>hpeXaK zv_}8r%+dzJ52^_`dXfo>k$9YqR~p?IH6P7m)ylN`^Z$yqgTuV~?`=LN*WlNq2T| zT_V9dG_*fWC*d{#*^ut!GZC(5+cn+mKkpVM&gUKB=e|m}|Gb0py;?qOY}WVVxNhOT z?qbiDcY`vUyYAS=S@bmz?7G*6lX?WeIkywVi^ArC*Ot|dw9yF#-Ux(7F1eSS8&mqJ z`VG!)qc&n8jhArzVl;F(d&!Q-a|zGbqj{2RdJ6eA?xf=zUpJIUF> z7<^TXFz-lBwt<@U%DI?max) z``aF0G^{7R(a_9m+8dbkC$Dz+Kyk82@l%iEi3i>Y-xISace+SjQ(p`HkVIhXf z{nDh9huGeDEYV+0s7tRKS$?h9FzOwQzfK+wUsDN0fr%?b%OV-*jiu&BCESV$Kem(M zIrbe2x7c?$oVV{tNMiqP8NBR~!T<2Fq;JXqt3McOZc0b8Zc}>JCcdA-a*$^uVf16V zl?1nyeo}`|BC+34jkZvHr}mY0v1IpZFEErL+^4U#iPNARnMAOBHwpFphZp`Fv^V?- zxpd#6Kwkg&Jy?jD+WEL0ogj@p{qZ1a9$~ocM8S(L)&iP5)fR+jL3#h~>bwyxk+%ZL z8>OSQd9O)hc|S;+#~5y&H<}ojWO4s!v9w+US!S6+X4MD8@I7G-B9zQyr+`Q;vq~c8sewoK6 z2|B2vPTsTpMn21ftg6SSOB2fjwoASY<}OtL8b7F*@XeA`yoH~Gf~JhBbB+NMSCQw^ z6M^O^v{3}quj|(&`m84W*ehH9ozd&)okUySm^i;&*-epM_*U}h$`G~mIKsLzE`6gP zoS9^!z{k>LqG>_zQE7~pr$NirqUDO1mXgt;-ws;LYqWGkuc75hYiMDO5ob9=mOKR= z=b1`q!r84n7vemJn&d5Q2DEF-Zvwz0IJoO7fIS1_*N1VAPY+G_6ns&c)o7fUWo{@U zNQG|`uiws3ch*Rset7su@~poO&S%4$0Jagb80{o+Pp&;&PH%94uLm%^lq9+6LO{Yt ziQ-IDg^sPyWO5mx0q~MX-qj_m&fim=YSn2D0)D?4U=9M_+zc=W0dHvrn4`H+)w?(8 z#K)M^_oyw_M^tJ%vf5aieuWOw6jHkwr#nc~C{1s!Zy^?$soL9HfN<|3ZO0@eT}cS>Mj__vl)et}#@f$Y@U_*5nHXc-j{r-4<)lKo zR{58P<#K*}7Uhr2g&t)5LqDFF= z=&7oAKYF@8UUWboo_qq)($Ox+{WS$d*YOL=Y%Y2RuG(`!%$>LAGg(A$1B%kEc)7_y zYc?#|GqNXoo|4$0xl^Yyy&p}SFw@!;FQwXB~GgjEah9KNm2!}`V0HRB8$7qM@G zW0Mn%$$G(dW{ zi+wawxo8_dh1}|h%N2n@#pkiZY%?mzFP#sf0!NENicw^@XfX!zAb zOWW~x+i%a(4kb>xPkQOOjA#YleOSi|BQKN}dbYeq0xa~(j!0v^zLW5^#Byskx`1#V z4|te9Y3Iv0-}+CTsm-$OG#^g%4f3LA);CoO`l5fLP*c6E-kmeLPzct4iv3Mz5_x56 zk|aU&K9>X)cyl(BaKhA?MBYMw(0|Sy^nsv1S|Jsx11-01%??zS(2N>5Qs6SX71)ed zKAxMer6amvf1=VFd$~qG6!gxV6b##-6AZdtM!^1LIC_MNFjQ(>q;G8JGAOdh`Msvu z%AkPnH-h!iBLOLlh>)wfBMl)NGQkEz*}pNmn8>M7cz%fxZHz9pPyJE)^qQ}7aGo?6ZEm0N?~qR88nC`=qYgt&Aa=@#)mWKqcLA1}H5e_jFl&7M)0RbtTdt zvGEyqpBtZbe$SdM!R9}xeHv6tgUHeuF&4=t+Qm;-<>{H|N*vvOt$Ih|`fP10zcv*` zocyyt&PGob@KyZqlvysi8VBmYwcCJ$e_Cbo(F)ThN0%z+BnO{f$+txYu{7bgR`QuC zUNmxIgz@Mw6!8$%yZE?MPS%#rKFh-s^Uu~4LT|!M3#WhPR+-pFxH7#PB2G}Ir>-f} zp;tXbna0~?`td-kOtl}ZP7{~t-V>DQmA|w^&GO9M*e*})CGGOOjEU`9TQ6xSxNX0w z_vV*&qSphf=uO-{!I8ZIVB>l zKONUA?xn8AFX6TrS40g@jBbyOKuxg2A}{O6%NluL+OJ&SPM6IbD{`+T?y{Na0O7^+ zAIB;+Y4cZ>+kcKLn-A1}LZw)Jn*o3dH)HdMi*w^a&Kj=+4Dv@TtF<-QG^^W0SN&*^ zTh;uSzc?_@oAj#LPwz@R>8A1WO?I3YOo&oE=8Y`dZJFCTq|9Z5r6$(<#II zb-K0QiQAq*QDh<1_iKL$K+#TmqU%Z4bv)d2fXmz{hEC-(Z`9Xmzez^+bn?|-?Sr7% z5FZ#hc?0KvNYC;w*QEdsiaI^Rot}{nkDVATkK5{WuU4n%g5(F5cHumzl5q~>+gmFL z+=JzJc9b>aMZZR&CjHuVuTETFiYf8#c7=+`&pGnDvy+3`o-?@RCqG+zAqm3s5RtSA zu>oUw!sxhm4IRpr_ha9!4{{p&jx7IS?6vR6@_S;heMgr6DE8V{>mg)Y2_(!xxQ%@C z;VG($^LKE!D@s6n>fSN@D3c;>P{(4q}T@B*5Eny5!yA%bV`0 zboeT{e@j2t3+!3^Ol|VR-&Tl2-W?;)#>m^Krt&pbc-M!o5O5o59C~Xj^p$emKobeg zC9TPpB#*_D!u=Y)}%Laj|L(#Qte3mP0vWvo2O`g126C zu1T@zj@uMx=P~gI$zn3G({#Wzcbbe>GEI92LUkpXYn|}@&Aw6lY?Jc{fo;3)t?co` z|Bt!%0I#ap+JM(SErk#g0!auh0TW^xBp@Iik^my0K@0>zz(P|wnt+9naA=BB0|=rB zDA-W2fCvgm7rhol5Kur-z=nbf7Et)#wbtx&P9pc-@B5zrdH(+>S+m|*vu0*ZpFMle zWX$KV7vsSx#`va=qrHRbZOssKYBMvGT~+FD%@A{n6TWasnxm?=0_|gw3>r1ct~XBF z$~>p56SrBD8c%ADv|w!TgnM_PuE#fT>qGLw=iVgpB@=3y&^ z^OX@9nz{ir^)Z;S`q3g-(q&uHWh_aKKt<$|)PVTded=^fQc@R5$cB91t{u{2LSihd z&21?sw&aS`sYt!vp31wF zKr+8A{uU*q2WWJfg!;D*$;oWmuvs+?|L+592yZ(SC+xsgn)l3HbQ|LDg=L9`H`lr0 zI)R#_V!gRgo`P!(8rGCY$FU7-%H4DgYvOrUi^nkxFGc4v&c4qiqXv4;oiMDq^!1yd zC^fN;w-+74QMDGgACC7-KOzD<1~? zpZ%x*6;J->@Oi=hmkB>2*#9!|PYd?HO!$}JUnz(Cf&;jg{3Ao%0aXexuqtpjfPhK< zso{9l0KWwXNLc!hD2D*@E4+;VGWb_Y(n~=B_TceO{iOF)&99rbh-phZ<+ zGyuQh=Ypvc<*xtYf2OL!@a0vaY)}2djo(5gdMRWPk-xBP9nfS|t{Vx-K z3H&S7gB?|YJyihTdb_hvuG-fp7gy;Il8dYK2g$`%+PiN9FBUNV@wu8Ipm$*`a%3br zbY~hDbv=whBs0q4eS~SK!#f=`1J8pU-q{%W!ae0Y8hNoj6pmNW$~keVPhyZt^OWH|fkzy5F~&~AoDbh7i!QkxtE=!m*Vqr6lZ!_; zq`MoY%9ea8-Z1l2D?SQy2O|-lJ-h8bt1K1D_ux^oc44@wzO{ESV)B$;2@m^G)-Ybe z%|hyuW87uzff>^)m>GK)K@^`utBJjd$KkA1{w5pfBrFG{F^rIT;wWy?!u#Sb-4+VN z(}IPfKP^cAaMnLt^oNwO2hPa7%KDeEeot$dlQ6?x${eQ5-Z>DJ_t2Y_<9#0-KLlQXFEg0{QxR zR%%-$*2ng<%Xkme;Am0wo*s7T7OzCo@bKtuh)UsxOa6|-B2c5oGHp--goRndS6&QYEwutY@f&ZGRfXZj5&ygoAR`&Rx2LCs7y!W_&f6< z^4*ntxZal_4&Pg_)}Cu^>xA!l*RIFaB<~@`YY|@F0Pz%m3TLqCs@N%{7<-SlY$BYO zmpvL2QQRHA`b5n?R4|wN~dpvvx=Qfx3 zF?f4scj9y{g0o?#tZUJ0z!m3x0)K7yV!t~!F1F-JaNZ?;DDe^!=UvLYa}3_wwYVIRQ0ZilCai>WBFE)n>5g)7}+;pW(s?Pj9Y)_{g{ha%iBsq5nRRC$oB7>NX@0-+EpRqG?;> zPn)>@ze-z1_1J%=?F~&O>?W=r4jjRVP2FBK3vc?x#MQ%rQzLPe#BfXq#juvM3MXDe z5~t*K5z5k(!{*DDLrO@zy}zmDkR0dyXMwaUm(c$xkSjv}2cbQgbsVBn700FSK#uTS zSxQOM{(2zo;{5+l+A}JpJ+@NXdlY|JbKY;r<9-M2y*LfHBNGQ2 zFm|Q8i#vfW?twq@;7NVax-RRBEupPk?%YSF+xq&lKJ1rEmnA!y#R3I*JDAw7gB|W} z4u^?H@iaJ)kXazk)B)&Ri`5Hh{D+sVssoN?@u-6X)f-WR(pEc4nd%mCoFnxhtKm1U z4c-y5cnN}GbSOOU9Ex=Cue`bRLDF;n3z;iKp8T zRlxrP_J3BwGn$G=*6DcFeFAqjsT&N#bygX9{@4j=xR?7i46*ZJ0v%!lT~oO8dXM)x z?wqVN=k?|+959FXd4T>ya582)D3hwS)^l&Mj#?Kjeg?qZW!f!{Ot+H(XQo>`aE7lN zdlZ(u05v{Np}IJEIHI%%S@G>*xBVC)+?iJ8|E!XOEOnrYR94C73?B-H8#u=#V!XkD z4W(HnKd=nm#0*5x)hW}#;-_H+?+=`~2Ch^m_TB$M3(a{IXPMiLA>38w0g-Zb5!;i0 z_R-Pe*PsqZM;+r?HnlxwsQl94NkQv4#&b}ECKUh0XlgFEnLr#N8b|1Vh{Lhc#KE%u zI9dpbI5_C#;`q%R7KzPqI~&Gi#})TP2)tc$3YnL;ip)lzE4JZnsc)kGl!bFz@HyQ7 zkQSev78Vj!oZl68T3CZYE9IB_2+U#R=WsS+ia)-Nsc&(-)i_>PRWyQ8w?A>!nZ?`Z zbu_J!V0F%Om#U?PVfIPf&2}PLvEN{a?L@L-zrjulTGuh26`G{iGa0Jy7|*d9v_WtI zW|jQORV)hL{yO|%Y{Q(AmE1s`rEDJWi|S^hcwd5B)0=D3Yv-4!VNRWrRVvFV_O4bp zr_=kgx;Y)*SJcfZ^}Y&s8a@XT%Z&f6Ys1T1*O*p%(jNH_5>7tl@A=P4+<3C~UzB)Q z1bO0q??sb06DyXIo;~~QDGfe*h+}(_VDaFIkIZ$NW0Dx`;onlr!nf1{^1lY;>r|5e z#P-O-w~7PuXZ>&`HFzd%M;o7(z*kL4kUsqE%Vv5q)5hNsus~XGn-%#o!8p%}$TMaM zm^&sTlsO0I^mYP+?*`#eT!?oltK)GEGcvxICLn92dHkYQTF6**n>hCv?aE9J8QY!R zcubMmC}iw4?8c*}%qv62_Fy+2EM+!Ji^gk*uW?Oq;XTW~XxJJ3qTQk1H3~WPTNioJ zI2X89psJtTsOhTuy$yW9%V7QwZFpbju;{6YOPUX;J8g%H9a$wfqT=!49h5*@z=d83 zsZpHm6e0Y`J4K$2v&-)FZ+7ZAQhvuCcinJRmaZ9f$w{4HEB@JL_hR7}UbYTt!SI3w zRCw7N>~@qgaeA@r3GZLFJ^Tp(h~#eW}l#3?qKQM%F>ep((Dr?EybQ!S$c9nnteo? zUz9dQc$Fy?p$X+J4J$OEyp>^vCX}}}tk8t=s|_nOp}dV@g-ZGIJ6!zJ51n6# zS~xCx9t@<8eS-3mvkKC^DoZ~UkY=AC=^-rLyR!7OfHeCANzY~J>nck>9FS(8An9!^ zeSKx=M*`C9qta4$&a!ktW$EcGtu>8(q&B3rjPtRT<9x=rZH@VaCX}}`tk8t=_J$Rj zP~O3?LKDhyhF038P;Fb^sOgREuUL&#-C(Dx4;&XgvjTZwpP(G~W$C_^rDq4E*(XSP z5=-A$S$a-Dntg)ovyP<;D@#`d?881mZRZR<{Ke-?;dbM%cU!RB0?1VpzXeo&GjVwv(cOa3;MznJwwZMOW)k-yFH*SjlNRk2e}-Fv%; z|2-%Tamt}?zr}B^BvcF`_0KA~gUhhAN*P|ld2_6Cz1VLRdWdOO$vcSGw>voU9DVWK zdmWwt`6Juq0CxBT5h8a%5kBabw(DDE|A+pI|1`gUR>=gm&cguwnQK|YDxQmWn))&9?MFK} zy^D}#N9lCPqWx6m?UWQd`LoS4AjDreV(Rq1%LWZ@aL6}**~(Em6aHoo!L%oM@y9~( z*+m5UH(vj`oENXu9Hn#o3Il773SdF|1k7SDVlJauB^8{1JRk_HTC}Sw?O-K20Os0gfr`4IxTIo?v{gTE`e0EPhzf^`N3@%JoovDiv6F!vs z1V|9C>M#ptwdE(E(^1MnobogxHuYa=? zmHds@q|11@gLT_sFQc~5gf(79aJbB#Bc#1hK&Hk@WpvC4r?R0$d-yf|Bk?RrCCtxyAdRY}B*`j?#UgnXy=} zG7uDkarI?sR>)M!0F;YOUN{v804_fP!~uW{QUGyqowMY7w=1zPQY+k1%6aB*|1$Ls z_VAWUP(S!NP<$x!2rCJ6mWP>zZh<>EQcof;j#5Tvjl@?Af_N`J+!#IvJ4SV}Z7uQ{ zq4@lrC;(;kY3L%DW=13P?2-C2QsMVvM7&rg^&ERi=#~!e35iy86yoOYFCbudvt>uY zAjPFj9i{B`IIN&TvNy!XdL6tsq`rK!hiE`XILxe(*q=a-??L)ekYBPo?6RP@XQ$kc z>}Rgy#tqNN*in8gkFkUjyyY7{&UkT;E}`+tqxTy2$DXn1ca?n&rTC2PaVE~PV@w>S zOiKT-FoJx=fUE3;5O@EwlW<_~gwKcHfPCPLINlEx-%|Fk8s1##$hB97i*B4XpHXclh2k<3ZTDkX=l)CYpqLSnOPn52i;39pn)lg{ciW}>Du zp#&)mA7=>Hc?aE%bo2MQG~RGmzEdjzKDSBIAaF*vl#9WMYt?WC4_k_zxHyQj0w-z) z`(tD3Q|AGaXi@NA93nz3RGvfozZZ73KRqVW|ABhl z3sd#n{e8DrepF2F*!TsE?8oWz zKyDitDKI}{Dcm_B&-|6cGyiYVdVl8NwaR|tfJ+>u98u|P2e4B1an8j(O?a>0QOW|& z3UB`q?cbf0gS_LkP;?xBNunk)&QXNS#a`uL{XD%tny)>0l-7n#dkoX6aXcr|Xk8jD zm#tgAHG=npP`0dS*2okrtl-eszhbMQ@+hvk=EUJJSY1zO$P&CJ%lQtS->^og+#B6r zFAh=#IfwDi7#b~SAgqEniet0WHFvlX75ARZPfbUro)q_N-WSJ9Tn%wet*Yv4-X1%H z;5i$_wr<#i2%mEDr!`!!IFa|$_;X*Fjc{V4L+0|~tBk8dlko-_KByLfR}^s5A+BbS z!&tG|u__2S4wrf0Vzv2mqeGh=0v{cMD;xO22KhXgCudc9Tv&XLGdA6g1SMCGsTLwi zaXUq5Jgz1M=qTmtk=htFr;Jk@nh-i5zDRG!aK(oXh%;_Cf08UB{VLp2fg3HNUGcbP zjIXN@M_m8fd>s_V63IaIxwx)?ud|5Y%P!$9cRZ(oGCPY-H(;>y8l1sHO9_sxD&5AN5Ibimkh?Fhd9F|G@2nUL2veR z`p~cpzHwf_ppG=OPEovco9Uf6pAd<@j7S|7sqe(AC7h;U1r4he)faDn)*EQn(QkTa zC-$35xi9i}ko>exdR8Y_E_`0LKR$YhS7&29=~3EMM&k-b`&MkzOPx_>MK|`HiJL_t6Nb^wz(4A=jTp%F*b4)0oy$ut|`AQG~ufWEH;hL5u=@}d_ z1~UcjqVd{kk=@1i@2geDgoG5n7>R3e_%U5<)2X5IH9H;w0HK;maJcblnE4rJY~EJJ zS2P+4wB?{1#zQfSQ6;8B=Veqj4%pgo&f3t}X3I3RjTx<>oJ7{Dc)XqT(4xNaAp`i* zCDx1!gYrz|xY!mqzNV+eqh@jM?+ZxRxtw%D$auUf`!b+8jeZdtr8Ukh6;KrF+Ji z#HdePQAOF`kO&k(y~@eZ1jIYWv&uSlffa`}jBefvDX$#1{^i2rG)<+jE&ma$!R3PC zoCpVNSWV;nMANHwMYg!uv#%`nzG`Rd-x$SF%1Oi(wycbeRV3wuCjFtUkyUk^$-}Lb zQX;lnsRAZD8_=yjv0BecK0{Nvqy=CIX;=N2l<6}Niu7}=BLYz{jiS#rpP(P zCP?EpS#!DY&?YlJR$hU`mvmxdQ}?nI^3OgF?~Q(9-W-P-!V$XA*H8f_Y)t>E`nZX zd4K%HMjawf#hy*L+l!4FY!=A*yEZC=mz@P{Q$B!$fW_WTc$)1SSJ^jCeeEK%X;w)e zwnD97D>QJH#su>{obvTFoWKEEb=%x{#r?zUH*>jEb7v~HkixxpUk3WW}D#p4Q)AD5{u(bsm=M3nlm*YPYTNTt7TY>@>j~RpTXY}L-`Hll=DZv{N>km z4}YvnPAfUR+Yn*eNn20Z2qXewJ2P>Y4P{dDIsTUQ2snGPGgdyH0sTW*KYymduRpMT zpH*@z`|`&V{J!H-ld!XtQCr?>Py7lq=#s-wn2PN#enerL98WifAZfUN5`pz;$NRy2 zR>??K%`bNN!!&TGK7&eejp$Ur?r{fx!|_<4u}ub_DmG6jvbDFQbSgY`xO;M4tB z|7HP;O*@JY?_#4WTv(ti>A2{*6LKYs@h4edbhw)prDFdJ>EwX8Ku04)zi5NF z)H=M^nN?ECetb?P*e{X&8pNhHVhTI(o_M^m2Y(J#j#*Xr#Ky}(#MF8$_FrTgh)QSb z6++`uQw&WtG>vIi$y81bAFBu?CpNX2$i$}R7}*wvwluUg(|;F#f~GNxWwJ_UvVJ}~ z5YQi&+R5nYU}!s`&eSf3<}uAGna^SQvARDj_xaIP?Sd~?cH;nIx6Auq^I!b0HU3?0 z=z%MNF~H%+FHGH=lhrKis%EWnW)zS**N2;SW&CrTXZpg*E5YJ9_(;*q0=mvESP zd`5l*UesuO;|=6Xc2N|t#@%kbhR=4*0e@;I_%3&PZ((8l9AsJjF1^w5;f7$&Tg~}U zAIIJef4;`Qfq1WdDH3uX`*XZkhU!Mc(MUeS{1aXXuj$MJ#W+Y|p!|WEiTH=-(1gRm z@?BoEUaAlOI8e49obtWhzu{o_T>L|4LpT~s_+$@$_s>!vbX&A?G;a3$m}9(%4McUd zz#|)Vh2KT={YR*?v}>;fg5L1bW_%Sb1s=l9hwOw6cZN=6&Y%P2JuJ5LE5Bd&i zjjNCrb%mkzXnt?F!**>%h*dKQ+nz+j;PTKS)Nu`q&(w*&ENX&5I-@b}521TQz&A`wrWM?-H0^FX3)=g;TEI;%;EtuN}C{c16QZIH!q}j=*a@ zoKg>+h8*WSlI}KT)Yf_hwSj*(IP4nXq0XU}DXr>w5nIdcCYNQbHq~F-fAXrGVGX?c zqc*+HU%uNOQLL>sXPHES6nt>4EjMV8?OKM>Q^TG?s?>F8q3zmt&`$j{)D6wYeWqkQ zly%gWYdy!V8<$MpJe2Xc9pdZvZQsUac5gd&v|V3LqpoSan(MdKUH)Eq{t7#eWv0G( zXw&C*2v;xLHTpAkh0{p%n5u6xhE{daN?S`GlpE(VhyL}O)|W_nVxy)sk~SW(UA57} zY0b87Qai)f*uGUzX`M!GSG%k>zLE5lab;1DYqUOR(Fd#TI^N2UZGr8%-g_t=b^MK` zTsNNl&W<;))UI`tMiPB~fPL|qDdYP3TdW*0u0+yF^yuuuZK7N3TpmR}IG5oxILgNE zM_bVHJ%F02uJIGp6-mobOL#Pmq^F15qvQ|%5OqG)^x+1YHFnrmlWo`NRkjujEs?d< zq;BZBbTsIUvgBURLwjInb-m@+JRfyK$CS-`Z5Q_ziWT)$&9U?dwCX7ID6Gb*uSxqi z*(1riTkM|tw?0IlzQ(@$zOsE|5LQQ=e7`H%xEj$t=yS9O_Mx4tYld-oXwE{7%|jPV zT0At--^zN{w0-X^v|TBYmZ=fNQ|!8dn=y^W3m~WU*fnDFavRHlugVR~v0Y6+P~UKRJxs0Ppj#W1>Kd4??I?$S-lJGYR}*OAUq@FP zwnsM~GVI)5(Dz~VRVXKTC@W%sQAT4&2`kFayJ#N^0B z>oHoXC13Wta%b`ZR}apd)4v-Qm=i}pD1OWfmTNd#k%yeU1LqZmP3Wx)w+>%JK8>LiKMk# zY}doTW9B=9vHOuX?YS-tw{y7yaj@nrs)PEcW9@mAo4UIB$KR24?f&Zb&((z<3GL(a zGpzYMTA8|5owEC(yARv05g4Pim+Fbw)Kwe#P*)qj+-4+8;LXs&)QWc+92GcC=$yER*Q{Mw-~9w69`3kO~A;bKpKk-&-od7 z0Z3~>c<#^0Dj-)2!fOCVUIo%d5MB!~@;Z>Vg7BJvkqtoF3BqdwMm7OyF9@#@7}*M> zgCM+CU}Ptdj)L%-fsuECbP|Nu4vc&Nq_ZI3Z_!X61L-0NuO(P!KagBOcum2`At2qv zhBZv6BS5+f(l`j|DahuxRS(NtFM5Wdm9w5AAbmxK*AI-a%pj5BUfq@%D#+K@C=zDX zrP~GJbp^{r0~tvZQ4_eg*HHCnv>@N11=~moS&31y@?fNng;}{EyaG{~26Uf<>TSxa zh!SbMAiNGynJW##P^ac8XUO?6g z(&C-~^12|SQ0G-9oz@F-y&uWIo&eX(c77xiyV;D~c9$J03uVv9Xa2aF(WipkgZ8DG zv*|NIUi0h8!4q~4wF`4hm1$1L1exuZxth)ha$MCK}E)h#HoYXn(n?6U_*PeFc1 zZ^?S{9KCSc73bpGpinmmvMMN4p&%cjx3yy*D9A2@BwD@j6)EoP>KkhmA+9l!!XMD{ zX{bJUV#+dm&~GTx7yTm?QLSZ)9KrL1>4Gf7h|DsK%ogO@$N(}=kdmkXvQUsiH3P^K zg0#T6YU^1l$oSd;WVs-bu>s^cL2il*ATL_nDtlo@u4(Q^FH7FB8*7jO^sa;&8)k21=JL$^_gAm13IC3?&-L9QmXVH+Ao)g0VXPX1uqc{o)E zLUi!BjoeRo0|&JiUk5g2F^-}f6M_8U&uKZ;#Ap&lwf6{Xg3LG8 z=nABfAQO!>dH}gXkZ+7NdIL!jq>HgeA0VlMJYuX-2qaCAdd3<9fMf_V(&YVSAX$>r zkNvrrM9t8-MA1U*#b~XYOgWOTn*R1ai>{GSe_{MmndfLJWH4vRS7a2v4)v-a9k9w} znbBBczAebN#y;O#FVTAv*W)K_J*(*xLB4v)MqZ(P5?9zU8(B;Hr7R9%G}qWSQa$Wc zM9~-68Bk<9$c0q_WS*1zmehWBs3Ww%$#o;agesyhX_07d`L!+cB|RhQDZMd(JS#Hu|FV%| zv`%Dd9I%nEXq(7f4`XY-zM`EXTx0nXt1WXQ-YacVoVx$S;&A2#*DdT%Z&eTkM73Y~&JU2=WHXTxDEVzKd%V z?^zs3&bxzVa^ zl}VbvfmTg(ZEJ$a6hc2EYaNN!EJ3C(RGGDoBx|7{1#4}jk@dJBQ%~E-71oP_+&95S zQml0@uHn9OiWE^3YrTv6!#|!OnvlWw2&P(J3ex{sMbc31Tdv4P$CcK%f-HW{ zM$)WPf|RbXk#y^f*yk7YhmB#8B1*S@6XaoVisV@2=9aPyGhs#uvxHQvsG?{GT7k;s zTiI?d_wCV&%%p3rPJ*<;T%BcR;`UxezL;wx{R}b{D|+w ziV%HdEtgPJ3{nK-SwWspw>5uktrFxuzs!E?P4_aS=K;UP4q7{r<|u0GmpN$dl2CUX zQ9VyOj#$SfUpE6+YkXmSC&)dR&nxm3BhdT-=5?B{?~KgAEs7kW)7DuDwW+5a>a_KT zggS*DNkjbtgv&JkT1Bv5<{0c@#2thT@o-$73{nJ{p@O9Ok!p@xCDiQeY(3Q+w|gcc z)KY^KVZ^;dLcL~?>zxsfVnKHLks6K%1gV*Br!d+v(ZfCMpMHzga!eB>e~!wWvtk`H zCDbzpdC3{?SOtV=jlX6#bZixwkgc}nMvi|8l3bz43TGq7haR2-{Auc85oJ5R7nz~k zaUy&%w~MPBze6U9n*LxTJsj0SxXyQhZCPd}-R_9M8Y_zC54L4S3PQ9MeS+$l=tvTo zW8)QB=3MB=5~Rc**Fs0r5YGF^*X>a29r=(U%I$33TtSn+Rhw7)*=^-oU{5m?-t}L8Ak#zij5kOTLR~A!Q+{N(v#%hF3^LI5uCq{(hy2I~ z&RfE`wjBD>PR~cq5lC|s{T+mi5@bUJ(GLsQ&L27N5#&vjyCNSu?-v9IgbcFJIZ;aI zoqaZP&^cXl5s#jPLlGTv&X(BQ)xd37_*VpEzJ#iYx@sc}#2Ra!R%E^7xO1W8;%?MT zmN{n~cRnGAg_@~Iv}>mzus?16m^HU`{Tjw~wI})nMLM}0;cWS&0!4^Ax!mDgeEenM+4cEYv~r)#hc~2O>FL z?CK)OC+I5_S?cO0p*muBMUkgny~DZX?gY|NLalHW39{dhJmVS?&gFFimj7`-$Mvl1 zcF{Z~2E95)tRi~Rbq^4t&9!XgCD&Mysfk>u%qmxzlou`;rmrQg)vk#WSKJ3S^0Mn8 zk$L+u8+pa`u%!7DtRgkktF9S9h~C~~BkNpGh@RS5w-$fSDSXqlRFG&`!$y_~;_}P9 zffCIAd=b6v`Y7C)6h+4~U^e`UXwN^81Fi#AWDdDL3&*mJnvK#>N9eHYs360G zkgvj#C9=+ncdj!M>gHQ*jD@Zl;Y>ISpca>Z?(f=rt>+UUS{>Eh^-Q3p+a=qI|u5lMs$?3K3 z8$@P1Y{Wn8g1QT>Y zAU*vye9(QrAQgV(A@?{zYWYJ=bC0hQ`@`;Xk$KJ^YPx%(Ah-CD8SW{9ocGtUneGP# z$?>OomV25YJ^aXQ_alPT@Q0e?o>9$-`usK4`1~uP3iqrk>6z!AQ|)QU_`0hMKee1E z_2=R>iWC8Pyc)W0`pn;NEO4(uTsV<=hst1o%Ke5QB|*qr)vBR}ulz%0Dy#+Ww*@)8 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 z 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