新建存档,删掉里面的内容,然后替换?谁教你这样做的!明明只需要运行mngmc!
——HTMonkeyG
《我的世界》(网易 PC 版)存档管理器:以全屏终端界面列出、导入、导出游戏数据目录中的存档,并在导出时将网易版存档的 XOR 加密 LevelDB 解密、导入时还原(见加密数据库)。
界面基于 @earendil-works/pi-tui,读取 level.dat 使用 parsenbt-js,MCBE NBT 模板来自 project-mirror-registry。
需要 Node 22.19 或更高版本。
npm i @htmonkeyg/manage-mc运行输入:
mngmc存档列表(主界面):
| 按键 | 操作 |
|---|---|
Enter |
打开所选存档的详情 |
i |
导入存档 |
e |
导出所选存档 |
E |
导出所有存在本地数据的存档 |
p |
登记所选存档 |
r |
从磁盘重新读取 |
s |
设置 |
Ctrl+C |
退出 |
其他界面:
| 界面 | 按键 | 操作 |
|---|---|---|
| 存档详情 | ↑ ↓ Home End / PgUp PgDn |
滚动面板 / 按页滚动;e 导出,b 返回,p 登记(仅未注册的存档),x 危险区 |
| 危险区 | ↑ ↓ / Enter |
选择操作 / 执行;每项操作都需输入当场生成的 4 位验证码 |
| 导入向导 | Enter |
提交输入框中的来源路径 |
| 导出向导 | Enter |
提交输入框中的目标目录 |
| 设置 | ↑ ↓ / Enter 空格 / r |
选择 / 切换取值 / 更换游戏数据目录 |
| 账号选择器 | 空格 / a / Enter |
勾选账号 / 手动添加账号 / 继续 |
| 对话框 | Enter y / n |
确认 / 取消 |
Esc 与 Ctrl+C:在存档列表(主界面)上 Ctrl+C 退出,且这是唯一的退出方式;在其他界面上二者等价,均为取消——返回上一级、关闭对话框,或中止正在进行的导入导出。中止后暂存目录、暂存包与已发布的内容会一并回滚。若界面正在执行不可中断的操作,取消被忽略并给出原因,任务不会转入后台。
每行显示名称与状态,其后依次为最近游玩时间、数据大小、状态备注与 level_id;检测到异常时名称后附 ⚠。
时间取自 level.dat 的 LastPlayed;没有世界数据的条目(如在线存档)退回到记录中账号时间戳的最大值,该值不精确,详情页会标注。数据大小在后台逐个统计,完成后行内刷新。刷新结束后状态栏给出各状态的条目数。
| 排序 | 行为 |
|---|---|
| 默认排序 | 按状态分组(正常、未注册、在线、数据缺失、记录损坏),组内按名称 |
| 按时间排序 | 最近游玩的在前,无时间的条目排在末尾 |
| 按名称排序 | 仅按名称 |
- 基本信息:名称、
level_id、id 形态、状态、世界目录、注册表文件、大小与文件数、最近游玩。 - 数据库:
db/的加密状态、加密与明文文件数、MANIFEST 文件名;已加密时给出推断出的密钥及其校验结果,无法推断时说明原因。 - 注册表记录:记录中的各字段,按客户端顺序。
- level.dat:
LevelName、游戏模式、最后游玩、随机种子与各版本号;level.dat与levelname.txt的名称不一致时提示。 - 账号:记录声明的账号数、本机存在目录的账号数、各账号的登记时间与目录是否存在、目录存在但记录未声明的账号。
- 异常:记录无法解析、
record.level_id与文件名不符、record.path指向别处。
设置保存在 ~/.manage-mc.json。
| 设置项 | 取值 | 说明 |
|---|---|---|
| 游戏数据目录 | — | 按 r 更换 |
| 导出格式 | 文件夹 / zip 压缩包 | zip 在内存中组装,超大存档建议用文件夹 |
| 存档列表排序 | 默认排序 / 按时间排序 / 按名称排序 | 见上表 |
| 导出时解密数据库 | 否 / 是 | 见加密数据库 |
| 收集孤立账号目录 | 否 / 是 | 导出时一并收集记录未声明、但目录名匹配的账号目录 |
| 同名世界冲突处理 | 导入为副本 / 替换现有 / 合并账号目录 | 导入时目标已存在同名世界或记录的处理方式 |
| 配置文件 | — | 显示配置文件路径 |
Important
由于网易版Minecraft优先使用游戏内存而非磁盘中的存档元数据,因此在使用替换或导入功能后,请务必先重启游戏刷新存档状态,再进行游玩;否则可能导致区块生成错乱或游戏规则失效。
一个典型的例子是,在新建存档->替换存档后直接进行游玩,会导致保持开启命令方块计划刻更新全部失效,且默认游戏模式、命令方块输出等均被重置。
来源按内容识别,不依赖扩展名:
- 本工具导出的包(含
manifest.json) - 单个存档目录(含
level.dat) minecraftWorlds目录,或完整游戏数据目录- 仅含注册表的
storage/storge目录 - 上述任意一种的 zip 压缩包
路径可粘贴或直接拖入终端。zip 逐条解包并校验,越界的条目跳过。包内清单所给的路径同样限制在包内,越界者被忽略并给出提示。
记录在导入时被重写而非照抄:path 按目标安装位置重新计算,不再指向来源机器。记录来自游戏时未知字段原样保留,缺失或不存在时按客户端所需字段合成一条。
目标已存在同名世界或记录时,按设置的冲突策略处理:默认分配新的 level_id 导入为副本;替换现有会覆盖目标世界;合并账号目录仅在目标既无世界目录也无记录时才并入已有账号目录,否则退回复制。存档目录名不能用作路径段时同样分配新 id。
导入前选择记录应关联的账号,结果以 账号: 时间戳 写入每条记录的 user_ids。候选包括当前账号(storage/stream/users/last_user_id,默认选中)、来源记录声明的账号、来源目录中的账号与本机已有的账号,每行标明来源,也可手动输入。来源记录中已有的时间戳原样保留,新增账号取当前时间,全部不选则写入空的 user_ids。本次选择覆盖本批全部世界。
导入只写记录,不创建账号目录——客户端在首次启动时自行创建。因此以新 id 重新导入不会更新 users/<uid>/last_play_data,即客户端“继续上次存档”的指针;导入会报告受影响的账号,客户端对这些账号退回到存档列表。
预检在写入任何字节之前完成,任一项不通过则整个导入被拒绝且目标不被触及:缺少 minecraftWorlds、目录不可写、磁盘空间不足、目标路径越出游戏根目录或经过链接、文件名为 Windows 保留名、名称或路径超长、客户端仍持有目标世界(db/LOCK 存在)。
执行时每个世界先写入暂存目录并校验字节数与文件数,再以单次重命名发布,最后写记录。记录最后写入,因此中断只会留下一个未注册的目录,可用 p 登记修复;替换已有世界时若失败则还原原世界。
导出产生一个不完整的游戏根目录,可手动查看,也可再次导入:
<name>/
manifest.json 包内各世界的说明、校对信息与解密记录
README.txt 包结构说明
minecraftWorlds/<level_id>/ 世界数据,逐字节复制
storage/stream/resource_management/world_records/<level_id>.json
storage/stream/users/<uid>/<level_id>/ 各账号目录
包名自动生成:单个世界为 <名称>_<level_id>_<时间戳>,多个世界为 <数据目录名>_all_<时间戳>。目标已存在时导出中止,不覆盖。格式取自设置,向导中不单独选择。
只有注册表条目、没有本地数据的世界同样会导出其记录,这是把在线存档的条目带到另一台机器的唯一方式;未注册的目录则合成一条记录。账号目录只收集目录名与该世界 id 相同的部分,可在设置中一并收集记录未声明的目录。
记录中的 path 仍指向导出它的机器,请用本工具导入,不要手动复制到位。
zip 由 adm-zip 在内存中组装,包体超过 512 MB 时给出提示。
原始逻辑见 HTMonkeyG/XOR-MC-Archive-Decrypt。
客户端把存档的 LevelDB 放在 db/ 下,其中部分文件经 XOR 加密:文件以 32 位魔数 80 1D 30 01 开头,正文与一个 8 字节周期密钥异或。客户端自身使用的密钥为 ASCII 字符串 88329851,存档也可能携带自定义密钥。
导出时解密,导入时重新加密。
- 密钥是推断而非假定:LevelDB 的
CURRENT文件内容即 MANIFEST 的文件名,而文件名本身从不加密,二者构成一对已知明文,异或即得密钥。结果还需通过两项校验——密钥须在整个文件上按预期周期重复,且解密后的表须复原 LevelDB 表尾标记。 - 推断出的密钥及其实际覆盖的文件清单写入包的
manifest.json的xor字段,并给出 hex 与 ASCII 两种形式。 - 导入时按该清单用默认密钥重新加密,因为客户端使用的就是它,故重新导入的存档与导出前逐字节一致。以清单为准而非按文件名规则:客户端的
.log文件不加密,加密它会破坏数据库。 - 无法推断密钥不会中断导出,数据库保持加密,原因记入警告。
- 本来就是明文的数据库在导入导出两个方向都不被改动。在设置中关闭该功能则
db/完全原样复制。
存档详情页按 x 进入危险区。三个操作都会改动或销毁需要花时间重建的数据,因此每一项都要输入当场随机生成的 4 位验证码才能执行——验证码每次不同,无法凭习惯按下去。
| 操作 | 影响范围 |
|---|---|
| 替换存档 | 从文件夹、zip 或导出包替换该存档,保留现有 level_id;世界目录、数据库与注册表记录被整体覆盖 |
| 删除账号记录 | 只从注册表记录的 user_ids 中移除所选账号;世界目录、数据库与账号目录原样保留,账号可以再添加回来 |
| 删除存档 | 删除世界目录、数据库、注册表记录与该存档的全部账号目录 |
替换复用导入器的「同名替换」路径,只是目标 id 由用户指定而不是由来源决定,因此语义与导入时的替换完全一致。
删除采用先移出、再擦除的方式:先把世界目录、记录与账号目录重命名到根目录下的 .manage-mc-trash/,此时客户端已经看不到该存档,然后才删除暂存内容。中途崩溃或删除暂存失败都不会留下半个存档——世界已经不在游戏视野里,字节仍可在暂存目录中找回,报告会给出路径。所有路径在移动之前都会先确认位于游戏根目录之内。
存档目录存在、注册表条目也在,并不代表它可用:db/CURRENT 可能在复制过程中丢失。因此会直接读取存档目录,检查客户端真正需要的文件。
必需文件缺失即为错误(客户端无法打开该存档):
| 检查 | 说明 |
|---|---|
level.dat |
缺失、为空、无法解析,或大小异常 |
db/ |
缺失或不是目录 |
db/CURRENT |
缺失 |
db/MANIFEST-* |
缺失 |
CURRENT 指向的清单 |
CURRENT 记录着数据库正在使用的清单名;若该文件不存在,会明确报出 |
以下为警告(存档仍可打开,但有缺失):levelname.txt、level.dat_old、以及任何 .ldb 表文件。
db/CURRENT 常是加密的,此时会像导出那样推断密钥后再读取它指向的清单名,因此加密存档也能核对。
详情页底部的键位提示与主界面一样常驻。后台读取(level.dat、大小、数据库、完整性)是逐项独立进行的:某一项失败只会记录到面板的「读取问题」一节,不会中断其余检查,也不会占用底部那行提示。
详情页的「完整性」一节用颜色直接表达结果,不再另列「有/无」:
完整性
结论 缺少或损坏 1 项必需文件
必需文件 level.dat · db/ · CURRENT · MANIFEST-*
数据库内容 .ldb: 4 · .log: 1
! 缺少 db 文件夹,存档没有方块数据
- 必需文件的名字粗体绿表示存在,粗体红表示缺失。
.ldb与.log是数量而非存在性,因此统一为粗体白的文件名: 数量;数量为 0 时只在下方的警告里说明,保持这一行中性。MANIFEST-*按类型着色;实际清单名在下一行「CURRENT 指向」中给出。
检测结果出现在:存档列表(损坏项标记 ⚠ 并在说明中写明原因)、详情页的「完整性」一节,以及导入与导出时——导入损坏的来源、导出损坏的存档都会给出警告,并说明导出的包同样是损坏的。
游戏把 world_records 当作注册表、minecraftWorlds 当作数据,二者不必一致。
| 状态 | 含义 |
|---|---|
| 正常 (registered) | 目录与注册表条目均存在 |
| 未注册 (unregistered) | 目录存在但无注册表条目,写入条目后客户端才会列出它;按 p 登记 |
| 在线 (online) | 市场或租赁存档,本地只有注册表条目 |
| 数据缺失 (dangling) | 注册表条目存在但目录已消失,从磁盘删除存档时常见 |
| 记录损坏 (error) | 注册表条目无法解析 |
level.dat只读,从不写入。record.name与level.dat的LevelName用途不同,有意不做同步,仅在二者都无名称时才取LevelName作为回退。- 记录引用的附加包与资源包存放在世界目录之外(
resource_management/addon_records、addon_location),不会被导出带走,跨机器迁移的存档可能缺失它们。 - 零售客户端的数据目录拼写为
storge,文档写作storage,两种拼写都会被探测,同时存在时优先选择真正存放world_records的那个。 - 界面不提供删除功能,删除存档、记录或账号目录需在文件系统中手动进行。
src/
main.js 入口
config.js ~/.manage-mc.json
os/
paths.js 游戏目录解析(storage 与 storge)、路径安全
fsx.js 目录遍历、分阶段复制、原子写入
pack.js zip 打包与解包
leveldat.js 只读解析 level.dat
xorenc.js db 的 XOR 加解密与密钥推断
detect.js 导入来源分类
importer.js 导入的规划、预检与执行
exporter.js 导出的规划与执行
records/
levelid.js level id 校验、生成与冲突探测
record.js 注册表条目的读写
schema.js 导入存档的记录字段规则
registry.js 目录/注册表外连接与存档状态
integrity.js 存档必需文件的完整性检测
removal.js 存档删除与账号记录移除
users.js 各账号目录的规划
ui/
app.js 终端外壳、界面栈、对话框
theme.js 配色与文本宽度测量
components/ 头部、状态栏、信息面板、对话框、账号选择器
screens/ 初始化、存档列表、详情、导入、导出、设置