AquaMai is a comprehensive Harmony-based mod suite for the Sinmai (maimai DX) arcade game, loaded via MelonLoader. C# / .NET Framework 4.7.2. Multi-project solution with embedded assembly loading.
AquaMai/
├── AquaMai/ # Entry point — MelonMod subclass, assembly loader
├── AquaMai.Core/ # Startup, helpers, lifecycle, i18n, shared state
├── AquaMai.Mods/ # All mod patches — categorized (Fix, UX, Tweaks, etc.)
├── AquaMai.Config/ # TOML config system — parse, serialize, migrate, reflect
├── AquaMai.Config.Interfaces/ # Abstraction layer for config (used by HeadlessLoader)
├── AquaMai.Config.HeadlessLoader/ # Config loading without game runtime
├── AquaMai.Build/ # Build-time tools — example config gen, post-build patching
├── AquaMai.ErrorReport/ # Standalone crash report WinForms app
├── MuMod/ # Separate auto-updater mod (downloads + signature-verifies AquaMai)
├── MelonLoader.TinyJSON/ # Vendored JSON lib (empty source, likely reference)
├── Libs/ # Game DLLs (Assembly-CSharp, UnityEngine, etc.) — gitignored contents
├── Output/ # Build artifacts
└── tools/ # NuGet/Cake build tooling
| Task | Location | Notes |
|---|---|---|
| Add new mod/feature | AquaMai.Mods/{Category}/ |
See category READMEs for placement rules |
| Modify startup/init | AquaMai.Core/Startup.cs |
Patch collection + lifecycle orchestration |
| Config system changes | AquaMai.Config/ |
Has its own AGENTS.md |
| Add config entry | Target mod class — use [ConfigEntry] on static readonly field |
|
| Add config section | Add [ConfigSection] class + entry in SectionNameOrder enum in General.cs |
|
| Conditional enable | [EnableIf], [EnableGameVersion] attributes from AquaMai.Core.Attributes |
|
| i18n strings | AquaMai.Core/Resources/Locale.resx + Locale.zh.resx |
|
| Build the project | ./build.ps1 → outputs Output/AquaMai.dll |
|
| Config migration | AquaMai.Config/Migration/ — implement IConfigMigration |
Assembly loading: AquaMai.dll is the MelonLoader entry. It embeds Config.Interfaces, Config, Core, Mods as compressed resources. AssemblyLoader.cs extracts and loads them at runtime via AppDomain.CurrentDomain.Load().
Mod lifecycle (defined in Startup.cs):
OnBeforeEnableCheck— init fields for[EnableIf]- Config-based collection — sections enabled in
AquaMai.toml OnBeforeAllPatch→OnBeforePatch→_harmony.PatchAll(type)→OnAfterPatch→OnAfterAllPatchOnPatchErroron failure
Config flow: AquaMai.toml (TOML) → ConfigView → ConfigMigrationManager (version upgrade) → ConfigParser.Parse() → reflection-based field population → re-serialize on save.
- Mod pattern: One static class per feature, decorated with
[ConfigSection]. Harmony patches as nested[HarmonyPatch]methods. Config asstatic readonlyfields with[ConfigEntry]. - Lifecycle hooks: Static methods named exactly
OnBeforePatch,OnAfterPatch, etc. — discovered by reflection. - Namespace:
AquaMai.Mods.{Category}matching directory structure. - Config bilingual: All
[ConfigEntry]and[ConfigSection]should have bothen:andzh:descriptions. - SectionNameOrder: When adding/removing sections, update the enum in
General.cs. - Fix patches: Must be
defaultOn: true, exampleHidden: true— no negative side-effects. - No DI: Static classes + shared instances (
SharedInstanceshelper). No IoC container. - Fody: Used in
AquaMai.Modsfor IL weaving (FodyWeavers.xml).
- Don't patch in General.cs — settings only, no Harmony patches.
- Don't add GameSettingsManager/JvsSwitchHook to startup collection — patch on-demand only (see comment in
Startup.csline 198-200). - Tweaks must not change game behavior — if they do, move to
GameSystem. - GameSettings vs GameSystem: GameSettings = override existing configurable settings; GameSystem = new behavior not possible in stock.
- Fix patches must have no visual side-effects on the original game.
- Fancy patches are not well-tested — enable only if you know what you're doing.
- Don't use
hideWhenDefaultto hide useful options — only truly unused ones. - Don't use
alwaysEnabled— reserved forGeneralsection only.
# Build (Release)
./build.ps1
# Build (Debug)
./build.ps1 -Configuration Debug
# Build + copy to game + launch
./build-run.ps1
# Config env override
$env:AQUAMAI_CONFIG = "path/to/AquaMai.toml"- Target framework is .NET Framework 4.7.2 (Unity Mono runtime)
- Game DLLs in
Libs/are gitignored — copy from game installation manually BuildInfo.g.csis auto-generated fromgit describe— don't edit manually- CI builds on Windows via GitHub Actions, signs + uploads to cloud on main branch push
- No unit tests — testing is manual
- Config versions are migrated automatically (V1.0 → V2.4 chain in
Migration/) configSort.yamlinAquaMai/defines TOML key ordering;checkSort.pyvalidates it