UmamusumeResponseAnalyzer 是基于 Terminal.Gui 的本地 TUI 宿主。它接收游戏请求/响应的 MessagePack payload,按 Gallop endpoint catalog 分发给已安装插件;插件 live state 显示在 workspace,notification 由 Host overlay 呈现,interactive Host session 中捕获的异常集中记录到启动 workspace。
- 任意可以把游戏请求/响应 MessagePack payload 发送到宿主
/notify/request//notify/response的 sender。请求必须带X-Hachimi-Game-Urlheader,值为游戏原始 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,比如 Fiddler 或 mitmproxy。
- 在 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/和 debugpackets/都写在工作目录下。- 默认监听
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 的TextField与ListView使用 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/中超过一天的旧文件会在下次保存时清理,单个旧文件清理失败不会中断当前请求/响应分析。
- 浏览器或命令行访问
http://127.0.0.1:4693/notify/ping,返回pong说明宿主 HTTP server 已启动。 - 启动游戏后,前往殿堂马列表、竞技场选择对手或查看好友信息。若 workspace 中出现插件输出,说明 sender、header 和插件分发配置正确。
插件仓库从https://ura.shuise.net/api/Plugins拉取插件目录,并按配置中的服务器目标过滤插件。插件自身未声明Targets时视为所有目标可用。安装只处理用户选中的插件,不根据 manifestDependencies自动增加其它插件。- 仓库安装先将 ZIP 下载到
Plugins/plugin-*.tmp,按下述包契约及请求的Author、InternalName、Version校验后,才替换Plugins/<InternalName>.zip并尝试热重载;下载或校验失败会删除临时文件并保留现有 ZIP 和运行实例。宿主只扫描Plugins/顶层的 ZIP 包;不扫描独立 DLL 文件或子目录。 InternalName按OrdinalIgnoreCase全局唯一;仓库目录出现重复名称时本次仓库操作失败,Plugins/中名称冲突的包不会加载,并写入全局最近日志和 error notification。- URACloud 网页集成挂在本地
/uracloud/*。/uracloud/status返回当前 URA 版本和已加载插件;/uracloud/install只接受白名单 Origin(https://ura.shuise.net或http://localhost:5173)提交的{author, internalName, version},下载源固定为 URACloud 插件仓库,并且安装前必须在本机控制台确认。成功响应为{ok: true, installed, version},其中installed和version取自已校验 manifest。
- 插件直接引用宿主程序集与 Terminal.Gui。宿主公开
IPlugin、AnalyzerAttribute、workspace UI contract 和 Gallop DTO/endpoint catalog;插件源码使用UmamusumeResponseAnalyzer.Plugin、UmamusumeResponseAnalyzer.TerminalGui、Terminal.Gui.ViewBase、Terminal.Gui.Views、Gallop、Gallop.Endpoints命名空间。 - 插件包只能是
Plugins/<InternalName>.zip。ZIP 根目录必须且只能有一个manifest.json,并且必须且只能有一个名为<InternalName>.dll的主程序集;根目录中的其它 managed DLL 是插件依赖程序集,卫星资源 DLL 放在对应 culture 子目录。标准打包产物只包含这些运行时文件,不包含 PDB、deps.json 或临时 metadata。ZIP 文件名必须等于 manifestInternalName;manifestInternalName、根主 DLL 文件名(不含扩展名)和主 DLL 内嵌AssemblyName必须一致。 manifest.json必须精确包含Author、InternalName、DisplayName、Description、Changelog、Version、Dependencies、Targets、RepositoryUrl、LastUpdate、Category、Homepage。Dependencies声明直接软联动的插件 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.Exact、Wildcard和Regex都匹配 catalog 的 canonical path,区分大小写。wildcard 的*不跨/;regex 以CultureInvariant、NonBacktracking和 100 ms timeout 匹配完整 path。每个 pattern 必须在注册时至少命中一个 catalog endpoint,多个 pattern 的命中结果会去重。- attribute 和程序化 analyzer handler 都返回
ValueTask。程序化注册只能在Initialize或宿主调用的OnStarted回调中发生;同一回调内的 analyzer 与 background 注册在回调成功后原子生效,失败时不留下部分注册。 - 宿主按
X-Hachimi-Game-Urlheader 中的 canonical game URL 解析 path,并在带/不带/umamusume前缀两种形式之间查询GameEndpointCatalog.ByPath;两种形式均未命中的数据会被静默丢弃。sender 可附带X-Hachimi-sid、X-Hachimi-app-ver、X-Hachimi-res-ver、X-Hachimi-viewerid、X-Hachimi-device、X-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 fixint0/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;SetPanel、RemovePanel、Notify、SwitchTo、BindHotkey和Remove均为Workspace实例方法。SetPanel(key, title, content, fullBleed, switchToWorkspace)接受WorkspaceContent,默认在 panel 更新时切到目标 workspace;静默刷新传switchToWorkspace: false。WorkspaceContent的 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、事件订阅及快捷键归属。Initialize或OnStarted回调抛异常时不会提交该回调暂存的 analyzer/background 注册。 Notify(..., shortcuts: UiShortcut[])注册 notification TTL 内的临时快捷键;HotkeyContext.BindShortcut(...)注册 popup 存活期间的临时快捷键。临时 handler 可重复触发,不关闭 popup、notification,也不延长 TTL;Command Mode 激活后由 focusedTextField处理输入,其他按键依次交给 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()外,SetPanel、RemovePanel、Notify、SwitchTo和BindHotkey均立即抛出InvalidOperationException;对“启动”调用Remove()会抛出InvalidOperationException。- 热重载按 manifest
InternalName应用;热安装软联动目标时重载 Consumer 并合并共享组,热卸载目标时重建仍安装的关联插件,不把整组保持为卸载状态。 - 插件配置由插件自行维护。宿主不预创建插件数据目录,不自动读写配置文件,也不提供通用属性编辑器;菜单入口调用
ConfigPromptAsync(IApplication application, CancellationToken cancellationToken = default),其中application与context.Application为同一个宿主实例。该方法可能在插件实例已加载、但Initialize尚未执行时调用,因此不得依赖Initialize产生的状态。插件需要配置或数据目录时,应在插件代码中创建目录、读取/校验自己的文件,用户取消时不得写入 draft,错误直接抛出明确异常。