Skip to content

Repository files navigation

UmamusumeResponseAnalyzer

UmamusumeResponseAnalyzer 是基于 Terminal.Gui 的本地 TUI 宿主。它接收游戏请求/响应的 MessagePack payload,按 Gallop endpoint catalog 分发给已安装插件;插件 live state 显示在 workspace,notification 由 Host overlay 呈现,interactive Host session 中捕获的异常集中记录到启动 workspace。

前置 Prerequisite

  • 任意可以把游戏请求/响应 MessagePack payload 发送到宿主 /notify/request / /notify/response 的 sender。请求必须带 X-Hachimi-Game-Url header,值为游戏原始 canonical URL;该 URL 的 path 必须命中 Gallop endpoint catalog,或能在带/不带 /umamusume 前缀两种形式之间切换后命中 catalog。推荐 ura-core
  • sender 的目标地址默认设置为 http://127.0.0.1:4693。如果游戏在手机或其他设备上运行,首次运行向导可把监听地址改为 0.0.0.0;启动时按控制台提示放行防火墙。
  • Windows 版主菜单提供 自动安装ura-core。该入口会查找本机游戏目录,选择安装 Hachimi 或 umamusume-localify,并在启用 DLL redirection 时请求管理员权限。
  • (可选,如果需要脱离 DMM 启动游戏) DMM Game Player β 及 HTTPS proxy,比如 Fiddlermitmproxy

安装 Installation

  • Release 页面下载最新版本程序。
  • 将程序放在任意位置,运行 UmamusumeResponseAnalyzer.exe
  • 普通启动要求 stdin 和 stdout 连接到 interactive terminal;redirected stdin/stdout 会以明确错误退出。--version--update <savePath>--enable-dll-redirection 等 CLI-only 路径不启动 TUI。
  • 首次运行按向导选择运行设备、服务器目标(日服 Cygames / 繁中服 Komoe)、事件数据语言和训练员性别。
  • 返回主菜单后先选择 更新数据文件。数据文件用于事件、技能、名称等本地解析;不完整或损坏时数据库保持不可用,插件不会初始化,HTTP server 也不会启动,程序会提示更新全部数据文件后重启。未知技能进化条件类型只记录 warning,并按条件未满足处理,不阻断完整数据快照加载。
  • 进入 插件仓库,安装需要的功能插件。没有插件时宿主仍会启动,并提供启动信息、异常记录、通知和基础分发能力。
  • 选择 启动! 后会立即进入 Terminal.Gui 的全屏启动 workspace;数据加载、插件初始化和 HTTP server 启动在后台推进。启动 workspace 在整个 Host session 内持续存在,显示运行环境、初始化结果、插件摘要和全局最近日志;最近日志收集宿主/插件日志和 Host 捕获的异常,最多保留 128 条。数据库加载警告、插件扫描/加载/安装诊断、程序更新文件损坏和请求分析异常还会显示 Host overlay notification;写入日志或显示 notification 均不会切换当前 workspace。异常行可右键打开 context menu,复制完整 backtrace 会将完整异常链和 stack trace 写入系统 clipboard;普通日志不提供该操作。四个区域的尺寸只随 viewport 变化;超出区域的表格与日志使用 Terminal.Gui 原生滚动条,内容增长不改变 panel 布局。启动状态以紧凑结果行实时更新,不显示额外 header 或 footer。

