Releases: KokoroLyase/ServerChatTranslator
Release list
v3.1.1-mc26.3-fabric
v3.1.1 — 游戏内切换合并显示 + 前缀重复修复
这一版改了什么
- 新增
/translator merge on|off:随时切换译文的显示方式——
on= 合并进原文一行(默认,原文 ▏ [译] 译文),off= 另起一行(旧行为)。
不带参数只显示当前状态;/translator status里也会显示「合并显示: 开/关」。 - 切换立即生效,只影响之后收到的消息;在途的合并按提交时的模式走完,不会出现中间态。
- 实机缺陷修复:合并显示会把译文里「与原文重复的说话人前缀」再显示一遍——
提示词要求模型原样保留[星级] [头衔] 玩家名:,合并进同一行后前缀出现两次
([271✫] [MVP++] t_rain: wa ta shi ▏ [译] [271✫] [MVP++] t_rain: 我是)。
现在 MERGE 显示时自动剥掉与原文重复的前缀,合并行变成
[271✫] [MVP++] t_rain: wa ta shi ▏ [译] 我是;译文没带前缀不剥,
正文自带的冒号(e445: 3/4 幸运方块)保留;APPEND 译文行形态不变。
升级后你要做什么
- 直接换 jar 即可:没有不兼容变更,环境要求没变,配置结构未变(
configVersion仍是 10),
不需要改任何配置。 - 新开关
/translator merge on|off的状态会写入配置,重启游戏后保持。
验证
- 离线自检 1058 → 1088 项 / 0 失败(+30):开关翻转、status 行、APPEND 对照、
「切换不影响在途」、前缀剥离判据(截图样本逐字、无前缀不硬剥、正文自带冒号保留)与端到端断言;
反向验证两组(中和开关赋值、中和前缀剥离)全部变红后按 sha256 校验还原。 - 另用真实 DeepSeek API + 生产提示词复现了截图里的消息,确认修复对真实模型输出有效。
- CI 两个构建成功;CI 产物与本机构建逐字节一致(Fabric 整包 sha256;Forge 73 个条目 0 差异)。
- 未覆盖自动化:游戏内实机体验。合并显示已在 Hypixel Bed Wars 实机验证通过
(含本版修复的「前缀重复」缺陷);遇到问题请按 issue 模板反馈/translator status与 debug 输出。
v3.1.1-mc1.8.9-forge
v3.1.1 — 游戏内切换合并显示 + 前缀重复修复
这一版仍然是一个核心插件(coremod):启动时对
EntityPlayerSP.sendChatMessage
做一次方法头注入(1.8.9 没有能拦截「自己发出的聊天」的事件,平台限制)。
本版改动全部在共享层、命令层与显示层,注入机制一个字没动。
这一版改了什么
- 新增
/translator merge on|off:随时切换译文的显示方式——
on= 合并进原文一行(默认,原文 ▏ [译] 译文),off= 另起一行(旧行为)。
不带参数只显示当前状态;/translator status里也会显示「合并显示: 开/关」。 - 切换立即生效,只影响之后收到的消息;在途的合并按提交时的模式走完,不会出现中间态。
- 实机缺陷修复:合并显示会把译文里「与原文重复的说话人前缀」再显示一遍——
提示词要求模型原样保留[星级] [头衔] 玩家名:,合并进同一行后前缀出现两次
([271✫] [MVP++] t_rain: wa ta shi ▏ [译] [271✫] [MVP++] t_rain: 我是)。
现在 MERGE 显示时自动剥掉与原文重复的前缀,合并行变成
[271✫] [MVP++] t_rain: wa ta shi ▏ [译] 我是;译文没带前缀不剥,
正文自带的冒号(e445: 3/4 幸运方块)保留;APPEND 译文行形态不变。
升级后你要做什么
- 直接换 jar 即可:没有不兼容变更,环境要求没变,配置结构未变(
configVersion仍是 10),
不需要改任何配置。 - 新开关
/translator merge on|off的状态会写入配置,重启游戏后保持。
验证
- 离线自检 1058 → 1088 项 / 0 失败(+30):开关翻转、status 行、APPEND 对照、
「切换不影响在途」、前缀剥离判据(截图样本逐字、无前缀不硬剥、正文自带冒号保留)与端到端断言;
反向验证两组(中和开关赋值、中和前缀剥离)全部变红后按 sha256 校验还原。 - 另用真实 DeepSeek API + 生产提示词复现了截图里的消息,确认修复对真实模型输出有效。
- CI 两个构建成功;CI 产物与本机构建逐字节一致(Fabric 整包 sha256;Forge 73 个条目 0 差异)。
- 未覆盖自动化:游戏内实机体验。合并显示已在 Hypixel Bed Wars 实机验证通过
(含本版修复的「前缀重复」缺陷);遇到问题请按 issue 模板反馈/translator status与 debug 输出。
v3.1.0-mc26.3-fabric
v3.1.0 — 稳定性与延迟改进 + 译文合并显示
这一版改了什么
译文默认合并进原文那一行。 以前译文异步另起一行,多人同时发言时译文会和原文错位,
容易被误读成「漏译」。现在显示成一行:
[MVP+] Steve: rush mid ▏ [译] 冲中路!
- 原消息的样式、悬停提示、点击事件原样保留(实现是复制原消息组件后追加译文,不是重拼文字)。
- 译文 3 秒(
mergeDeadlineSeconds)内没回来时,原文先照常显示,译文后到再补一行
└从属行——网络再慢也不会把你的原文扣住。 - 翻译失败、整条是玩家名、排队超时:原文全部照常显示,行为与统计口径和旧版一致。
延迟与稳定性(改进依据是 2026-09-19 的实测日志:单条最坏 ≈ 61 秒、网络变慢时连续丢消息):
- 读超时不再自动重试:30 秒读超时已经花掉了,再自动重试一轮就是实测 61 秒/条的来源。
读超时照常计入熔断(连续读超时正是网络黑洞的典型形态),只是不再立刻重发。
429 同样不再自动重试(服务端在限流,立刻重发只会加剧)。5xx / 普通网络抖动保持重试一次。 - 单条时间预算(
requestBudgetSeconds,默认 20 秒):最坏耗时从「30 秒 × 2 + 退避」的
乘法叠加变成预算封顶。 - 队列按年龄丢弃(
maxQueueAgeSeconds,默认 45 秒):排队等太久的消息直接跳过——
等了 5 分钟才出来的译文,聊天记录早就滚过去了。 - 入站线程 2 → 3(
incomingThreads可配 1–8):以前 2 个慢请求就能把容量打到 0。 - 熔断渐进退避:首次 15 秒、连续触发逐次加倍、封顶 60 秒(以前固定 60 秒太钝)。
- 连接复用:成功路径不再断开连接,每次请求省掉一整个 TCP+TLS 握手。
升级后你要做什么
- 直接换 jar 即可,没有不兼容变更,环境要求没变。
- 配置文件升级时自动补写 5 个新字段(
requestBudgetSeconds/maxQueueAgeSeconds/
incomingThreads/incomingDisplay/mergeDeadlineSeconds),
你已有的设置(Key、术语表、提示词、忽略规则等)一个字都不会动。 - 把
incomingThreads改成非默认值后需要重启游戏才生效;其它配置照旧/translator reload。 - 想改回「译文另起一行」的旧显示:把
incomingDisplay改成APPEND,/translator reload。
验证
- 离线自检 1058 项 / 0 失败(v3.0.8 基线 982,本版 +76 项),
含重试分级、时间预算、队列超龄、MERGE 状态机的端到端用例;
另做 4 组反向验证(把修复逐条中和,确认对应用例全部变红,再按 sha256 校验还原)。 - CI 两个构建全部成功;CI 产物与本机构建逐字节一致
(Fabric 比整包 sha256;Forge 解开比内容,73 个条目 0 处差异)。 - 未覆盖自动化的部分:游戏内实机体验(合并显示的实际观感、真实网络下的表现)。
遇到问题请按 issue 模板贴/translator status与debug on的输出。
v3.1.0-mc1.8.9-forge
v3.1.0 — 稳定性与延迟改进 + 译文合并显示
这一版仍然是一个核心插件(coremod):它在启动时对
EntityPlayerSP.sendChatMessage做一次方法头注入(1.8.9 没有任何能拦截「自己发出的聊天」
的事件,这是平台限制)。本版的改动全部在共享层与显示层,注入机制一个字没动;
装其它 coremod 前请照旧确认没有别的插件也在改聊天发送路径。
这一版改了什么
译文默认合并进原文那一行。 以前译文异步另起一行,多人同时发言时译文会和原文错位,
容易被误读成「漏译」。现在显示成一行:
[MVP] Kevin: our bed is gone ▏ [译] 我们的床没了
- 原消息的样式与格式原样保留(实现是复制原消息组件后追加译文,不是重拼文字)。
- 译文 3 秒(
mergeDeadlineSeconds)内没回来时,原文先照常显示,译文后到再补一行
└从属行——网络再慢也不会把你的原文扣住。 - 翻译失败、整条是玩家名、排队超时:原文全部照常显示,行为与统计口径和旧版一致。
延迟与稳定性(改进依据是 2026-09-19 的实测日志:单条最坏 ≈ 61 秒、网络变慢时连续丢消息):
- 读超时不再自动重试:30 秒读超时已经花掉了,再自动重试一轮就是实测 61 秒/条的来源。
读超时照常计入熔断(连续读超时正是网络黑洞的典型形态),只是不再立刻重发。
429 同样不再自动重试(服务端在限流,立刻重发只会加剧)。5xx / 普通网络抖动保持重试一次。 - 单条时间预算(
requestBudgetSeconds,默认 20 秒):最坏耗时从「30 秒 × 2 + 退避」的
乘法叠加变成预算封顶。 - 队列按年龄丢弃(
maxQueueAgeSeconds,默认 45 秒):排队等太久的消息直接跳过——
等了 5 分钟才出来的译文,聊天记录早就滚过去了。 - 入站线程 2 → 3(
incomingThreads可配 1–8):以前 2 个慢请求就能把容量打到 0。 - 熔断渐进退避:首次 15 秒、连续触发逐次加倍、封顶 60 秒(以前固定 60 秒太钝)。
- 连接复用:成功路径不再断开连接,每次请求省掉一整个 TCP+TLS 握手。
升级后你要做什么
- 直接换 jar 即可,没有不兼容变更,环境要求没变。
- 配置文件升级时自动补写 5 个新字段(
requestBudgetSeconds/maxQueueAgeSeconds/
incomingThreads/incomingDisplay/mergeDeadlineSeconds),
你已有的设置(Key、术语表、提示词、忽略规则等)一个字都不会动。 - 把
incomingThreads改成非默认值后需要重启游戏才生效;其它配置照旧/translator reload。 - 想改回「译文另起一行」的旧显示:把
incomingDisplay改成APPEND,/translator reload。
验证
- 离线自检 1058 项 / 0 失败(v3.0.8 基线 982,本版 +76 项),
含重试分级、时间预算、队列超龄、MERGE 状态机的端到端用例;
另做 4 组反向验证(把修复逐条中和,确认对应用例全部变红,再按 sha256 校验还原)。 - CI 两个构建全部成功;CI 产物与本机构建逐字节一致
(Fabric 比整包 sha256;Forge 解开比内容,73 个条目 0 处差异)。 - 未覆盖自动化的部分:游戏内实机体验(合并显示的实际观感、真实网络下的表现)。
遇到问题请按 issue 模板贴/translator status与debug on的输出。
v3.0.8-mc26.3-fabric
本版是对整个工程项目的一轮完整洁净度审计(本仓库全部跟踪文件 + 两条构建 + 装配层 +
核心插件 + 文档与 CI)之后修出来的东西。没有新功能,翻译质量与提示词一个字未改。
玩家侧能感知的变化只有一处:未配 API Key 时的提示不再显示两个[sct]。
这一版改了什么
审计分三路并行(共享层与 Fabric 装配层、Forge 线与核心插件、仓库洁净度与元数据),
每条结论都逐字读过源码复核之后才动手。共改 10 处,全都是「构建与离线自检看不见」的那类问题。
下面只列**本线(26.3 / Fabric)**会受影响的部分,完整清单见 CHANGELOG。
1. 未配 Key 时的提示会显示两个 [sct] 前缀(唯一玩家可见的一条)
ChatTranslator 的「未配置 DeepSeek API Key」文案自己拼了一个前缀,而它只经由
fallbackToOriginal → notifyFallback → feedback.error/hint 出去,两个装配层的
error()/hint() 自己就会加一个 —— 聊天栏里显示的是
§8[§bsct§8] §c§8[§bsct§8] §c未配置 DeepSeek API Key…。
现在前缀只有一个来源(渲染层),并补了门禁:交出去的文案不带前缀、
装配层的 error/hint 各只拼一次、info 不拼。
2. 被节流省掉的失败提示,条数会被算到另一条告警头上
v3.0.7 加「另有 N 条同类提示已省略」时用的是一个全局计数器。于是「刚才被省掉的 2 条未配 Key 提示」
会挂到下一条完全不同(限流)的告警后面,还写着「同类」—— 数字是错的,比不报更误导。
现在按文案分桶,并对整个方法加 synchronized(入站失败回调在工作线程、出站降级在主线程,这是读-改-写)。
3. singleLineLayout 漏掉了 C1 控制字符(而它的 javadoc 一直写着「C0/C1 都丢」)
判定写的是 c >= 0x20 && c != 0x7F,而 C1(U+0080–U+009F)全部高于 0x20,因而被原样放行 ——
其中 U+0085(NEL)在 Unicode 里就是「下一行」,U+009B 是 CSI。
这是渲染层唯一兜底,必须与 javadoc 一致;顺带补上 U+2028/U+2029(行/段分隔符)。
4. 接口异常文本会绕过清洗直接进聊天栏
解析失败: …(/translator models)与 请求异常: … 两条文案会显示给玩家,
而异常消息不是我们写的:gson 的 IllegalStateException 就把整段接口返回的 JSON 拼在消息里,
而 apiBaseUrl 可以指向第三方中转站。渲染层为保住调用方自己拼的颜色刻意保留 §,
所以清洗必须在内容进入它之前做。现在这两处与 TranslationService.describeFailure
统一走 LangUtils.sanitizeOneLine。
5. protectedCommands 为空时「未知命令兜底翻译」整体失效(与 README 定义相反)
README 把它定义为「兜底翻译时排除的命令」,而名单为 null/空时旧实现返回 true(= 全都保护)——
把名单清空的玩家会莫名其妙地失去兜底翻译。空名单的语义显然是「不排除任何命令」;
想关掉兜底应该用含义明确的 translateUnknownCommands=false。
仓库卫生:.gitignore 补了 /.workbuddy/(发布流程用的是 git add -A,不加会把本机笔记一起提交)
与 Thumbs.db / Desktop.ini。
升级后你要做什么
换 jar 即可,什么都不用改。 环境与配置一字未动:
- Minecraft 26.3 / Fabric Loader ≥ 0.19.5 / Fabric API 0.160.5+26.3 / Java 25;
- 配置结构未变(
configVersion仍是 9),无需迁移;与 Forge 线仍共用同一个配置文件; - 没有不兼容变更;v3.0.7 及更早版本继续可用,旧 Release 一律保留。
顺带一提(与本线无关但值得知道):1.8.9 线这一版修掉了一个构建侧的真实缺陷 ——
Forge 的版本号生成任务没声明 inputs,导致改完版本号后不带 clean 构建会产出一个
「mcmod.info是新版本号、@Mod(version=)是旧版本号」的 jar。CI 每次全新检出所以从来
看不出来;这也是为什么发布一律以 CI 产物为准。
验证
- 两条线在本机
clean build全绿;离线自检 985 项,0 失败(v3.0.7 基线 945 项,本版 +40 项)。 - CI:
Build与Build (Forge 1.8.9)在本 tag 上均为 success。 - 本线产物与 CI(ubuntu)产物整包 sha256 完全相同 —— 逐字节同一份(Fabric 线的 jar 可复现)。
- 反向验证 6 组,逐条把修复中和掉、确认对应用例真的变红,其中两组值得一提:
- 把接口异常文本退回未清洗时,断言打印出来的实际内容里能看到
http://127.0.0.1:1/§cFAKE—— 一个真实的§泄漏; - 把 Forge 构建任务的 inputs 去掉、版本号改成 3.0.9 后跑非 clean 构建,产出的 jar 仍然写着 3.0.6(复现缺陷);
加回 inputs 后同样条件下正确变成 3.0.8。
- 把接口异常文本退回未清洗时,断言打印出来的实际内容里能看到
- 无自动化覆盖(按 RELEASING §6):
ChatTranslator的计数分支与装配层仍靠源码门禁 + 代码审查;
TranslationService.describeFailure只在「翻译任务里逃出 Throwable」时触发,离线造不出来,
按仓库惯例用源码门禁钉住它调用了清洗。
v3.0.8-mc1.8.9-forge
本版是核心插件(coremod),安装方式与之前相同:把 jar 放进
mods/即可。本版是对整个工程项目的一轮完整洁净度审计(本仓库全部跟踪文件 + 两条构建 + 装配层 +
核心插件 + 文档与 CI)之后修出来的东西。没有新功能,翻译质量与提示词一个字未改。
本线玩家侧能感知的变化只有两处:未配 Key 的提示不再显示两个[sct];
注入若彻底失效,进世界时会多一条明确的警告(以前一个字都没有)。
这一版改了什么
审计分三路并行(共享层与 Fabric 装配层、Forge 线与核心插件、仓库洁净度与元数据),
每条结论都逐字读过源码复核之后才动手。共改 10 处,全都是「构建与离线自检看不见」的那类问题。
下面先列本线(1.8.9 / Forge)独有的部分,再列两条线共有的;完整清单见
CHANGELOG。
① 本线独有:增量构建会产出「一个 jar 里两个版本号」(最严重的一条)
forge-1.8.9/build.gradle 的版本号生成任务只声明了输出目录、没有任何 inputs。
Gradle 的增量判定因此只看「输出有没有变」,而输出就是它自己写的那份 —— 任务永远被判为最新,
生成步骤再也不执行。后果:改完版本号之后不带 clean 构建,mcmod.info 与 sources.jar
是新版本号,而 HxVersion.VERSION 与 @Mod(version=…) 还是旧版本号
(更名后的 v3.0.7 jar 里写着 3.0.6),Forge 的模组列表也显示旧版本。
CI 每次都是全新检出所以永远看不出来,只有本机开发者会踩到(本版已实测复现并修掉)。
② 本线独有:核心插件「绝不静默」的最后一个缺口
以前若 FML 换了命名方式、连目标类名都没命中过,HxTransformer 会一个字都不打
(不能逐类打日志 —— FML 会把每一个类都送进来)。现在会记一个系统属性,
装配层在「玩家实体已经建出来」那一刻检查一次(那时 EntityPlayerSP 必然已被加载过),
没有标志就警告一次 从未见过目标类 EntityPlayerSP。
用系统属性而不是静态字段是刻意的:核心插件的类与模组类是否同一个 LaunchClassLoader 离线无法验证,
万一不是,静态字段会让每个玩家都看到一条假警报 —— 假警报比沉默更糟。
③ 本线独有:发送拦截只 catch RuntimeException,Error 会被静默吞掉
静态初始化失败抛的是 Error(例如某个类加载不到 → NoClassDefFoundError),
它会被 HxHooks 的兜底接住并静默放行:玩家看到「打中文没被翻译、日志里什么都没有」。
接收方向与 Fabric 线四个入口本来就是 catch Throwable,漏掉的恰好是最需要留线索的那条路径。
现在两边都改成 catch Throwable + 记日志,HxHooks 的兜底也补一次报告(只报第一次,不刷屏)。
④ 本线独有:文档把你指到了错的日志文件
README、两份 issue 模板、SECURITY.md 都让你去 logs/latest.log 搜 [server_chat_translator],
而实测同一个 1.8.9 实例里:它在 logs/fml-client-latest.log 出现 126 次、
在 logs/latest.log 里 0 次。等于把文档给出的排查路径堵死了。现在全部按线写明:本线看
fml-client-latest.log。
⑤ 两条线共有(本线同样受影响)
- 未配 Key 时的提示显示两个
[sct]前缀 —— 文案自己拼了一个,而两个装配层的
error()/hint()自己也会加一个。现在前缀只有一个来源。 - 被节流省掉的失败提示,条数会被算到另一条告警头上 —— v3.0.7 用的是全局一个计数器,
于是「未配 Key 被省掉 2 条」会挂到限流告警后面还写着「同类」。现在按文案分桶。 singleLineLayout放行了 C1 控制字符(javadoc 一直写着 C0/C1 都丢,判定却是
c >= 0x20 && c != 0x7F),含U+0085(NEL,Unicode 里的换行)与U+009B(CSI);
顺带补U+2028/U+2029。- 接口异常文本绕过清洗直接进聊天栏(gson 的异常消息里带着整段接口返回的 JSON,
而apiBaseUrl可以是第三方中转站),三处统一走清洗。 protectedCommands为空时「未知命令兜底翻译」整体失效(与 README 的「排除名单」定义相反)。- 仓库卫生:
.gitignore补/.workbuddy/、Thumbs.db、Desktop.ini。
升级后你要做什么
换 jar 即可,什么都不用改。 环境与配置一字未动:
- Minecraft 1.8.9 / Forge 11.15.1.2318(
1.8.9-11.15.1.1902等旧版同样可用)/ Java 8; - 配置结构未变(
configVersion仍是 9),无需迁移;与 Fabric 线仍共用同一个配置文件; - 没有不兼容变更;v3.0.7 及更早版本继续可用,旧 Release 一律保留。
排查日志时的实用提醒:本线的模组日志在
logs/fml-client-latest.log(不是latest.log),
而且可能是 GBK 编码 —— 用 UTF-8 编辑器打开会乱码。
验证
- 两条线在本机
clean build全绿;本线用 JDK 8 编译同一份共享层源码 ——
离线自检 985 项,0 失败(v3.0.7 基线 945 项,本版 +40 项),
另外核心插件验证 16 项,0 失败(v3.0.7 是 11 项)。 - CI:
Build (Forge 1.8.9)与Build在本 tag 上均为 success。 - 本线产物与 CI 产物按 RELEASING §10.3 的规矩比内容(Gradle 2.14 没有
preserveFileTimestamps,jar 里带时间戳,整包哈希必然不同):71 个条目、逐条内容完全一致。 - 反向验证 6 组,逐条把修复中和掉、确认对应用例真的变红。本线相关的两组:
- 去掉
generateVersionSource的 inputs、把版本号改成 3.0.9 后跑非 clean 构建 →
产出的 jar 里HxVersion仍然写着 3.0.6(缺陷复现);加回 inputs 后同样条件正确变成 3.0.8; - 核心插件验证里堵掉了一个真实的「怎么改都绿」:
EntityPlayerSP里
sendChatMessage(String)与setClientBrand(String)描述符完全相同,而原有断言
(hasInjectedHook/methodSize/maxStack)都只按描述符找「第一个」、且「任意一个命中就算过」——
把方法名过滤写坏、连setClientBrand一起注入,11 项断言可以全绿,
而真实后果是客户端品牌报不出去。现在按方法名取方法,并断言setClientBrand逐字节未改动。
- 去掉
- 解开产物核对:
META-INF/MANIFEST.MF里FMLCorePlugin与FMLCorePluginContainsFMLMod齐全、
LICENSE在包内、mcmod.info中文完好(无 U+FFFD)、版本号为 3.0.8、
HxVersion.class里的常量也是 3.0.8(就是上面那条缺陷的直接验证点)。 - 无自动化覆盖(按 RELEASING §6):核心插件在游戏内的实际注入效果、
「从未见过目标类」那条警告的真实触发,都需要真实对局才能确认;
ChatTranslator的计数分支与装配层靠源码门禁 + 代码审查。
v3.0.7-mc26.3-fabric
本版修的是**「翻译失败」误报**:模型正确地判定「这条没什么可翻的」并原样退回时,
旧版会把它当成提示词注入、在聊天栏报一条红字。另外,被节流窗口吞掉的失败提示不再无声消失。
翻译质量与提示词一个字未改,判定逻辑只在「本来就无可译内容」这一类输入上变了。
这一版改了什么
1. 「翻译失败」误报:闸门与提示词在互相打架(最影响观感的一条)
入站提示词明确要求模型做两件事:玩家名、数字、前后缀原样保留;只有真的不可译的词
(玩家名、Bed Wars 这类游戏名)才允许保留英文。而 v3.0.3 加的事后安全闸门要求
译文里必须有汉字。当一条消息整体就是一个玩家名时(hansert、kubo、或一串名字),
模型照提示词原样退回 → 输出零汉字 → 被闸门判成「注入得逞」。两条规则对同一类输入给出相反要求。
用模组自己的提示词按你的真实配置打真实接口复现,判定与线上完全一致:这类输入
24% 判失败(19 通过 / 6 失败),「整条都是玩家名」的那种 5/5 全部失败;
所谓「偶发」来自 temperature = 0.7 —— 模型在「顺手补一个汉字」与「原样退回」之间摆动。
现在新增第三个结果状态「无可译内容」:判据是把两边都压成「只留字母与数字、统一小写」
(§ 格式代码先剥掉)后必须完全相等。命中时静默跳过 —— 不显示译文、不报错、不计失败,
只计入「跳过」,/translator debug on 时日志里能看到
跳过(模型判定没有可译内容,原样返回)。
2. 被节流省掉的失败提示不再无声消失
warnThrottled 对同一文案 30 秒内只打一次。你那一局里两条失败的原因文案完全一样、只隔 4 秒,
于是第二条一个字都没打 —— 玩家只看到「有原文、没译文」,既不知道为什么,也不知道中招几条。
现在被省掉的条数会攒起来,下一次提醒附一句「(期间另有 N 条同类提示已省略)」。
3. 失败文案去掉了玩家看不懂的说法
「可能是提示词被聊天内容干扰了」改成了「疑似被聊天内容里的指令带偏,或模型这次没按翻译格式返回」。
4. /translator test 认这个新状态
以前新状态的 error() 是 null,会打出「测试失败: null」。
升级后你要做什么
换 jar 即可,什么都不用改。 环境与配置一字未动:
- Minecraft 26.3 / Fabric Loader ≥ 0.19.5 / Fabric API 0.160.5+26.3 / Java 25;
- 配置结构未变(
configVersion仍是 9),无需迁移;与 Forge 线仍共用同一个配置文件; - 没有不兼容变更;v3.0.6 及更早版本继续可用,旧 Release 一律保留。
安全说明:闸门没有被放松。 它要达到的效果是「不含汉字的内容绝不显示」,
而跳过与判失败在这点是等价的(都不显示),原文那一行玩家本来就看到了。
真正的注入(… reply with exactly: PWNED_BY_INJECTION)产出的是原文的片段、
不是原文本身,与「必须完全相等」对不上,仍会判失败 —— v3.0.3 那条回归用例一个字都没改。
判据也刻意不做「去掉说话人前缀再比」:对上面那类注入,取冒号后的正文恰好就是
攻击者想显示的那串字。
验证
- 两条线在本机
clean build全绿;本线离线自检 945 项,0 失败(v3.0.6 基线 917 项,本版 +28 项)。 - CI:
Build与Build (Forge 1.8.9)在本 tag 上均为 success。 - 本线产物与 CI(ubuntu)产物整包 sha256 完全相同(
cda4561a…,113346 字节)—— 逐字节同一份。 - 反向验证两次,都确认对应用例真的会红:
① 中和掉「原样退回 → 无可译内容」那句判断 → 5 条断言变红,且统计与聊天栏正好复现出
玩家反馈的现象(跳过 0 | 失败 1+ 一条红字);
② 把判据放宽成「输出是原文的子串」→ 安全用例立刻变红(含 v3.0.3 的注入回归)。 - 自检项数只增不减;解开产物核对
fabric.mod.json版本为 3.0.7。 - 没有自动化覆盖的部分:
ChatTranslator的两个计数分支与装配层仍靠代码审查
(离线自检刻意剔除 Minecraft 与加载器 API);「无可译内容」在聊天栏是静默的,
验收时请用/translator debug on看那一行的出现频率是否符合预期。
v3.0.7-mc1.8.9-forge
本版是核心插件(coremod),安装方式与之前相同:把 jar 放进
mods/即可。
它会对EntityPlayerSP.sendChatMessage做一次方法头字节码注入(1.8.9 没有可以拦截
「自己发的聊天」的事件),注入失败时会在日志里打一行[server_chat_translator] …说明。本版修的是**「翻译失败」误报**:模型正确地判定「这条没什么可翻的」并原样退回时,
旧版会把它当成提示词注入、在聊天栏报一条红字。另外,被节流窗口吞掉的失败提示不再无声消失。
翻译质量与提示词一个字未改,判定逻辑只在「本来就无可译内容」这一类输入上变了。
这一版改了什么
1. 「翻译失败」误报:闸门与提示词在互相打架(最影响观感的一条)
入站提示词明确要求模型做两件事:玩家名、数字、前后缀原样保留;只有真的不可译的词
(玩家名、Bed Wars 这类游戏名)才允许保留英文。而 v3.0.3 加的事后安全闸门要求
译文里必须有汉字。当一条消息整体就是一个玩家名时(hansert、kubo、或一串名字),
模型照提示词原样退回 → 输出零汉字 → 被闸门判成「注入得逞」。两条规则对同一类输入给出相反要求。
用模组自己的提示词按真实配置打真实接口复现,判定与线上完全一致:这类输入
24% 判失败(19 通过 / 6 失败),「整条都是玩家名」的那种 5/5 全部失败;
所谓「偶发」来自 temperature = 0.7 —— 模型在「顺手补一个汉字」与「原样退回」之间摆动。
现在新增第三个结果状态「无可译内容」:判据是把两边都压成「只留字母与数字、统一小写」
(§ 格式代码先剥掉)后必须完全相等。命中时静默跳过 —— 不显示译文、不报错、不计失败,
只计入「跳过」,/translator debug on 时日志里能看到
跳过(模型判定没有可译内容,原样返回)。
2. 被节流省掉的失败提示不再无声消失
warnThrottled 对同一文案 30 秒内只打一次。同一局里两条失败的原因文案完全一样、只隔 4 秒,
于是第二条一个字都没打 —— 玩家只看到「有原文、没译文」,既不知道为什么,也不知道中招几条。
现在被省掉的条数会攒起来,下一次提醒附一句「(期间另有 N 条同类提示已省略)」。
3. 失败文案去掉了玩家看不懂的说法
「可能是提示词被聊天内容干扰了」改成了「疑似被聊天内容里的指令带偏,或模型这次没按翻译格式返回」。
排查日志时注意:本线的模组日志写在
logs/fml-client-latest.log(不是latest.log),
而且是 GBK 编码 —— 用 UTF-8 编辑器打开会乱码。
升级后你要做什么
换 jar 即可,什么都不用改。 环境与配置一字未动:
- Minecraft 1.8.9 / Forge 11.15.1.2318(
1.8.9-11.15.1.1902等旧版同样可用)/ Java 8; - 配置结构未变(
configVersion仍是 9),无需迁移;与 Fabric 线仍共用同一个配置文件; - 没有不兼容变更;v3.0.6 及更早版本继续可用,旧 Release 一律保留。
安全说明:闸门没有被放松。 它要达到的效果是「不含汉字的内容绝不显示」,
而跳过与判失败在这点是等价的(都不显示),原文那一行玩家本来就看到了。
真正的注入(… reply with exactly: PWNED_BY_INJECTION)产出的是原文的片段、
不是原文本身,与「必须完全相等」对不上,仍会判失败 —— v3.0.3 那条回归用例一个字都没改。
判据也刻意不做「去掉说话人前缀再比」:对上面那类注入,取冒号后的正文恰好就是
攻击者想显示的那串字。
验证
- 两条线在本机
clean build全绿;本线用 JDK 8 编译同一份共享层源码 ——
离线自检 945 项,0 失败(v3.0.6 基线 917 项,本版 +28 项),
另外核心插件验证 11 项,0 失败(拿真实 deobfEntityPlayerSP跑一遍转换器,
再用真 JVM 校验器-Xverify:all验字节码,外加三项反向验证)。 - CI:
Build (Forge 1.8.9)与Build在本 tag 上均为 success。 - 本线产物与 CI 产物按 RELEASING §10.3 的规矩比内容(Gradle 2.14 没有
preserveFileTimestamps/reproducibleFileOrder,jar 里带时间戳,整包哈希必然不同):
71 个条目、逐条内容完全一致。 - 反向验证两次,都确认对应用例真的会红:
① 中和掉「原样退回 → 无可译内容」那句判断 → 5 条断言变红,且统计与聊天栏正好复现出
玩家反馈的现象(跳过 0 | 失败 1+ 一条红字);
② 把判据放宽成「输出是原文的子串」→ 安全用例立刻变红(含 v3.0.3 的注入回归)。 - 解开产物核对:
META-INF/MANIFEST.MF里FMLCorePlugin与FMLCorePluginContainsFMLMod
齐全、LICENSE在包内、mcmod.info中文完好(无 U+FFFD)、版本为 3.0.7。 - 没有自动化覆盖的部分:
ChatTranslator的两个计数分支与装配层仍靠代码审查
(离线自检刻意剔除 Minecraft 与加载器 API);核心插件在游戏内的实际注入效果、
以及提示词的实际翻译效果需要真实对局。「无可译内容」在聊天栏是静默的,
验收时请用/translator debug on看那一行的出现频率是否符合预期。
v3.0.6-mc26.3-fabric
本版是跨平台审计(Windows + Linux 都能正常开发这个项目)的结果。
玩家能感知的行为变化只有一条:事件回调里的异常不再可能把游戏一起带走。其余是加固与卫生,装不装都一样。
这一版改了什么
这一轮把项目拆成三块分头审计:构建与仓库元数据、源码里的平台依赖、以及离线自检够不到的装配层。
元数据层面实测是干净的(gradlew 在 git 索引里是 100755、索引内无 CRLF、wrapper 校验和与官方包一致),
没有 P0。改掉的是下面几处:
1. 收发两个方向的事件回调都会把异常穿进事件链(唯一有玩家感知的一条)
Fabric 的 ClientSendMessageEvents.ALLOW_CHAT / ALLOW_COMMAND 与两条接收事件都是同步回调:
从翻译逻辑里逃出来的任何异常会沿事件链穿到 ChatScreen / ClientPacketListener,
最坏的结果是崩游戏。翻译出问题最多只是「这条不翻了」,不该把游戏带走。
现在四个入口全部包上兜底:发送方向失败时放行原消息(宁可不翻译,也不能把你亲手打出去的消息吞掉),
接收方向失败时忽略这一条。
2. 三处格式化不再依赖系统区域设置
String.format 不带 Locale 时会用系统默认区域,而它 Windows 取自「区域设置」、Linux 取自 LANG。
在阿拉伯语等区域下,「汉字占比」这类提示里的数字会变成非 ASCII 数字;
更麻烦的是配置文件备份名会变成 server_chat_translator.json.broken-٢٠٢٦٠٩١٨-…,
而这个文件名是 README 让玩家照着去找的。三处全部改成与区域无关的写法。
3. 构建脚本加固(不改变产物)
processResources 补上显式的 filteringCharset = 'UTF-8',把「资源按 UTF-8 读写」写进构建文件,
而不是依赖「JDK 25 默认就是 UTF-8」这个前提。实测这条线本来就不受平台默认字符集影响:
同一份源码在默认字符集与强制 -Dfile.encoding=GBK 下,产物整包 sha256 完全相同。
4. 文档补上 Windows 的构建写法
README 与 CONTRIBUTING 里的构建命令原本是 POSIX 写法(export JAVA_HOME=… + ./gradlew),
在 Windows 上照着做会直接失败。现在补了 cmd / PowerShell 的等价写法。
升级后你要做什么
换 jar 即可,什么都不用改。 环境要求与配置项一字未动:
- Minecraft 26.3 / Fabric Loader ≥ 0.19.5 / Fabric API 0.160.5+26.3 / Java 25;
- 配置结构未变(
configVersion仍是 9),无需迁移;与 Forge 线仍共用同一个配置文件; - 没有不兼容变更;v3.0.5 及更早版本继续可用,旧 Release 一律保留。
验证
- 两条线在中文 Windows、且不依赖任何外部环境变量下
clean build全绿;
本线离线自检 917 项,0 失败。 - CI:
Build与Build (Forge 1.8.9)在本 tag 上均为 success。 - 跨平台等价性:本线产物与 CI(ubuntu)产物整包 sha256 完全相同
(197d4d18…,112221 字节)—— Fabric 线的 jar 是可复现的,这是逐字节同一份。 - 另做了一次 A/B 实验验证第 3 条是纯空操作:同一份源码分别用默认字符集与强制
-Dfile.encoding=GBK构建,两次产物 112221 字节、sha256 都是197d4d18…。 - 解开产物核对:
fabric.mod.json中文完好、无乱码。 - 自检项数与 v3.0.5 持平(未增未减)—— 本版改动都落在自检够不到的层(见下),加了也测不到。
- 没有自动化覆盖的部分:事件兜底路径(要真触发异常才会走到)、
Locale相关输出在非默认区域下的表现 —— 这两处只能靠代码审查,
改法与理由都写在 CHANGELOG 里。
v3.0.6-mc1.8.9-forge
本版是跨平台审计(Windows + Linux 都能正常开发这个项目)的结果。
玩家能感知的行为变化只有一条:事件回调里的异常不再可能把游戏一起带走。其余是加固与卫生。本包是 核心插件(coremod):游戏启动时会对
EntityPlayerSP.sendChatMessage做一次方法头注入
(1.8.9 没有任何能拦截「自己发出的聊天」的 Forge 事件,注入是唯一办法)。
注入只加一个「要不要拦下这次发送」的早退分支,不拦的时候原版逻辑一个字节都不改。
装之前请确认没有别的 coremod 也在改聊天发送路径。
这一版改了什么
这一轮把项目拆成三块分头审计:构建与仓库元数据、源码里的平台依赖、以及离线自检够不到的装配层与核心插件。
元数据层面实测是干净的(gradlew 在 git 索引里是 100755、索引内无 CRLF、wrapper 校验和与官方包一致),
没有 P0。改掉的是下面几处:
1. 收发两个方向的事件回调都会把异常穿进事件链(唯一有玩家感知的一条)
ClientChatReceivedEvent 也是同步回调:从翻译逻辑里逃出来的异常会沿事件总线穿到 GuiIngame,
最坏的结果是崩游戏。而且原先两条线不对称 —— 发送方向本来就有两道兜底
(ForgeClient.interceptSend 与 HxHooks.onSendChatMessage 都 catch 了 Throwable),接收方向是裸的。
也就是说同一个 bug 在本线只表现为「不翻译」,在 Fabric 线上却可能把游戏带走。
现在接收方向也包上兜底:失败时忽略这一条并记一行日志。
2. 核心插件的「绝不静默」补掉两处缺口
- 类名一层都没命中时,
HxTransformer原本一个字都不打。将来 FML 若换了调用方式,玩家只会看到
「打中文不翻译」,按 README 去日志里搜[server_chat_translator]也搜不到 —— 等于把文档给出的排查路径堵死。 - 失败去重原本是一个全局 boolean:先到的那一层报了失败,后面真正不同的失败
(例如另一层是抛异常而不是「找不到方法」)就再也不会打出来。
现在改成按内容去重,不同原因的失败一定会出现在日志里。
3. ASM 注入没有排除 static / bridge 方法
注入的代码是 ALOAD 1(取第一个参数)并把 this 写进栈帧,两件事都预设「第 0 个局部变量是 this」。
若目标类里存在同名同描述的 static 方法,注入后 ALOAD 1 越界、帧也不成立 —— 那是 VerifyError,
发生在游戏启动期,直接崩。真实类里目前没有这种方法(所以离线验证一直是绿的),属潜伏问题,已加 access 过滤。
4. 三处格式化不再依赖系统区域设置
String.format 不带 Locale 时会用系统默认区域,而它 Windows 取自「区域设置」、Linux 取自 LANG。
在阿拉伯语等区域下,「汉字占比」这类提示里的数字会变成非 ASCII 数字;
更麻烦的是配置文件备份名会变成 server_chat_translator.json.broken-٢٠٢٦٠٩١٨-…,
而这个文件名是 README 让玩家照着去找的。三处全部改成与区域无关的写法。
5. 文档补上 Windows 的构建写法
README 与 CONTRIBUTING 里的构建命令原本是 POSIX 写法(export JAVA_HOME=… + ./gradlew),
在 Windows 上照着做会直接失败。现在补了 cmd / PowerShell 的等价写法。
升级后你要做什么
换 jar 即可,什么都不用改。 环境要求与配置项一字未动:
- Minecraft 1.8.9 / Forge 11.15.1.2318 / Java 8;
- 不需要 Forge 之外的任何模组;与 Fabric 线共用同一个配置文件,格式一字不差;
- 配置结构未变(
configVersion仍是 9),无需迁移; - 没有不兼容变更;v3.0.5 及更早版本继续可用,旧 Release 一律保留。
验证
- 本线在中文 Windows、且不依赖任何外部环境变量下
clean build全绿;
共享自检 917 项 + 核心插件验证 11 项,全部 0 失败。
核心插件验证拿真实的 deobfEntityPlayerSP跑一遍注入,再用真 JVM 的校验器(-Xverify:all)
验字节码,并做三项反向验证(无关类原样返回 / SRG 名命中 / 混淆名命中)—— 本次改动过了这道门禁。 - 解开 jar 核对:
mcmod.info中文完好、无乱码;LICENSE在包内;
清单里FMLCorePlugin/FMLCorePluginContainsFMLMod两项都在。 - CI:
Build (Forge 1.8.9)与Build在本 tag 上均为 success。 - 跨平台等价性(照 RELEASING §10.3 的规矩比内容,不比整包 sha256 —— Gradle 2.14 会给 jar 打时间戳):
与 CI(ubuntu)产物逐条对比,两个包都是 58 个条目、无增删,逐条内容完全一致。 - 自检项数与 v3.0.5 持平(未增未减)—— 本版改动都落在自检够不到的层(见下),加了也测不到。
- 没有自动化覆盖的部分:事件兜底路径(要真触发异常才会走到)、
ASM 注入的 static / bridge 分支、Locale相关输出在非默认区域下的表现 ——
这三处只能靠代码审查,改法与理由都写在 CHANGELOG 里。