本文件用于给后续代理提供可直接复用的仓库上下文。当前仓库已经统一改为由根目录 AGENTS.md 维护全部项目级、后端级、前端级说明;历史 CLAUDE.md 已全部删除,不再作为维护入口。
开始动手前,优先参考这些文件:
AGENTS.mdREADME.md
规则:
AGENTS.md是当前唯一维护中的仓库工作手册,所有原CLAUDE.md内容已并入本文件。README.md主要面向产品说明、构建入口和用户侧信息。- 仓库内不再保留任何
CLAUDE.md;如发现重新出现,应视为需要回收进AGENTS.md的重复文档。 - 若文档与源码冲突,以源码为准,并在改动后同步更新
AGENTS.md。
补充说明:
- 仓库内
.claude/目录只有scheduled_tasks.lock,没有额外项目记忆文件。 - 后端与前端原先分散在
CLAUDE.md、XUnityToolkit-WebUI/CLAUDE.md、XUnityToolkit-Vue/CLAUDE.md的内容,现已统一整理到本文件后半部分的专项章节中。
XUnityToolkit-WebUI 是一个面向 Unity 游戏汉化/翻译工作流的 Windows 桌面工具,能力包括:
- 一键安装 BepInEx 与 XUnity.AutoTranslator
- 通过
LLMTranslate.dll将游戏文本转发到本地 Web API 做 AI 翻译 - 云端 LLM 与本地 llama.cpp 模式
- 资产提取与预翻译
- TextMesh Pro 字体替换与 SDF 字体生成
- 游戏库管理、封面/图标/背景图管理
- BepInEx 日志分析、插件健康检查
- 更新器与 MSI 安装包
顶层关键目录:
XUnityToolkit-WebUI/后端主程序。ASP.NET Core Minimal API + WinForms/WebView2 宿主。XUnityToolkit-Vue/前端。Vue 3 + TypeScript + Naive UI + Pinia + Vite。XUnityToolkit-WebUI.Tests/后端测试工程。xUnit,当前覆盖翻译响应解析与运行时占位符保护等关键回归。TranslatorEndpoint/net35的LLMTranslate.dll,供 XUnity.AutoTranslator 调用。Updater/AOT 更新器,负责文件替换、删除、回滚、重启。Installer/WiX 安装器工程。bundled/构建/发布时附带的字体、脚本预设、BepInEx/XUnity/llama 资源。.github/workflows/CI/CD 工作流。
后端:
.NET 10net10.0-windows- ASP.NET Core Minimal API
- SignalR
- WinForms + WebView2
- AssetsTools.NET
- FreeTypeSharp
前端:
- Vue 3
- TypeScript
- Naive UI
- Pinia
- Vite 8
其他子项目:
TranslatorEndpoint:net35, C# 7.3Updater:net10.0,PublishAot=trueInstaller: WixToolset v6(当前工程为WixToolset.Sdk/6.0.2)
后端构建:
dotnet build XUnityToolkit-WebUI/XUnityToolkit-WebUI.csproj
dotnet build XUnityToolkit-WebUI/XUnityToolkit-WebUI.csproj -p:SkipFrontendBuild=true
dotnet run --project XUnityToolkit-WebUI/XUnityToolkit-WebUI.csproj前端:
cd XUnityToolkit-Vue
npm run dev
npm run build
npx vue-tsc --build测试:
dotnet test补充约束:
dotnet build XUnityToolkit-WebUI/...与dotnet test XUnityToolkit-WebUI.Tests/...不要并行执行;StaticWebAssets会竞争obj/.../rpswa.dswa.cache.json,应串行验证
翻译端点:
dotnet build TranslatorEndpoint/TranslatorEndpoint.csproj -c Release本地完整构建:
.\build.ps1
.\build.ps1 -SkipDownload重要说明:
XUnityToolkit-WebUI.csproj默认会在构建前自动执行前端npm install+npm run build。- 前端开发代理到
http://127.0.0.1:51821,不要改成localhost。 - 完整 UI 预览优先看后端端口
51821,因为它同时承载静态前端和 API。 - 发布流程当前不会在构建脚本或 GitHub Actions 中自动启动 EXE 做首页 smoke check;若需要运行态验收,请单独执行。
后端启动入口:
XUnityToolkit-WebUI/Program.cs
主要职责:
- 读取
settings.json中aiTranslation.port,动态决定监听端口,默认51821 - 强制绑定
http://127.0.0.1:{port} ContentRootPath与WebRootPath必须固定到AppContext.BaseDirectory,不要依赖当前工作目录;否则更新器、安装器或外部启动器从错误目录拉起时会出现首页 404 但 API 仍可访问- 注册各类命名
HttpClient - 注册所有核心服务为单例
- 配置 SignalR
- 提供静态文件与 SPA fallback
- 启动日志必须记录
CurrentDirectory、BaseDirectory、ContentRoot、WebRoot以及wwwroot/index.html是否存在;若入口文件缺失应记录Critical - 注册全部 Minimal API 端点
- 在
ApplicationStopping时立即隐藏 UI,并刷新脏的翻译记忆 - 在
ApplicationStarted时异步初始化 AI 翻译状态,并自动检查更新
前端入口:
XUnityToolkit-Vue/src/main.tsXUnityToolkit-Vue/src/App.vueXUnityToolkit-Vue/src/components/layout/AppShell.vue
前端骨架:
RouterView + KeepAlive + Pinia- 顶层页面:游戏库、AI 翻译、字体生成、运行日志、设置
- 游戏子页面:配置编辑、资产提取、翻译编辑、术语编辑、字体替换、BepInEx 日志、插件管理
实时通信:
- 单个 SignalR Hub:
InstallProgressHub - 关键分组:
game-{id}ai-translationlogspre-translation-{gameId}local-llmfont-replacement-{gameId}font-generationupdate
运行时数据根目录:
- 默认:
%AppData%\XUnityToolkit - 可通过配置键
AppData:Root覆盖 - 主要路径集中定义在
XUnityToolkit-WebUI/Infrastructure/AppDataPaths.cs - 更新暂存目录
update-staging/当前由XUnityToolkit-WebUI/Services/UpdateService.cs直接管理
关键文件/目录:
library.jsonsettings.jsonlocal-llm-settings.jsonglossaries/script-tags/translation-memory/dynamic-patterns/term-candidates/cache/coverscache/iconscache/backgroundscache/extracted-textscache/pre-translation-regex当前cache/pre-translation-regex/<gameId>.txt只镜像 legacy compatibility 所需的 custom 正则区块;完整托管文件位于游戏目录BepInEx/Translation/<lang>/Text/_PreTranslated_Regex.txtcache/pre-translation-sessionsmodels/llama/llama/launch-cachefont-generation/uploadsfont-generation/tempgenerated-fonts/font-backups/custom-fonts/当前字体替换自定义源按custom-fonts/<gameId>/ttf/与custom-fonts/<gameId>/tmp/分目录管理backups/logs/update-staging/
安全相关:
- API Key 与 SteamGridDB Key 使用 DPAPI 加密
- JSON 原子写入统一走
FileHelper.WriteJsonAtomicAsync - 路径拼接统一优先使用
PathSecurity.SafeJoin - 外部 URL 校验使用
PathSecurity.ValidateExternalUrl
高频关键服务:
GameLibraryService游戏库增删改查,落盘到library.jsonAppSettingsService设置缓存、DPAPI 加解密、读改写ConfigurationService读写AutoTranslatorConfig.iniUnityDetectionService检测 Unity 后端、架构、可执行文件、TextMeshPro 支持InstallOrchestrator安装/卸载编排LlmTranslationServiceAI 翻译总入口,负责并发、统计、术语、TM、端点调度TranslationMemoryService每游戏翻译记忆,精确/模式/模糊匹配PreTranslationService资产文本批量预翻译、术语提取、多轮流程LocalLlmService管理 llama-server、GPU 检测、模型下载、llama 二进制下载AssetExtractionService资产提取FontReplacementServiceTMP/TTF 字体扫描与替换TmpFontGeneratorServiceSDF 字体生成UpdateService更新检查、下载、应用SystemTrayService托盘、窗口显示/隐藏、通知
关键端点目录:
XUnityToolkit-WebUI/Endpoints/
高频端点文件:
GameEndpoints.csSettingsEndpoints.csTranslateEndpoints.csTranslationEditorEndpoints.csFontGenerationEndpoints.csFontReplacementEndpoints.csImageEndpoints.csAssetEndpoints.csLocalLlmEndpoints.csUpdateEndpoints.cs
核心骨架:
src/components/layout/AppShell.vuesrc/router/index.tssrc/api/client.tssrc/api/games.tssrc/api/types.ts
高频页面:
src/views/GameDetailView.vuesrc/views/AiTranslationView.vuesrc/views/SettingsView.vuesrc/views/AssetExtractionView.vuesrc/views/FontGeneratorView.vuesrc/views/TermEditorView.vuesrc/views/TranslationEditorView.vuesrc/views/LibraryView.vue
高频组件:
src/components/settings/LocalAiPanel.vuesrc/components/settings/AiTranslationCard.vuesrc/components/config/ConfigPanel.vuesrc/components/common/FileExplorerModal.vuesrc/components/translation/RegexRuleEditor.vue
核心 store:
src/stores/games.tssrc/stores/theme.tssrc/stores/sidebar.tssrc/stores/install.tssrc/stores/assetExtraction.tssrc/stores/update.ts
核心 composable:
src/composables/useAddGameFlow.tssrc/composables/useAutoSave.tssrc/composables/useFileExplorer.tssrc/composables/useWindowControls.ts
在线翻译主链路:
- 游戏内 XUnity.AutoTranslator 捕获文本
LLMTranslate.dll将文本发到POST /api/translateLlmTranslationService处理术语、TM、端点选择、并发控制- 结果返回 DLL,再回显到游戏
重要事实:
POST /api/translate是给 DLL 直接调用的,返回格式不是常规ApiResult<T>LLMTranslate.dll目标框架是net35LLMTranslate.dll通过[LLMTranslate]INI 区段读取ToolkitUrl、GameId等配置Program.cs必须使用127.0.0.1,不要使用localhostLlmTranslationService.TranslateDetailedAsync(...)是翻译主链路的权威实现;TranslateAsync(...)只是返回Translations的轻包装。凡是需要决定“是否允许写入 TM / 术语提取 / 运行时上下文缓存”的调用点,都必须使用详细结果而不是只拿字符串数组- LLM 返回解析顺序当前固定为:剥离
<think>/ 代码块包装 → JSON 数组 → 单条 JSON 字符串(仅单条场景)→ 单条纯文本候选(仅单条场景);批量场景不再接受非结构化原始输出作为译文 - 单条纯文本候选模式会继续保留给本地 LLM 兼容使用,但所有被接受的译文现在都必须额外经过
TranslationOuterWrapperGuard检查;若原文没有整句外层引号/括号,而候选文本新增了“”、「」、『』、【】、[]、""、''这类整句包裹,则会自动去壳 POST /api/translate与PreTranslationService现在都会过滤Persistable == false的结果:这些结果可以回显给调用方,但不能进入自动术语提取、运行时上下文缓存或翻译记忆
多阶段翻译:
- Phase 0:TM 查找
- Phase 1:自然翻译
- Phase 2:术语/DNT 占位符替换
- Phase 3:强制修正
- 在 Phase 1 / Phase 2 进入 LLM 之前,运行时占位符会先替换成内部
{{XU_RT_n}}占位符;LLM 返回后会做宽松恢复,但最终必须逐字回到源文本中的原始 token - 运行时占位符当前同时覆盖半角/全角
SPECIAL_*(如[SPECIAL_01]、【SPECIAL_01】)以及安全白名单内的花括号模板变量(如{PLAYER}、{PC}、{Quest_Id});括号样式、大小写、数量和位置都必须与输入完全一致。任一环节校验失败时,整段安全回退原文 - Phase 0 的 TM 命中也必须经过同样的运行时占位符 round-trip 校验;历史坏缓存命中要视为 miss,不能继续复用
- Phase 0 / Phase 1 / Phase 2 / Phase 3、TM 可持久化过滤、预翻译动态正则生成与
_PreTranslated.txt写回,现在都必须复用同一套“外层包裹守卫”;去壳后若为空或全空白,要视为无效结果并阻止写入缓存/TM
翻译记忆:
- 每游戏持久化
- 顺序:精确 → 动态模式 → 模糊
- 写入是同步加入内存,持久化有 5 秒防抖
- 关闭时会强制刷新脏数据
术语系统:
- 统一术语表替代旧的 glossary/do-not-translate 分离设计
TermService管理每游戏术语TermMatchingService做命中与占位符处理TermAuditService做阶段合规审查
脚本标签:
ScriptTagService负责清理与缓存归一化- 与预翻译缓存、术语和翻译记忆关系紧密,修改时要注意
NormalizeForCache调用点一致性
预翻译检查点:
PreTranslationService会把每游戏恢复检查点写到cache/pre-translation-sessions/<gameId>.json- 恢复资格取决于提取文本经过
ScriptTagService.FilterAndCleanAsync(...)后得到的文本签名;提取缓存或脚本标签规则变化时必须阻止 resume,并要求重新开始 GET /api/games/{id}/pre-translate/status是非运行态 checkpoint 解析后的权威状态;取消或失败后,CanResume/ResumeBlockedReason必须以它重新校验后的结果为准,而不是直接复用终态广播里的原始字段- 删除
extracted-texts缓存或删除游戏时,必须同时清理对应的预翻译检查点
预翻译文本与正则文件:
TranslationEditorPathResolver是普通译文、预翻译文本与预翻译正则文件路径的唯一权威入口;普通译文沿用config.OutputFile/TargetLanguage,预翻译文本与正则固定落到BepInEx/Translation/<lang>/Text/_PreTranslated.txt与_PreTranslated_Regex.txtlang解析顺序固定为:显式请求语言 →TargetLanguage→ 游戏目录里已存在的预翻译文件扫描结果 →zh;语言代码与最终路径都必须经过 resolver 校验,不能绕开BepInEx根目录PreTranslationRegexFormat统一管理_PreTranslated_Regex.txt的base/custom/dynamic三个区块;再次执行预翻译时只保留custom,base与dynamic都会被系统重建GET/PUT /api/games/{id}/pre-translate/regex与cache/pre-translation-regex/<gameId>.txt现在只承担 custom 区块兼容层;改动托管格式时必须同时维护兼容层的读写
主服务:
XUnityToolkit-WebUI/Services/LocalLlmService.cs
关键点:
- GPU 检测优先 DXGI,WMI 兜底
- 后端选择逻辑:NVIDIA→CUDA,AMD/Intel→Vulkan,无显卡→CPU
- 本地模式强制更保守的并发与批处理
- llama.cpp 版本当前固定为
b8756 - 下载模型支持 HuggingFace 与 ModelScope
- 模型启动路径必须走
LocalLlmLaunchPathResolver:先尝试相对路径,再尝试 Windows 8.3 短路径,最后才在llama/launch-cache/创建 ASCII hard link / symbolic link 别名;不要绕开这条兜底链路 - llama 二进制和模型是两个概念:
- llama 运行时二进制:
bundled/llama/或用户下载 - 模型文件:
%AppData%\XUnityToolkit\models
- llama 运行时二进制:
版本同步点:
build.ps1.github/workflows/build.ymlLocalLlmService.LlamaVersion- 文档描述
字体替换:
FontReplacementService- 支持扫描并替换 TMP_FontAsset
- 也支持扫描 Unity Legacy
Font资源;支持直接替换dynamicEmbedded类型的 TTF/OTF,也支持把依赖FontNames的osFallback/ 名称映射动态字体转为内嵌字体 - 带
CharacterRects的静态图集字体以及模式不明的 LegacyFont会明确标记为不支持;osFallback转内嵌时默认保留原FontNames作为缺字兜底 - Legacy
Font.m_FontData在不少游戏里是vector -> Array -> char结构,字段Value可能为null;扫描和替换时判断字节长度要优先看m_FontData["Array"].Children.Count,不能只依赖AsByteArray - TTF 替换写回后必须立即重新读取目标
Font并验证FontDataSize ==目标字节长度且TtfMode == dynamicEmbedded;验证失败要视为替换失败并回滚该字体 - 自定义替换源按游戏隔离,目录为
custom-fonts/<gameId>/ttf/与custom-fonts/<gameId>/tmp/,支持累计上传多个源,不再按类型整类覆盖 - 替换请求按逐字体
sourceId传递,允许同一次操作里为不同 TMP / TTF 字体选择不同默认源或自定义源 - 状态接口会返回
availableSources与usedSources;备份清单中的ReplacedFontEntry会记录SourceId与SourceDisplayName GET /font-replacement/status当前主要返回备份、来源和外部还原状态;其中ReplacedFonts来自manifest.json摘要,不是实时重扫结果。需要当前ttfMode/fontDataSize时,以POST /font-replacement/scan为准- 替换前会建立备份,恢复依赖备份清单和哈希
字体生成:
TmpFontGeneratorService- 基于
FreeTypeSharp + EDT - 输出用于 TMP 的 SDF 字体资源
资产提取:
AssetExtractionService- 使用 AssetsTools.NET
- 目标是提取可翻译文本,供预翻译与缓存生成
本地构建脚本:
build.ps1
作用:
- 下载 bundled 资源
- 提取 XUnity 依赖 DLL 到
TranslatorEndpoint/libs - 构建前端
- 构建
LLMTranslate.dll - 构建 AOT
Updater - 发布到
Release/win-x64
CI:
.github/workflows/build.yml.github/workflows/release.yml.github/workflows/dep-check.yml
重要事实:
- CI 逻辑与
build.ps1是两份并行维护的实现,改构建流程时必须双改 - CI 不直接调用
build.ps1 - 更新器是增量更新的关键组件,含备份、替换、删除、回滚逻辑
- 若调整启动端口解析、静态资源目录或启动方式,注意分别评估本地构建脚本与 GitHub Actions 的发布行为,但当前没有内置 smoke check 守卫
- MSI 由 WiX 生成,且不同 edition 构建时必须注意清理
Installer/obj/...
通用:
- 返回
ApiResult的端点要用Results.Ok(ApiResult<T>.Ok(...)) - 输入验证失败时应返回
BadRequest,不要返回 200 + fail body - 新增每游戏数据文件时,必须同步补充删除逻辑与缓存清理
- 新增设置字段时,要检查后端默认值、TS 类型、前端默认值、保存逻辑是否同步
- 提交到 Git 与 GitHub 的提交标题、提交正文、PR 标题、PR 描述、评审回复等协作文案统一使用中文,保持与仓库既有历史风格一致;仅在引用外部专有名词、接口名或命令时保留必要英文
- Git 提交标题默认使用
type: 中文摘要单行格式,type统一使用仓库和更新面板已识别的英文小写类型:feat、fix、docs、refactor、perf、ci、chore、style、test;不要写成中文类型、emoji、无类型标题、[标签] 标题、type(scope):或其他自造前缀 - Git 提交标题中的冒号必须使用半角
:且后跟一个空格;摘要要直接描述本次主要改动结果,保持中文、具体、可读,避免“更新一下”“修一点问题”“若干调整”这类空泛表述 - 工具箱内更新内容当前由
.github/workflows/build.yml通过git log --pretty=format:"- %s (\%h`)" --no-merges生成,再由XUnityToolkit-Vue/src/views/SettingsView.vue按- type: description (hash)` 解析;想让更新面板正确显示类型徽标和文本时,提交标题必须遵循上述格式,且不要把关键变更信息只写进提交正文或 merge commit 标题 - Git 提交正文用于补充背景、约束、风险与验证结论,统一中文书写;正文应围绕“为什么改 / 改了什么 / 需要注意什么”展开,不要套英文模板、AI 套话或与实际改动不符的泛化总结
- 版本相关提交沿用仓库既有风格:正式发布使用
feat: 发布 vX.Y,纯版本号抬升使用chore: 版本号提升至 vX.Y - 若提交包含
.github/workflows/*,用于git push/gh的 GitHub 凭据必须具备workflowscope;只有repo/read:org/gist时,本地提交会成功,但远端会拒绝更新 workflow 文件
后端:
- 不要从头重建
AutoTranslatorConfig.ini,而是使用补丁式修改 AppSettingsService.GetAsync()返回缓存对象,不要原地乱改,优先UpdateAsync/SaveAsync- 热路径避免磁盘 I/O,尤其
POST /api/translate GameId用作文件路径时必须校验 GUID- 用户提供的 URL 在真正请求前必须走 SSRF 校验
Program.cs中首页静态资源根目录必须锚定到AppContext.BaseDirectory,不要让wwwroot跟随Environment.CurrentDirectory
前端:
- 顶层缓存页面依赖
KeepAlive,涉及 SignalR、定时器、window 监听器时必须同时处理onActivated/onDeactivated/onBeforeUnmount - 不要直接从页面修改 store 的内部状态,优先走 actions
- 自动保存页面切换/刷新数据时,遵循:
disable -> load/assign -> nextTick -> enable - UI 改动后,至少执行:
npx vue-tsc --buildnpm run build
- 若是明显视觉改动,优先补做一次实际 UI 验证
以下改动非常容易漏:
InstallStep需要同步后端模型、TS 类型、安装进度 UI、状态文案映射AppSettings需要同步:Models/AppSettings.cssrc/api/types.ts- 相关 store 的读写
SettingsView.vue
AiTranslationSettings需要同步:- C# 模型
- TS 类型
- AI 翻译页
- 设置页默认值
- 后端
Math.Clamp - 默认系统提示词文案;当前后端默认值与
XUnityToolkit-Vue/src/constants/prompts.ts必须同时要求[SPECIAL_01]/【SPECIAL_01】/{PLAYER}这类占位符按输入原样保留,并明确禁止模型擅自新增整句外层引号/括号、说话人前缀、解释性文本
LocalLlmSettings需要同步:- C# 模型
LocalLlmEndpoints- TS 类型
LocalAiPanel.vue
TranslationEditor需要同步:TranslationEditorEndpoints.csTranslationEditorPathResolver.csPreTranslationRegexFormat.cssrc/api/types.tssrc/api/games.tsTranslationEditorView.vueRegexRuleEditor.vueAssetExtractionView.vue里的跳转入口与 query 参数
- 新 per-game 缓存/目录
需要同步:
AppDataPaths.cs- 删除逻辑
- 导出排除列表
- 清缓存逻辑
当需要接手某一块时,建议优先看:
安装与游戏接入:
XUnityToolkit-WebUI/Endpoints/GameEndpoints.csXUnityToolkit-WebUI/Services/InstallOrchestrator.csXUnityToolkit-WebUI/Services/UnityDetectionService.csXUnityToolkit-WebUI/Services/BepInExInstallerService.csXUnityToolkit-WebUI/Services/XUnityInstallerService.cs
在线翻译与预翻译:
XUnityToolkit-WebUI/Endpoints/TranslateEndpoints.csXUnityToolkit-WebUI/Endpoints/TranslationEditorEndpoints.csXUnityToolkit-WebUI/Services/LlmTranslationService.csXUnityToolkit-WebUI/Services/TranslationMemoryService.csXUnityToolkit-WebUI/Services/PreTranslationService.csXUnityToolkit-WebUI/Services/TranslationEditorPathResolver.csXUnityToolkit-WebUI/Services/PreTranslationRegexFormat.csXUnityToolkit-WebUI/Services/TermService.cs
本地模型:
XUnityToolkit-WebUI/Endpoints/LocalLlmEndpoints.csXUnityToolkit-WebUI/Services/LocalLlmService.csXUnityToolkit-Vue/src/components/settings/LocalAiPanel.vue
字体相关:
XUnityToolkit-WebUI/Endpoints/FontReplacementEndpoints.csXUnityToolkit-WebUI/Endpoints/FontGenerationEndpoints.csXUnityToolkit-WebUI/Services/FontReplacementService.csXUnityToolkit-WebUI/Services/TmpFontGeneratorService.cs
前端主交互:
XUnityToolkit-Vue/src/components/layout/AppShell.vueXUnityToolkit-Vue/src/views/GameDetailView.vueXUnityToolkit-Vue/src/views/AiTranslationView.vueXUnityToolkit-Vue/src/views/SettingsView.vueXUnityToolkit-Vue/src/api/games.tsXUnityToolkit-Vue/src/api/types.ts
构建/发布/更新:
build.ps1.github/workflows/build.ymlUpdater/Program.csInstaller/Package.wxs
截至本次整理时,已确认:
- 仓库当前存在完整的项目级说明文档,且与源码主干大体一致
- 根目录
AGENTS.md已成为统一维护入口,原三份CLAUDE.md的内容已完成合并并删除旧文件 .claude/中没有额外项目说明- 构建链路、前后端入口、运行时数据目录、更新器、翻译端点均已做过静态核对
后续如有下列变更,请同步更新本文件:
- 新增顶层子项目
- 调整构建/发布流程
- 修改运行时数据目录布局
- 大幅重构翻译链路、本地 LLM、字体链路、更新链路
- 引入新的高频同步点或新的全局不变量
以下接口族原先分散记录在历史 CLAUDE.md 中,现统一收敛到此:
- 游戏管理:
GET/POST /api/games、GET/DELETE /api/games/{id}、POST /api/games/add-with-detection、POST /api/games/batch-add、PUT /api/games/{id}、POST /api/games/{id}/detect、POST /api/games/{id}/open-folder、POST /api/games/{id}/launch - TMP 字体:
GET/POST/DELETE /api/games/{id}/tmp-font - 安装与状态:
POST /api/games/{id}/install、DELETE /api/games/{id}/install、GET /api/games/{id}/status、POST /api/games/{id}/cancel - 图标、封面、背景:均提供
upload、*-from-path、SteamGridDB、网页搜索、选择、删除等配套端点 - 配置:
GET/PUT /api/games/{id}/config、GET/PUT /api/games/{id}/config/raw - 应用设置:
GET/PUT /api/settings、GET /api/settings/version、POST /api/settings/reset、POST /api/settings/export、POST /api/settings/import、POST /api/settings/import-from-path、POST /api/settings/open-data-folder - 文件浏览器:
GET /api/filesystem/drives、GET /api/filesystem/quick-access、POST /api/filesystem/list、POST /api/filesystem/read-text - AI 翻译:
POST /api/translate、GET /api/translate/stats、GET /api/translate/cache-stats、POST /api/translate/test、GET /api/translate/ping - AI 控制与模型:
POST /api/ai/toggle、GET /api/ai/models、GET /api/ai/extraction/stats - 本地 LLM:
GET/PUT /api/local-llm/settings、GET /api/local-llm/status、GET /api/local-llm/gpus、POST /api/local-llm/gpus/refresh、GET /api/local-llm/catalog、GET /api/local-llm/llama-status、POST /api/local-llm/test、POST /api/local-llm/start、POST /api/local-llm/stop、下载/暂停/取消模型、下载/取消 llama 运行时 - AI 端点、术语、描述:
/api/games/{id}/ai-endpoint、/api/games/{id}/terms、/api/games/{id}/description - 兼容层:
/api/games/{id}/glossary、/api/games/{id}/do-not-translate保留兼容旧调用,但底层统一走TermService - 翻译记忆与动态模式:
/api/games/{id}/translation-memory、/api/games/{id}/dynamic-patterns、/api/games/{id}/term-candidates - 脚本标签:
GET /api/script-tag-presets、GET/PUT /api/games/{id}/script-tags - 资源提取与预翻译:
POST /api/games/{id}/extract-assets、GET/DELETE /api/games/{id}/extracted-texts、POST /api/games/{id}/pre-translate、POST /api/games/{id}/pre-translate/resume、GET /api/games/{id}/pre-translate/status、POST /api/games/{id}/pre-translate/cancel、GET/PUT /api/games/{id}/pre-translate/regex GET/PUT /api/games/{id}/pre-translate/regex当前是 legacy compatibility 端点,只读写 custom 正则区块;完整多区块编辑统一走translation-editor/regex- 翻译编辑器:
GET/PUT /api/games/{id}/translation-editor?source={default|pretranslated}&lang={lang}、POST /api/games/{id}/translation-editor/import、GET /api/games/{id}/translation-editor/export、GET/PUT /api/games/{id}/translation-editor/regex?lang={lang}、POST /api/games/{id}/translation-editor/regex/import、GET /api/games/{id}/translation-editor/regex/export - 字体替换:
POST /api/games/{id}/font-replacement/scan、POST /api/games/{id}/font-replacement/replace、POST /api/games/{id}/font-replacement/restore、GET /api/games/{id}/font-replacement/status、POST /api/games/{id}/font-replacement/upload、POST /api/games/{id}/font-replacement/upload-from-path、POST /api/games/{id}/font-replacement/cancel、DELETE /api/games/{id}/font-replacement/custom-fonts/{sourceId} - 字体替换上传端点现在要求显式区分
kind={ttf|tmp};状态端点会返回默认源/自定义源列表与已使用源摘要;替换请求中的fonts[]需要携带逐字体sourceId POST /api/games/{id}/font-replacement/scan是字体当前资源状态的权威来源;GET /api/games/{id}/font-replacement/status主要基于manifest.json汇总替换状态,不返回实时重扫后的ttfMode/fontDataSize- 字体生成:上传、生成、状态、取消、下载、历史、删除、安装 TMP 字体、字符集预览/上传、报告查询均由
/api/font-generation/*提供 - BepInEx 日志与健康:
/api/games/{id}/bepinex-log、/api/games/{id}/health-check - 插件管理与插件包:
/api/games/{id}/plugins、/api/games/{id}/plugin-package/export、/api/games/{id}/plugin-package/import - 日志与更新:
GET /api/logs、GET /api/logs/history、GET /api/logs/download、/api/update/* - 所有
multipart/form-data上传端点都必须显式.DisableAntiforgery() - 所有
*-from-path端点都必须与对应 multipart 端点保持相同业务校验与副作用,不允许只做“简化版实现” POST /api/translate直接服务于LLMTranslate.dll,不是ApiResult<T>,前端若直接调用必须使用原始fetch处理响应- 常规 JSON 端点统一使用
Results.Ok(ApiResult<T>.Ok(...))或Results.BadRequest(ApiResult.Fail(...)),不要直接返回裸对象
InstallStep、UpdateInfo、VersionInfo、DataPathInfo、BatchAddResult、UnityGameInfo、FileExplorer、FontReplacement、FontGeneration、PluginHealth、BepInExPlugin、LocalLlmSettings、BuiltInModelInfo、LlamaStatus等模型,新增字段时都必须同时同步 C# 模型、TS 类型、相关 API、对应前端页面- 字体替换链路改动时,要一起核对
FontReplacementRequest.Fonts[].SourceId、ReplacementSource/ReplacementSourceSet、FontReplacementStatus.AvailableSources/UsedSources、ReplacedFontEntry.SourceId/SourceDisplayName,并同步FontReplacement.cs、src/api/types.ts、FontReplacementView.vue、FontReplacementEndpoints.cs、FontReplacementService.cs - 涉及 Legacy
Font的 TTF 分析或写回时,还要一起核对AnalyzeTtfFont、GetByteArrayLength、SetByteArrayContents、写后重读验证日志、GetStatusAsync和前端状态文案;scan与status的语义不要混用 SettingsView.vue的默认AppSettings、AiTranslationView.vue的DEFAULT_AI_TRANSLATION、后端AppSettings/AiTranslationSettings默认值必须保持一致- 数值型设置新增字段时,要同步后端的
Math.Clamp逻辑,否则前端与后端会出现边界不一致 TermEntry的Type/Category/Source、ScriptTagRule/ScriptTagConfig、TranslationStats/RecentTranslation/TranslationError、PreTranslationStatus/PreTranslationCacheStats都属于容易漏同步的高频模型PreTranslationStatus的CanResume/CheckpointUpdatedAt/ResumeBlockedReason与POST /api/games/{id}/pre-translate/resume属于一组联动点;改动时要同时核对Models/AssetExtraction.cs、src/api/types.ts、src/api/games.ts、src/stores/assetExtraction.ts、AssetExtractionView.vue,并确认运行中始终CanResume=false,取消/失败后会基于 checkpoint 重新解析恢复资格TranslationEditorData.Source/Language/AvailablePreTranslationLanguages、TranslationRegexEditorData、RegexTranslationRule、TranslationEditorSource/TranslationEditorTextSource属于一组联动点;改动时要同时核对TranslationEditorEndpoints.cs、TranslationEditorPathResolver.cs、src/api/types.ts、src/api/games.ts、TranslationEditorView.vue、RegexRuleEditor.vuePreTranslationRegexFormat的base/custom/dynamic分区、AssetEndpoints.cs的兼容接口、PreTranslationService.cs的托管文件重建、AppDataPaths.PreTranslationRegexFile(...)的 legacy 镜像属于另一组联动点;custom必须保留,base与dynamic可以重建- 新增每游戏目录时,除了
AppDataPaths.cs,还要同步DELETE /api/games/{id}清理逻辑、缓存驱逐、设置导出排除列表、必要时的设置导入重建逻辑 RecordError、NormalizeForCache、ApplicationStopping回调、日志级别过滤、SignalR 事件名与阶段名,都属于“改一处必须全链路核对”的同步点- 翻译解析契约、运行时占位符保护与
Persistable过滤属于新的高频同步点;凡是新增翻译调用方或缓存写入点,都要核对是否错误接收了非结构化回退结果 TranslationOuterWrapperGuard属于翻译链路新的全局守卫;凡是新增 TM 命中复用、预翻译缓存写入、动态正则生成或其他持久化出口,都要核对是否同步做了“原文无外层包裹时禁止译文新增整句外层包裹”的归一化/拦截build.ps1、.github/workflows/build.yml与.github/workflows/dep-check.yml都包含版本前缀/发版假设;流程、版本号、资源来源、构建 edition 或自动依赖构建版本策略发生变化时必须一起核对- 若变更首页可用性、静态资源目录、启动端口或启动方式,需要分别核对
build.ps1与.github/workflows/build.yml的发布流程,但当前不再维护Test-FrontendSmoke回归守卫 - Git 提交标题规范、
.github/workflows/build.yml中### Changelog的生成逻辑,以及XUnityToolkit-Vue/src/views/SettingsView.vue的typeLabels/ 正则解析属于联动点;若调整提交格式、更新内容展示样式或 changelog 生成方式,必须同时核对这三处,且注意--no-merges会让 merge commit 不进入工具箱更新列表 llama.cpp版本更新需要同时同步build.ps1、build.yml、LocalLlmService.LlamaVersion、下载资源命名模式、README/本手册说明
- 项目当前不做向后兼容迁移或旧格式自动转换;默认按“预稳定阶段、可以清晰断代”的思路维护
Program.cs必须在 DI 之前读取AppData:Root并回写到builder.Configuration["AppData:Root"],否则AppDataPaths看到的是旧值- 静态资源缓存策略必须区分
/assets/*与index.html/favicon.ico;SPA fallback 也必须显式设置no-cache - 命名
HttpClient已经承载超时、连接数、UA 和用途假设;新增外部网络调用优先复用命名客户端,不要随处new HttpClient() Updater/是 AOT 工程,不允许依赖 WinForms、反射式 JSON 序列化或Microsoft.Win32.Registry的常规托管封装;涉及注册表时应使用 P/Invoke
TranslatorEndpoint目标为net35,并依赖build.ps1从 XUnity 包里提取libs/引用 DLL[LLMTranslate]INI 区段的ToolkitUrl、GameId等值由POST /api/games/{id}/ai-endpoint、InstallOrchestrator和 DLL 初始化共同维护,修改其约定必须三处同改- 不要从零重写
AutoTranslatorConfig.ini;统一通过ConfigurationService.PatchAsync做补丁式修改 PatchAsync中null表示跳过字段,空字符串表示清空字段,这个语义不能改- 默认最优配置会写入
Language=zh、OverrideFont=Microsoft YaHei、Endpoint=LLMTranslate等值;若调整默认配置,必须同时核对安装链路和文档说明 LLMTranslate.dll的日志分为始终输出的Log()和仅在DebugMode下输出的DebugLog(),不要把关键初始化和错误信息放进DebugLog()
- 在线翻译主链路仍然是 Phase 0 TM 查找、Phase 1 自然翻译、Phase 2 术语/DNT 占位符替换、Phase 3 强制修正
TranslationMemoryService的写入先落内存,持久化走防抖;热路径上不要引入额外磁盘 I/OGlossaryExtractionService与TermExtractionService共享解析与分类逻辑,但故意保持两个独立服务;不要因为“看起来重复”而强行合并- 所有翻译路径都必须在满足条件时调用
BufferTranslation+TryTriggerExtraction,否则术语提取统计会失真 ScriptTagService.NormalizeForCache是缓存归一化的唯一入口;涉及预翻译缓存、动态模式或脚本标签的变更都要核对调用点TranslationStats.Queued是推导值,不等于内部_queued;TM 命中和失败文本统计也有各自独立含义,不能混用RecentTranslation.EndpointName在 TM 命中场景下需要显式写成“翻译记忆”,否则前端最近翻译列表会出现空白端点名PreTranslationRegexFormat负责托管_PreTranslated_Regex.txt的base/custom/dynamic区块;PreTranslationService重建托管文件时必须保留custom,并同步回写AppDataPaths.PreTranslationRegexFile(gameId)兼容镜像TranslationEditorPathResolver是translation-editor、translation-editor/regex与 legacy/pre-translate/regex共用的唯一路径解析入口;语言选择、目录扫描和路径防穿越都不要散落重写
- 热路径避免
GameLibraryService.GetByIdAsync等磁盘或大对象访问,优先使用缓存 SemaphoreSlim相关逻辑要区分“是否成功获取信号量”再释放,避免超时后误Release()BroadcastStats的节流与force: true语义是前端实时流水线显示的前提,不能随意删减- KeepAlive 或触发即忘的后台任务,如果依赖 SignalR 结束事件来复位前端状态,那么失败、取消路径也必须广播完成/错误事件
FileLoggerProvider的内存 ring buffer 只用于运行中日志页展示;GET /api/logs/download必须导出当前 session 的磁盘日志快照,不能退化成只导出 ring buffer 截断结果- 向前端返回的错误消息要避免泄露内部绝对路径、异常堆栈或敏感配置;详细信息只写服务器日志
AssetExtractionService使用 AssetsTools.NET,数组字段访问统一遵循field -> "Array" -> elements模式- TTF 字体替换支持
dynamicEmbedded的 Unity LegacyFont,也支持将osFallback/ 名称映射动态字体原位转成内嵌字体;staticAtlas与unknown仍统一扫描但拒绝替换 - Legacy
Font.m_FontData的单字节元素既可能是UInt8也可能是Int8/char;写回数组项时要按AssetValueType选择AsByte或AsSByte,否则会在SetNewData时触发有符号溢出 TmpFontGeneratorService基于 FreeTypeSharp 与 Felzenszwalb EDT 生成 SDF;生成出的 atlas、padding、gradient scale、render mode 之间有强耦合,不要局部改一个字段WebImageSearchService通过网页抓取提供图片搜索;所有 URL 在真正请求之前必须先走 SSRF 校验,保存前还要校验内容类型- 图标、封面、背景图这类外链下载现在统一通过禁用自动重定向的
HttpClient+PathSecurity.SendWithValidatedRedirectsAsync(...)逐跳校验;不要再直接GetAsync(url)后信任框架自动跟随 30x - 外链图片下载必须额外限制响应体积(当前上限 10 MB),避免网页搜索结果或第三方 CDN 把超大文件直接读进内存
WebViewWindow、SystemTrayService、WebView2 预热、加载 overlay、快速隐藏 UI、关闭超时等机制都属于桌面宿主层不变量,改动前要完整回看历史实现WebViewWindow.InitializeAsync()现在会先探测GET /,并且只在首页首次NavigationCompleted成功后才隐藏原生 loading overlay;首页探测或首屏导航失败时必须保留 overlay 并给出明确错误,不能直接暴露系统 404 页面Updater/Program.cs在成功重启和回滚重启两条路径里都必须保持WorkingDirectory = appDir,否则可能出现 API 正常但首页因wwwroot解析到错误目录而 404QuickAccessHelper使用 Shell COM 且要求 STA 线程;相关 COM 对象必须逐级Marshal.ReleaseComObjectBepInExLogService、PluginHealthCheckService、BepInExPluginService都依赖文件共享读或被动分析模式,不要在这些路径里引入“加载用户 DLL 到当前进程”这类高风险操作
gameId、语言代码、用户上传文件名、可执行文件名、导入 ZIP 内部路径都必须做路径穿越和格式校验PathSecurity.SafeJoin与PathSecurity.ValidateExternalUrl是路径安全和 SSRF 防护的统一入口;不要自行复制一套近似实现- 外部 URL 的 SSRF 校验不能只看原始主机名;若输入是域名,还必须检查 DNS 解析后的 IP 是否落到回环、链路本地或私网网段
- 任何允许重定向的外链下载都必须对每一跳目标重复做同样的 SSRF 校验;首跳安全不代表后续跳转安全
- 用户提供或本地选择的 ZIP 导入(设置导入、插件 ZIP、汉化包导入等)必须同时限制压缩包原始大小、单文件解压大小与总解压大小,并统一走
PathSecurity.PrepareZipExtractionPath(...)/ExtractZipEntryAsync(...) POST /api/settings/reset、POST /api/settings/import会引发跨服务缓存失效,相关服务新增缓存后必须并入这两条路径- JSON 数据文件统一走
FileHelper.WriteJsonAtomicAsync;非 JSON 关键文件也应采用.tmp + move的原子落盘模式
- 使用 Vue 3 Composition API 和
<script setup lang="ts"> - API 调用统一从
@/api/client或@/api/games进入,不要直接散落 axios 实现 - 生产路径不要留下
console.* - 共享工具优先放在
src/composables/、src/utils/、src/constants/,不要在页面内重复定义一份几乎相同的 helper gamesStore.launchGame(id)是所有启动游戏入口的唯一调用点,不要绕开它直调 API
- 项目使用自托管字体,禁止引入 Google Fonts CDN
- 主题模式由
useThemeStore控制,resolvedTheme才是渲染依据;mode可能是system - 共享布局和卡片类名以
main.css为准,例如.page-title、.section-card、.section-header、.header-actions、.table-container - 顶层页面和游戏子页面的标题、回退按钮、section card 样式有明确约定,不要在每个页面重新发明一套
- 这是桌面应用而不是全宽网页,默认窗口宽度扣掉侧边栏和卡片内边距后,经常只剩“中等内容宽度”;双列卡片、表单和设置面板不要只依赖
768px这类移动端断点,优先使用repeat(auto-fit, minmax(...))、minmax(0, 1fr)或补充中间断点,确保默认窗口大小下不裁切 section-card、折叠区正文和局部 grid/flex 列在需要收缩时通常都要显式min-width: 0;src/assets/main.css已为共享卡片容器补了这层约束,新增类似ConfigPanel.vue、FontGeneratorView.vue的双列布局时要一起检查 Naive UI 输入控件和外层容器是否真正允许收缩- 避免无必要的内联样式;除一次性的
animation-delay等极少数情况外,统一落到作用域 CSS - 颜色和边框一律使用设计系统变量,不要发明不存在的 CSS 变量名
- 顶层页面通过
KeepAlive缓存,因此涉及 SignalR、window 监听器、定时器的页面与子组件都要同时处理onActivated、onDeactivated、onBeforeUnmount HubConnection不要在模块顶层创建,统一在生命周期钩子内创建和销毁onBeforeUnmount是默认清理钩子,不要改成onUnmounted- 自动保存页面在加载外部数据时统一采用
disable -> load/assign -> nextTick -> enable AiTranslationView、SettingsView这类共享AppSettings的 KeepAlive 页面,重新激活时必须重新从后端加载,避免旧副本覆盖新改动AssetExtractionView的预翻译按钮显示依赖src/stores/assetExtraction.ts中的状态兜底:收到带CheckpointUpdatedAt的终态preTranslationUpdate,或“开始预翻译”因旧 checkpoint 被拒绝时,都必须主动刷新GET /api/games/{id}/pre-translate/status,不要只依赖一次 SignalR 终态包TranslationEditorView通过route.query.source/route.query.lang切换普通译文、预翻译文本与预翻译正则;从AssetExtractionView跳转进入预翻译编辑时必须带上这些 query,不要再派生一套页面内来源状态
NInputNumber的@update:value会发出number | null,保存时要显式处理nullNDialogOptions.onPositiveClick返回 Promise 会阻塞对话框关闭;耗时操作通常用 fire-and-forget 更合适NDataTable的排序、虚拟滚动、row-key唯一性、弹性列minWidth都有历史坑,表格变更前要核对现有模式NColorPicker使用自定义触发器时应走#trigger槽,并锁定hex模式NUpload、NEllipsis在 flex 容器里有额外包裹层时,需要用:deep()或外层容器修正布局NTabs type="segment"、折叠面板、按钮渐变边框等视觉模式已有历史实现,改动前先复用现有样式结构
- 顶层页面加路由、导航、缓存名;游戏子页面加
/games/:id/...路由和GameDetailView入口按钮,不要塞进主导航 TermEditorView是统一术语编辑页,替代早期独立 glossary/do-not-translate 页面;相关新能力优先接到这里- 文件浏览器由
FileExplorerModal.vue全局挂载一次,useFileExplorer()通过 Promise 返回选中的服务器路径 - 背景图、封面图、图标、网页图片搜索、游戏详情 hero 视觉、视差滚动、缓存失效时间戳,都有现成模式,不要局部重写
- 涉及复杂多步骤交互时,优先抽成 composable,例如
useAddGameFlow、useAutoSave、useWindowControls
dotnet build会自动触发前端构建;仅构建后端时用-p:SkipFrontendBuild=truebuild.ps1负责本地完整构建,但不负责生成所有 CI 产物;CI 逻辑在 workflow 内独立实现- 版本号应通过
InformationalVersion传递,不要滥用Version导致AssemblyVersion溢出问题 - 当前发布是多文件模式,不再使用单文件发布;相关
ExcludeFromSingleFile历史逻辑已失效 SatelliteResourceLanguages=en、WiX 的obj清理、PowerShell ZIP、组件 ZIP、manifest 命名、edition 构建参数等都已成为既定约束- WiX/MSI 细节包括:每用户安装、
MajorUpgrade调度、可选删除%AppData%\XUnityToolkit、注册表同步、许可证文本一致性、中文字符串本地化方式、SuppressValidation=true - CI 包含可复用的
build.yml、发布release.yml、依赖巡检dep-check.yml - GitHub Actions 中的 AOT 发布、
$GITHUB_OUTPUT多行写法、gh release create --notes-file、detached HEAD 场景下回写main等问题都属于既有坑位 dep-check.yml只跟踪 BepInEx 与 XUnity;llama.cpp版本目前仍然固定人工维护
- 历史
CLAUDE.md已全部删除 - 原先三份
CLAUDE.md的有效内容已并入本AGENTS.md - 后续若有人重新添加
CLAUDE.md,应默认视为重复文档并回收至AGENTS.md
PluginHealthCheckService现已改为 settings-aware 的异步检查流程:被动检查GET /api/games/{id}/health-check与主动验证POST /api/games/{id}/health-check/verify都统一走CheckAsync(...)- 插件健康状态不再只靠 BepInEx 日志猜测原因;检查顺序调整为:文件完整性 -> 工具箱 AI 状态 -> 日志归类 -> 验证后的工具箱连通性
- 当工具箱侧存在可明确识别的问题时,后端会新增
toolboxAiState健康项,前端展示名称为“工具箱 AI 翻译”,状态固定为Warning toolboxAiState当前覆盖三类场景:AiTranslation.Enabled == false- 当前没有任何可用端点,判定规则与
LlmTranslationService保持一致:Enabled && ApiKey 非空 - 当前
ActiveMode == "local"且LocalLlmService.IsRunning == false
- 对于
toolboxAiState,详情文案必须直接指出工具箱侧问题,不允许再使用“可能不完全兼容 XUnity”这类兼容性描述 - 日志错误归类已拆分为两层:
- 真正的 XUnity 兼容性/Hook 异常:只在没有明确工具箱 AI 阻塞证据时才给出“当前游戏版本可能不完全兼容 XUnity”
- 泛化的翻译失败:如
Failed: 'Continue'、Failed: 'Credits'、Cannot translate、AutoTranslator failed,在存在工具箱 AI 问题时必须归因到工具箱侧,不得误标为 XUnity 兼容性问题
PluginHealthReport、HealthCheckItem、HealthCheckDetail的 JSON 结构本次未扩展;兼容性要求是只新增checks[].id = toolboxAiState,不要再额外添加新字段PluginHealthCard.vue目前对异常项使用固定排序:toolboxAiState最先显示logErrors次之- 其他异常项保持原始顺序
- 插件健康状态属于文件共享读 + 被动分析路径;允许读取
settings.json、本地 LLM 运行状态和BepInEx/LogOutput.log,但不要引入任何会加载用户插件 DLL、修改游戏文件、或为分析而重写配置文件的实现