运行与文件位置

  • 默认工作目录为 %LocalAppData%\UmamusumeResponseAnalyzer。启动时如果当前目录存在 .portable 文件夹,工作目录会改为 ./.portable
  • config.yaml、数据文件、Plugins/ 和 debug packets/ 都写在工作目录下。
  • 默认监听 http://127.0.0.1:4693/notify/ping 返回 pong,可作为 smoke test。
  • 启动、设置和插件设置使用占满 terminal 且无外边框的 Terminal.Gui 原生 Menu 页面;mouse hover 或 Up / Down 移动当前菜单项,click 或 Enter 直接激活。插件仓库、更新、安装 Mod 和字段编辑使用各自的 dialog,完成后返回对应的上级菜单。启动后按 /,或在 focused control 未使用 Enter 时,打开 Command Mode;内置命令需以 / 开头。Command Mode 使用 Terminal.Gui 原生 TextField 输入,支持 Tab 补全、Up / Down 浏览当前进程内提交过的命令、Esc 取消和 Enter 执行。底部全宽带框 overlay 显示 Command Mode 标题、补全候选及 +N more 输入区和操作 footer;窄窗口或高度不足时使用紧凑布局。鼠标移到 terminal 最底部会显示覆盖在 workspace 上的居中 taskbar popup,不占用 workspace 布局空间;过长标题会随 terminal 宽度以 缩略,当前项和 hovered 项优先显示完整标题。click workspace title 直接切换,按住左键左右拖拽可调整并持久化 taskbar 顺序;该顺序只影响 taskbar。当前项和 hover 由背景属性区分。Command Mode 显示时 taskbar 被抑制,并拥有更高的图层优先级。/workspace/workspace switch 打开 workspace 选择器,/workspace switch <title> 直接切换;标题也可用双引号包围,其中 \"\\ 分别表示双引号和反斜杠。/workspace list 列出 workspace。workspace 内容超出终端时默认显示底部;Up / Down 按行滚动,PageUp / PageDown 按页滚动,Home / End 跳到顶部或底部。Left / Right 不参与 workspace viewport 滚动;focused view 未处理时继续匹配已注册 hotkey。Terminal.Gui 主界面中,滚轮产生与无 modifier 的 Up / Down 相同的 workspace viewport 滚动效果;滚轮不触发快捷键,popup 或 Command Mode 存活时会被忽略。Host dialog 的 TextFieldListView 使用 Terminal.Gui 原生 whole-view hover;focus 视觉优先于 hover,hover 不改变 focus、selection 或 marked 状态。Button、菜单及插件提供的 View 使用各控件自身的 Terminal.Gui 默认行为。workspace/plugin popup 保持 mouse click-through。popup 或 Command Mode 存活时由其优先处理按键;workspace 已到边界时按键继续交给已注册 hotkey。/plugin/plugin list 列出插件运行期状态,/plugin load|unload|reload <InternalName> 只改变当前进程内加载状态,不安装、不删除插件文件、不写禁用配置。Command 结果始终写入全局最近日志;只有 message 而没有 display 的结果同时显示 global notification,有 display 的结果改用 popup 呈现且不重复通知。插件更新 workspace panel 时默认会切到该 workspace;bootstrap 刷新和异常写入不会切换当前 workspace。按 Ctrl+B 返回启动 workspace,按 P 查看已加载插件列表,按 Ctrl+C 退出程序。
  • 程序启动后会检查已加载插件是否有新版本;发现更新时只通知,不自动安装。更新插件需要进入 插件仓库 手动选择。
  • 更新数据文件时会先写入临时文件,下载成功后替换目标文件;失败时清理临时文件并保留已有文件。
  • 开启 debug packet 保存后,请求写为 Q、响应写为 R.msgpack 文件,文件名包含时间戳、UUIDv7 和 API endpoint path(/ 写为 -);DEBUG 构建额外写 .json,文件名只包含时间戳和 Q/R,完整 canonical URL 写在 JSON 内容中。packets/ 中超过一天的旧文件会在下次保存时清理,单个旧文件清理失败不会中断当前请求/响应分析。

检查安装 Checking

  • 浏览器或命令行访问 http://127.0.0.1:4693/notify/ping,返回 pong 说明宿主 HTTP server 已启动。
  • 启动游戏后,前往殿堂马列表、竞技场选择对手或查看好友信息。若 workspace 中出现插件输出,说明 sender、header 和插件分发配置正确。

