自定义主页开发文档
1. 主题令牌总览
启动器与官网统一使用一套 CSS 自定义属性(design tokens)。:root 为浅色,[data-theme='dark'] 为深色;两侧必须成对提供同名变量,切换主题时只需切换 data-theme,无需改动任何组件样式。
文字 --text-*
| 令牌 | 暗色值 | 浅色值 | 用途 |
|---|---|---|---|
--text-primary | rgba(255,255,255,0.92) | rgba(0,0,0,0.9) | 正文、标题等主要文字 |
--text-secondary | rgba(255,255,255,0.62) | rgba(0,0,0,0.58) | 次要说明、标签、注释 |
--text-tertiary | rgba(255,255,255,0.4) | rgba(0,0,0,0.38) | 占位符、禁用态、极弱信息 |
玻璃材质 --glass-*
| 令牌 | 暗色值 | 浅色值 | 用途 |
|---|---|---|---|
--glass-bg | rgba(28,28,36,0.5) | rgba(255,255,255,0.52) | 标准玻璃卡片底色(配 blur(36px) saturate(180%)) |
--glass-bg-strong | rgba(32,32,42,0.62) | rgba(255,255,255,0.64) | 强调层 / 首屏 hero / 不支持磨砂时的回退底色 |
--glass-bg-soft | rgba(24,24,32,0.34) | rgba(255,255,255,0.34) | 轻量玻璃层(配 blur(24px)) |
--glass-border | rgba(255,255,255,0.14) | rgba(255,255,255,0.6) | 玻璃边缘描边 |
--glass-highlight | rgba(255,255,255,0.2) | rgba(255,255,255,0.8) | 顶部高光渐变起点(环境光反射) |
--glass-shadow | 0 24px 60px -24px rgba(0,0,0,0.65) | 0 24px 60px -24px rgba(15,23,42,0.35) | 玻璃悬空投影 |
填充与状态色 --fill-*
| 令牌 | 暗色值 | 浅色值 | 用途 |
|---|---|---|---|
--fill-primary | #0a84ff | 主按钮、选中态、焦点描边(明暗共用,iOS 蓝) | |
--fill-primary-hover | #0a78f0 | 主按钮 hover | |
--fill-secondary | rgba(255,255,255,0.08) | rgba(0,0,0,0.06) | 次要按钮 / 输入框 / 列表行底色 |
--fill-secondary-hover | rgba(255,255,255,0.14) | rgba(0,0,0,0.1) | 次要按钮 / 列表行 hover |
--fill-danger | #ff453a | 危险操作(删除 / 拒绝)与错误文案 | |
--fill-success | #30d158 | 成功提示 / 通过状态 | |
分隔与遮罩
| 令牌 | 暗色值 | 浅色值 | 用途 |
|---|---|---|---|
--chip-bg | rgba(255,255,255,0.08) | rgba(0,0,0,0.05) | 标签 / 徽章 / 分段控件底色 |
--divider | rgba(255,255,255,0.1) | rgba(0,0,0,0.08) | 细分隔线、输入框描边 |
--scrim | rgba(0,0,0,0.55) | rgba(0,0,0,0.4) | 弹窗 / 抽屉背后的遮罩层 |
补充:另有 --surface-flat、--bg-flat 两个纯色表面令牌,供「极简(二维化)」模式关闭玻璃材质时使用;自定义主题可按需覆盖,不影响本页其余令牌。
2. 背景预设与光斑规范
背景由「三色线性渐变」(--bg-1/2/3)+「三个漂移光斑(blob)」构成,由 <html data-bg="…"> 切换预设。每个预设都必须同时给出暗色与浅色两套值,避免浅色主题配深色底。
渐变变量
| 预设 | 暗色 --bg-1/2/3 | 浅色 --bg-1/2/3 |
|---|---|---|
midnight(默认) | #04060f → #0b1330 → #10101c | #eef1fa → #e8ecf8 → #f2f0fb |
sunset | #2a0a14 → #7a2a1e → #d4762a | #fff4ec → #ffe9d9 → #ffe2cf |
forest | #04120f → #0a2e24 → #144d3a | #eefaf3 → #e4f6ec → #eef9e8 |
rose | #2a0a1c → #6b1740 → #c94d6e | #fff0f6 → #ffe6f0 → #fbeafc |
mono | #0d0d10 → #1c1c22 → #2a2a30 | #f4f4f6 → #ededf0 → #e6e6ea |
.app-background {
position: fixed; inset: 0; z-index: -1; overflow: hidden;
/* 暗色 160deg;浅色 165deg */
background: linear-gradient(160deg, var(--bg-1) 0%, var(--bg-2) 45%, var(--bg-3) 100%);
}
光斑(blob)规范
| 光斑 | 尺寸 | 定位 | 动画 |
|---|---|---|---|
.blob-1 | 52vw × 52vw | left:-14vw; top:-16vw | drift1 26s ease-in-out infinite alternate |
.blob-2 | 44vw × 44vw | right:-12vw; top:-8vw | drift2 32s ease-in-out infinite alternate |
.blob-3 | 40vw × 40vw | left:24vw; bottom:-20vw | drift3 38s ease-in-out infinite alternate |
通用样式:position:absolute; border-radius:50%; filter:blur(90px); will-change:transform;,不透明度由 --blob-opacity 控制(暗色 0.4 / 浅色 0.55)。关键帧终态位移/缩放为:drift1 translate3d(10vw,8vh,0) scale(1.18)、drift2 translate3d(-8vw,12vh,0) scale(0.96)、drift3 translate3d(-6vw,-10vh,0) scale(1.16)。
每个预设可覆盖三个光斑的 background(radial-gradient(circle, 主色 0%, 次色 60%+, transparent 72%~76%))以与背景基调协调。例如 sunset 为 #ff9f45→#ff375f / #ffd23f→#ff7a00 / #ff5c8a→#a855f7;forest 为 #34d399→#0ea5e9 / #22c55e→#14b8a6 / #84cc16→#10b981;rose 为 #fb7185→#f43f5e / #ec4899→#d946ef / #f472b6→#a855f7;mono 为 #64748b→#475569 / #94a3b8→#64748b / #cbd5e1→#94a3b8;默认 midnight 为 #4f7cff→#7a5cff / #ff5c8a→#c84dff / #2fd0c0→#2f7bff。
3. 如何做一套自定义主题
最小令牌集合
一套可用主题至少需要覆盖以下令牌(其余可留空继承默认值):文字 --text-primary/secondary/tertiary;玻璃 --glass-bg、--glass-border;填充 --fill-primary、--fill-secondary;分隔 --divider。背景预设的 --bg-1/2/3 决定整体氛围。
明暗成对提供
同一套主题必须同时提供 :root(浅色)与 [data-theme='dark'](深色)两组值,命名一一对应。切勿只做单侧,否则切换模式会出现低对比度或白底白字。
对比度与命名建议
- 正文文字与所在背景的对比度建议 ≥ 4.5:1,次要文字 ≥ 3:1。
- 玻璃底色保持半透明(
alpha0.34 ~ 0.64),过实会失去 Liquid Glass 质感,过虚则看不清内容。 --fill-primary用于主操作,务必与白色文字形成足够对比(深色系主题可沿用 iOS 蓝或换成更亮的强调色)。- 令牌命名沿用既有前缀(
--text-/--glass-/--fill-),不要自造新前缀,以保证组件样式可复用。
减弱动效
当用户开启「减弱动效」(prefers-reduced-motion: reduce 或 <html data-reduced-motion>)时,请关闭光斑漂移与入场动画,并取消按钮缩放:
@media (prefers-reduced-motion: reduce) {
.app-background .blob { animation: none !important; }
.hero, .timeline { animation: none !important; }
.btn { transition: opacity 160ms ease; }
.btn:active { transform: none; }
}
同理,prefers-reduced-transparency: reduce 时应关闭 backdrop-filter 并回退到 --glass-bg-strong 实色。
4. 主页脚本如何消费主题
主页脚本运行在宿主注入的 <iframe> 沙箱中。宿主会通过白名单 API hc.theme.get() 暴露当前主题,并把主题令牌以 CSS 变量注入到 iframe 的根元素上,脚本直接使用即可自动适配明暗与背景预设。
hc.theme.get() 返回字段
| 字段 | 类型 | 含义 |
|---|---|---|
mode | 'light' | 'dark' | 当前实际生效的明暗模式(已根据偏好与系统解析后的结果) |
setting | 'light' | 'dark' | 'system' | 用户在设置中选择的主题偏好;system 表示跟随系统 |
accentColor | string | 用户自定义主题色(十六进制,如 #0a84ff) |
background | string | 背景预设键:midnight / sunset / forest / rose / mono |
reducedMotion | boolean | 用户是否启用「减弱动效」;为 true 时脚本应关闭装饰性动画 |
debug | boolean | 启动器是否处于 Debug 模式(该模式下才会推送运行日志) |
宿主注入 iframe 的 CSS 变量
以下变量名必须与宿主完全一致,脚本应直接引用(它们由宿主写入 iframe 根元素):
| 变量 | 对应语义 |
|---|---|
--hc-text-primary | 主文字色 |
--hc-text-secondary | 次要文字色 |
--hc-text-tertiary | 极弱文字色 |
--hc-fill-primary | 主强调色 / 主按钮底色 |
--hc-fill-secondary | 次级填充(标签底、悬停底色) |
--hc-fill-danger | 危险操作色(删除 / 结束游戏) |
--hc-fill-success | 成功状态色 |
--hc-glass-bg | 玻璃卡片底色 |
--hc-glass-border | 玻璃边缘描边 |
--hc-divider | 分隔线色 |
--hc-scrim | 遮罩层色 |
--hc-accent | 用户自定义主题色(等价于 hc.theme.get().accentColor) |
:root {
color: var(--hc-text-primary);
background: transparent; /* 透出宿主的渐变背景 */
}
.card {
background: var(--hc-glass-bg);
border: 1px solid var(--hc-glass-border);
backdrop-filter: blur(36px) saturate(180%);
}
.btn { background: var(--hc-fill-primary); color: #fff; }
hr { border: 0; border-top: 1px solid var(--hc-divider); }
建议:主页脚本所有颜色优先取自 --hc-* 变量,不要写死明暗固定色;这样用户切换明暗模式或更换背景预设时无需刷新即可正确呈现。
形状与材质令牌(--hc-radius-* / --hc-surface-*)
除颜色外,宿主还把当前皮肤的圆角与材质也作为令牌注入。它们与颜色令牌一样由宿主写入 iframe 根元素,脚本用 var() 引用即可——用户换皮肤(Material / GitHub / WinUI3 / WinUI2 / Minecraft…)时无需刷新、无需改代码,圆角与磨砂/实色自动跟着变。
| 变量 | 含义 |
|---|---|
--hc-radius-control | 控件圆角(按钮 / 输入框 / 芯片 / 下拉) |
--hc-radius-card | 卡片与浮层圆角 |
--hc-radius-full | 全圆角(药丸 / 开关滑块 / 状态点) |
--hc-surface-material | 材质关键字:glass(磨砂)/ mica / acrylic / solid(实色)。可作为给 backdrop-filter 兜底的开关 |
--hc-surface-blur | 卡片背景的实际模糊值(含饱和),如 blur(36px) saturate(180%);实色皮肤为 none |
--hc-surface-border-width | 卡片描边宽度;立体倒角皮肤(如 Minecraft)为 2px,其余多为 1px 或 0px |
各皮肤的实际取值(脚本无需硬记,直接 var() 即可):
| 皮肤 | --hc-radius-control | --hc-radius-card | --hc-surface-material | --hc-surface-blur | --hc-surface-border-width |
|---|---|---|---|---|---|
| Material(默认) | 12px | 24px | glass | blur(36px) saturate(180%) | 1px |
| Win10 | 12px | 26px | solid | none | 1px |
| GitHub | 6px | 12px | solid | none | 1px |
| WinUI3 | 4px | 8px | mica | none | 1px |
| WinUI2 | 0px | 2px | acrylic | none | 1px |
| Minecraft | 0px | 0px | solid | none | 2px |
.card {
border-radius: var(--hc-radius-card, 22px); /* 跟随皮肤圆角 */
border: var(--hc-surface-border-width, 1px) solid var(--hc-glass-border);
background: var(--hc-glass-bg);
backdrop-filter: var(--hc-surface-blur, blur(36px) saturate(180%)); /* 实色皮肤为 none,天然失效 */
}
.btn { border-radius: var(--hc-radius-control, 12px); } /* 跟随控件圆角 */
.pill { border-radius: var(--hc-radius-full, 999px); }
兜底:宿主令牌缺失时沙箱会注入中性默认值(控件 12px / 卡片 24px / 全圆角 999px、实色材质),所以没写 var() 的旧脚本与新皮肤都不会「开天窗」,但也不会自适应——新脚本请一律走 var(--hc-…, 回退值)。
5. 主页脚本开发要点
单文件与元信息块
主页脚本是单个 HTML 文件,元信息块 <!--@hcpage … --> 位于文件开头(约定位置,实际可出现在任意位置),块内为一段 JSON:
<!--@hcpage
{
"id": "",
"name": "示例主页",
"author": "作者",
"version": "1.0.0",
"description": "一句话描述",
"minLauncher": "0.4.0"
}
-->
| 字段 | 说明 |
|---|---|
id | 编号。提交时留空或任意值均可;服务端会原字节替换为分配到的 HC-XXXXXX。若块内没有该字段,服务端会在 { 后插入。 |
name | 脚本名称(市场展示用) |
author | 作者名 |
version | 语义化版本号,如 1.0.0 |
description | 一句话描述 |
minLauncher | 要求的最低启动器版本(如 0.4.0),低于该版本的启动器将拒绝运行 |
API 白名单
沙箱只向脚本暴露以下经宿主审核的 window.hc 接口(全部为 Promise,10 秒无响应即超时)。白名单之外的宿主能力一律不可用;脚本不应假设存在其他 hc.* 接口。需要网络等额外能力时,必须由用户显式授权。
| 方法 | 返回 | 说明 |
|---|---|---|
hc.state() | object | 宿主最近一次推送的快照(与 hc.onUpdate 收到的对象相同,见下表) |
hc.onReady(fn) | 取消订阅函数 | SDK 就绪时触发一次 |
hc.onUpdate(fn) | 取消订阅函数 | 宿主快照变化时触发(内存 / 账号 / 版本 / 运行状态 / 主题) |
hc.onLog(fn) | 取消订阅函数 | 接收运行日志增量;仅 Debug 模式下有数据 |
hc.system.memory() | { total, used, free } | 系统内存(MB) |
hc.settings.memory.get() | number | 当前分配给游戏的内存(MB) |
hc.settings.memory.set(mb) | number | 修改分配内存,返回宿主夹取后的实际值 |
hc.account.current() | { name, id, avatarUrl, authType, siteName } | null | 当前账号;未登录为 null。siteName 为账号的站点归属名:第三方登录时自动获取(如 LittleSkin、馋猫认证中心,取不到时回落认证域名),非第三方账号为空串 |
hc.account.avatar() | string | 头像地址;无头像为空串 |
hc.versions.list() | InstalledVersion[] | 当前版本目录下的已安装实例列表,见下方字段表 |
hc.versions.selected() | string | 当前选中版本的 id |
hc.versions.select(id) | string | 切换选中版本(并持久化);id 不在当前版本目录的列表中时抛错 |
hc.versions.info() | { id, number, loader, loaderName } | null | 选中版本的加载器与版本号摘要;无版本为 null |
hc.versions.loader() | string | 选中版本的加载器显示名(Fabric / Quilt / Forge / NeoForge);无加载器返回「原版」,无选中版本同样返回「原版」 |
hc.versions.number() | string | 选中版本的版本号(Minecraft 版本,如 1.21.4);无选中版本返回空串 |
hc.versionDirs.list() | VersionDir[] | 版本目录列表(默认目录在最前),见下方字段表 |
hc.versionDirs.selected() | string | 当前生效的版本目录 id(默认目录为 'default') |
hc.versionDirs.select(id) | string | 切换版本目录(并持久化),返回切换后的 id;id 不在列表中时抛错。切换后 hc.versions.list() / 快照 versions 会收敛到新目录 |
hc.launcher.version() | string | 启动器版本号(如 0.4.11);取不到时为空串 |
hc.game.launch() | { ok, versionId } | 启动选中版本;无账号 / 无已安装版本时抛错 |
hc.game.stop() | { ok, stopped } | 结束当前运行的游戏;无运行中进程时 stopped 为 false |
hc.game.state() | { state, running, starting, busy, pid, versionId, debug } | 运行状态 |
hc.log.info(msg) / .warn(msg) / .error(msg) | null | 写入启动器日志(仅 Debug 模式可见,单条截断 4000 字符) |
hc.theme.get() | object | 见上一节「hc.theme.get() 返回字段」 |
hc.openExternal(url) | string | 用系统浏览器打开链接;需脚本已获联网授权,且仅允许 http(s) |
hc.model3d.skin() | { skinUrl, capeUrl, skinModel } | null | 当前账号的 3D 模型皮肤数据;离线账号 / 未设置皮肤为 null。见「3D 玩家模型容器」 |
hc.model3d.mount(target, options) | 控制器对象 | 在目标元素内挂载可自定义尺寸、自带旋转的 3D 玩家模型。见「3D 玩家模型容器」 |
快照字段(hc.state() / hc.onUpdate)
| 字段 | 类型 | 含义 |
|---|---|---|
memory | { total, used, free } | null | 系统内存(MB) |
allocatedMemory | number | 分配给游戏的内存(MB) |
account | { name, id, avatarUrl, authType, siteName } | null | 当前账号;siteName 为账号的站点归属名(第三方自动获取,非第三方为空串) |
versions | InstalledVersion[] | 当前版本目录下的已安装实例列表 |
selectedVersionId | string | 选中版本 id |
selectedVersion | { id, number, loader, loaderName } | null | 选中版本的加载器与版本号摘要 |
versionDirs | VersionDir[] | 版本目录列表(默认目录在最前) |
selectedVersionDirId | string | 当前生效的版本目录 id(默认目录为 'default') |
launcherVersion | string | 启动器版本号 |
launch | { state, running, starting, busy, pid, debug } | 运行状态与是否 Debug 模式 |
theme | { mode, setting, accentColor, background, reducedMotion } | 当前主题(强调色 / 背景预设 / 减弱动效) |
InstalledVersion 字段(hc.versions.list())
| 字段 | 类型 | 含义 |
|---|---|---|
id | string | 实例 id(选中版本使用此值) |
mcVersion | string | 对应的 Minecraft 版本号 |
loader | string | null | 加载器标识;原版为 null |
worlds | string[] | 存档目录名 |
servers | { name, address }[] | 服务器列表 |
VersionDir 字段(hc.versionDirs.list())
| 字段 | 类型 | 含义 |
|---|---|---|
id | string | 目录唯一 id(切换版本目录、写入 hc.versionDirs.select 时使用此值);默认目录固定为 'default' |
path | string | 目录绝对路径(默认目录即设置里的「默认版本列表目录」) |
alias | string | 用户设置的别名;未设置为空串 |
label | string | 展示名:优先别名,默认目录无别名时为「默认版本列表目录」,其余回退目录名;可直接用于渲染 |
isDefault | boolean | 是否为默认目录(默认目录不可删除) |
hc.versions.loader().then(function (loader) {
document.getElementById('loader').textContent = loader; // 「原版」 / 「Fabric」 …
});
hc.versions.number().then(function (ver) {
document.getElementById('ver').textContent = ver; // 「1.21.4」
});
hc.launcher.version().then(function (v) {
document.getElementById('launcher').textContent = '启动器 ' + v; // 「启动器 0.4.11」
});
// 或直接从快照取,无需再次请求:
hc.onUpdate(function (s) {
var info = s.selectedVersion; // null 表示没有已安装版本
if (info) console.log(info.loaderName, info.number);
console.log('启动器版本', s.launcherVersion);
});
版本列表与选中版本
版本(实例)列表由启动器扫描当前版本目录得到,脚本只能读取列表与切换选中项,不能安装或删除实例(安装请引导用户去「资源下载」页)。列表内容随当前版本目录收敛——切换版本目录后,hc.versions.list() 与快照 versions 都会变成新目录下的实例;详见下一节「版本目录」。
| 接口 | 行为 |
|---|---|
hc.versions.list() | 返回当前版本目录下全部已安装实例(InstalledVersion[],字段见上表),按实例 id 字典序排列,与内置页一致;空数组表示该目录尚未安装任何实例 |
hc.versions.selected() | 返回当前选中实例的 id;当前版本目录下没有任何已安装实例时返回空串 |
hc.versions.select(id) | 切换到指定实例并立即持久化,返回切换后的 id;id 不在当前版本目录的已安装列表中时抛错 版本不可用:<id> |
- 与内置界面共享:选中版本保存在启动器设置里,脚本调用
select后,内置「启动游戏」界面同样会切到该实例;反之亦然。 - 自动回落:若持久化的
id已不在当前版本目录的已安装列表中(例如切换了目录或实例被卸载),宿主会自动回落到列表第一项并写回设置,因此selected()不会返回一个不存在的id。 hc.versions.select('')一定失败(空串不在列表中),切换前请先用list()校验。
// 渲染版本下拉框,并让用户切换
hc.versions.list().then(function (list) {
var sel = document.getElementById('ver');
sel.innerHTML = list.map(function (v) {
return '<option value="' + v.id + '">' + v.mcVersion + ' · ' + (v.loader || '原版') + '</option>';
}).join('');
return hc.versions.selected();
}).then(function (current) {
document.getElementById('ver').value = current; // 与启动器保持一致
});
function onPick(id) {
hc.versions.select(id)
.then(function (next) { console.log('已切换选中版本:', next); })
.catch(function (e) { console.error(e.message); });
}
// 快照里已带列表与摘要,也可不调用接口:
hc.onUpdate(function (s) {
console.log('已安装', s.versions.length, '个实例,当前选中', s.selectedVersionId);
});
版本目录(多目录切换)
启动器支持多个版本目录:每个目录都是一份独立的「版本列表根目录」(其下各有自己的 versions/)。脚本可以通过 hc.versionDirs.* 列出这些目录并切换当前目录;切换后,hc.versions.list()、hc.versions.selected() 以及快照 versions / selectedVersionId 都会立即收敛到新目录,hc.versions.select() 也只能指定当前目录内的实例。
| 接口 | 行为 |
|---|---|
hc.versionDirs.list() | 返回版本目录列表(VersionDir[],字段见上表),默认目录始终在最前;默认目录的 id 固定为 'default' |
hc.versionDirs.selected() | 返回当前生效的版本目录 id |
hc.versionDirs.select(id) | 切换到指定版本目录并立即持久化,返回切换后的 id;id 不在列表中时抛错 版本目录不可用:<id>。切换后宿主会重新扫描新目录,versions 快照随之刷新 |
- 与内置界面共享:当前版本目录保存在启动器设置里,脚本调用
select后,内置「启动游戏」与「版本」界面同样切到该目录;反之亦然。 - 版本列表从属于目录:不存在「跨目录的全量版本列表」。要启动某个实例,需先切到它所在的版本目录,再通过
hc.versions.select(id)选中它。 - 只读能力:脚本不能新增 / 删除版本目录或修改别名(这些只能在「设置」与「版本」界面由用户操作),
hc.versionDirs.*仅提供列表与切换。 - 展示名:优先使用
label字段渲染(已按别名 / 默认名 / 目录名算好),需要更细的信息时再用path与alias。
// 渲染版本目录下拉框,并让用户切换
hc.versionDirs.list().then(function (dirs) {
var sel = document.getElementById('dir');
sel.innerHTML = dirs.map(function (d) {
return '<option value="' + d.id + '">' + d.label + '</option>';
}).join('');
return hc.versionDirs.selected();
}).then(function (current) {
document.getElementById('dir').value = current;
});
function onPickDir(id) {
// 切换目录后,版本列表会随快照更新到新目录
hc.versionDirs.select(id)
.then(function (next) { console.log('已切换版本目录:', next); })
.catch(function (e) { console.error(e.message); });
}
// 快照里同时带目录列表、当前目录与当前目录下的版本
hc.onUpdate(function (s) {
console.log('当前目录', s.selectedVersionDirId, '共', s.versionDirs.length, '个目录');
console.log('该目录下有', s.versions.length, '个实例');
});
启动与结束游戏
启动器对游戏进程只提供「启动」与「结束」两个动作,另有运行状态查询。
| 接口 | 返回 | 说明 |
|---|---|---|
hc.game.launch() | { ok, versionId } | 启动当前选中版本(使用的内存 / 当前版本目录 / Java 路径均取用户设置);见下方前置条件与注意事项 |
hc.game.stop() | { ok, stopped } | 结束当前运行中的游戏进程;没有运行中进程时返回 { ok: true, stopped: false },不报错,便于幂等调用 |
hc.game.state() | { state, running, starting, busy, pid, versionId, debug } | 运行状态;state 取值见下表 |
state | 含义 |
|---|---|
starting | 正在准备启动(校验 / 补齐资源等) |
downloading | 正在下载启动所需文件 |
launching | 已拉起进程,等待进入游戏 |
running | 游戏正在运行(running 为 true) |
exited | 游戏已退出 |
error | 启动或运行出错 |
null | 本次会话尚未启动过 |
- 前置条件:未登录账号时
launch()抛尚未登录账号,请先在「账号」页登录;没有任何已安装实例时抛没有已安装的游戏版本,请先在「资源下载」页安装。 - Java 检查:若所选实例要求的 Java 不兼容,启动器会先弹出「安装 Java」提示,此时
launch()会提前返回{ ok: true, versionId }但游戏并未开始。请以launch.state是否变为running作为「真的启动成功」的判据,不要只看返回值。 - 结束是强制终止:
stop()直接结束进程,游戏不会走正常的退出保存流程;请在界面上给出明确提示,避免玩家丢失进度。 - 状态推送:优先用
hc.onUpdate(function (s) { s.launch })被动接收变化(内容为{ state, running, starting, busy, pid, debug }),比轮询更省资源。
关于「暂停」:启动器不提供暂停 / 恢复游戏进程的能力(Minecraft 进程无法可靠挂起)。只有「启动」与「结束」两个动作;需要停止游戏的脚本请调用 hc.game.stop(),并注意它是强制终止。
// 启动按钮:按状态禁用 + 给出可读错误
function startGame() {
hc.game.launch()
.then(function () { /* 仅表示请求已受理,真正开跑见 s.launch.state === 'running' */ })
.catch(function (e) { alert(e.message); });
}
function stopGame() {
hc.game.stop().then(function (r) {
if (!r.stopped) console.log('当前没有运行中的游戏');
});
}
// 用状态驱动按钮
hc.onUpdate(function (s) {
var run = s.launch.running;
var starting = s.launch.starting;
document.getElementById('start').disabled = run || starting;
document.getElementById('stop').disabled = !run && !starting;
document.getElementById('state').textContent = s.launch.state || '未启动';
});
3D 玩家模型容器
宿主提供一个纯 CSS 3D 玩家模型容器:脚本可把它挂载到自己的任意元素上,并自定义尺寸比例;模型自带旋转——静止时缓慢自转,按住可像转盘一样拖动旋转到任意角度。模型在脚本自己的 DOM 内构建,不依赖任何外部 3D 库,也不访问宿主 DOM。
| 接口 | 返回 | 说明 |
|---|---|---|
hc.model3d.skin() | { skinUrl, capeUrl, skinModel } | null | 当前账号的皮肤贴图地址、披风贴图地址与模型类型('classic' | 'slim');离线账号或账号未设置皮肤时为 null |
hc.model3d.mount(target, options) | 控制器对象(同步返回) | 在 target(元素或选择器字符串)内挂载 3D 模型;options 见下表,返回控制器用于改尺寸 / 刷新 / 销毁 |
options 字段:
| 字段 | 类型 | 默认 | 含义 |
|---|---|---|---|
size | number | 200 | 模型容器边长(像素),决定整体比例;允许 40–1024,越界自动夹取 |
autoSpin | boolean | true | 是否自动缓慢自转(拖动时自动暂停,松手继续) |
draggable | boolean | true | 是否允许按住拖动旋转(俯仰限制 ±89°) |
showCape | boolean | true | 是否显示披风(账号有披风贴图时才可见) |
返回的控制器对象:
| 成员 | 说明 |
|---|---|
el | 模型根元素 |
setSize(n) | 就地改尺寸(返回夹取后的实际值),用于实现「缩放滑块」等 |
refresh() | 重新拉取皮肤数据并重建(账号切换 / 换肤后调用) |
destroy() | 销毁模型并从 DOM 移除 |
- 仅正版 / 第三方账号有皮肤:离线账号或账号未设置皮肤时
hc.model3d.skin()返回null,模型显示「暂无皮肤」占位。 - 皮肤贴图自动放行:皮肤 / 披风来自启动器自身使用的官方或认证站源,宿主会自动放行其 CSP
img-src并在运行时探针中豁免——脚本无需为此申请联网授权,也不会被判定为外链。 - 兼容现代与旧版皮肤:自动识别 64×64 现代皮肤(含细手臂 slim 与各部位外层)与 64×32 旧版皮肤,脚本无需处理。
- 尺寸自定义:初始用
options.size,运行期用setSize()调整,两者按同一套比例缩放,观感一致。
// 在 #skin 容器内挂一个 220px、自带旋转的 3D 模型(无皮肤时显示占位)
var model = hc.model3d.mount('#skin', { size: 220, showCape: true });
// 用滑块实时改大小(例如实现「模型缩放」)
document.getElementById('zoom').addEventListener('input', function (e) {
model.setSize(Number(e.target.value)); // 40 ~ 1024
});
// 想先判断是否有皮肤再决定布局:
hc.model3d.skin().then(function (skin) {
document.getElementById('tip').textContent = skin ? '按住拖动旋转' : '当前账号没有皮肤';
});
// 账号切换 / 换肤后刷新
hc.onUpdate(function () { model.refresh(); });
沙箱限制
- 无 Node:没有
require/process/fs等 Node 能力。 - 无同源:iframe 与宿主不同源,无法访问宿主 DOM、Cookie、本地存储。
- 默认无网络:默认禁止网络请求,需用户授权后才可发起。
- 仅可渲染单文件内的 HTML/CSS/JS,无法加载本地文件系统资源。
安全红线(一律拒绝运行)
含有以下任一行为的脚本将被直接拒绝,且账号/投稿可能被限制:
- 使用
eval执行动态字符串,或对代码进行混淆 / 伪装以规避审核; - 删除文件、格式化磁盘等破坏性命令;
- 下载可执行文件、压缩包或
.svg等潜在可执行内容。
编号与 SHA256 联网校验
服务端为脚本分配唯一编号(HC- + 6 位大写十六进制),并对注入编号后的最终文件计算 SHA256。启动器运行前会联网校验:
GET api.php?action=script_verify&id=HC-3F9A2C&sha256=<64位小写hex>
// 编号存在且哈希全等(大小写不敏感)
{ "ok": true, "verified": true, "script": { "id": "HC-3F9A2C", "name": "…", "author": "…", "version": "…" } }
// 编号不存在或哈希不匹配
{ "ok": true, "verified": false, "script": null }
注意:校验基于「注入编号后」的最终文件哈希,故请务必以服务端返回的已编号脚本为准(见下节)。
6. 投稿与审核流程
流程
- 在官网「投稿」区或启动器「主页 → 投稿」选择脚本文件,填写名称 / 作者 / 描述 / 版本、开发者邮箱与邮箱验证码,选择可见性;邮箱必须先点「发送验证码」并填入收到的 6 位数字才能真正提交(验证码 10 分钟内有效、单次使用);
- 服务端校验(≤512KB、
.html/.htm、含可解析的<!--@hcpage元信息块),分配编号并注入,重算 SHA256,保存为待审核记录; - 服务端静态安全检测:投稿时先按启动器同款规则扫一遍源码(见上节「安全红线」)。命中拒绝级规则的脚本会被直接退回(返回 400 并说明原因,不分配编号、不落盘);只是链接了外部服务的脚本允许投稿,但外链清单会随记录保存,供后台审核核对;
- 响应返回编号、SHA256、
risk(safe/warn)与外链清单,以及「已编号脚本」的content_base64,请保存该脚本用于本地与联网校验; - 后台人工审核:通过 或 拒绝,两者都可以填写备注;审核备注会随结果邮件一并发送给投稿时绑定的邮箱。待审核记录在后台可直接「下载」查看源码;
- 审核结束后系统立即向该邮箱发送结果邮件(含审核备注);通过时邮件里还附带一条永久管理链接,用于后续自助管理。公开脚本通过后上架主页市场,可被下载;私密脚本通过后仅保留记录与哈希,文件随即删除。
同一 IP 每小时最多投稿 5 次,超出将返回 429 限流提示;验证码另有「同邮箱 60 秒冷却 / 每小时 5 封」与「同 IP 每小时 10 封」的限制。
服务端只做静态文本检测,不会取回外链脚本正文(避免 SSRF 与慢请求);外链 .js 的深度核验由启动器在运行时完成。
开发者后台(管理链接)
审核结束后,系统会向投稿邮箱发送结果邮件,无论通过还是拒绝都会带上后台填写的审核备注。审核通过时,邮件里还会包含一条形如 manage.php?token=… 的永久管理链接。
- 妥善保存这条链接:它是唯一凭据,不依赖登录,也不必再经过后台;
- 凭它可以随时:下架 / 重新上架(立即生效,无需再审)、变更公开 · 私密、提交更新(需重新审核)、删除脚本;
- 变更公开 · 私密:改为私密会立即删除服务端脚本文件(不再托管),之后要重新公开必须走「提交更新」重新上传并等待审核;
- 提交更新:新版本先进入待审核,线上旧版本在此之前保持可用;审核通过后自动切换到新版本并删除旧文件,被拒则线上版本不受影响;
- 链接等同于密码,请勿转发给他人;管理链接只在「首次通过审核」那封邮件里给出(后续的更新审核结果邮件不再重复附上),请务必当时就保存好;若邮件丢失或链接失效,请联系站长为你「重置并重发管理链接」——重置后旧链接会立即失效。
脚本下载入口
网站与启动器都通过 script.php?id=<编号> 下载脚本:该入口以附件形式返回内容(浏览器不会把脚本当页面渲染),并顺带把该脚本的下载次数 +1。请勿直接把 scripts/ 下的文件当作网页地址分发。
该入口只对「已上架 + 公开」的脚本开放;下架、私密、待审核与被拒的脚本一律返回 403,即使拿到直链也无法下载。
公开 vs 私密
| 可见性 | 文件处理 | 适用场景 |
|---|---|---|
public 公开 | 文件长期保留在服务端(scripts/<编号>.html),审核通过后出现在主页市场可被下载 | 愿意分享给社区的脚本 |
private 私密 | 仅用于获取编号与联网校验哈希;审核后文件立即从服务端删除;审核通过后由你自行通过管理链接变更为私密时,同样会立即删除服务端文件 | 自用脚本,仅需编号与联网校验 |
私密投稿请务必保存服务端返回的「已编号脚本」(content_base64 解码后的文件)。文件删除后无法再从服务端取回,但记录与 SHA256 仍保留,可用于 script_verify 联网校验。
审核标准
- 元信息块完整、JSON 合法,
name/author/version等信息真实不误导; - 无上节「安全红线」所列行为(eval / 混淆、删除文件、下载可执行内容等);
- UI 使用主题令牌(
--hc-*)以适配明暗与背景预设,不写死对比度过低的颜色;卡片圆角与材质应使用--hc-radius-*/--hc-surface-*跟随当前皮肤(至少在写死固定圆角/模糊时要说明理由); - 不采集用户隐私、不诱导跳转外部站点、不含违规或侵权内容;
- 功能可正常加载运行,不与宿主沙箱限制冲突。