mc-tunnel
v0.2.3
Published
面向 Electron / Node.js 的 Minecraft 局域网联机模块:房间码一键组网,基于 EasyTier 的 P2P 隧道(Minecraft LAN tunnel)
Maintainers
Readme
mc-tunnel
面向 Electron / Node.js 的 Minecraft 局域网联机模块。
房主在游戏里「对局域网开放」,把 房间码 发给朋友;朋友输入房间码后, 游戏「多人游戏 → 局域网世界」里就会出现这条房间,双击即可进入。
EasyTier 以外部子进程方式调用完成 P2P 组网 (详见 合规说明)。
📖 完整使用文档(全部参数、事件、错误码、Electron 集成、排错表)见 docs/USAGE.md。 🔧 口令模式与中文房间码的源码级实现说明(位布局、算法、行号索引)见 docs/PASSPHRASE-ZH-CODE.md。
特性
| | |
|---|---|
| 零依赖 | 运行时只使用 Node 内置模块(http / dgram / net / child_process),不引入任何 npm 依赖 |
| 免管理员权限 | EasyTier 以 --no-tun 运行,不创建虚拟网卡 |
| 无需自建服务器 | 房间码自包含凭据,节点发现交给 EasyTier 公共节点;也支持完全离线直连 |
| 无需 mod | 通过伪造 Minecraft 局域网多播让原版客户端直接可见 |
| 安全 | 房间码含 CRC-12 校验;协调服务使用握手指纹 + 令牌鉴权(恒定时间比较) |
| 口令联机 | 用一段短语代替房间码:passphrase: '彩蛋口令',两端相同即可入同一房间(强度取决于口令) |
| 中文房间码 | 可选子入口 mc-tunnel/zh,把房间码显示成 7 个汉字(彩蛋玩法) |
| 双格式 | 同时产出 ESM 与 CJS,附带 .d.ts 类型声明 |
| 可观测 | 全流程 state / players / error 事件,失败时携带 EasyTier 日志尾部 |
安装
npm install mc-tunnel同时需要准备 EasyTier 的两个可执行文件(本包不打包它们,避免体积与许可问题):
easytier-core(.exe)
easytier-cli(.exe)放置位置三选一:
- Electron:
resources/easytier/(配合 electron-builder 的extraResources) - 任意目录 + 环境变量
MC_TUNNEL_EASYTIER=/path/to/dir - 代码里传
easyTier: { binDir: '/path/to/dir' }
快速开始
import { createHost, createGuest } from 'mc-tunnel';
// ---------- 房主 ----------
const host = await createHost({
mcPort: 25565, // 不传则自动监听局域网广播发现
playerName: 'Alice',
});
console.log('房间码:', host.roomCode); // 例如 2GJY-DYSK-8825-DZJY
host.on('player-join', (player) => console.log(`${player.name} 加入了`));
host.on('mc-closed', () => console.log('存档已关闭,房间失效'));
// ---------- 客人 ----------
const guest = await createGuest({
roomCode: '2GJY-DYSK-8825-DZJY',
playerName: 'Bob',
});
console.log('在游戏里连接:', guest.address); // 127.0.0.1:25565
// 此时「局域网世界」里已经出现房间,双击即可进入
// 退出时务必关闭,否则会残留 easytier-core 进程
await guest.close();
await host.close();口令模式(用一段短语代替房间码)
// 房主
const host = await createHost({ mcPort: 25565, passphrase: '彩蛋口令 演示' });
// 客人:口令相同即可(大小写、空白、全角不敏感)
const guest = await createGuest({ passphrase: ' 彩蛋口令 演示 ' });
// 口令也会派生出一个等价的标准房间码,UI 可二选一展示
import { roomCodeFromPassphrase } from 'mc-tunnel';
console.log(roomCodeFromPassphrase('彩蛋口令 演示'));口令强度取决于口令本身:弱口令等于把房间公开。需要严格安全时请用默认的随机房间码。
中文房间码(彩蛋,可选子入口)
import { toChineseRoomCode, parseChineseRoomCode } from 'mc-tunnel/zh';
const zh = toChineseRoomCode(host.roomCode); // 7 个汉字,例如 '朱上岸雪存违户'
const guest = await createGuest({ roomCode: parseChineseRoomCode(zh) });16 个 Base32 字符 ⇄ 7 个汉字 无损双向互转(2048⁷ = 2⁷⁷,正好等于版本 1 的有效位宽),
字符集取自 BIP39 简体中文词表(2048 个去重单字,标准且永不改动),支持繁简/全角/标点容错。
Electron 集成
import { app, BrowserWindow } from 'electron';
import { createHostInElectron, manageSessions } from 'mc-tunnel/electron';
const host = await createHostInElectron(app, { mcPort: 25565 });
// 应用退出时自动关闭会话,避免孤儿进程
manageSessions(app, host);electronDefaults() 会自动把:
- 设备标识放到
app.getPath('userData') - EasyTier 目录指向
process.resourcesPath/easytier
electron-builder 配置示例:
{
"extraResources": [
{ "from": "vendor/easytier", "to": "easytier" }
]
}⚠️ 如果使用 ASAR 打包,EasyTier 必须放在
extraResources(ASAR 内的可执行文件无法spawn)。
API
createHost(options?): Promise<TunnelHost>
| 参数 | 类型 | 说明 |
|---|---|---|
| roomCode | string | 指定房间码,不传则随机生成 |
| passphrase | string | 口令模式,与 roomCode 二选一 |
| mcPort | number | 指定端口;不传则监听局域网广播自动发现 |
| playerName | string | 昵称,默认 Host |
| motd | string | 局域网列表显示名 |
| coordPort | number | 协调服务端口,默认 25991(双方必须一致) |
| listeners | string[] | 额外监听地址,如 ["tcp://0.0.0.0:11010"] |
| peers | string[] | 直连对端,如 ["tcp://1.2.3.4:11010"] |
| scanTimeoutMs | number | 自动发现超时,默认 30000 |
| dataDir | string | 设备标识存储目录 |
| easyTier | EasyTierOptions | { binDir, publicNodes, p2pOnly } |
会话属性:roomCode mcPort coordPort virtualIp phase,方法:players() close()。
事件:state players player-join player-leave mc-closed error。
createGuest(options): Promise<TunnelGuest>
| 参数 | 类型 | 说明 |
|---|---|---|
| roomCode | string | 与 passphrase 二选一必填 |
| passphrase | string | 与 roomCode 二选一必填 |
| playerName | string | 昵称,默认 Guest |
| preferredLocalPort | number | 期望本地端口,默认与房主端口同号 |
| peers | string[] | 直连对端,可完全绕开公共节点 |
| timeoutMs | number | 建链总超时,默认 90000 |
| dataDir / easyTier | | 同上 |
会话属性:roomCode mcPort localPort address natDifficulty phase,
方法:hostPlayers() close()。
事件:state players server-status error。
工具函数
import {
generateRoomCode, // () => string
verifyRoomCode, // (code) => boolean
normalizeRoomCode, // (input) => string | null
parseRoomCode, // (code) => { code, networkName, networkSecret }
pingMc, // (port, host?, timeoutMs?) => Promise<McStatus | null>
isPortOpen, // (port, host?, timeoutMs?) => Promise<boolean>
resolveBinaries, // (binDir?) => { core, cli }
} from 'mc-tunnel';工作原理
房主 客人
──── ────
扫描局域网广播 ← 房间码解析出网络名/密钥
↓ ↓
EasyTier 固定虚拟 IP 10.144.144.1 EasyTier DHCP 拿 IP
↓ ↓
协调服务 0.0.0.0:25991 ← 端口映射 127.0.0.1:x → 10.144.144.1:25991
↓ ↓
提供 /v1/session 返回 MC 端口 HTTP 握手校验 + 取端口
↓ ↓
Minecraft 0.0.0.0:25565 ← 端口映射 TCP/UDP → 10.144.144.1:25565
↓
多播广播「局域网世界」房间码格式(自研)
16 个字符 × 5 bit = 80 bit:
[4 bit 版本][64 bit 随机种子][12 bit CRC]- 字符集:Crockford Base32(
0-9A-Z去掉I/L/O/U) - 展示:
XXXX-XXXX-XXXX-XXXX - 输入容错:小写、空格、
MT-前缀、I→1L→1O→0U→V自动纠正 - 网络名/密钥由种子经 HMAC-SHA256 域分离派生(
mc-tunnel/v1/*),房间码本身不含明文凭据
协调协议(自研)
HTTP/1.1 + JSON,跑在隧道内:
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /v1/hello | 否 | 返回握手指纹,客人据此确认对端身份 |
| GET | /v1/session | 是 | 返回房主 MC 端口与玩家列表 |
| POST | /v1/players | 是 | 玩家心跳 / 昵称上报 |
鉴权用房间码派生的令牌(x-mct-token),比较用 timingSafeEqual。
常见问题
为什么不能把 127.0.0.1 写进 peers?
EasyTier 连接对端时会绑定本地地址,回环地址会报 10049 AddrNotAvailable。
直连请使用真实网卡 IP(可参考 test/helpers.mjs 的 primaryIpv4())。
客人提前退出了房主仍在? 房主侧玩家列表 12 秒无心跳自动剔除,不阻塞后续加入。
端口冲突怎么办?
协调服务默认 25991,被占用会直接报错(不会静默换端口,否则客人找不到)。
Minecraft 映射端口优先与房主同号,被占用时自动改用随机端口,功能不受影响,
但依赖固定 UDP 端口的 mod(如 SimpleVoiceChat)可能需要手动指定 preferredLocalPort。
对称 NAT 连不上?
默认 p2pOnly: true,不走中继。若需要中继,请自建 EasyTier 公共节点并设置
easyTier: { p2pOnly: false, publicNodes: ['tcp://your-node:11010'] }。
支持哪些平台?
Node.js >= 18.17(使用内置 fetch 与 AbortSignal.timeout)。Windows / macOS / Linux 均可用。
开发
npm install # 国内镜像已写入 .npmrc
npm run build # 产出 dist/{esm,cjs,types}
npm test # node:test,无需额外框架端到端测试需要 EasyTier 二进制,放到 test/fixtures/bin/ 或设置
MC_TUNNEL_EASYTIER,缺失时自动跳过。
合规说明
本包为独立实现:
- 房间码编码、校验算法、协调协议、状态机均为本项目原创;
- 不含任何第三方项目的源代码、常量或协议格式;
- EasyTier 仅在运行时以独立子进程调用(
spawn),不做静态/动态链接, 也不修改其可执行文件。
因此:
- 你可以把本包用于闭源商业软件;
- 本包自身采用 MIT 许可,你可以自由修改、私有化(也可以把
license改成UNLICENSED); - 分发时请遵守 EasyTier 自身的许可要求,详见
THIRD_PARTY_NOTICES.md。
许可
MIT
