AHNUMCL 开发记录
项目地址:https://github.com/ahnumc/AHNUMCL
AHNUMCL 不是重新发明一个 Minecraft 启动器,而是在成熟的上游项目基础上,逐步建立自己的品牌、服务和发行节奏。
暑假,我加入了 AHNUMC 社群。AHNUMC 是一个由安徽师范大学学生自发组建的 Minecraft 社群,大家因为游戏聚在一起,也尝试为社群成员提供服务器、账号、整合包和交流内容。AHNUMCL 正是在这样的背景下开始开发的:它不是为了单纯制作一个启动器,而是希望把社群已有的服务更方便地交给玩家使用,降低进入服务器、安装客户端和获取相关信息的门槛。
因此,项目中的 AHNUMC 登录、服务器列表、客户端整合包、新闻接口和多人联机功能,都不是孤立的演示功能,而是围绕社群玩家的实际需求逐步加入的。启动器既是一个桌面软件,也是 AHNUMC 社群服务玩家的一部分。
这段开发过程最有意思的地方,不是某一个单独的页面,而是一个分支项目怎样从“改几个名称”逐渐变成可以独立发布、独立维护的桌面应用。
技术栈与整体架构
AHNUMCL 使用 Tauri 作为桌面容器,前端是 Next.js、React、TypeScript 和 Chakra UI,后端则由 Rust 实现。前端负责页面、表单、列表和交互状态,Rust 负责文件系统、网络请求、游戏启动、下载任务和系统集成。
项目大致可以分成几层:
src/pages和相关前端服务负责启动器页面与用户交互。src-tauri/src负责 Tauri command、实例管理、账号、下载、启动和配置。- CLI 和 MCP 服务提供与外部程序、网页或 Agent 协作的入口。
- GitHub Actions 负责 Windows、macOS 和 Linux 的构建与发行。
开发环境使用 pnpm,启动桌面开发版本只需要:
pnpm install
pnpm tauri dev
这种架构的好处是边界比较清楚:前端可以专注于操作体验,涉及本地文件和系统能力的逻辑则放在 Rust 中处理。代价是同一个功能经常需要同时修改 TypeScript 页面、服务层、Tauri command 和 Rust 的领域模块。
从上游分叉到独立品牌
最早需要解决的问题不是功能,而是身份。项目经历了从 ANUMCL 到 AHNUMCL 的命名整理,应用名、CLI 名称、MCP 服务名、User-Agent、Yggdrasil 元数据、安装包名称和 CI 脚本都必须保持一致。
2026 年 8 月 14 日发布的 1.0.0 是一个重要节点。随后提交 f4c437b 对 46 个文件进行了大范围重命名,把残留的旧品牌从代码、配置、文案和发布流程中清理出去。这个工作看起来像机械替换,实际却很容易漏掉深层配置:桌面应用标识、更新器、脚本产物名和服务端识别字段都可能在用户看不到的地方继续保留旧名称。
同时,发布工作流也做了相应调整。macOS 签名相关的流程允许在缺少签名密钥时继续生成未签名产物,避免一次可选的签名失败阻断整个构建。品牌迁移的目标因此不只是“界面上看起来不一样”,而是让源码、构建产物和发行渠道都真正成为同一个项目。
接入 AHNUMC 服务
品牌独立之后,启动器需要有自己的服务内容。首先接入的是 AHNUMC 的 Yggdrasil 认证服务:
https://skin.ahnumc.org/api/yggdrasil
这让用户可以在启动器中使用 AHNUMC 的账号体系,并在账号模型中识别对应的认证服务器。相关提交还使用了 Yggdrasil Connect 的登录流程,减少对旧式登录入口的依赖。
接着是 AHNUMC 服务器和客户端整合包。启动器增加了服务器清单模型、服务器页面和整合包安装任务,可以从远程 JSON 获取服务器信息,再把对应的客户端资源导入到本地实例中。安装流程不是简单下载一个压缩包:它还要处理整合包覆盖文件、实例目录、导入状态和安装完成后的页面跳转。
早期实现中,AHNUMC 服务器整合包安装后的自动导入曾经出现回归,后来在 f907ebc 中恢复。任务上下文根据 ahnumc-server 任务组打开导入弹窗,让安装和导入重新连成完整流程。
这里还有一个需要谨慎处理的边界:远程 JSON 是外部输入,不能只因为它来自自己的服务就完全信任。获取远程清单时需要限制 URL scheme,并把网络请求、解析和本地写入分开处理,避免服务器配置直接变成任意本地操作。
下载和安装体验
启动器最容易被用户感知的性能问题,往往发生在下载和安装阶段。AHNUMCL 后续对这部分做了几次针对性调整。
下载体验不只取决于带宽,还包括镜像选择、任务调度、文件解压和进度反馈。任何一个环节阻塞,都可能让用户感觉整个启动器变慢。
首先是 GitHub 镜像选择。下载任务可以并发测速多个镜像,使用 FuturesUnordered 管理并发请求,从可用镜像中选择响应更快的地址。这样做比固定使用一个下载源更适合网络环境差异明显的用户,但也需要处理超时、全部失败和下载地址切换等情况。
并发测速可以减少对单一下载源的依赖,但需要为超时、全部失败和地址切换准备明确的回退路径。
其次是把阻塞操作从 Tokio 异步运行时中移开。整合包 override 解压属于 CPU 和文件系统密集型工作,直接放进异步任务会阻塞其他任务。提交 206a450 使用 spawn_blocking 处理这类操作,让版本列表和实例安装流程不再互相卡住。
异步函数不代表其中的所有操作都是非阻塞的。压缩包解压和大量文件读写仍然可能占用线程,应该交给专门的阻塞任务处理。
下载速度显示也做了平滑处理。分块下载的瞬时速度经常会出现尖峰,如果直接显示最近一次采样,界面上的数字会不断跳动。提交 1295636 使用历史速度和当前采样的加权结果,降低短时间采样带来的抖动,让速度和剩余时间更接近用户的直觉。
对速度和剩余时间做平滑,不会让下载本身变快,但能让反馈更稳定,也更接近用户对当前进度的判断。
资源页面的嵌套依赖弹窗也进行过优化。原本大量条件渲染会让依赖查看流程变得复杂,后来改为通过 key 查找共享弹窗组件,减少重复渲染,同时保留连续查看依赖的操作路径。
账号、实例和排障
启动器真正出问题时,用户通常不会先打开开发者工具,而是会问“为什么没有启动”。因此设置页面增加了实例最新日志查看入口,方便快速判断 Java 参数、模组加载或资源文件是否存在问题。
启动器的体验不只是“成功启动游戏”,也包括失败时能不能告诉用户发生了什么。
账号侧则补充了 Authlib-Injector 的下载回退,并移除了不再适合当前服务策略的 LittleSkin 登录入口。登录流程的改动不能只看 UI:认证服务器列表、账号类型判断、下载失败后的备用来源和错误提示都要一起调整,否则用户看到的只是一个看似正常、实际无法完成的登录按钮。
登录入口、认证服务器和账号类型是一个完整链路。只修改页面按钮而不同时更新后端判断,容易产生“看得到但用不了”的登录流程。
这些功能让我更加意识到,启动器的核心体验不只是“成功启动游戏”,还包括失败时能不能告诉用户发生了什么,以及用户能不能自己恢复问题。
让启动器拥有自己的外观
在功能逐渐稳定后,界面开始加入 AHNU 的视觉元素。外观系统支持自定义主题色、字体、界面背景透明度和明暗主题壁纸,先后加入了 AHNU Flowy 和 AHNU Blocky 两组壁纸资源。
相关改动并不是把一张图片替换到背景上这么简单。壁纸需要进入默认配置和多语言设置,明暗主题要分别选择资源,透明度滑块需要在实时调整时保持平滑,窗口失去焦点时还要正确隐藏标题栏控制按钮,避免透明窗口下的控件互相遮挡。
提交 9b13538 加入了 AHNU Blocky 壁纸,之后 1.0.6 版本也把这组视觉资源纳入发布内容。对启动器来说,外观不是功能之外的装饰,它会影响用户对“这是哪个发行版”的第一印象。
壁纸、主题色、字体和窗口透明度共同构成发行版的识别度。它们需要进入默认配置、主题逻辑和发布资源,而不只是替换一张背景图片。
Terracotta 多人联机
8 月 28 日加入的 Terracotta 是这一阶段规模最大的功能之一。它同时涉及 Rust 后端模块、Tauri command、前端服务、状态模型、多人页面、公共节点配置、首页入口和多语言文案。
从用户角度看,它提供了一个进入多人联机服务的入口;从实现角度看,客户端需要处理节点配置、连接状态、房间信息、下载进度和错误反馈。这类功能不能只把一个网页嵌进启动器,否则账号、任务和实例之间的状态会割裂。把它纳入启动器自己的任务和状态体系,才能让多人服务与下载、启动流程协同起来。
Terracotta 的加入也体现了 AHNUMCL 与上游项目的关系:上游提供了可扩展的桌面应用基础,而分支项目可以根据自己的社区和服务,继续增加垂直功能。
Terracotta 不是一个孤立的网页入口,而是被接入了启动器自己的账号、任务和实例状态体系,因此可以与下载和启动流程协同工作。
发布工程
AHNUMCL 当前支持 Windows、macOS 和 Linux,并通过 GitHub Actions 生成对应的安装包、便携版本或压缩包。发布时需要同时更新前端 package.json、Cargo workspace、Tauri 配置、版本说明和构建工作流。
1.0.0、1.0.1 到 1.0.6 的连续发布让我体会到,版本号不是最后才修改的字符串。它还关联着应用标识、更新器、构建产物、变更日志和用户下载到的实际文件。发布提交的价值,就是把这些容易分散的状态集中确认一次。
项目基于 SJMCL,并遵守 GPLv3 以及上游附加条款。因此独立品牌并不意味着可以忽略来源,README、软件关于页面和发行文档都需要明确项目基础与许可证要求。
独立品牌不等于切断上游关系。项目发布时仍然需要遵守 GPLv3 和上游附加条款,并在相关文档中说明项目来源。
这段开发经历带来的经验
第一,分叉项目最先要整理的往往是边界。哪些能力继承自上游,哪些服务属于自己的发行版,哪些修复应该回馈上游,最好在改代码之前就分清楚。
第二,桌面应用的功能链通常比页面看起来更长。一个“安装整合包”按钮,背后可能经过远程清单、Rust 服务、异步任务、文件解压、状态更新和前端导入;只改最后一层很容易留下半完成状态。
第三,异步代码中要区分等待和阻塞。网络请求适合异步等待,压缩包解压和大量文件操作则应该交给专门的阻塞线程,否则一个安装任务就可能影响整个启动器的响应。
第四,发行版的个性既来自功能,也来自细节。认证服务、服务器内容、壁纸、主题色和发布名称共同构成用户对 AHNUMCL 的认识,任何一处还显示旧品牌,都会让独立发行显得不完整。
一个分支项目真正独立下来,靠的不是一次性改名,而是持续维护自己的用户、服务和发行流程。
AHNUMCL 的开发并不是把上游项目复制一份再改名,而是围绕自己的用户、服务和发行流程,逐步建立一套能够独立运行的启动器体验。
后续还可以继续完善扩展生态、MCP 自动化、多人服务和跨平台发布流程。对我来说,这个项目最有价值的地方,是它把前端交互、Rust 系统能力、异步任务和开源项目协作放进了同一个真实产品里。