展示服启动器兼容性说明

本文内容已过期。

本文内容已过期。

本文内容已过期。

文中说法目前已不再适用于最新版整合包,保留本文仅出于保存历史之考虑。


TeaCon 2023 的展示服整合包基于 MultiMC 格式,使用 Packwiz 实现模组自动更新。


使用该架构的动机包括但不限于:

  • 解决一部分「模组」(包括但不限于 kotliin-for-forge 之类的 Language Provider)无法通过原 RemoteSync 实现自动更新的问题

  • 解决 TeaCon 2021、2022 展示服整合包为同时提供两种格式(MultiMC 导出格式及 MCBBS 格式)的支持,而被迫在同一压缩包内提供两份一模一样的模组及配置文件,进而导致压缩包整体体积过大的问题

  • 解决客户端配置文件无法自动更新的问题

  • 解决 RemoteSync 本身硬依赖 Forge 生态(具体来说,是 FML 的 IModLocator 接口),无法在 Fabric 等其他生态上复用的问题

  • 解决 RemoteSync 默认缓存所有已下载模组,导致在试图使用二分法排查故障时,难以确定当前在使用哪个版本的模组的问题

  • 解决 RemoteSync 无法提供「可选模组」的问题

  • 解决 RemoteSync 没有可视化界面,玩家无从得知当前下载进度的问题

  • 解决无法正确区分「只需要安装在客户端」、「只需要安装在服务器」上的模组的问题

  • 解决前置模组列表需要手动维护的问题(实际上,这个问题更多是「碧螺春-龙井」系统的问题)

  • ……

上述问题均基于 TeaCon 2021、TeaCon 2022 中收获的惨痛经验教训总结而来。


如果你是普通玩家,你可能是从游戏启动前的弹窗那来到这里的。

如果你是启动器开发者,你大概则是从你的用户那里拿到本文的链接的。

本文将简要阐述该整合包所需的 MultiMC 特性。

从这里开始,本文的预期读者是启动器开发者。若你是普通玩家,以下内容对你来说将基本等于天书。

你可以在寻求启动器开发者帮助时,附带上本文地址,这样可以帮助开发者快速厘清头绪。


MultiMC 导出包格式

这是所有需求的起点。

关于 MultliMC 导出包格式的内容,可参考 MultiMC 自己的说明:点我跳转。

这里只简述最基础支持所需要考虑的内容:

  • instance.cfg:整合包配置文件,包括但不限于下列信息:

    • 名称

    • 图标

    • 备注

    • 启动所需 Java 位置

    • 预启动命令(重要。稍后会阐述其细节)

    • ……

  • mmc-pack.json:整合包所需库(libraries)信息,包括但不限于下列信息:

    • Minecraft 版本

    • LWJGL 版本

    • Fabric 版本(可能不存在)

    • Forge 版本(可能不存在)

    • LiteLoader 版本(可能不存在)

    • Quilt 版本(可能不存在)


预启动命令(Pre-Launch Command)

这是一串会在游戏启动前执行的命令。

若你有类 Unix 操作系统的使用经验,这里所谓的「命令」可简单理解为交由 exec(3) 启动的程序。或者,如果你熟悉 Windows 平台开发,Processthreadsapi.h 里的 CreateProcess。

若你有 Java 开发相关经验,这里所谓的「命令」亦可简单理解为「通过 java.lang.ProcessBuilder 创建一新的进程(java.lang.Process)并等待其运行完毕」。

Packwiz 利用此功能,在游戏正式启动前,运行其 Packwiz Installer,自动完成整合包文件的下载与更新。

MultiMC 只会在预启动命令执行成功(即,退出时状态码为 0)时,继续游戏启动。

MultiMC 执行预启动命令时,使用游戏目录(即俗称的 .minecraft 目录)作为当前工作目录。因此,所有相对路径均会以此为基准解析或展开。


预启动命令中的环境变量展开

根据 MultiMC 的用户交互界面中的说明,你可以在 MultiMC 中使用下列变量:

  • $INST_NAME:实例名

  • $INST_ID:实例 ID(即,该实例所在目录的名字)

  • $INST_DIR:实例目录的绝对路径

  • $INST_MC_DIR:Minecraft 本体所在目录的绝对路径

  • $INST_JAVA:运行该实例时所使用的 Java 的绝对路径

  • $INST_JAVA_ARGS:启动该实例时,使用的全部命令行参数


不论操作系统,所有变量均以上述格式出现。举例:

  • 预启动命令:$INST_JAVA -version 会输出当前 Java 版本,因为 $INST_JAVA 已展开为 Java 程序的绝对路径。

  • 预启动命令:%INST_JAVA% -version 会报错,因为 %INST_JAVA% 不会展开,系统也找不到名为 %INST_JAVA% 的程序。


MultiMC 在将命令传入 QtProcess 构造器(下文中将提及)前,会先行展开这些变量,并将这些变量注入预启动命令所在进程的环境变量中。

Packwiz 导出的 MultiMC 格式整合包只用到了上述参数中的 $INST_JAVA。


预启动命令中对引号的处理

MultiMC 基于 Qt 编写。自然地,MultiMC 在实现预启动命令时,也使用了 Qt 的 QtProcess。

MultiMC 的开发环境目前要求 Qt 5.6 及以上版本。这里有一份 Qt 5 的 QtProcess 的文档可供参考:https://doc.qt.io/qt-5/qprocess.html


QtProcess 在处理命令时,对于引号有特殊的处理(由 QtProcess::splitCommand 负责):

  1. 通常按空格分词

  2. 遇到双引号把中间内容视为一个词

  3. 三个连续的双引号视为双引号本身

举例说明:假设我们使用位于 /app/my custom java 17/bin/java的 Java 启动本整合包,且并不存在名为 /app/my 的文件或文件夹,同时,在 $PATH 中可以找到名为 echo 的程序,作用是复读其全部参数,每个参数之间用一个空格隔开,那么

  • 预启动命令 $INST_JAVA -version 将会报错,提示 /app/my 不是可执行程序

  • 预启动命令 "$INST_JAVA" -version 将正确打印当前 Java 的版本信息

  • 预启动命令 echo hello world(注意 hello 和 world 之间有三个空格)将会打印 hello world,因为 echo 最终接受了两个参数,分别为 hello 和 world

  • 预启动命令 echo "hello world"(注意 hello 和 world 之间有三个空格)将会打印 hello world,因为 echo最终接受了一个参数,即 hello world

  • 预启动命令 echo """将会打印 "


Packwiz 导出的 MultiMC 格式整合包中利用了这一特性,用于规避 Java 路径中存在空格的情况。