vrhd
v1.0.1
Published
Automatic downloader and deobfuscator for VRoid Hub preview models
Downloads
16
Maintainers
Readme
vrhd
VRoid Hub 模型自动下载与解密工具。仅供学习研究使用,请勿用于其他用途。
安装
npm install -g vrhd或者不安装直接运行:
npx vrhd <url-or-id>使用
vrhd https://hub.vroid.com/en/characters/147647967680376151/models/259570871427745417默认将结果保存到 ./vrm/<id>.deob.vrm。
参数
| 参数 | 缩写 | 说明 |
|------|------|------|
| --output <dir> | -o | 输出目录(默认:./vrm) |
| --cache <dir> | | 缓存目录(默认:~/.cache/vrhd) |
| --debug | | 保存中间纹理和扩展 JSON,用于排查问题 |
示例
# 指定输出目录
vrhd -o ~/Downloads https://hub.vroid.com/...
# 指定缓存目录
vrhd --cache /tmp/vrhd-cache https://hub.vroid.com/...
# 开启调试输出
vrhd --debug https://hub.vroid.com/...文件目录结构
~/.cache/vrhd/ # 缓存(所有运行共享,不重复下载)
<id>.glb
<id>.json
./vrm/ # 输出目录(默认)
<id>.deob.vrm
debug/ # 仅在 --debug 时生成作为库使用
import { deobfuscateVRoidHubGLB } from 'vrhd';
const glb = await deobfuscateVRoidHubGLB('259570871427745417', {
outputDir: './output', // 默认:./vrm
cacheDir: './cache', // 默认:~/.cache/vrhd
debug: false, // 默认:false
});常见问题
gltf-transform 在处理模型时会做一些操作,可能导致导出的 VRM 无法正常使用。工具已自动对模型进行修复,但如果仍有问题,可以将输出的 VRM 导入 Blender 或 Unity(安装 VRM 插件),重新导出一次即可。建议两个版本的 Unity 插件和 Blender 都试试。
解密原理
整体流程
VRoid Hub URL
│
▼
① HTTP 请求(HTTP/2)→ 获取加密的 GLB 二进制
│
▼
② AES-CBC 解密(IV + Key 内嵌于文件头)
│
▼
③ Zstd 解压缩
│
▼
④ 解析 GLB,读取混淆参数(version + timestamp)
│
▼
⑤ 计算 Seed Map(根据模型 ID / CDN URL 派生种子)
│
▼
⑥ 顶点坐标反混淆
│
▼
⑦ 纹理解码(KTX2 / Basis → PNG)
│
▼
⑧ 输出 .deob.vrmAES-CBC 解密
响应二进制的文件头结构:
┌─────────────┬──────────────┬─────────────────────────┐
│ IV(16字节) │ Key(32字节) │ AES-CBC 密文(剩余字节) │
└─────────────┴──────────────┴─────────────────────────┘密钥和 IV 直接明文嵌入文件头,VRoid Hub 的保护不依赖密钥保密,而是依赖后续的混淆层。
Seed Map 计算
工具本地维护一张基础种子表(seedMapStartingState),key 为 timestamp,value 为基础种子。最终种子根据 CDN URL 动态派生:
- Signed URL(含
s=op):seed = baseValue ^ SHA1(path)[-4字节 int32LE] - 普通 URL:
seed = baseValue + parseInt(modelId)
顶点反混淆
对每个顶点,根据 seed 生成的 256×256 元纹理查找缩放系数,执行逆运算:
vertex[X] = vertex[X] * (2 ** (r / 8))
vertex[Y] = vertex[Y] * (2 ** (g / 8))
vertex[Z] = vertex[Z] * (2 ** (b / 8))安全分析
VRoid Hub 的保护属于客户端混淆,并非真正的加密保护:AES 密钥明文存于文件头,顶点种子可由模型 ID 和 CDN URL 确定性推导,真正的访问控制依赖 CDN signed URL 的时效性。
维护指南
故障信号
当 VRoid Hub 添加了新的混淆参数时,工具会报错:
Error: Seed not found for timestamp: XXXXXXXX日志中同时会打印出:
Obfuscation version and timestamp: 5.0 XXXXXXXX记录下这个 timestamp 值,按以下步骤更新。
Step 1:提取新 Seed
运行内置的自动提取脚本:
node .claude/skills/vrh-seed-maintenance/scripts/extract-seeds.mjs脚本通过 Playwright 加载 VRoid Hub 页面,直接调用 webpack 模块 73648 的 computeSeedMap 函数,与本地种子表对比后输出差异和建议的更新代码。
首次使用需安装依赖:
pnpm add -D playwright && pnpm exec playwright install chromium⚠️ DevTools Console 手动提取不可行:模块 73648 有反调试保护,检测到 DevTools 时静默中断,
r(73648)返回undefined。若脚本失效,参考references/static-analysis.md进行静态分析。
Step 2:验证计算公式
确认 seed 派生公式是否变更,当前公式(2026-03 起):
Signed URL: seed = baseValue ^ SHA1(path)[-4字节, int32 LE]
普通 URL: seed = baseValue + parseInt(modelId)Step 3:更新代码
编辑 src/index.js 约第 21 行,添加新条目:
const seedMapStartingState = {
98756153: 74670526,
53816997: 38325553,
4058237768: 1289559305,
58245139: 9402684,
XXXXXXXX: YYYYYYYY, // ← 新增
};Step 4:测试
vrhd https://hub.vroid.com/en/characters/.../models/{触发报错的model_id}成功标志:不再出现 Seed not found,正常生成 .deob.vrm 文件。
历史更新记录
| 时间 | 新增 timestamp | seed 值 | 公式变更 |
|------|---------------|---------|---------|
| 2026-03 | 58245139 | 9402684 | 是:所有条目统一改为 XOR |
