插件 API 完整参考
插件脚本通过全局对象window.__PLUGIN_API__ 与启动器交互。本文档列出全部可调用方法、签名、权限要求与示例。
全局变量
PLUGIN_API_BASE — 插件文件地址前缀
L2 沙箱内,启动器会自动注入window.__PLUGIN_API_BASE__,值为当前插件的文件访问基地址:
l2)和悬浮窗(overlay iframe)时该变量不存在,相对路径需通过其他方式解析。
调用方式
权限机制
每个 API 方法对应一个权限。调用时启动器会检查插件 manifest 的permissions 是否包含该权限,否则报错:
完整权限表
「风险」用于安装详情弹窗的视觉提示:普通=蓝、警告=黄、危险=红。声明权限时遵循最小权限原则,只声明你真正用到的。
方法参考
getSettings — 读取插件配置
读取插件自己的settings.json。
- 权限:
config:read - 返回:
Record<string, unknown>(无文件时返回{}) - 存储位置:
{数据目录}/plugins/{插件id}/settings.json
setSettings — 写入插件配置
按 key 合并写入配置(合并而非覆盖整个文件)。- 权限:
config:write - 参数:
(key: string, value: any),value 可为任意 JSON 值 - 并发写入由后端加锁保护
setCache / getCache — 插件缓存
按 key 读写插件自己的缓存文件,支持 TTL 过期。- 权限:
cache:access - 存储位置:
{数据目录}/plugins/{插件id}/cache.json - 内部结构
{ key: { v: 值, e: 过期时间戳|null } },无需插件关心
callBackend — 调用启动器后端 API
直接请求启动器后端(http://localhost:5000/api/...),绕过 CORS 限制。
- 权限:
network:fetch - 参数:
(endpoint: string, data?: any) - 有
data→ POST;无data→ GET data对象中可传入_method字段覆盖 HTTP 方法(如{ _method: 'PUT', State: 'active' }),_method不会被发送到后端endpoint以/开头(如/instance),不含/api前缀- 返回:后端 JSON 响应;非 2xx 抛错
- 可用端点清单:见启动器后端 OpenAPI(开发模式
/openapi/v1.json),常用如/instance、/resources/search、/settings等
sandbox="allow-scripts",无 allow-popups)里 window.open(url, '_blank') 会被拦截。要打开外部链接,应通过后端端点:
http/https 链接(仅接受这两种协议,其余返回 400)。
proxyFetch — CORS 代理请求
经启动器后端转发请求任意外部 URL,绕开浏览器 CORS 限制,且带 SSRF 防护。- 权限:
network:cors_proxy - 请求
ProxyRequest:
- 响应
ProxyResponse:
- SSRF 防护:仅 http/https;禁止内网/保留地址(localhost、127.x、10.x、172.16-31.x、192.168.x、169.254.x 等),返回
PROXY_PRIVATE_ADDRESS(400)
proxyFetchStream — 流式代理请求
消费 SSE 流(如 AI 对话逐字输出),逐块回调。- 权限:
network:cors_proxy - 后端自动带
stream: true转发上游流 signal(AbortSignal)用于中断;req.signal经 postMessage 前自动剥离,改走__plugin_api_abort消息- ⚠️ 已知限制:
proxyFetchStream返回的 Promise 在流结束时 resolve,但 chunk 回调可能在 resolve 之后仍触发,建议以onError/ 流内[DONE]标记判断完成
registerMethod — 注册插件方法
将当前插件的方法注册到全局注册表,供其他插件通过callPlugin 调用。
- 权限:
config:write - 参数:
(method: string, fn: Function),fn 返回值为值或 Promise - 插件停用时自动注销所有方法
- 详细见 插件依赖与互调用
callPlugin — 调用其他插件方法
调用目标插件已注册的方法。- 权限:
network:fetch - 参数:
(pluginId: string, method: string, ...args) - 目标未安装 / 未激活 / 方法未注册 → reject
registerHook — 注册启动器方法 Hook
将当前插件注册为启动器某方法的拦截者,在该方法执行前后注入逻辑(修改参数/返回值、阻止执行、完全替换实现)。- 权限:
hook:register(危险) - 参数:
(method: string, handler: (ctx, next) => Promise<void>) handler为 Koa 式洋葱中间件:next()前改ctx.args,next()后改ctx.result,ctx.prevent()阻止默认实现- 插件停用时自动注销其全部 hooks
- 详细见 Hook 系统
callWasm — 调用 WASM 插件导出函数
调用 L3 WASM 插件导出的函数(on_load / on_unload / 自定义导出)。经启动器 Rust 网关(wasmtime)执行。
- 权限:
wasm:execute - 参数:
(pluginId: string, exportName?: string),exportName缺省on_load - 返回:
{ ok: true, result: ... }或错误 - 详细见 WASM 插件
listWasmPlugins — 列出已加载的 WASM 插件
- 权限:
wasm:execute
readText — 读取文本文件(授权制)
读取指定路径的文本文件(UTF-8),支持分段读取。首次访问需用户授权(弹窗确认)。- 权限:
filesystem:read - 参数:
(path: string, options?: { start?: number; length?: number }) start:起始位置(字符),默认 0length:读取长度(字符),默认读到文件尾- 返回:
{ path, content: string } - 授权机制:首次访问未授权路径返回 403
FS_AUTHORIZATION_REQUIRED,启动器自动弹窗询问「是否允许插件访问该路径」,用户同意后授权(按路径前缀持久化),同插件同路径不再弹窗 - 文件不存在 →
FS_FILE_NOT_FOUND(404)
readBytes — 读取二进制文件(授权制)
读取指定路径的二进制文件,返回 base64,支持分段读取。- 权限:
filesystem:read - 参数:
(path: string, options?: { start?: number; length?: number }) start:起始位置(字节),默认 0length:读取长度(字节),默认读到文件尾- 返回:
{ path, contentBase64: string } - 授权机制同
readText
writeText — 写入文本文件(授权制)
- 权限:
filesystem:write - 参数:
(path: string, content: string),UTF-8 写入 - 授权机制同
readText;自动创建父目录 - 返回:
{ path }
writeBytes — 写入二进制文件(授权制)
- 权限:
filesystem:write - 参数:
(path: string, bytes: Uint8Array),自动转 base64 传输 - 授权机制同
readText;自动创建父目录 - 返回:
{ path }
deleteFile — 删除文件(授权制)
- 权限:
filesystem:write - 参数:
(path: string),仅删除文件;目录请用deleteDir - 授权机制同
readText(破坏性操作同样需授权确认) - 文件不存在 →
FS_FILE_NOT_FOUND(404) - 返回:
{ path }
deleteDir — 删除目录(授权制)
- 权限:
filesystem:write - 参数:
(path: string, options?: { recursive?: boolean }) - 授权机制同
readText;路径必须在已授权前缀内 - 请求
{ recursive: false }时只接受空目录,非空返回FS_DIR_NOT_EMPTY(400) - 危险根永远拒绝:盘符根(
D:、D:\)与文件系统根返回FS_REFUSE_ROOT(403)。 授权是按路径前缀覆盖的,误把整个盘授权给插件后一次递归删除就是灾难—— 插件侧仍建议自己做业务层校验(例如拒绝层级过浅的目录) - 目录不存在 →
FS_DIR_NOT_FOUND(404) - 返回:
{ path, removed }
典型用途:删除实例/服务端目录。此前后端只能删单个文件,插件想删目录只能
递归 deleteFile 再把空壳 rename 挪走,会在磁盘上留下无法清理的垃圾目录。
execCommand — 执行 shell 命令
执行系统命令,返回退出码与输出。Windows 用 powershell,Linux/macOS 用 /bin/sh。- 权限:
shell:execute(危险权限,安装时红色提示) - 参数:
(command: string, timeoutMs?: number),timeoutMs默认 15000,范围 1000–120000 - 返回:
{ exitCode: number, stdout: string, stderr: string } - 超时 →
SHELL_TIMEOUT(400),命令被强制终止 - 命令为空 →
SHELL_COMMAND_REQUIRED(400)
execCommand 是一次性执行模型:stdout/stderr 全量缓冲到进程结束才返回、无进程句柄、无 stdin。不要用它拉起常驻进程(服务端、守护进程等)——超时会被强制杀掉。这类场景用下面的 process.*。
process.spawn — 启动常驻进程
拉起一个长期运行的外部进程(如 Minecraft 服务端java -jar server.jar nogui),返回进程句柄。
- 权限:
process:manage(危险权限) - 参数:
{ command: string, args?: string[], cwd?: string, env?: Record<string, string> } - 返回:
{ pid: number, startedAtMs: number } - stdout/stderr 逐行进入环形缓冲(2000 行),经
process.logs游标增量拉取 - 归属隔离:插件只能管理自己 spawn 的进程(其他插件的 pid 一律 404
PROCESS_NOT_FOUND) - 每插件同时管理的进程数上限 32(
PROCESS_SPAWN_FAILED) - 进程退出后句柄保留 10 分钟供收尾日志,之后自动回收
process.stdin — 向进程标准输入写入
- 权限:
process:manage - 参数:
(pid: number, text: string, opts?: { appendNewline?: boolean }) - 交互式控制台命令(如服务端
stop/say/whitelist add)由此实现 - 进程已退出 →
PROCESS_STDIN_FAILED
process.kill — 终止进程树
- 权限:
process:manage - 终止整个进程树(Windows
taskkill /T /F,Unix 逐个 kill 子孙进程) - 对已退出进程幂等返回成功
process.alive — 查询进程状态
- 权限:
process:manage alive: false时exitCode为退出码(被信号终止或无法获取时为null)
process.logs — 游标式增量日志
- 权限:
process:manage - 参数:
(pid: number, afterSeq?: number),缺省afterSeq从缓冲最旧行开始 - 返回:
{ lines: [{ seq, stream, text }], nextSeq, oldestSeq, alive, exitCode } oldestSeq:环形缓冲淘汰后仍在的最旧 seq —— 若你的游标落后超过缓冲容量(2000 行),中间的行已丢失,建议据此提示用户- 进程退出后日志仍可拉取(保留期内)
process.list — 列出本插件的全部受管进程
- 权限:
process:manage - 典型用途:插件页面重载/启动器重启后,凭此找回仍在运行的进程(配合持久化的 pid)
pickFile — 文件选择对话框
弹出系统文件选择对话框(主窗口 Tauri 原生实现)。沙箱里<input type="file"> 拿不到绝对路径,window.showOpenFilePicker 不可用 —— 需要绝对路径时必须用此方法。
- 权限:
dialog:open - 参数:
{ title?, defaultPath?, filters?: [{ name, extensions: string[] }], multiple? }全部可选 - 返回:
string | null(选中的绝对路径;取消返回null)
pickDirectory — 目录选择对话框
- 权限:
dialog:open - 返回:
string | null
extractArchive — 解压本地 zip 到目录(授权制)
- 权限:
filesystem:write - 参数:
(archivePath: string, destPath: string, opts?: { prefix?: string, stripPrefix?: boolean }),目标目录自动创建 prefix只解压该子目录下的条目;搭配stripPrefix: true时剥掉前缀——整合包的overrides/要提到服务器根目录就靠这两个选项(overrides/mods/a.jar→mods/a.jar)- 防护:zip-slip(非法路径条目拒绝)+ 条目数 / 单文件 / 总量体积上限(复用整合包同一解压器)
- 授权机制:压缩包路径需读授权、目标目录需写授权 —— 首次使用弹一次确认、同时授权两个路径前缀
- 仅支持 zip(rar/7z 暂不支持)
listDir — 列举目录条目(授权制)
- 权限:
filesystem:read - 参数:
(path: string, opts?: { recursive?: boolean, maxEntries?: number }) maxEntries默认 1000,上限 10000- 授权机制同
readText - 典型用途:导入后探测服务端 jar、检查
eula.txt/server.properties是否存在
sendNotification — 系统级通知
发送系统级桌面通知(WebView Notification API,首次调用触发系统授权询问)。插件页面不在前台时也能触达用户;失败/被拒返回false,可降级为 showToast。
- 权限:
system:notification - 参数:
{ title: string, body?: string } - 返回:
boolean(通知已发出为true;API 不可用 / 未授权 / 被拒为false)
openUrl — 打开外部链接
用系统默认浏览器打开外部 URL。L2 沙箱内window.open 会被 sandbox 拦截,请用此方法打开外链。
- 权限:
system:notification - 后台走
POST /api/system/open-url,仅接受http:///https://协议,非法 URL 返回 400
getSystemInfo — 读取系统与启动器信息
读取操作系统、启动器与内存信息,可用于minLauncherVersion 兼容检查或运行时判断平台。
- 权限:
system:info - 后台走
GET /api/systeminfo(与/api/system/info等价) GitCommit为启动器 Git 提交哈希;Memory/AvailableMemory为字节数DataDir:启动器数据目录(如%LOCALAPPDATA%/qomicex-launcher)。插件需要默认工作区(存放自己的服务器/存档/下载等数据)时以此定位,如{DataDir}/Servers- 启动器元信息字段:
LauncherName:启动器名称(如Qomicex Launcher)LauncherVersion:启动器版本号(程序集版本,如0.1.1)VersionType:版本类型,按版本号预发布标签动态判断(alpha/beta/rc/dev,否则stable)Developers:[{ name, role }]开发者列表CoreDependencies:[{ name, version, license }]核心库列表(.NET / Tauri / Rust / Qomicex.Core 等)
listPlugins — 读取已安装插件列表
返回已安装插件列表,可用于依赖检测或商店联动。- 权限:
plugin:list - 后台走
GET /api/plugins/
uploadPlugin — 安装插件
上传.qplugin 包安装插件。
- 权限:
plugin:install(危险权限) - 参数:
(fileData: number[], fileName: string),multipart 字段名plugin
navigate — 跳转页面
跳转到启动器内部路由。- 权限:
config:read - 注意:应使用启动器内部路由,勿用外部 URL
openPluginSettings — 打开插件设置页
跳转到「设置 → 插件 → 插件设置」并定位到指定插件的设置 tab。适合在插件页面 / 悬浮窗里提供”设置”按钮。- 权限:
config:read - 参数:
(targetPluginId?: string),缺省定位到当前插件 - 目标插件须在 manifest 声明
contributes.settingsPages才有对应 tab(见 manifest);目标无设置页时仍会打开插件设置区,停留在默认位置 - 同插件有多个设置页时定位到第一个
showToast — 应用内通知
弹出一条 toast 提示。- 权限:
ui:toast - 参数:
(message: string, type?: 'info' | 'error' | 'success'),默认info
log — 输出插件日志
把日志写入启动器日志,便于在「设置 → 日志」中统一排查。- 权限:
plugin:log - 参数:
(message: string, level?: 'debug' | 'info' | 'warn' | 'error'),默认info
文件拖放事件(file-drop)
把文件拖入启动器窗口时,主窗口广播file-drop 事件,payload 为拖入文件的绝对路径数组。前端(主界面)可用 @tauri-apps/api/event 监听:
- 由启动器 Rust
WindowEvent::DragDrop::Drop { paths, .. }触发(dragDropEnabled开启时) - 路径为系统绝对路径,适合”拖文件自动填入路径”等交互
- 注意:前端原生
drop事件的File对象拿不到.path(Tauri 拦截系统拖放),必须走该事件 - L2 沙箱插件如需使用:由主界面前端监听后经
__pluginRegistry.call(pluginId, '方法', [paths])/callPlugin转发,插件侧registerMethod接收
getThemeColor — 获取当前主题种子色
返回当前启动器主题的种子色 hex 值,供插件根据主题色调整自身 UI。- 权限:
config:read - 返回:
string | null(#rrggbb格式,或null) - 逻辑:优先取外观设置中的静态 hex 色值;若为 Monet 动态模式(
background),取当前--primaryCSS 变量转换为 hex
applyThemeOverride — 应用主题覆盖
动态覆盖启动器 UI 的 CSS 变量(颜色主题),实现插件驱动的全局换肤。- 权限:
config:write - 参数:
(vars: Record<string, string>)— 键为语义 token(支持点分或横杠命名),值为H S% L%格式字符串 - 安全限制:
- token 白名单:仅允许
background、foreground、primary、accent、border等 30 个预定义语义 token - HSL 格式校验:值必须严格匹配
^\d{1,3} \d{1,3}% \d{1,3}%$,拒绝任意 CSS 注入 - 实现:注入
<style id="qomicex-plugin-theme-override">到document.head,选择器:root:root保证高优先级 - 重复调用会覆盖前一次的 override 样式
- 插件停用时不会自动清除,需手动调用
clearThemeOverride
clearThemeOverride — 清除主题覆盖
移除由applyThemeOverride 注入的覆盖样式,恢复到原主题。
- 权限:
config:write - 移除
id="qomicex-plugin-theme-override"的<style>元素 - 建议在插件停用钩子或用户取消自定义主题时调用
overlay.create — 创建悬浮窗
创建一个可拖拽的独立悬浮窗,返回悬浮窗 id。- 权限:
ui:sub_window - 返回:悬浮窗 id(string)
- 详细见 悬浮窗开发
overlay.show / hide / destroy — 悬浮窗控制
- 权限:
ui:sub_window
overlay.setHtml — 更新悬浮窗内容
- 权限:
ui:sub_window
overlay.setPosition — 移动悬浮窗
- 权限:
ui:sub_window
addMenuItem — 动态注册侧边栏菜单项
运行时动态向侧边栏添加菜单项,无需修改 manifest。- 权限:
ui:inject_sidebar - 参数:
PluginMenuItem(同contributes.menuItems元素) - 插件停用时自动移除动态注册的菜单项
onLauncherExit — 启动器退出事件
注册启动器关窗前的回调(尽力而为)。适合做同步快速收尾:保存状态、提示用户插件拉起的进程的去留等。- 无需权限(被动事件)
- 回调是同步、尽力而为的:启动器不会等待插件完成,关闭流程照常进行 —— 回调里不能发网络请求或做耗时异步操作(不保证执行完)
- 仅在启动器主窗口正常关闭流程中触发;进程被强杀 / 崩溃不保证触发
getPluginStrings — 读取插件自己的 i18n 包
返回本插件l10n/ 目录合并后的字符串包(bundle.json 打底、当前 locale 的 bundle.<locale>.json 覆盖)。与 manifest %key% 解析同源,插件运行时 UI 用它渲染自己的文案。
- 无需权限(读取的是插件自带数据)
- 需在 manifest 声明
"l10n": "l10n"(见 manifest)
getLocale — 当前启动器语言
- 无需权限;与
getPluginStrings的覆盖层一致
图标
推荐:<Glyph> + 单图标子路径(零 IPC、可 tree-shake)
插件页面里的图标不要走桥。图标数据随 @qomicex/plugin-ui 分发,静态导入后同步渲染:
fa-solid fa- 前缀(fa-solid fa-folder-open →
@qomicex/plugin-ui/icons/folder-open)。品牌图标加 brand- 前缀
(fa-brands fa-github → @qomicex/plugin-ui/icons/brand-github)。导出名是
PascalCase + Icon。
打包器只保留真正导入的图标(27 个图标实测 0 条冗余),且不产生任何 IPC 往返。
<Icon name> — 图标名来自运行期数据时
name 是运行期字符串,打包器无法静态分析,整张图标表(约 160KB)会作为
独立 chunk 懒加载——只在真的渲染 <Icon> 时才下载。图标名在编译期已知时请用
上面的 <Glyph>。
未登记的类名渲染 null(不留空档)并在开发构建 console.warn。
发现可用图标:listIcons
- 无需权限
- 返回内置表 + 当前颜色主题映射 + 本插件
contributes.iconTheme的全部可用键 - 做图标选择器或自检时以它为准,不要硬编码清单(内置表随启动器演进)
底层:getIconSvg
- 无需权限
- 查找顺序:本插件
contributes.iconTheme→ 当前颜色主题映射 → 启动器内置表;source字段标明命中来源(plugin/theme/builtin) - 未命中不再静默返回 null:控制台会给出告警,附
listIcons()与contributes.iconTheme的修复提示 - 仅当图标名来自运行期数据、又不方便用
<Icon>时才直接调它
图标名必须在启动器内置表内
fa-solid fa-* / fa-brands fa-* 写错会渲染成空白(历史上是静默的,问题能一路
发到用户侧)。现在两道闸门:
qomicex verify/qomicex lint扫描源码字面量与 manifesticon字段, 表外类名报 error 并给出相近的内置图标建议;<Icon>运行时未命中会console.warn。
contributes.iconTheme 声明私有图标(见
manifest — icon 字段规范),
或随包附一个 .svg 用 <img> 渲染。
语言切换事件(plugin:locale-changed)
启动器切换语言时向所有插件沙箱广播;插件收到后重拉getPluginStrings 并重渲染(与主题跟随对齐):
- 无需权限(被动事件)
下载与安装
download.addTask — 创建下载任务
创建下载任务,任务自动出现在启动器「下载中心」,进度由 SSE 推送驱动,可取消。- 权限:
download:manage - 返回:
{ taskId, status, targetPath } - 后端复用
DownloadSessionManager(type"resource"),与 mod 下载同一条线
download.progress — 查询下载进度
- 权限:
download:manage taskId不存在返回null
download.list — 列出全部下载会话
- 权限:
download:manage - 返回:
download.progress同结构的快照数组
download.cancel — 取消下载
- 权限:
download:manage
download.addBatch — 批量下载(一张卡片)
一次调用下载多个文件,但只占下载中心一张卡片——下载几百个 mod 不会刷屏。底层与安装管线同一套下载器(并发、同目标去重、镜像兜底、.part 原子改名)。
- 权限:
download:manage - 返回:
{ taskId, batchId, totalFiles } - 下载在后台进行,完成状态用
download.batchStatus(batchId)轮询 - 镜像兜底自动生效:Modrinth/CF 文件挂 MCIM、Forge/NeoForge maven 挂 ForgeCDN/BMCLAPI、原版库挂 BMCLAPI——插件传官方 URL 即可
download.batchStatus — 批量下载批次状态
- 权限:
download:manage - 轮询建议间隔 800ms~1s;
failed时error带首个失败原因
download.registerInstall — 在下载中心登记安装任务
仅在前端下载中心登记一个「游戏安装」任务(type'game'),不创建真实下载。适合插件发起安装后让用户能在下载中心看到进度。
- 权限:
instance:write
插件自报任务(分步进度)
插件自己编排的长流程(解析 → 下载 → 安装 → 写配置)用task.* 上报:任务进 InstallTracker,自动出现在下载中心,与内置安装任务同一套分步卡片渲染。
task.register — 注册分步任务
- 权限:
download:manage weight只影响合成总进度的占比(内部按 Σweight 归一化);label为卡片上的步骤文案(插件自带,无需改启动器 i18n)- 同
taskId已有非终态任务时抛TASK_BUSY(409)
task.update — 推进步骤/状态
- 权限:
download:manage - 所有字段可选,只更新提供的部分;
status传终态(completed/failed/cancelled)会被 400 拒绝——终态必须走task.complete
task.complete — 任务收尾
- 权限:
download:manage - 任务上有
blockedFiles(受限文件数)时成功收尾会转为awaiting-files而非 completed(提醒用户补文件)
task.progress — 查询任务进度
- 权限:
download:manage
curseforge.files — CurseForge 元数据代理
把 CurseForge 的projectID:fileID 批量换成下载直链。API key 始终留在启动器进程内,插件拿不到——这是插件生态里唯一的 CF 查询通道。
- 权限:
network:fetch - 单次最多 20000 个文件(
CF_TOO_MANY_FILES)
文件系统扩展
readArchiveEntry — 读 zip 内单个条目(授权制)
不解压整包,直接读压缩包里一个条目的文本。模组/整合包的元数据都在包里(fabric.mod.json、META-INF/mods.toml、modrinth.index.json),这是读它们的最快路径。
- 权限:
filesystem:read,且压缩包路径需逐路径授权(与readText同一套授权流程,首次弹确认) maxBytes缺省 4 MiB、上限 32 MiB,超限报ARCHIVE_ENTRY_TOO_LARGE- 仅支持 zip 系(jar/mrpack/litemod 均为 zip 容器)
renamePath — 移动/重命名(授权制)
- 权限:
filesystem:write,源与目标两个路径都需授权(一次确认同时授两端) - 支持目录;目标父目录自动创建
- 没有「移动」时插件只能 readBytes + writeBytes + deleteFile 三倍搬运大文件——把 client-only 模组隔离到别的目录这类操作就该用它
modpack.install — 一键安装整合包
直接安装整合包,走启动器正常整合包安装流程(与前端「整合包」页同一管线):解析来源 → 创建实例 → 下载安装。- 权限:
instance:write - 走
POST /api/modpack/install-direct,复用ModpackService(ParseFileAsync/ResolveOnlineAsync/InstallAsync)与InstallTracker - 返回:
{ instanceId },安装异步进行(进度在下载中心 / SSE 查看) - 错误码:
MODPACK_NAME_REQUIRED、MODPACK_GAME_DIR_REQUIRED、MODPACK_FILE_NOT_FOUND、MODPACK_SOURCE_REQUIRED、MODPACK_SOURCE_INVALID - 需要下载中心显示安装进度时,可配合
download.registerInstall登记
整合包一键开服(参考实现)
「把整合包装成可运行的 Minecraft 服务端」没有专用端点——启动器只提供通用能力,流程由插件用上面这些 API 编排。官方插件top.qomicex.server-manager(src/api/setup.js)是完整范例,管线与要点:
- 解析包:
readArchiveEntry读包内modrinth.index.json(Modrinth)或manifest.json(CurseForge),得到 MC 版本 / 加载器 / 文件清单。服务端侧按env.server != "unsupported"过滤(与客户端的env.client == "required"不同)。 - 装运行时(
download.addBatch+process.spawn):- vanilla:
proxyFetch拿版本清单 → 下载server.jar(download.addBatch自动挂 BMCLAPI 兜底) - fabric:fabric meta → 下载 server-launcher jar
- forge / neoforge:下载官方安装器 → 先预填
libraries/(用readArchiveEntry读安装器内嵌install_profile.json/version.json里的库清单,经download.addBatch并发下载,NeoForge maven 因此拿到 ForgeCDN 兜底)→ 再process.spawn执行java -jar installer --installServer,轮询process.alive等退出码。安装器对已存在且 sha1 匹配的文件直接跳过,--mirror参数对 neoforged.net 无效(实测)
- vanilla:
- 下载 mods:Modrinth 清单自带直链;CurseForge 经
curseforge.files换直链,取不到直链(作者关闭分发)的记入{dir}/.qml-server/blocked-mods.json,进度经task.update上报 - 释放 overrides:
extractArchive(packPath, dir, { prefix: 'overrides', stripPrefix: true }) - 隔离 client-only:
listDir扫mods/*.jar,readArchiveEntry读fabric.mod.json的environment或(neo)forge.mods.toml的clientSideOnly,命中的renamePath进{dir}/mods-client-only/(不删,可恢复)。注意:元数据没声明 side 的漏标 mod 无法静态识别,只能靠启动崩溃后手动隔离 - 写基础配置:
writeText写eula.txt(false,同意流程由用户负责)/server.properties/user_jvm_args.txt;已存在的文件不覆盖(overrides 可能带正式配置) - 写启动清单:
{dir}/.qml-server/launch.json(readText读取需该目录授权):
mode=argfile:java @<windowsArgfile 或 unixArgfile>(按平台选);mode=jar:java -jar <jar>- 服务端务必带
nogui:GUI 会接管控制台输出且不读 stdin 管道(stop 命令失效) - 进度全程经
task.register/task.update/task.complete上报,下载中心可看分步卡片
错误处理
所有 API 调用失败会 reject,错误信息形如:Permission denied: requires xxx— manifest 权限未包含Backend error: 404—callBackend端点不存在Proxy failed: 400—proxyFetch参数错误(含 SSRF 拦截)插件 xxx 未提供方法 yyy(可能未安装或未激活)—callPlugin目标不可用