Warning
该项目几乎完全使用 DeepSeek V4 Pro 生成,使用材料:MeiBrowser,Reqable辅助
!全局替换某动漫游戏->Game
基于硬链接与全局 MD5 去重的游戏版本切换器——为某大厂动漫游戏提供秒级版本切换能力。(暂时只支持开放性大世界游戏)
源文件 (E:\Game\xxx.dat) ← 只读,绝不修改
│ 硬链接 (零拷贝)
▼
缓存 (cache/{MD5}) ← 全局去重,同 MD5 仅存一份
│ 硬链接 (零拷贝)
▼
输出 (E:\Game-Switcher\6.5_CN\) ← 逻辑视图,目录结构符合目标版本
- 绝对不可变源:从不修改源目录任何字节
- 全局 MD5 去重:不同版本/路径中相同内容的文件,物理存储仅一份
- 硬链接农场:输出目录是纯硬链接集合,零额外磁盘占用
| 功能 | 说明 |
|---|---|
| 同版本切换 | 本地 pkg_version 解析 → 纯硬链接(秒级) |
| 跨版本切换 | Sophon API 获取远程清单 → 缓存命中硬链接 → 差异文件 CDN 下载 |
| 版本差异诊断 | --diagnose 对比任意两版本的文件 MD5 差异 |
| 并行下载 | 8 线程并发下载 chunk,tqdm 进度条 |
| 未压缩 chunk 检测 | 自动识别 chunkSize == chunkSizeDecompressed 的原始数据 |
| 跨盘回退 | 硬链接失败自动 shutil.copy2 + Warning |
| 交互模式 | 无参数运行,逐步选择 版本 → 地区 → 语音包 |
GameHardLinkSwitcher/
├── switcher.py # CLI 入口(argparse + 交互模式)
├── parser.py # ManifestParser — 解析本地 pkg_version (NDJSON)
├── manager.py # ManifestManager — 清单加载/合并/枚举
├── cache.py # CacheManager — MD5 缓存池 + 文件锁
├── linker.py # LinkExecutor — 硬链接 + 跨盘回退复制
├── sophon.py # SophonClient — Sophon API (protobuf manifest)
├── dispatch.py # DispatchClient — GitHub hoyodata.json (NDJSON)
├── downloader.py # Downloader — CDN chunk 并行下载
├── utils.py # MD5 / 文件锁 / 格式化 / 常量
├── requirements.txt
├── cache/ # 运行时创建 — 全局 MD5 缓存池
├── manifests/ # 运行时创建 — JSON 清单仓库
│ ├── game/ # 游戏核心清单
│ └── voice/ # 语音包清单
└── outputs/ # 运行时创建 — 硬链接输出目录
pip install -r requirements.txt依赖:portalocker, requests, zstandard, protobuf, tqdm
python switcher.py \
--source "E:\Game_Impact" \
--product "GameImpact" \
--target-version "6.7" \
--target-region "CN" \
--target-voice "zh-cn" \
--output "E:\Game-Switcher\GameImpact_6.7_CN" \
--cache-root "E:\Game-Switcher\cache"# cache 已健全时可省略 --source
python switcher.py \
--product "GameImpact" \
--target-version "6.5" \
--target-region "CN" \
--target-voice "zh-cn" \
--output "E:\Game-Switcher\GameImpact_6.5_CN" \
--cache-root "E:\Game-Switcher\cache" \
--download --insecurepython switcher.py --diagnose \
--diagnose-version-a "6.5" \
--diagnose-version-b "6.6" \
--insecure -v输出示例:
版本 6.5: 2468 文件
版本 6.6: 2508 文件
─────────────────────────────
相同 (MD5一致): 1200
变更 (MD5不同): 1238
仅 6.5 有: 30
仅 6.6 有: 70
python switcher.py --sophon-fetch \
--target-version "6.5" \
--target-region "CN" \
--insecurepython switcher.py --parse-only \
--source "E:\Game_Impact"python switcher.py按提示选择:游戏目录 → 版本 → 地区 → 语音包 → 确认执行。
| 参数 | 说明 | 默认值 |
|---|---|---|
--source |
游戏根目录(只读,cache 健全时可省略) | 必填(除 --diagnose / --download) |
--product |
产品标识 | GameImpact |
--target-version |
目标版本号 | — |
--target-region |
目标地区 | — |
--target-voice |
语音包语言代码 | — |
--output |
输出目录 | ./outputs/{Product}_{Version}_{Region} |
--cache-root |
缓存根目录 | ./cache |
--manifests-dir |
清单目录 | ./manifests |
--download |
启用 CDN 下载(跨版本切换必须) | 关闭 |
--dispatch |
使用 GitHub Dispatch 模式 | 关闭(默认 Sophon API) |
--insecure |
跳过 SSL 证书验证 | 关闭 |
--parse-only |
仅解析本地,不构建 | — |
--sophon-fetch |
从 API 获取远程清单 | — |
--diagnose |
对比两版本差异 | — |
--diagnose-version-a |
对比版本 A | — |
--diagnose-version-b |
对比版本 B | — |
-v / --verbose |
详细日志 | 关闭 |
pkg_version (NDJSON) → parser.py → manifests/xxx.json
↓
manager.py 加载
↓
源文件 → cache.py (硬链接) → linker.py (硬链接) → 输出
Sophon API → getGameBranches → getBuild(tag=6.5.0)
↓
protobuf manifest (Zstd)
↓
sophon.py 解析 → files[]
↓
缓存命中 → linker.py 硬链接
缓存未命中 → downloader.py → CDN chunk 并行下载 → 入缓存 → 硬链接
{Project_Root}/
├── cache/ # MD5 缓存池(全局去重)
│ ├── a1b2c3d4e5f6... # 文件名 = MD5(无扩展名)
│ └── ...
├── manifests/
│ ├── game/
│ │ ├── GameImpact_6.6_CN.json # Sophon 获取的远程清单
│ │ └── GameImpact_local_local.json # 本地 pkg_version
│ └── voice/
│ └── GameImpact_6.6_CN_zh-cn.json
└── outputs/
└── GameImpact_6.5_CN_zh-cn/ # 硬链接农场(目录结构完整)
| 场景 | 处理 |
|---|---|
| 跨盘硬链接失败 | 捕获 OSError → shutil.copy2 + Warning |
| 目标路径不存在 | os.makedirs 递归创建父目录 |
| 下载中断/校验失败 | 删除临时文件,重试 3 次 |
| 并发写缓存 | portalocker 文件锁 |
| SSL 证书错误 | --insecure 跳过验证 |
| 未压缩 chunk | chunkSize == chunkSizeDecompressed 自动检测 |
- 同版本切换:2750 文件 100% 硬链接,秒级完成
-
fsutil hardlink list确认多路径共享同一 inode - 源目录
E:\Game_Impact无任何写入 - 缓存
cache/中无重复 MD5 文件 - 6.5 ↔ 6.6 版本差异诊断:1238 文件变更