Skip to content

Feat/mumod-integration#44

Merged
clansty merged 17 commits intomainfrom
feat/mumod-integration
Mar 7, 2026
Merged

Feat/mumod-integration#44
clansty merged 17 commits intomainfrom
feat/mumod-integration

Conversation

@clansty
Copy link
Copy Markdown
Member

@clansty clansty commented Mar 6, 2026

Summary by Sourcery

集成 MuMod 作为 AquaMai 的替代/补充模组加载器,包括后端服务、配置处理以及前端 UI 支持,并使扫描与初始化流程支持异步感知。

新特性:

  • 在现有 AquaMai 功能之外,新增 MuMod 的安装、删除、配置和缓存管理相关的 API 与服务。
  • 通过游戏模组信息和前端客户端暴露 MuMod 的状态、版本、渠道和缓存信息,包括更新渠道选择和缓存校验/确保功能。
  • 引入基于下拉框的模组安装 UI,让用户可以在 AquaMai 渠道和 MuMod 之间进行选择,并在配置视图中提供 MuMod 专用的状态显示和渠道控制。

增强:

  • 将 AquaMai 配置加载重构为共享的 ModConfigService,该服务可以从 AquaMai 或 MuMod 缓存中获取配置,并复用签名/API 版本检查逻辑。
  • 使资源重新扫描和游戏数据初始化路径支持异步执行,并在推导资源路径时复用统一的模组配置服务。
  • 调整更新检查逻辑和设置 UI 以支持 MuMod 模式,在 MuMod 激活时隐藏 AquaMai 专用的渠道设置,并改进 i18n 字符串中的版本/标签文案。
  • 允许 HID 驱动状态 DTO 的设备路径为可空,并微调浏览器启动和开发服务器配置以改善开发者体验。

构建:

  • 更新 TypeScript 客户端生成器以使用 tsx 替代 ts-node,并重命名生成的 API 标题以区分该客户端。

杂务:

  • 从模组管理器入口视图中移除旧的 “未安装 AquaMai” 警告弹窗以及相关的 i18n 字符串。
Original summary in English

Summary by Sourcery

Integrate MuMod as an alternative/companion mod loader alongside AquaMai, including backend services, configuration handling, and frontend UI support, while making scanning and initialization flows async-aware.

New Features:

  • Add MuMod installation, deletion, configuration, and cache-management APIs and services alongside existing AquaMai functionality.
  • Expose MuMod state, version, channel, and cache information through the game mod info and frontend client, including update-channel selection and cache ensuring.
  • Introduce a dropdown-based mod installer UI that lets users choose between AquaMai channels and MuMod, with MuMod-specific status display and channel controls in the configuration view.

Enhancements:

  • Refactor AquaMai configuration loading into a shared ModConfigService that can source config from either AquaMai or MuMod cache and reuse signature/API version checks.
  • Make asset rescanning and game-data initialization paths asynchronous and reuse the unified mod configuration service when deriving asset paths.
  • Adjust update-checking logic and settings UI to support MuMod mode, hide AquaMai-specific channel settings when MuMod is active, and improve version/label wording in i18n strings.
  • Allow HID driver status DTO to have nullable device paths and tweak browser startup and dev server configuration for better developer experience.

Build:

  • Update the TypeScript client generator to use tsx instead of ts-node and rename the generated API title to distinguish the client.

Chores:

  • Remove the legacy AquaMai-not-installed warning modal from the mod manager entry view and related i18n strings.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Mar 6, 2026

审阅者指南

集成 MuMod 作为 AquaMai 的备选方案,新增用于管理 MuMod 安装与缓存的后端服务和 API,通过共享的 ModConfigService 统一配置加载逻辑,并更新前端 Mod Manager UI 与设置页,以支持 MuMod 渠道、冲突处理以及异步重新扫描。

从 Mod Manager 下拉菜单安装 MuMod 的时序图

sequenceDiagram
    actor User
    participant ModManagerUI as ModInstallDropdown
    participant ApiClient as Api_Client
    participant InstallCtrl as InstallationController
    participant MuModSvc as MuModService
    participant FS as FileSystem
    participant VersionApi as Version_API
    participant SigVerifier as AquaMaiSignatureV2

    User->>ModManagerUI: Click MuMod option in DropMenu
    ModManagerUI->>ModManagerUI: doInstall(mumod)
    ModManagerUI->>ApiClient: InstallMuMod()
    ApiClient->>InstallCtrl: POST InstallMuModApi

    InstallCtrl->>FS: CopyFile(MuModDllBuiltinPath, MuModDllInstalledPath)
    alt AquaMai.dll exists
        InstallCtrl->>FS: Delete(AquaMaiDllInstalledPath)
    end
    alt MuMod.toml not exists
        InstallCtrl->>FS: WriteAllText(MuModConfigPath, default config)
    end

    InstallCtrl->>MuModSvc: EnsureCache()
    MuModSvc->>MuModSvc: GetResolvedCachePath()
    MuModSvc->>FS: File.Exists(cachePath)
    alt Cache up to date
        MuModSvc-->>InstallCtrl: EnsureCacheResult(Success=true, Version, Error=null)
    else Cache missing or outdated
        MuModSvc->>MuModSvc: ResolveApiType(ReadConfig().Channel)
        MuModSvc->>VersionApi: FetchVersionInfosAsync(CosVersionApiUrl)
        MuModSvc->>VersionApi: FetchVersionInfosAsync(CfVersionApiUrl)
        VersionApi-->>MuModSvc: VersionInfoModel[]
        MuModSvc->>MuModSvc: Select VersionInfoModel by Type
        MuModSvc->>VersionApi: DownloadFromUrlsAsync(urls)
        VersionApi-->>MuModSvc: byte[] cacheData
        MuModSvc->>SigVerifier: VerifySignature(cacheData)
        SigVerifier-->>MuModSvc: VerifyStatus
        MuModSvc->>FS: WriteAllBytesAsync(tempCachePath, cacheData)
        MuModSvc->>FS: Move(tempCachePath, cachePath, overwrite=true)
        MuModSvc-->>InstallCtrl: EnsureCacheResult(Success, Version, Error)
    end

    InstallCtrl-->>ApiClient: 200 OK
    ApiClient-->>ModManagerUI: Response
    ModManagerUI->>ApiClient: GetGameModInfo()
    ApiClient-->>ModManagerUI: GameModInfo(muModInstalled=true,...)
    ModManagerUI->>ApiClient: GetAquaMaiConfig(forceDefault=false, skipSignatureCheck=false)
    ApiClient->>ConfigCtrl: GET GetAquaMaiConfigApi
    participant ConfigCtrl as ConfigurationController

    ConfigCtrl->>ModConfigSvc: GetAquaMaiDllPath()
    participant ModConfigSvc as ModConfigService
    ModConfigSvc->>MuModSvc: IsMuModInstalled()
    MuModSvc-->>ModConfigSvc: bool
    ModConfigSvc->>FS: File.Exists(AquaMaiDllInstalledPath)
    alt Only MuMod installed
        ModConfigSvc->>MuModSvc: GetResolvedCachePath()
        MuModSvc-->>ModConfigSvc: cachePath
        ModConfigSvc-->>ConfigCtrl: dllPath=cachePath
    else AquaMai installed
        ModConfigSvc-->>ConfigCtrl: dllPath=AquaMaiDllInstalledPath
    end

    ConfigCtrl->>FS: FileStream(dllPath)
    ConfigCtrl->>ModConfigSvc: GetCurrentAquaMaiConfig(forceDefault, shouldSkipSignatureCheck)
    ModConfigSvc->>FS: ReadAllBytes(dllPath)
    ModConfigSvc->>SigVerifier: VerifySignature(binary)
    SigVerifier-->>ModConfigSvc: VerifyStatus.Valid
    ModConfigSvc->>ModConfigSvc: CheckConfigApiVersion(configInterface)
    ModConfigSvc->>FS: File.Exists(AquaMaiConfigPath)
    alt Config exists and !forceDefault
        ModConfigSvc->>FS: ReadAllText(AquaMaiConfigPath)
        ModConfigSvc->>ModConfigSvc: Migrate and Parse config
        ModConfigSvc->>StaticSettings: UpdateAssetPathsFromAquaMaiConfig(config)
        participant StaticSettings
    end
    ModConfigSvc-->>ConfigCtrl: IConfig

    ConfigCtrl-->>ApiClient: AquaMaiConfigDto
    ApiClient-->>ModManagerUI: AquaMaiConfigDto
    ModManagerUI->>ModManagerUI: updateModInfo(), updateAquaMaiConfig()
    ModManagerUI-->>User: UI shows MuMod installed, channel and cache status