插件仓库与 URACloud

  • 插件仓库https://ura.shuise.net/api/Plugins 拉取插件目录,并按配置中的服务器目标过滤插件。插件自身未声明 Targets 时视为所有目标可用。安装只处理用户选中的插件,不根据 manifest Dependencies 自动增加其它插件。
  • 仓库安装先将 ZIP 下载到 Plugins/plugin-*.tmp,按下述包契约及请求的 AuthorInternalNameVersion 校验后,才替换 Plugins/<InternalName>.zip 并尝试热重载;下载或校验失败会删除临时文件并保留现有 ZIP 和运行实例。宿主只扫描 Plugins/ 顶层的 ZIP 包;不扫描独立 DLL 文件或子目录。
  • InternalNameOrdinalIgnoreCase 全局唯一;仓库目录出现重复名称时本次仓库操作失败,Plugins/ 中名称冲突的包不会加载,并写入全局最近日志和 error notification。
  • URACloud 网页集成挂在本地 /uracloud/*/uracloud/status 返回当前 URA 版本和已加载插件;/uracloud/install 只接受白名单 Origin(https://ura.shuise.nethttp://localhost:5173)提交的 {author, internalName, version},下载源固定为 URACloud 插件仓库,并且安装前必须在本机控制台确认。成功响应为 {ok: true, installed, version},其中 installedversion 取自已校验 manifest。

插件开发 Plugin Development

  • 插件直接引用宿主程序集与 Terminal.Gui。宿主公开 IPluginAnalyzerAttribute、workspace UI contract 和 Gallop DTO/endpoint catalog;插件源码使用 UmamusumeResponseAnalyzer.PluginUmamusumeResponseAnalyzer.TerminalGuiTerminal.Gui.ViewBaseTerminal.Gui.ViewsGallopGallop.Endpoints 命名空间。
  • 插件包只能是 Plugins/<InternalName>.zip。ZIP 根目录必须且只能有一个 manifest.json,并且必须且只能有一个名为 <InternalName>.dll 的主程序集;根目录中的其它 managed DLL 是插件依赖程序集,卫星资源 DLL 放在对应 culture 子目录。标准打包产物只包含这些运行时文件,不包含 PDB、deps.json 或临时 metadata。ZIP 文件名必须等于 manifest InternalName;manifest InternalName、根主 DLL 文件名(不含扩展名)和主 DLL 内嵌 AssemblyName 必须一致。
  • manifest.json 必须精确包含 AuthorInternalNameDisplayNameDescriptionChangelogVersionDependenciesTargetsRepositoryUrlLastUpdateCategoryHomepageDependencies 声明直接软联动的插件 internal name:目标未进入本轮运行期时忽略该边且 Consumer 独立加载;双方均进入本轮运行期时,宿主按声明顺序先初始化目标插件、按反向顺序卸载,并让依赖连通组共享 collectible load context。已安装图中的自依赖、重复名称和依赖环使插件加载失败。Consumer 通过 IPluginContext.IsPluginAvailable 判断本轮共享组中的声明目标;可选插件类型不得出现在 Consumer 导出类型签名或启动必经字段初始化中。Targets 为空、命中配置的 repository targets,或配置未设置 targets 时,宿主才创建插件实例并使其进入运行期;否则 ZIP 保持安装但插件不初始化。
  • 宿主提供基础 TurnInfo / CommandInfo 领域视图。UAF、L'Arc、Cook、Mecha、Legend、Pioneer、Onsen、Breeders 等场景专用聚合模型由场景插件基于 Gallop DTO 派生。
  • 精确请求/响应 analyzer 可以使用 endpoint attribute,例如 [ResponseAnalyzer<GameApi.Account.Index>] ValueTask Analyze(DataLinkIndexResponse response)。方法必须返回 ValueTask,并且只能有一个闭合具体 Gallop DTO 参数;DTO 类型必须精确匹配 endpoint descriptor 对应方向的 payload 类型。同一个方法可以挂多个 analyzer attribute,但这些 attribute 必须要求同一个 DTO 类型。
  • 程序化 analyzer 统一使用 context.Analyzers.Register<TPayload>(AnalyzerKind kind, IReadOnlyList<EndpointPattern> patterns, Func<AnalyzerInvocation<TPayload>, ValueTask> handler, int priority = 0)。DTO 的 TPayload 必须是宿主当前 catalog 中该方向的闭合具体 Gallop DTO;raw analyzer 使用 ReadOnlyMemory<byte>AnalyzerInvocation<TPayload> 提供匹配后的 descriptor、payload 和非 null 的 GameHttpHeaders,其中六个 header 值分别可为 null
  • EndpointPattern.ExactWildcardRegex 都匹配 catalog 的 canonical path,区分大小写。wildcard 的 * 不跨 /;regex 以 CultureInvariantNonBacktracking 和 100 ms timeout 匹配完整 path。每个 pattern 必须在注册时至少命中一个 catalog endpoint,多个 pattern 的命中结果会去重。
  • attribute 和程序化 analyzer handler 都返回 ValueTask。程序化注册只能在 Initialize 或宿主调用的 OnStarted 回调中发生;同一回调内的 analyzer 与 background 注册在回调成功后原子生效,失败时不留下部分注册。
  • 宿主按 X-Hachimi-Game-Url header 中的 canonical game URL 解析 path,并在带/不带 /umamusume 前缀两种形式之间查询 GameEndpointCatalog.ByPath;两种形式均未命中的数据会被静默丢弃。sender 可附带 X-Hachimi-sidX-Hachimi-app-verX-Hachimi-res-verX-Hachimi-vieweridX-Hachimi-deviceX-Hachimi-device-subtype;raw analyzer 收到的是原始 MessagePack payload bytes。
  • 分发以 raw payload 为基础;DTO analyzer 在执行点按 Gallop descriptor 反序列化,同一分发中的 DTO analyzer 共享反序列化结果。raw analyzer 和 DTO analyzer 都按 priority 顺序执行。
  • Gallop DTO 的 bool 字段在反序列化时接受 MessagePack boolean 和 positive fixint 0 / 1;其它整数编码会抛出 MessagePackSerializationException,序列化始终写入 MessagePack boolean。
  • 插件必须实现 Initialize(IPluginContext context)Dispose()ConfigPromptAsync(...) 有默认空实现。context.Application 是宿主进程唯一的 IApplication,插件不得自行创建或释放 Terminal.Gui application。插件的配置与其它 dialog 直接使用该 application 和 Terminal.Gui 控件;宿主 modal helper 不属于插件 ABI。插件通过 Workspace.Create(title) 获取 workspace,Workspace.Current 读取当前 workspace;SetPanelRemovePanelNotifySwitchToBindHotkeyRemove 均为 Workspace 实例方法。SetPanel(key, title, content, fullBleed, switchToWorkspace) 接受 WorkspaceContent,默认在 panel 更新时切到目标 workspace;静默刷新传 switchToWorkspace: falseWorkspaceContent 的 factory 每次返回一个未挂载的新 Terminal.Gui.ViewBase.View,View 的挂载和释放由宿主管理;纯文本可用 WorkspaceContent.Text(...)TerminalUi.Log(source, text, severity) 写入启动 workspace 的全局“最近日志”,全局保留最新 128 条;Workspace.Notify 写入对应 workspace scope,TerminalUi.Notify 写入 global scope。普通 workspace 只显示 panel live state,notification 按现有 scope 由 Host overlay 显示。interactive Host session 中,TerminalUi.LogException 捕获的异常也写入全局“最近日志”,且不会切换 active workspace。插件 hotkey 通过 HotkeyManager 注册。context.Events.OnStarted(Func<CancellationToken, ValueTask>) 用于订阅宿主启动事件;context.RunBackground(Func<CancellationToken, ValueTask>) 把插件长期任务交给宿主管理。卸载时宿主先停止接收该 generation 的回调、取消并等待 background operation,再调用 Dispose() 和清理该插件的 analyzer、事件订阅及快捷键归属。InitializeOnStarted 回调抛异常时不会提交该回调暂存的 analyzer/background 注册。
  • Notify(..., shortcuts: UiShortcut[]) 注册 notification TTL 内的临时快捷键;HotkeyContext.BindShortcut(...) 注册 popup 存活期间的临时快捷键。临时 handler 可重复触发,不关闭 popup、notification,也不延长 TTL;Command Mode 激活后由 focused TextField 处理输入,其他按键依次交给 popup shortcut、popup built-in、最新 TTL-live notification shortcut、workspace navigation 和持久 hotkey,仍未处理的 /Enter 再打开 Command Mode。notification 未显示在 active workspace、窄屏或 overflow 中时,TTL 内的快捷键仍然有效。临时快捷键不写入持久 hotkey dictionary,也不自动渲染按键提示;workspace 删除、宿主停止和插件卸载会清理对应注册。
  • Workspace.Create(title) 原样保留 title(包括边界空白),纯空白 title 非法;进程内以 OrdinalIgnoreCase 将 title canonicalize 为全局共享的 Workspace,所有调用方取得同一实例。Workspace.Current 始终返回当前 workspace;每个 Host session 都以不可移除的“启动” workspace 作为初始值和默认回退目标。普通 workspace 只显示各 panel 的当前 live state;同一 workspace 内的 panel key 以 Ordinal 区分并由所有调用方共享,SetPanel(...) 替换同 key panel,RemovePanel(key) 删除同 key panel并返回是否存在。Workspace.Remove() 清除普通 workspace 的 panel、滚动位置、notification/临时快捷键和 workspace hotkey;删除 active 普通 workspace 时回退到注册顺序首项,即“启动”。相同 title 此后再次 Workspace.Create(...) 会取得新的 generation;旧 handle 成为 tombstone,除重复 Remove() 外,SetPanelRemovePanelNotifySwitchToBindHotkey 均立即抛出 InvalidOperationException;对“启动”调用 Remove() 会抛出 InvalidOperationException
  • 热重载按 manifest InternalName 应用;热安装软联动目标时重载 Consumer 并合并共享组,热卸载目标时重建仍安装的关联插件,不把整组保持为卸载状态。
  • 插件配置由插件自行维护。宿主不预创建插件数据目录,不自动读写配置文件,也不提供通用属性编辑器;菜单入口调用 ConfigPromptAsync(IApplication application, CancellationToken cancellationToken = default),其中 applicationcontext.Application 为同一个宿主实例。该方法可能在插件实例已加载、但 Initialize 尚未执行时调用,因此不得依赖 Initialize 产生的状态。插件需要配置或数据目录时,应在插件代码中创建目录、读取/校验自己的文件,用户取消时不得写入 draft,错误直接抛出明确异常。

About

No description or website provided.

Topics

Resources

Stars

194 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages