AetherGate 本地游戏自动查找机制分析
速览
AetherGate 在「首次启动」时弹出的「自动查找本地游戏」功能,其核心实现是 GameSelector.xaml.cs 中的 AutoSearchInstalledGames() 方法。
该方法的查找依据是 Windows 注册表,具体读取的是 米哈游官方启动器 HoYoPlay(HYP)写入的注册表项中的 GameInstallPath 值,而不是全盘扫描磁盘文件,也不是基于游戏 exe 的 MD5 校验。
- 国服游戏注册表根键:
HKEY_CURRENT_USER\Software\miHoYo\HYP\1_1\{gameBiz} - 国际服游戏注册表根键:
HKEY_CURRENT_USER\Software\Cognosphere\HYP\1_0\{gameBiz}
其中 {gameBiz} 为游戏业务标识,例如 hk4e_cn(原神国服)、hkrpg_global(星穹铁道国际服)、nap_cn(绝区零国服)等。
一、首次启动的触发流程
整个「首次启动自动查找」并不是一个独立后台任务,而是由 UI 引导链路触发的:
- 程序入口 App.xaml.cs 的
OnLaunched调用AppConfig.CheckEnviromentAsync()。 - 首次运行判定 在 AppConfig.Configuration.cs 的
CheckEnviromentAsync中:若未配置UserDataFolder(第一次启动),弹出WelcomeWindow让用户选择数据目录并做环境检查(WebView2 / WebP 解码器 / 写入权限)。 - 主窗口创建 通过验证后创建 MainWindow,其内容为 MainView.xaml 中托管的
GameSelector用户控件。 - GameSelector 初始化 GameSelector.xaml.cs 构造函数调用
InitializeGameSelector()→InitializeGameIconsArea()。 - 自动弹出引导提示 在 GameSelector.xaml.cs#L232-L235 中:当
GameBizIcons.Count == 0 && CurrentGameBizIcon is null(首次启动没有任何已选游戏)时,自动打开TeachTip_SelectGame。 - 提示中的「自动查找」按钮 GameSelector.xaml#L74-L88 这个 TeachingTip 的 ActionButton 绑定到
AutoSearchInstalledGamesCommand,按钮文案为「自动查找」。 - 用户点击后 执行
AutoSearchInstalledGames(),完成注册表扫描与结果写入。
此外,在「已安装游戏」展开面板的头部还有一个常驻的「自动查找」按钮 GameSelector.xaml#L327-L338(Button_AutoSearch),用户可随时再次触发该扫描。
二、核心查找逻辑:AutoSearchInstalledGames()
源码位于 GameSelector.xaml.cs#L893-L949,逻辑分两层:
第 1 层:优先复用已记录的安装路径
对每一个来自 HoYoPlay 缓存游戏列表(GetCachedGameInfos())的游戏:
- 调用
GameLauncherService.GetGameInstallPath(gameBiz),该方法在 GameLauncherService.cs#L60-L81 中读取AppConfig中持久化的install_path_{biz}配置项。 - 若该路径
Directory.Exists为真,或虽不存在但标记为可移动存储(GetGameInstallPathRemovable),则视为已找到,直接加入选中列表,跳过注册表查询。
第 2 层:注册表回退查找
当配置中无有效路径时,按服务器类型构造注册表键并读取:
国服 (Server == "cn"):
HKEY_CURRENT_USER\Software\miHoYo\HYP\1_1\{gameBiz}
读取值名: GameInstallPath
国际服 (Server == "global"):
HKEY_CURRENT_USER\Software\Cognosphere\HYP\1_0\{gameBiz}
读取值名: GameInstallPath
读取实现为 Registry.GetValue(key, "GameInstallPath", null) as string。
若读出的路径 Directory.Exists 为真,则:
- 通过
AppConfig.SetGameInstallPath(gameBiz, path)写回配置持久化; - 将该
gameBiz追加到结果字符串(逗号分隔)。
循环结束后:
AppConfig.SelectedGameBizs = sb.ToString().TrimEnd(',')写入选中游戏集合;- 调用
InitializeGameSelector()刷新 UI; - 若未固定则调用
Pin()固定选择器面板。
注:
{gameBiz}直接使用业务标识本身(如hk4e_cn、hkrpg_global、nap_cn),与 HoYoPlay 启动器在注册表中为每个游戏创建的子键命名一致。
三、Bilibili 服的特殊处理
代码对 Bilibili 渠道服做了显式处理 GameSelector.xaml.cs#L903-L906:
if (item.IsBilibiliServer())
{
gameBiz = $"{gameBiz.Game}_bilibili"; // 例如 hk4e_bilibili
}
随后注册表分支判定 gameBiz.Server is "cn" / "global":
hk4e_bilibili的Server为"bilibili",既非"cn"也非"global",因此key保持为空字符串,跳过注册表查找。
结论:Bilibili 服游戏无法被自动查找,因为 HoYoPlay 启动器不为 B 站渠道服单独写 GameInstallPath 注册表项。B 站服需要用户通过「定位游戏」手动指定文件夹(见下文「手动定位」)。
四、注册表键来源说明
GameRegistry.cs 中定义了两套相关常量,注意区分用途:
- 本功能实际使用的(HYP 新启动器):
GamePath_HYP_cn = HKEY_CURRENT_USER\SOFTWARE\miHoYo\HYP\1_1与GamePath_HYP_os = HKEY_CURRENT_USER\SOFTWARE\Cognosphere\HYP\1_0。AutoSearchInstalledGames中拼接的键正是这两个根键 +{gameBiz}子键。 - 旧版独立启动器的游戏配置键:如
GamePath_hk4e_cn = HKEY_CURRENT_USER\Software\miHoYo\原神等。这些通过 GameBiz.GetGameRegistryKey() 暴露,但仅被GameSettingService、GameAccountService、GameNoticeService用于读取游戏内设置/账号/公告,不参与安装路径的自动查找。
五、路径持久化与可移动存储
- 持久化位置:AppConfig.Setting.cs,
SetGameInstallPath以键名install_path_{biz}写入配置(便携版写入 exe 同级config.ini,安装版写入注册表HKCU\Software\AetherGate,详见AppConfig.Configuration.cs)。 - 可移动存储相对路径:GameLauncherService.GetRelativePathIfInRemovableStorage 当检测到路径位于 USB / 可移动设备时,存为相对路径,并在
install_path_removable_{biz}标记true,便于设备拔出后仍保留配置。 - 失效清理:GetGameInstallPath 若路径不存在且非可移动存储,会自动清空该配置项(
ChangeGameInstallPath(gameBiz, null)),避免脏数据。
六、查找后的「已安装」校验
自动查找只负责定位安装目录;判断游戏是否真实存在/获取版本,使用以下方法(均在 GameLauncherService.cs):
- 本地版本
GetLocalGameVersionAsync:读取{installPath}/config.ini,正则匹配game_version=(.+)。 - exe 是否存在
IsGameExeExistsAsync:检查{installPath}/{exe_name}。 - 各游戏 exe 名(GameLauncherService.cs#L227-L242):
- 原神国服 / B 站服:YuanShen.exe
- 原神国际服:GenshinImpact.exe
- 星穹铁道:StarRail.exe
- 崩坏 3:BH3.exe
- 绝区零:ZenlessZoneZero.exe
- 占用空间统计 GameSelector.InitializeInstalledGamesAsync 递归枚举安装目录文件计算实际占用,并通过
FileIdInfo去重统计硬链接节省的空间(针对多服共享文件场景)。
七、未参与自动查找的相关机制
为避免误解,以下机制虽然存在于代码或 API 中,但未用于本「自动查找本地游戏」功能:
GameScanInfo/getGameScanInfo接口:GameScanInfo.cs 定义了「不同版本游戏 exe 的 MD5 列表」结构,HoYoPlayClient.GetGameScanInfosAsync 提供了从 HoYoPlay API 拉取该信息的客户端方法。但全工程检索显示,GetGameScanInfosAsync在 AetherGate 主项目中从未被调用。也就是说,基于 exe MD5 的版本识别/校验通道已预留但尚未启用,本功能并不依赖它。- 旧版独立启动器注册表项:
HKCU\Software\miHoYo\原神等键仅用于读取游戏内设置 / 账号 / 公告,不用于安装路径发现(见第四节)。 - 手动「定位游戏」:GameLauncherPage.LocateGameAsync 与 GameLauncherSettingDialog.LocateGameAsync 提供文件夹选择器,由用户手工指定安装目录,属于手动补充手段,非自动查找。
八、整体流程图
- 首次启动 →
CheckEnviromentAsync→WelcomeWindow(数据目录 + 环境检查) - → 创建
MainWindow/MainView/GameSelector - →
InitializeGameIconsArea发现无已选游戏 → 自动弹出TeachTip_SelectGame - → 用户点击「自动查找」→
AutoSearchInstalledGames() - → 遍历 HoYoPlay 缓存游戏列表:
- 先查 AppConfig 中的 install_path_{biz},有效则直接采用
- 否则查注册表 HKCU\Software\miHoYo\HYP\1_1\{biz}(国服)或 HKCU\Software\Cognosphere\HYP\1_0\{biz}(国际服)的 GameInstallPath
- 路径存在则写回 AppConfig 并加入选中集合
- →
AppConfig.SelectedGameBizs写入 →InitializeGameSelector()刷新 →Pin()固定 - 后续启动时第 3 步条件不再满足,不再自动弹提示;用户可通过常驻「自动查找」按钮再次触发。
九、小结
- 查找依据:HoYoPlay 官方启动器写入注册表的
GameInstallPath,非磁盘扫描、非 MD5 校验。 - 触发方式:首次启动时无已选游戏自动弹出引导提示,用户点击「自动查找」按钮执行;之后可手动触发。
- 覆盖范围:仅覆盖国服(miHoYo HYP 1_1)与国际服(Cognosphere HYP 1_0)两个渠道;Bilibili 服不在自动查找覆盖范围内。
- 前置依赖:用户须先用官方 HoYoPlay 启动器安装过游戏,注册表中才会存在对应
GameInstallPath;否则自动查找无果,需走手动「定位游戏」流程。 - 健壮性:路径失效会自动清理;可移动存储支持相对路径持久化;多服共享文件通过文件 ID 去重统计空间。