Loading

MuMod 与统一 Mod 配置服务的类图

classDiagram
    class MuModService {
        -logger ILogger~MuModService~
        +MuModService(logger ILogger~MuModService~)
        +MuModConfigModel ReadConfig()
        +void WriteChannel(channel string)
        +string GetResolvedCachePath()
        +Task~EnsureCacheResult~ EnsureCache(ct CancellationToken)
        +string? GetCacheInfo()
        +bool IsMuModInstalled()
        +string? GetMuModVersion()
        -string ResolveApiType(channel string)
        -string? ReadProductVersion(path string)
        -string? NormalizeVersion(version string)
        -Task~(VersionInfoModel,VersionSource)~ ResolveVersionInfoAsync(apiType string, ct CancellationToken)
        -IEnumerable~string~ BuildDownloadUrls(info VersionInfoModel, source VersionSource)
        -Task~VersionInfoModel[]~ FetchVersionInfosAsync(url string, ct CancellationToken)
        -Task~byte[]~ DownloadFromUrlsAsync(urls IReadOnlyList~string~, ct CancellationToken)
    }

    class MuModConfigModel {
        +string Channel
        +string CachePath
    }

    class ModConfigService {
        -MuModService _muModService
        +ModConfigService(muModService MuModService)
        +void CheckConfigApiVersion(configInterface HeadlessConfigInterface)
        +Task~string~ GetAquaMaiDllPath(ct CancellationToken)
        +Task~IConfig~ GetCurrentAquaMaiConfig(forceDefault bool, skipSignatureCheck bool, ct CancellationToken)
        <<exception>> UnsupportedConfigApiVersionException
        <<exception>> ConfigCorruptedException
        <<exception>> AquaMaiNotInstalledException
        <<exception>> AquaMaiSignatureVerificationFailedException
    }

    class InstallationController {
        -StaticSettings settings
        -ILogger~InstallationController~ logger
        -MuModService muModService
        +InstallationController(settings StaticSettings, logger ILogger~InstallationController~, muModService MuModService)
        +GameModInfo GetGameModInfo()
        +void InstallMelonLoader()
        +void InstallAquaMai()
        +Task InstallAquaMaiOnline(req InstallAquaMaiOnlineDto)
        +Task InstallMuMod()
        +void DeleteAquaMai()
        +void DeleteMuMod()
        +void KillGameProcess()
    }

    class ConfigurationController {
        -StaticSettings settings
        -ILogger~ConfigurationController~ logger
        -ModConfigService modConfigService
        +ConfigurationController(settings StaticSettings, logger ILogger~ConfigurationController~, modConfigService ModConfigService)
        +Task~AquaMaiConfigDto.ConfigDto~ GetAquaMaiConfig(forceDefault bool, skipSignatureCheck bool)
        +Task SetAquaMaiConfig(config AquaMaiConfigDto.ConfigSaveDto)
    }

    class StaticSettings {
        -ILogger~StaticSettings~ _logger
        -ModConfigService _modConfigService
        +StaticSettings(logger ILogger~StaticSettings~, modConfigService ModConfigService)
        +Task InitializeGameData()
        +Task RescanAll()
        +List~MusicXmlWithABJacket~ GetMusicList()
        +string GetFreeAssetDir()
        +static void UpdateAssetPathsFromAquaMaiConfig(config IConfig)
    }

    class MuModController {
        -MuModService muModService
        +MuModController(muModService MuModService)
        +MuModConfigDto GetMuModConfig()
        +Task~EnsureCacheResultDto~ SetMuModChannelAndEnsureCache(req SetChannelDto)
        +Task~EnsureCacheResultDto~ EnsureMuModCache()
    }

    class ModPaths {
        +string AquaMaiConfigPath
        +string AquaMaiConfigBackupDirPath
        +string AquaMaiDllInstalledPath
        +string AquaMaiDllBuiltinPath
        +string MuModDllInstalledPath
        +string MuModDllBuiltinPath
        +string MuModConfigPath
        +string MuModDefaultCachePath
    }

    class GameModInfo {
        +bool MelonLoaderInstalled
        +bool AquaMaiInstalled
        +string AquaMaiVersion
        +string BundledAquaMaiVersion
        +bool IsVersionTooLow
        +bool IsHidConflictExist
        +VerifyResult Signature
        +bool IsAdxHidIoModAbsent
        +bool IsMmlLegacyLibsInstalled
        +bool MuModInstalled
        +string MuModVersion
        +string MuModChannel
        +string MuModCacheVersion
        +bool IsBothModsPresent
    }

    StaticSettings --> ModConfigService : uses
    ConfigurationController --> ModConfigService : uses
    InstallationController --> MuModService : uses
    MuModController --> MuModService : uses
    ModConfigService --> MuModService : uses
    InstallationController --> StaticSettings : uses
    MuModService --> ModPaths : uses
    ModConfigService --> ModPaths : uses
    InstallationController --> ModPaths : uses
    InstallationController --> GameModInfo : returns
Loading

从前端进行 MuMod 渠道切换与缓存更新的流程图

flowchart TD
    A["User changes MuMod channel radio in ConfigEditor"] --> B["localMuModChannel updated"]
    B --> C["watch(localMuModChannel) triggers"]
    C --> D["updateMuModChannel(newChannel) (debounced)"]
    D --> E["updateMuModChannelImpl(channel)"]
    E --> F["Api.SetMuModChannelAndEnsureCache({ channel })"]
    F --> G["MuModController.SetMuModChannelAndEnsureCache"]
    G --> H["MuModService.WriteChannel(channel)"]
    H --> I["MuModService.EnsureCache()
- resolve version
- download cache
- verify signature
- write cache file"]
    I --> J["EnsureCacheResultDto(Success, Version, Error)"]
    J --> K["Frontend updateModInfo()
(fetch GameModInfo)"]
    K --> L["Frontend updateAquaMaiConfig()
(fetch config via GetAquaMaiConfig)"]
    L --> M["UI reflects new channel and cache version"]
    F -->|error| N["globalCapture(e, 'mod.cacheFailed')"]
Loading

文件级改动

Change Details Files
在后端 API 与服务层面添加 MuMod 安装、配置与缓存管理支持。
  • 引入 MuModService 来读写 MuMod.toml,解析缓存路径,从两个远端源拉取版本元数据,下载并在签名校验后保存缓存,并暴露缓存/版本相关的辅助方法。
  • 新增 MuModController,提供读取 MuMod 配置、在设置渠道的同时确保缓存,以及手动确保缓存的端点,这些端点返回的 DTO 会映射到生成的 TypeScript 客户端类型。
  • 扩展 InstallationController,使其接收 MuModService,暴露 InstallMuMod/DeleteAquaMai/DeleteMuMod 端点,在安装时保持 AquaMai 与 MuMod 互斥,初始化默认 MuMod.toml,并在安装时确保 MuMod 缓存且记录错误日志。
  • 扩展 GameModInfo,增加 MuMod 安装状态、版本、渠道、缓存版本以及一个标记同时存在 AquaMai 与 MuMod 的布尔字段。
  • 在 ModPaths 中定义 MuMod 相关路径(已安装 / 内置 DLL、配置、默认缓存等)。
  • 在 ServerManager 的 DI 中将 MuModService 与 ModConfigService 注册为单例,并按需要调整项目配置。
MaiChartManager/Controllers/Mod/MuModService.cs
MaiChartManager/Controllers/Mod/MuModController.cs
MaiChartManager/Controllers/Mod/InstallationController.cs
MaiChartManager/Controllers/Mod/ModPaths.cs
MaiChartManager/ServerManager.cs
MaiChartManager/MaiChartManager.csproj
将 AquaMai 配置加载重构到 ModConfigService 中,并让配置相关控制器和 StaticSettings 具备异步与 MuMod 感知能力。
  • 把 AquaMai 配置加载、迁移、签名校验以及错误类型从 ConfigurationController 中抽取到 ModConfigService,并加入逻辑:根据安装状态在 AquaMai.dll 与 MuMod 缓存之间进行选择。
  • 更新 ConfigurationController,使其使用注入的 StaticSettings、ILogger 和 ModConfigService,将 GetAquaMaiConfig 改为异步,使用 ModConfigService 选择 DLL 路径,并将配置 API 版本检查委托给服务。
  • 修改 StaticSettings 构造函数以及 InitializeGameData/RescanAll 为异步,并使用 ModConfigService.GetCurrentAquaMaiConfig 来更新资源路径,同时优雅地处理失败情况。
  • 调整 UpdateAssetPathsFromAquaMaiConfig,使其始终接收一个 IConfig 实例,而不是在内部自行加载配置。
  • 更新若干控制器(AssetDirController、MusicListController、MusicTransferController、OobeController),在资源或乐曲发生变更后等待 StaticSettings.RescanAll/InitializeGameData 完成。
  • 小改动:在调试模式下 WebView 初始化时最小化主窗口,并将 GenClient 从 ts-node 改为使用 tsx。
MaiChartManager/Controllers/Mod/ModConfigService.cs
MaiChartManager/Controllers/Mod/ConfigurationController.cs
MaiChartManager/StaticSettings.cs
MaiChartManager/Controllers/AssetDir/AssetDirController.cs
MaiChartManager/Controllers/AssetDir/MusicListController.cs
MaiChartManager/Controllers/Music/MusicTransferController.cs
MaiChartManager/Controllers/App/OobeController.cs
MaiChartManager/Browser.cs
MaiChartManager.GenClient/Program.cs
扩展生成的 API 客户端与共享 DTO,以覆盖 MuMod 以及新的安装/配置操作。
  • 在 TypeScript API 类型中增加 EnsureCacheResultDto、MuModConfigDto 与 SetChannelDto 接口,以及 GameModInfo 中的 MuMod 相关字段和可空的 PdxDriverStatusDto.devicePaths。
  • 在 Api.Install 与 Api.MuMod 区域暴露新的安装 API:InstallMuMod、DeleteAquaMai、DeleteMuMod,以及 MuMod API:GetMuModConfig、SetMuModChannelAndEnsureCache、EnsureMuModCache。
  • 将 OpenAPI 客户端标题从 MaiChartManager 改为 MaiChartManager.GenClient,以体现其生成器用途。
MaiChartManager/Front/src/client/apiGen.ts
更新前端 Mod Manager UI,以支持 MuMod 安装、渠道选择、冲突解决,并通过下拉菜单集中安装/更新流程。
  • 将单一的 AquaMai 安装按钮替换为 DropMenu,提供稳定渠道、快速渠道和 MuMod 选项;通过共享的异步处理函数驱动安装,该函数根据类型调用 InstallAquaMai、InstallAquaMaiOnline 或 InstallMuMod,随后刷新 mod 信息/配置并展示短暂的成功图标。
  • 加入按渠道计算版本描述的逻辑,在在线版本缺失时回退到内置安装,并同时使用 AquaMai 和 MuMod 的安装标记来选择“安装”与“重装/更新”的文案。
  • 在 ConfigEditor 中增加 MuMod 模式检测 isMuModMode,在 MuMod 模式下展示 MuMod 已安装状态与版本,隐藏 AquaMai 签名信息,并显示 MuMod 加载版本标签。
  • 将 MuMod 渠道状态(muModChannel)绑定到一组单选按钮(Radio),经由防抖的 updateMuModChannel 调用 SetMuModChannelAndEnsureCache,并刷新 mod 信息/配置,错误通过 globalCapture 上报。
  • 当两个 Mod 同时存在时显示冲突 UI,通过本地化文案和按钮调用新的 DeleteAquaMai/DeleteMuMod API,在冲突解决之前隐藏配置编辑器。
  • 移除 Mod Manager 入口页面中旧的 ModNotInstalledWarning 行为,让用户直接通过更新后的 ConfigEditor 进行操作。
MaiChartManager/Front/src/views/ModManager/ModInstallDropdown.tsx
MaiChartManager/Front/src/views/ModManager/ConfigEditor.tsx
MaiChartManager/Front/src/views/ModManager/refs.ts
MaiChartManager/Front/src/views/ModManager/index.tsx
调整更新与设置逻辑,使其感知 MuMod 模式与 MuMod 缓存状态,用于更新提示与设置展示。
  • 扩展 shouldShowUpdateController,通过 modInfo.muModInstalled 和 isBothModsPresent 来检测 MuMod 模式,将 MuMod 渠道映射到对应的 API 类型,并据此计算 latestVersion。
  • 修改 shouldShowUpdate 逻辑:在 MuMod 模式下,将 muModCacheVersion 与解析出的渠道对应的最新版本进行比较;在 AquaMai 模式下,则沿用现有的 AquaMai 版本比较逻辑。
  • 在 MuMod 模式下隐藏 AquaMai 更新渠道设置(AquaMaiSection),避免产生冲突的渠道配置。
  • 在前端暴露 muModChannel 和 isBothModsPresent 的计算值,并确保 MuMod 更新渠道变化时会触发缓存更新以及 mod 信息/配置刷新。
MaiChartManager/Front/src/views/ModManager/shouldShowUpdateController.ts
MaiChartManager/Front/src/views/Settings/AquaMaiSection.tsx
MaiChartManager/Front/src/views/ModManager/refs.ts
为新的 MuMod UX 和开发服务器更新本地化与开发环境配置。
  • 在 zh、zh-TW 与 en 语言文件中增加 MuMod 相关字符串(菜单标签、描述、渠道名称、缓存状态消息、冲突文案等);微调可用版本的文案,并增加 MuMod 模式下使用的“已加载”标签;移除不再使用的旧 AquaMai 未安装警告文本。
  • 修改 Vite 开发服务器配置,指定固定前端端口(5182),同时保持到 5181 的后端代理。
MaiChartManager/Front/src/locales/en.yaml
MaiChartManager/Front/src/locales/zh.yaml
MaiChartManager/Front/src/locales/zh-TW.yaml
MaiChartManager/Front/vite.config.ts

提示与命令

与 Sourcery 交互

  • 触发一次新的审查: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 在某条审查评论下回复,请求 Sourcery 根据该评论创建 issue。你也可以直接回复 @sourcery-ai issue 来从该评论生成 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题中写入 @sourcery-ai,即可随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 内容中任意位置写入 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 Pull Request 中评论 @sourcery-ai summary 来随时(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 一次性解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 撤销所有 Sourcery 审查: 在 Pull Request 中评论 @sourcery-ai dismiss,即可撤销所有现有的 Sourcery 审查。特别适合在你想重新开始一次新的审查时使用——别忘了随后评论 @sourcery-ai review 来触发新审查!

自定义使用体验

打开你的 控制面板 以:

  • 启用或禁用诸如 Sourcery 自动生成 Pull Request 摘要、审阅者指南等审查功能。
  • 修改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其它审查相关设置。

获取帮助

Original review guide in English

Reviewer's Guide

Integrates MuMod as an alternative to AquaMai, adds backend services and APIs to manage MuMod installation and cache, unifies config loading through a shared ModConfigService, and updates the frontend mod manager UI and settings to support MuMod channels, conflict handling, and async rescans.

Sequence diagram for installing MuMod from the Mod Manager dropdown

sequenceDiagram
    actor User
    participant ModManagerUI as ModInstallDropdown
    participant ApiClient as Api_Client
    participant InstallCtrl as InstallationController
    participant MuModSvc as MuModService
    participant FS as FileSystem
    participant VersionApi as Version_API
    participant SigVerifier as AquaMaiSignatureV2

    User->>ModManagerUI: Click MuMod option in DropMenu
    ModManagerUI->>ModManagerUI: doInstall(mumod)
    ModManagerUI->>ApiClient: InstallMuMod()
    ApiClient->>InstallCtrl: POST InstallMuModApi

    InstallCtrl->>FS: CopyFile(MuModDllBuiltinPath, MuModDllInstalledPath)
    alt AquaMai.dll exists
        InstallCtrl->>FS: Delete(AquaMaiDllInstalledPath)
    end
    alt MuMod.toml not exists
        InstallCtrl->>FS: WriteAllText(MuModConfigPath, default config)
    end

    InstallCtrl->>MuModSvc: EnsureCache()
    MuModSvc->>MuModSvc: GetResolvedCachePath()
    MuModSvc->>FS: File.Exists(cachePath)
    alt Cache up to date
        MuModSvc-->>InstallCtrl: EnsureCacheResult(Success=true, Version, Error=null)
    else Cache missing or outdated
        MuModSvc->>MuModSvc: ResolveApiType(ReadConfig().Channel)
        MuModSvc->>VersionApi: FetchVersionInfosAsync(CosVersionApiUrl)
        MuModSvc->>VersionApi: FetchVersionInfosAsync(CfVersionApiUrl)
        VersionApi-->>MuModSvc: VersionInfoModel[]
        MuModSvc->>MuModSvc: Select VersionInfoModel by Type
        MuModSvc->>VersionApi: DownloadFromUrlsAsync(urls)
        VersionApi-->>MuModSvc: byte[] cacheData
        MuModSvc->>SigVerifier: VerifySignature(cacheData)
        SigVerifier-->>MuModSvc: VerifyStatus
        MuModSvc->>FS: WriteAllBytesAsync(tempCachePath, cacheData)
        MuModSvc->>FS: Move(tempCachePath, cachePath, overwrite=true)
        MuModSvc-->>InstallCtrl: EnsureCacheResult(Success, Version, Error)
    end

    InstallCtrl-->>ApiClient: 200 OK
    ApiClient-->>ModManagerUI: Response
    ModManagerUI->>ApiClient: GetGameModInfo()
    ApiClient-->>ModManagerUI: GameModInfo(muModInstalled=true,...)
    ModManagerUI->>ApiClient: GetAquaMaiConfig(forceDefault=false, skipSignatureCheck=false)
    ApiClient->>ConfigCtrl: GET GetAquaMaiConfigApi
    participant ConfigCtrl as ConfigurationController

    ConfigCtrl->>ModConfigSvc: GetAquaMaiDllPath()
    participant ModConfigSvc as ModConfigService
    ModConfigSvc->>MuModSvc: IsMuModInstalled()
    MuModSvc-->>ModConfigSvc: bool
    ModConfigSvc->>FS: File.Exists(AquaMaiDllInstalledPath)
    alt Only MuMod installed
        ModConfigSvc->>MuModSvc: GetResolvedCachePath()
        MuModSvc-->>ModConfigSvc: cachePath
        ModConfigSvc-->>ConfigCtrl: dllPath=cachePath
    else AquaMai installed
        ModConfigSvc-->>ConfigCtrl: dllPath=AquaMaiDllInstalledPath
    end

    ConfigCtrl->>FS: FileStream(dllPath)
    ConfigCtrl->>ModConfigSvc: GetCurrentAquaMaiConfig(forceDefault, shouldSkipSignatureCheck)
    ModConfigSvc->>FS: ReadAllBytes(dllPath)
    ModConfigSvc->>SigVerifier: VerifySignature(binary)
    SigVerifier-->>ModConfigSvc: VerifyStatus.Valid
    ModConfigSvc->>ModConfigSvc: CheckConfigApiVersion(configInterface)
    ModConfigSvc->>FS: File.Exists(AquaMaiConfigPath)
    alt Config exists and !forceDefault
        ModConfigSvc->>FS: ReadAllText(AquaMaiConfigPath)
        ModConfigSvc->>ModConfigSvc: Migrate and Parse config
        ModConfigSvc->>StaticSettings: UpdateAssetPathsFromAquaMaiConfig(config)
        participant StaticSettings
    end
    ModConfigSvc-->>ConfigCtrl: IConfig

    ConfigCtrl-->>ApiClient: AquaMaiConfigDto
    ApiClient-->>ModManagerUI: AquaMaiConfigDto
    ModManagerUI->>ModManagerUI: updateModInfo(), updateAquaMaiConfig()
    ModManagerUI-->>User: UI shows MuMod installed, channel and cache status
Loading

Class diagram for MuMod and unified mod configuration services

classDiagram
    class MuModService {
        -logger ILogger~MuModService~
        +MuModService(logger ILogger~MuModService~)
        +MuModConfigModel ReadConfig()
        +void WriteChannel(channel string)
        +string GetResolvedCachePath()
        +Task~EnsureCacheResult~ EnsureCache(ct CancellationToken)
        +string? GetCacheInfo()
        +bool IsMuModInstalled()
        +string? GetMuModVersion()
        -string ResolveApiType(channel string)
        -string? ReadProductVersion(path string)
        -string? NormalizeVersion(version string)
        -Task~(VersionInfoModel,VersionSource)~ ResolveVersionInfoAsync(apiType string, ct CancellationToken)
        -IEnumerable~string~ BuildDownloadUrls(info VersionInfoModel, source VersionSource)
        -Task~VersionInfoModel[]~ FetchVersionInfosAsync(url string, ct CancellationToken)
        -Task~byte[]~ DownloadFromUrlsAsync(urls IReadOnlyList~string~, ct CancellationToken)
    }

    class MuModConfigModel {
        +string Channel
        +string CachePath
    }

    class ModConfigService {
        -MuModService _muModService
        +ModConfigService(muModService MuModService)
        +void CheckConfigApiVersion(configInterface HeadlessConfigInterface)
        +Task~string~ GetAquaMaiDllPath(ct CancellationToken)
        +Task~IConfig~ GetCurrentAquaMaiConfig(forceDefault bool, skipSignatureCheck bool, ct CancellationToken)
        <<exception>> UnsupportedConfigApiVersionException
        <<exception>> ConfigCorruptedException
        <<exception>> AquaMaiNotInstalledException
        <<exception>> AquaMaiSignatureVerificationFailedException
    }

    class InstallationController {
        -StaticSettings settings
        -ILogger~InstallationController~ logger
        -MuModService muModService
        +InstallationController(settings StaticSettings, logger ILogger~InstallationController~, muModService MuModService)
        +GameModInfo GetGameModInfo()
        +void InstallMelonLoader()
        +void InstallAquaMai()
        +Task InstallAquaMaiOnline(req InstallAquaMaiOnlineDto)
        +Task InstallMuMod()
        +void DeleteAquaMai()
        +void DeleteMuMod()
        +void KillGameProcess()
    }

    class ConfigurationController {
        -StaticSettings settings
        -ILogger~ConfigurationController~ logger
        -ModConfigService modConfigService
        +ConfigurationController(settings StaticSettings, logger ILogger~ConfigurationController~, modConfigService ModConfigService)
        +Task~AquaMaiConfigDto.ConfigDto~ GetAquaMaiConfig(forceDefault bool, skipSignatureCheck bool)
        +Task SetAquaMaiConfig(config AquaMaiConfigDto.ConfigSaveDto)
    }

    class StaticSettings {
        -ILogger~StaticSettings~ _logger
        -ModConfigService _modConfigService
        +StaticSettings(logger ILogger~StaticSettings~, modConfigService ModConfigService)
        +Task InitializeGameData()
        +Task RescanAll()
        +List~MusicXmlWithABJacket~ GetMusicList()
        +string GetFreeAssetDir()
        +static void UpdateAssetPathsFromAquaMaiConfig(config IConfig)
    }

    class MuModController {
        -MuModService muModService
        +MuModController(muModService MuModService)
        +MuModConfigDto GetMuModConfig()
        +Task~EnsureCacheResultDto~ SetMuModChannelAndEnsureCache(req SetChannelDto)
        +Task~EnsureCacheResultDto~ EnsureMuModCache()
    }

    class ModPaths {
        +string AquaMaiConfigPath
        +string AquaMaiConfigBackupDirPath
        +string AquaMaiDllInstalledPath
        +string AquaMaiDllBuiltinPath
        +string MuModDllInstalledPath
        +string MuModDllBuiltinPath
        +string MuModConfigPath
        +string MuModDefaultCachePath
    }

    class GameModInfo {
        +bool MelonLoaderInstalled
        +bool AquaMaiInstalled
        +string AquaMaiVersion
        +string BundledAquaMaiVersion
        +bool IsVersionTooLow
        +bool IsHidConflictExist
        +VerifyResult Signature
        +bool IsAdxHidIoModAbsent
        +bool IsMmlLegacyLibsInstalled
        +bool MuModInstalled
        +string MuModVersion
        +string MuModChannel
        +string MuModCacheVersion
        +bool IsBothModsPresent
    }

    StaticSettings --> ModConfigService : uses
    ConfigurationController --> ModConfigService : uses
    InstallationController --> MuModService : uses
    MuModController --> MuModService : uses
    ModConfigService --> MuModService : uses
    InstallationController --> StaticSettings : uses
    MuModService --> ModPaths : uses
    ModConfigService --> ModPaths : uses
    InstallationController --> ModPaths : uses
    InstallationController --> GameModInfo : returns
Loading

Flow diagram for MuMod channel change and cache update from frontend

flowchart TD
    A["User changes MuMod channel radio in ConfigEditor"] --> B["localMuModChannel updated"]
    B --> C["watch(localMuModChannel) triggers"]
    C --> D["updateMuModChannel(newChannel) (debounced)"]
    D --> E["updateMuModChannelImpl(channel)"]
    E --> F["Api.SetMuModChannelAndEnsureCache({ channel })"]
    F --> G["MuModController.SetMuModChannelAndEnsureCache"]
    G --> H["MuModService.WriteChannel(channel)"]
    H --> I["MuModService.EnsureCache()
- resolve version
- download cache
- verify signature
- write cache file"]
    I --> J["EnsureCacheResultDto(Success, Version, Error)"]
    J --> K["Frontend updateModInfo()
(fetch GameModInfo)"]
    K --> L["Frontend updateAquaMaiConfig()
(fetch config via GetAquaMaiConfig)"]
    L --> M["UI reflects new channel and cache version"]
    F -->|error| N["globalCapture(e, 'mod.cacheFailed')"]
Loading

File-Level Changes

Change Details Files
Add MuMod installation, configuration, and cache management support across backend APIs and services.
  • Introduce MuModService to read/write MuMod.toml, resolve cache paths, fetch version metadata from two remote sources, download and verify cache with signature checking, and expose cache/version helpers.
  • Add MuModController with endpoints for reading MuMod config, setting channel while ensuring cache, and manually ensuring cache, returning DTOs that map to the generated TypeScript client.
  • Extend InstallationController to accept MuModService, expose InstallMuMod/DeleteAquaMai/DeleteMuMod endpoints, keep AquaMai and MuMod mutually exclusive on install, bootstrap default MuMod.toml, and ensure MuMod cache on install with error logging.
  • Extend GameModInfo to expose MuMod installation state, version, channel, cache version, and a flag indicating when both AquaMai and MuMod are present.
  • Define MuMod-related paths in ModPaths (installed/builtin DLL, config, default cache).
  • Register MuModService and ModConfigService as singletons in ServerManager DI and adjust project configuration as needed.
MaiChartManager/Controllers/Mod/MuModService.cs
MaiChartManager/Controllers/Mod/MuModController.cs
MaiChartManager/Controllers/Mod/InstallationController.cs
MaiChartManager/Controllers/Mod/ModPaths.cs
MaiChartManager/ServerManager.cs
MaiChartManager/MaiChartManager.csproj
Refactor AquaMai configuration loading into ModConfigService and update configuration-related controllers and StaticSettings to be async-aware and MuMod-aware.
  • Extract AquaMai config loading, migration, signature verification, and error types from ConfigurationController into ModConfigService, adding logic to choose between AquaMai.dll and MuMod cache based on installation state.
  • Update ConfigurationController to use injected StaticSettings, ILogger, and ModConfigService, make GetAquaMaiConfig async, choose DLL path via ModConfigService, and delegate config API version checks to the service.
  • Change StaticSettings constructor and InitializeGameData/RescanAll to be async and to use ModConfigService.GetCurrentAquaMaiConfig for updating asset paths while handling failures gracefully.
  • Adjust UpdateAssetPathsFromAquaMaiConfig to always receive an IConfig instance instead of internally loading config.
  • Update several controllers (AssetDirController, MusicListController, MusicTransferController, OobeController) to await StaticSettings.RescanAll/InitializeGameData after asset or music changes.
  • Minor: minimize main window in debug on WebView initialization and update GenClient to use tsx instead of ts-node.
MaiChartManager/Controllers/Mod/ModConfigService.cs
MaiChartManager/Controllers/Mod/ConfigurationController.cs
MaiChartManager/StaticSettings.cs
MaiChartManager/Controllers/AssetDir/AssetDirController.cs
MaiChartManager/Controllers/AssetDir/MusicListController.cs
MaiChartManager/Controllers/Music/MusicTransferController.cs
MaiChartManager/Controllers/App/OobeController.cs
MaiChartManager/Browser.cs
MaiChartManager.GenClient/Program.cs
Extend the generated API client and shared DTOs to cover MuMod and new installation/configuration operations.
  • Add EnsureCacheResultDto, MuModConfigDto, and SetChannelDto interfaces to the TypeScript API types along with new MuMod-related fields on GameModInfo and nullable PdxDriverStatusDto.devicePaths.
  • Expose new Installation APIs: InstallMuMod, DeleteAquaMai, DeleteMuMod, and new MuMod APIs: GetMuModConfig, SetMuModChannelAndEnsureCache, EnsureMuModCache in Api.Install and Api.MuMod sections.
  • Retitle the OpenAPI client from MaiChartManager to MaiChartManager.GenClient to reflect its generator usage.
MaiChartManager/Front/src/client/apiGen.ts
Update the frontend Mod Manager UI to support MuMod installation, channel selection, conflict resolution, and to centralize install/update flow with a dropdown.
  • Replace the single AquaMai install button with a DropMenu that offers stable channel, fast channel, and MuMod options; drive installation via a shared async handler that calls InstallAquaMai, InstallAquaMaiOnline, or InstallMuMod based on type, refreshes mod info/config, and shows a transient success icon.
  • Introduce logic to compute channel-specific version descriptions, fall back to builtin installation when online versions are missing, and use both AquaMai and MuMod installation flags to choose install vs reinstall/update labels.
  • In ConfigEditor, add MuMod mode detection via isMuModMode, show MuMod-installed state and version when in MuMod mode, hide AquaMai signature display in that mode, and show the loaded version tag for MuMod.
  • Wire MuMod channel state (muModChannel) to a pair of Radio buttons, debounced updateMuModChannel calls SetMuModChannelAndEnsureCache and refreshes mod info/config, reporting errors via globalCapture.
  • Add conflict UI when both mods are present with localized messaging and buttons calling new DeleteAquaMai/DeleteMuMod APIs, hiding the config editor until the conflict is resolved.
  • Remove the old ModNotInstalledWarning behavior from the mod manager entry page so users interact directly via the updated ConfigEditor.
MaiChartManager/Front/src/views/ModManager/ModInstallDropdown.tsx
MaiChartManager/Front/src/views/ModManager/ConfigEditor.tsx
MaiChartManager/Front/src/views/ModManager/refs.ts
MaiChartManager/Front/src/views/ModManager/index.tsx
Adjust update and settings logic to be aware of MuMod mode and MuMod cache state for update prompts and settings display.
  • Extend shouldShowUpdateController to detect MuMod mode based on modInfo.muModInstalled and isBothModsPresent, map MuMod channels to corresponding API types, and compute latestVersion accordingly.
  • Change shouldShowUpdate logic so that in MuMod mode it compares muModCacheVersion against the latest version for the resolved channel, while in AquaMai mode it preserves the existing comparison on AquaMai version.
  • Hide AquaMai update-channel settings (AquaMaiSection) when in MuMod mode, to avoid conflicting channel configuration.
  • Expose muModChannel and isBothModsPresent computed values on the frontend and ensure MuMod update-channel changes trigger cache updates and mod info/config refresh.
MaiChartManager/Front/src/views/ModManager/shouldShowUpdateController.ts
MaiChartManager/Front/src/views/Settings/AquaMaiSection.tsx
MaiChartManager/Front/src/views/ModManager/refs.ts
Update localization and development environment configuration for the new MuMod UX and dev server.
  • Add MuMod-related strings (menu labels, descriptions, channel names, cache status messages, conflict texts) to zh, zh-TW, and en locale files; tweak wording of available version and add a 'Loaded' label used in MuMod mode; remove the old AquaMai not-installed warning texts that are no longer used.
  • Change Vite dev server config to specify a fixed frontend port (5182) while keeping the backend proxy to 5181.
MaiChartManager/Front/src/locales/en.yaml
MaiChartManager/Front/src/locales/zh.yaml
MaiChartManager/Front/src/locales/zh-TW.yaml
MaiChartManager/Front/vite.config.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's mod management capabilities by integrating MuMod, an auto-update loader. This integration provides a more dynamic and streamlined approach to managing game modifications, particularly AquaMai. The changes involve a substantial refactoring of existing mod configuration logic into a dedicated service, converting several operations to be asynchronous for better performance, and updating the user interface to support MuMod-specific features and handle potential mod conflicts. The overall goal is to offer users more flexibility and a smoother experience in keeping their game mods up-to-date.

Highlights

  • MuMod Integration: Introduced MuMod, an auto-update loader, allowing for dynamic management and updates of game modifications. This includes new services, API endpoints, and UI components for MuMod installation, configuration, and cache management.
  • AquaMai Configuration Refactoring: Centralized AquaMai configuration logic into a new ModConfigService. This service now handles loading, validating, and migrating AquaMai configurations, supporting both direct AquaMai installations and AquaMai DLLs provided by MuMod.
  • Asynchronous Operations: Converted several synchronous methods related to game data initialization, asset directory scanning, and music ID modification to asynchronous operations, improving application responsiveness during potentially long-running tasks.
  • Mod Conflict Resolution: Implemented logic and UI to detect and resolve conflicts when both AquaMai and MuMod are present, allowing users to choose which mod to keep.
  • Frontend Enhancements: Updated the Mod Manager UI to display MuMod status, allow selection of MuMod update channels (stable/fast), and provide options for conflict resolution. The build process for the frontend client was also updated to use tsx instead of ts-node.
Changelog
  • MaiChartManager.GenClient/Program.cs
    • Updated the client generation script to use tsx instead of ts-node.
  • MaiChartManager/Browser.cs
    • Added a debug-only feature to minimize the browser window on initialization.
  • MaiChartManager/Controllers/App/OobeController.cs
    • Converted InitializeGameData method to be asynchronous.
  • MaiChartManager/Controllers/AssetDir/AssetDirController.cs
    • Converted RequestLocalImportDir method to be asynchronous.
    • Updated calls to settings.RescanAll() to use await.
  • MaiChartManager/Controllers/AssetDir/MusicListController.cs
    • Converted ReloadAll method to be asynchronous.
  • MaiChartManager/Controllers/Mod/ConfigurationController.cs
    • Refactored AquaMai configuration logic by moving it to ModConfigService.
    • Converted GetAquaMaiConfig and SetAquaMaiConfig methods to be asynchronous.
    • Updated constructor to inject ModConfigService.
  • MaiChartManager/Controllers/Mod/InstallationController.cs
    • Updated constructor to inject MuModService.
    • Expanded GameModInfo record to include MuMod-specific details (installed status, version, channel, cache version, conflict status).
    • Added logic to delete MuMod when AquaMai is installed.
    • Added new API endpoints for InstallMuMod, DeleteAquaMai, and DeleteMuMod.
    • Added logic to delete AquaMai when MuMod is installed during InstallMuMod.
  • MaiChartManager/Controllers/Mod/ModConfigService.cs
    • Added new service to encapsulate AquaMai configuration logic.
    • Implemented methods for checking config API version, getting AquaMai DLL path (considering MuMod cache), and retrieving current AquaMai config.
    • Introduced new exception types for mod configuration errors.
  • MaiChartManager/Controllers/Mod/ModPaths.cs
    • Added new static properties for MuMod DLL paths, config path, and default cache path.
  • MaiChartManager/Controllers/Mod/MuModController.cs
    • Added new API controller for MuMod-specific operations.
    • Implemented endpoints to get MuMod configuration, set MuMod channel and ensure cache, and manually ensure MuMod cache.
  • MaiChartManager/Controllers/Mod/MuModService.cs
    • Added new service to manage MuMod configuration, cache, and versioning.
    • Implemented methods to read/write MuMod config, resolve cache path, ensure cache updates, and retrieve MuMod installation info.
    • Included logic for fetching version information from multiple sources and handling downloads.
  • MaiChartManager/Controllers/Music/MusicTransferController.cs
    • Converted ModifyId method to be asynchronous.
    • Updated calls to settings.RescanAll() to use await.
  • MaiChartManager/Front/src/client/apiGen.ts
    • Added new TypeScript interfaces for EnsureCacheResultDto, MuModConfigDto, and SetChannelDto.
    • Updated GameModInfo interface to include MuMod-related properties.
    • Modified PdxDriverStatusDto to allow devicePaths to be null.
    • Updated API client to include new MuMod and Installation endpoints.
    • Changed API title from MaiChartManager to MaiChartManager.GenClient.
  • MaiChartManager/Front/src/locales/en.yaml
    • Removed deprecated AquaMai not installed warning messages.
    • Added new locale strings for MuMod, update channels, cache status, and conflict resolution messages.
  • MaiChartManager/Front/src/locales/zh-TW.yaml
    • Removed deprecated AquaMai not installed warning messages.
    • Added new locale strings for MuMod, update channels, cache status, and conflict resolution messages.
  • MaiChartManager/Front/src/locales/zh.yaml
    • Removed deprecated AquaMai not installed warning messages.
    • Added new locale strings for MuMod, update channels, cache status, and conflict resolution messages.
  • MaiChartManager/Front/src/views/ModManager/ConfigEditor.tsx
    • Updated component to display MuMod status and version.
    • Added UI for MuMod channel selection.
    • Implemented conflict resolution UI with buttons to delete AquaMai or MuMod.
    • Removed the ModNotInstalledWarning component integration.
  • MaiChartManager/Front/src/views/ModManager/ModInstallDropdown.tsx
    • Refactored mod installation dropdown to support MuMod installation and channel-specific AquaMai installations.
    • Updated installation logic to handle different mod types and online/builtin versions.
  • MaiChartManager/Front/src/views/ModManager/ModNotInstalledWarning.tsx
    • Removed file, as the warning mechanism was deprecated.
  • MaiChartManager/Front/src/views/ModManager/index.tsx
    • Removed the ModNotInstalledWarning component from the ModManager view.
  • MaiChartManager/Front/src/views/ModManager/refs.ts
    • Added computed properties for muModChannel and isBothModsPresent.
    • Implemented updateMuModChannel with debouncing for setting MuMod update channels.
  • MaiChartManager/Front/src/views/ModManager/shouldShowUpdateController.ts
    • Added isMuModMode computed property to determine if MuMod is active.
    • Updated latestVersion and shouldShowUpdate logic to account for MuMod's update channel and cache version.
  • MaiChartManager/Front/src/views/Settings/AquaMaiSection.tsx
    • Modified component to hide AquaMai channel selection when MuMod mode is active.
  • MaiChartManager/Front/vite.config.ts
    • Updated Vite server configuration to specify port 5182.
  • MaiChartManager/MaiChartManager.csproj
    • Added Tomlyn NuGet package for TOML configuration parsing.
  • MaiChartManager/ServerManager.cs
    • Registered MuModService and ModConfigService as singletons in the dependency injection container.
  • MaiChartManager/StaticSettings.cs
    • Injected ModConfigService into the constructor.
    • Converted InitializeGameData and RescanAll methods to be asynchronous.
    • Updated UpdateAssetPathsFromAquaMaiConfig to accept an IConfig object and removed internal config loading logic, relying on ModConfigService.
Activity
  • No specific activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些高层次的反馈:

  • StaticSettings.RescanAll 现在会调用 _modConfigService.GetCurrentAquaMaiConfig(),而后者内部又会调用 StaticSettings.UpdateAssetPathsFromAquaMaiConfig;建议将更新资源路径的责任集中到一个地方,以避免隐式副作用和重复更新。
  • MuModController.SetMuModChannelAndEnsureCacheEnsureMuModCache 会捕获所有异常,并始终返回带有 Success 标志的 HTTP 200;如果调用方需要区分传输错误和逻辑失败,建议返回合适的 HTTP 状态码或更丰富的错误细节,而不是直接吞掉异常。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- StaticSettings.RescanAll now calls _modConfigService.GetCurrentAquaMaiConfig(), which itself calls StaticSettings.UpdateAssetPathsFromAquaMaiConfig; consider centralizing the responsibility for updating asset paths in one place to avoid implicit side effects and double updates.
- MuModController.SetMuModChannelAndEnsureCache and EnsureMuModCache catch all exceptions and always return HTTP 200 with a Success flag; if callers need to distinguish transport errors from logical failures, consider returning appropriate HTTP status codes or richer error details instead of swallowing the exceptions.

## Individual Comments

### Comment 1
<location path="MaiChartManager/Controllers/Mod/MuModController.cs" line_range="21-25" />
<code_context>
+    }
+
+    [HttpPut]
+    public async Task<EnsureCacheResultDto> SetMuModChannelAndEnsureCache([FromBody] SetChannelDto req)
+    {
+        if (req.Channel != "slow" && req.Channel != "fast")
+        {
+            throw new ArgumentException("Channel must be 'slow' or 'fast'", nameof(req));
+        }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid throwing `ArgumentException` for invalid channel and return a controlled error response instead.

Throwing `ArgumentException` here will surface as a 500 and won’t match the `EnsureCacheResultDto` contract the client expects. Instead, either return `BadRequest` with a clear validation message or an `EnsureCacheResultDto` with `Success = false` and an appropriate error message so clients always receive a predictable response shape.
</issue_to_address>

Sourcery 对开源项目免费使用——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • StaticSettings.RescanAll now calls _modConfigService.GetCurrentAquaMaiConfig(), which itself calls StaticSettings.UpdateAssetPathsFromAquaMaiConfig; consider centralizing the responsibility for updating asset paths in one place to avoid implicit side effects and double updates.
  • MuModController.SetMuModChannelAndEnsureCache and EnsureMuModCache catch all exceptions and always return HTTP 200 with a Success flag; if callers need to distinguish transport errors from logical failures, consider returning appropriate HTTP status codes or richer error details instead of swallowing the exceptions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- StaticSettings.RescanAll now calls _modConfigService.GetCurrentAquaMaiConfig(), which itself calls StaticSettings.UpdateAssetPathsFromAquaMaiConfig; consider centralizing the responsibility for updating asset paths in one place to avoid implicit side effects and double updates.
- MuModController.SetMuModChannelAndEnsureCache and EnsureMuModCache catch all exceptions and always return HTTP 200 with a Success flag; if callers need to distinguish transport errors from logical failures, consider returning appropriate HTTP status codes or richer error details instead of swallowing the exceptions.

## Individual Comments

### Comment 1
<location path="MaiChartManager/Controllers/Mod/MuModController.cs" line_range="21-25" />
<code_context>
+    }
+
+    [HttpPut]
+    public async Task<EnsureCacheResultDto> SetMuModChannelAndEnsureCache([FromBody] SetChannelDto req)
+    {
+        if (req.Channel != "slow" && req.Channel != "fast")
+        {
+            throw new ArgumentException("Channel must be 'slow' or 'fast'", nameof(req));
+        }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid throwing `ArgumentException` for invalid channel and return a controlled error response instead.

Throwing `ArgumentException` here will surface as a 500 and won’t match the `EnsureCacheResultDto` contract the client expects. Instead, either return `BadRequest` with a clear validation message or an `EnsureCacheResultDto` with `Success = false` and an appropriate error message so clients always receive a predictable response shape.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread MaiChartManager/Controllers/Mod/MuModController.cs Outdated
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 成功集成了 MuMod 作为新的模组加载器,这是一个重要的功能增强。整体代码结构清晰,特别是在后端引入了 ModConfigServiceMuModService 来分离关注点,使得逻辑更加模块化。对网络请求和文件操作的健壮性考虑得非常周到,例如 MuModService 中的 API 回退机制和原子化文件写入。将多个同步操作改造为异步也提升了应用的响应性。

我发现了一些可以改进的地方,主要集中在异步编程的最佳实践和资源管理方面,例如避免在构造函数中同步等待异步任务、正确使用 HttpClient 等。这些修改将有助于提升应用的稳定性和性能。详细建议请见具体的 review comments。

{
GetGameVersion();
RescanAll();
RescanAll().GetAwaiter().GetResult();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

StaticSettings 的构造函数中,使用了 RescanAll().GetAwaiter().GetResult() 来同步等待一个异步方法。这是一种“同步阻塞异步”的反模式,在某些上下文(例如 UI 线程或 ASP.NET Core 的同步上下文中)中很容易导致死锁。虽然在当前应用启动流程中可能不会立即引发问题,但这使得代码变得脆弱且难以维护。

建议将 StaticSettings 的初始化逻辑重构为异步方式,例如提供一个异步的 InitializeAsync 方法,并在应用程序启动时异步调用它,而不是在构造函数中阻塞。

string? muModCacheVersion = null;
if (muModInstalled)
{
try { muModChannel = muModService.ReadConfig().Channel; } catch { }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

GetGameModInfo 方法中,使用了空的 catch 块来静默处理读取 MuMod 配置失败时可能发生的异常。虽然这可以防止程序崩溃,但完全吞掉异常会使调试变得困难,因为无法得知读取失败的原因(例如,文件被占用、权限问题或格式错误)。建议至少在 catch 块中记录一条警告或调试日志,以便于追踪潜在问题。

            try { muModChannel = muModService.ReadConfig().Channel; } catch (Exception e) { logger.LogWarning(e, "Failed to read MuMod config channel."); }

Comment on lines +68 to +116
public async Task<IConfig> GetCurrentAquaMaiConfig(bool forceDefault = false, bool skipSignatureCheck = false, CancellationToken ct = default)
{
var dllPath = await GetAquaMaiDllPath(ct);

var binary = File.ReadAllBytes(dllPath);
if (!skipSignatureCheck)
{
var sigResult = AquaMaiSignatureV2.VerifySignature(binary);
if (sigResult.Status != AquaMaiSignatureV2.VerifyStatus.Valid)
{
throw new AquaMaiSignatureVerificationFailedException();
}
}
var configInterface = HeadlessConfigLoader.LoadFromPacked(binary);
var config = configInterface.CreateConfig();
CheckConfigApiVersion(configInterface);
if (File.Exists(ModPaths.AquaMaiConfigPath) && !forceDefault)
{
try
{
var view = configInterface.CreateConfigView(File.ReadAllText(ModPaths.AquaMaiConfigPath));
var migrationManager = configInterface.GetConfigMigrationManager();

if (migrationManager.GetVersion(view) != migrationManager.LatestVersion)
{
Console.WriteLine("Migrating AquaMai config from {0} to {1}", migrationManager.GetVersion(view), migrationManager.LatestVersion);
view = migrationManager.Migrate(view);
}

var parser = configInterface.GetConfigParser();
parser.Parse(config, view);
StaticSettings.UpdateAssetPathsFromAquaMaiConfig(config);
}
catch (Exception ex)
{
Console.WriteLine("无法加载 AquaMai 配置");
Console.WriteLine(ex);
if (ex.Message.Contains("Could not migrate the config"))
{
// 这个应该是,AquaMai 未安装或需要更新
throw;
}
// 这个的提示是 AquaMai 配置文件损坏
throw new ConfigCorruptedException();
}
}

return config;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

GetCurrentAquaMaiConfig 方法被标记为 async,但其内部的文件读取操作 File.ReadAllBytesFile.ReadAllText 却是同步的。这会阻塞执行线程,降低了异步的优势。为了充分利用异步编程,建议改用 File.ReadAllBytesAsyncFile.ReadAllTextAsync,并传递 CancellationToken 以支持取消操作。

    public async Task<IConfig> GetCurrentAquaMaiConfig(bool forceDefault = false, bool skipSignatureCheck = false, CancellationToken ct = default)
    {
        var dllPath = await GetAquaMaiDllPath(ct);

        var binary = await File.ReadAllBytesAsync(dllPath, ct);
        if (!skipSignatureCheck)
        {
            var sigResult = AquaMaiSignatureV2.VerifySignature(binary);
            if (sigResult.Status != AquaMaiSignatureV2.VerifyStatus.Valid)
            {
                throw new AquaMaiSignatureVerificationFailedException();
            }
        }
        var configInterface = HeadlessConfigLoader.LoadFromPacked(binary);
        var config = configInterface.CreateConfig();
        CheckConfigApiVersion(configInterface);
        if (File.Exists(ModPaths.AquaMaiConfigPath) && !forceDefault)
        {
            try
            {
                var view = configInterface.CreateConfigView(await File.ReadAllTextAsync(ModPaths.AquaMaiConfigPath, ct));
                var migrationManager = configInterface.GetConfigMigrationManager();

                if (migrationManager.GetVersion(view) != migrationManager.LatestVersion)
                {
                    Console.WriteLine("Migrating AquaMai config from {0} to {1}", migrationManager.GetVersion(view), migrationManager.LatestVersion);
                    view = migrationManager.Migrate(view);
                }

                var parser = configInterface.GetConfigParser();
                parser.Parse(config, view);
                StaticSettings.UpdateAssetPathsFromAquaMaiConfig(config);
            }
            catch (Exception ex)
            {
                Console.WriteLine("无法加载 AquaMai 配置");
                Console.WriteLine(ex);
                if (ex.Message.Contains("Could not migrate the config"))
                {
                    // 这个应该是,AquaMai 未安装或需要更新
                    throw;
                }
                // 这个的提示是 AquaMai 配置文件损坏
                throw new ConfigCorruptedException();
            }
        }

        return config;
    }

Comment on lines +271 to +285
private static async Task<VersionInfoModel[]> FetchVersionInfosAsync(string url, CancellationToken ct)
{
using var client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(15)
};

var json = await client.GetStringAsync(url, ct);
var result = JsonSerializer.Deserialize<VersionInfoModel[]>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});

return result ?? [];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

FetchVersionInfosAsyncDownloadFromUrlsAsync 方法中,每次调用都创建了一个新的 HttpClient 实例。这是一种反模式,在高并发下可能导致套接字耗尽(socket exhaustion)。推荐通过依赖注入使用 IHttpClientFactory 来创建和管理 HttpClient 实例,这样可以更好地重用底层连接。

另外,在 ResolveVersionInfoAsync 中通过 CancellationTokenSource 设置了超时,同时在 FetchVersionInfosAsyncDownloadFromUrlsAsyncHttpClient 上也设置了 Timeout 属性。这两种超时机制是冗余的。建议移除 HttpClient.Timeout 设置,统一使用 CancellationToken 来控制超时,这样代码意图更清晰。

@clansty clansty merged commit 7b270a5 into main Mar 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant