Files
hunyuan3dweb/README_REVERSE_ENGINEERING_CN.md
KawasakiAkasei 5328c213fe feat: add on-demand format conversion support (FBX, STL, USDZ, MP4, GIF)
- Discover /creations/resourceConvert endpoint via browser inspection
- Add resource_convert() method to api.py and api_complete.py
- Extend MODEL_FORMAT_KEYS with fbx, stl, usdz, mp4, gif
- Update get_model_urls() and download_model() with include_converted flag
- Update CLI with --converted flag for formats and download commands
- Update reverse engineering docs with native vs converted format tables
2026-05-27 13:42:29 +08:00

335 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 腾讯混元3D API 逆向工程文档
## 项目概述
本项目通过逆向工程破解了腾讯混元3Dhttps://3d.hunyuan.tencent.com/)的前端签名算法,实现了**纯 Python HTTP 调用**无需浏览器环境即可使用图生3D、查询配额等功能。
---
## 核心成果
### 1. 签名算法破解
**算法位置**: webpack Chunk 3057, Module 47436
**签名流程**:
```
1. nonce 生成: 16次 Math.random(),从 62 字符集 (A-Za-z0-9) 选取
2. timestamp: Math.floor(Date.now() / 1000)(秒级时间戳)
3. 密钥派生: 硬编码字节数组 → XOR → 循环左移 → 置换 → 截断
4. 签名: HMAC-SHA256(排序后的查询参数字符串, 派生密钥) → Hex
```
**关键常量**:
```python
C = bytes([122, 59, 92, 165, 30, 79, 166, 139, 142, 129, 139, 89, 219, 131, 101, 204])
D = bytes([122, 59, 92, 45, 30, 79, 106, 139, 156, 13, 46, 63, 74, 91, 108, 125])
U = [3, 5, 2, 7, 1, 4, 6, 2, 5, 3, 1, 4, 2, 6, 3, 5] # 左移位数
M = [14, 11, 13, 9, 15, 10, 12, 8, 6, 3, 5, 1, 7, 2, 4, 0] # 置换表
```
**派生密钥**: `Hf6d6KFB3D`10字符
### 2. 签名范围(重要发现)
⚠️ **签名只包含 URL 查询参数,不包含请求体数据**
浏览器实际请求:
```
POST /api/3d/creations/generations?timestamp=xxx&nonce=yyy&sign=zzz
Body: {"sceneType":"playGround3D-2.0",...}
```
签名计算:
```python
# 只签查询参数timestamp + nonce
param_str = "nonce=yyy&timestamp=xxx"
sign = HMAC-SHA256(param_str, key="Hf6d6KFB3D")
```
### 3. 图片上传方式演进
#### 方式一:浏览器自动化上传(原始方式)
通过 `generator.py` 在浏览器内完成上传,获取腾讯内部资源格式的 `resourceId`
#### 方式二:纯 Python COS 直传(新方式)
通过 `cos_upload.py` 直接调用腾讯 COS API 上传文件,无需浏览器:
1. 调用 `/resource/genUploadInfo` 获取临时 STS 凭证(`encryptTmpSecretId` / `encryptTmpSecretKey` / `encryptToken`
2. 使用 COS V1 签名算法生成 `Authorization` 请求头
3. 直接 `PUT` 上传到 `*.cos.accelerate.myqcloud.com`
4. 获得 `resourceUrl` 供后续图生3D API 使用
```python
from hunyuan3dweb.cos_upload import upload_image
resource_url = upload_image("/path/to/image.png")
```
---
## 文件说明
| 文件 | 说明 |
|------|------|
| `hunyuan3dweb/sign.py` | 签名算法纯 Python 实现 |
| `hunyuan3dweb/api.py` | 基础 API 客户端图生3D、文生3D |
| `hunyuan3dweb/api_complete.py` | 完整 API 客户端(所有生成模式) |
| `hunyuan3dweb/cos_upload.py` | 纯 Python COS 上传工具(无需浏览器) |
| `hunyuan3dweb/config.py` | 用户配置路径管理cookie、profile |
| `hunyuan3dweb/cli.py` | CLI 入口 |
| `hunyuan3dweb/browser/login.py` | 浏览器自动化登录工具 |
| `hunyuan3dweb/browser/generator.py` | 浏览器自动化图生3D |
| `hunyuan3dweb/browser/sniffer.py` | API 请求嗅探工具 |
---
## 使用流程
### 1. 登录获取 Cookie
```bash
hunyuan3dweb-login
# 按提示输入邮箱和验证码
# 登录状态自动保存到 ~/.config/hunyuan3dweb/profile
# Cookie 同时导出到 ~/.config/hunyuan3dweb/cookies.txt
```
**登录状态检测逻辑**(双重验证):
- 检查当前页面 URL 是否包含 `"login"`
- 检查页面上是否存在"登录"按钮
- 只有 URL 不含 login **且** 没有登录按钮时,才判定为已登录
### 2. 纯 Python 生成3D模型
```python
from hunyuan3dweb import Hunyuan3DAPI
# 自动从 ~/.config/hunyuan3dweb/cookies.txt 加载 cookie
api = Hunyuan3DAPI()
# 查询配额
quota = api.get_quota_info()
# 文生3D
result = api.generate_text("一只可爱的熊猫", title="熊猫模型")
# 查询状态
status = api.get_generation_status(result["creationsId"])
```
### 3. 本地图片上传 + 图生3D纯 Python
```python
from hunyuan3dweb.cos_upload import upload_image
from hunyuan3dweb import Hunyuan3DAPIComplete
# 上传本地图片到 COS
resource_url = upload_image("/path/to/image.png")
# 调用图生3D API
api = Hunyuan3DAPIComplete()
result = api.generate_from_image(resource_url, title="我的模型")
```
### 4. CLI 下载模型
```bash
# 列出某个创作所有可用的下载格式
hunyuan3dweb formats <creation_id>
# 包含转换格式fbx, stl, usdz, mp4, gif
hunyuan3dweb formats <creation_id> --converted
# 下载指定格式(默认 glb
hunyuan3dweb download <creation_id> --format glb -o model.glb
# 下载转换格式(如 usdz
hunyuan3dweb download <creation_id> --format usdz --converted -o model.usdz
```
---
## API 端点
| 功能 | 方法 | 端点 | 签名范围 |
|------|------|------|----------|
| 用户信息 | GET | `/getuserinfo` | 查询参数 |
| 配额查询 | POST | `/quotainfo` | 查询参数 |
| 生成3D | POST | `/creations/generations` | 查询参数 |
| 查询状态 | GET | `/creations/detail` | 查询参数 |
| 作品列表 | POST | `/creations/list` | 查询参数 |
| 作品数量 | POST | `/creations/count` | 查询参数 |
| 取消生成 | POST | `/creations/cancel` | 查询参数 |
| 资源转换 | POST | `/creations/resourceConvert` | 查询参数 |
| 上传凭证 | POST | `/resource/genUploadInfo` | 查询参数 |
| 资源审核 | POST | `/resource/review` | 查询参数 |
| 创建分享 | POST | `/share` | 查询参数 |
| 全局配置 | GET | `/config` | 查询参数 |
| 动画模板 | GET | `/workflow/action/templates` | 查询参数 |
### 模型下载格式
#### 原生格式(来自 `urlResult`
`/creations/detail` 返回的 `urlResult` 字段包含 14 种原生格式键:
| 键名 | 说明 |
|-----|------|
| `glb` | 带 PBR 贴图的 GLB 二进制模型 |
| `obj` | OBJ 模型(通常打包在 zip 中) |
| `mtl` | MTL 材质文件 |
| `obj_url` | 独立 OBJ 文件 URL |
| `geometryGlb` | 纯几何 GLB无材质 |
| `textureGlb` | GLB 纹理包 |
| `textureObj` | OBJ 纹理包 |
| `image_url` | 预览图/缩略图 |
| `pbrImage` | PBR 综合贴图 |
| `pbrMetallicImage` | PBR 金属度贴图 |
| `pbrRoughnessImage` | PBR 粗糙度贴图 |
| `pbrNormalImage` | PBR 法线贴图 |
| `invisible_wall` | 不可见碰撞墙 |
| `air_wall` | 空气墙(碰撞体) |
#### 转换格式(通过 `/creations/resourceConvert`
以下格式由网站按需将 OBJ zip 转换生成:
| 键名 | 说明 | 来源 |
|-----|------|------|
| `fbx` | Autodesk FBX 格式 | OBJ zip 转换 |
| `stl` | STL 格式3D打印 | OBJ zip 转换 |
| `usdz` | USDZ 格式iOS AR | OBJ zip 转换 |
| `mp4` | MP4 视频格式 | OBJ zip 转换 |
| `gif` | GIF 动图格式 | OBJ zip 转换 |
**转换接口示例**
```python
POST /creations/resourceConvert
Body: {
"sourceResource": [{"format": "zip", "url": "<obj_zip_url>"}],
"targetFormatList": ["usdz", "fbx"]
}
Response: {"convertResult": [{"format": "usdz", "url": "..."}]}
```
---
## 技术细节
### 签名算法实现
```python
def derive_key(c: bytes) -> str:
"""从硬编码常量派生签名密钥"""
# 1. XOR with D
t = bytearray(16)
for i in range(16):
t[i] = c[i] ^ D[i]
# 2. 循环左移
o = bytearray(16)
for i in range(16):
n = U[i]
val = t[i]
o[i] = (val << n | val >> (8 - n)) & 0xFF
# 3. 置换
n = bytearray(16)
for i in range(16):
n[i] = o[M[i]]
# 4. 截断找到第一个0字节
try:
r = n.index(0)
except ValueError:
r = 16
return n[:r].decode('utf-8')
```
### COS V1 签名(用于直传)
```python
def _cos_v1_auth(method, pathname, query_params, headers, secret_id, secret_key, key_time):
sign_key = hmac.new(secret_key.encode("utf-8"), key_time.encode("utf-8"), hashlib.sha1).hexdigest()
query_str = _obj2str(query_params, True)
headers_str = _obj2str(headers, True)
format_string = f"{method}\n{pathname}\n{query_str}\n{headers_str}\n"
format_string_sha1 = hashlib.sha1(format_string.encode("utf-8")).hexdigest()
string_to_sign = f"sha1\n{key_time}\n{format_string_sha1}\n"
signature = hmac.new(sign_key.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha1).hexdigest()
return "&".join([
"q-sign-algorithm=sha1",
f"q-ak={secret_id}",
f"q-sign-time={key_time}",
f"q-key-time={key_time}",
f"q-header-list={';'.join(sorted(headers.keys())).lower()}",
f"q-url-param-list={';'.join(sorted(query_params.keys())).lower()}",
f"q-signature={signature}",
])
```
### 请求签名
```python
def sign(params: dict) -> dict:
result = dict(params)
result["timestamp"] = int(time.time())
result["nonce"] = generate_nonce(16)
# 排序并拼接JSON格式用于列表/字典)
sorted_items = sort_params(result)
param_str = join_params(sorted_items)
# HMAC-SHA256
key = derive_key(C)
signature = hmac.new(
key.encode('utf-8'),
param_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
result["sign"] = signature
return result
```
---
## 验证结果
| 测试项 | 浏览器签名 | Python 签名 | 匹配 |
|--------|-----------|-------------|------|
| 固定参数 | `2214e55a...` | `2214e55a...` | ✅ |
| 配额查询 | 可用 | 可用 | ✅ |
| 生成请求 | 可用 | 可用 | ✅ |
| 状态查询 | 可用 | 可用 | ✅ |
| COS 直传 | 浏览器上传 | Python 上传 | ✅ |
---
## 限制与注意事项
1. **Cookie 有效期**: 登录状态会过期,需要定期重新登录
2. **配额限制**: 每个账号有生成配额限制默认20次/天)
3. **风控检测**: 频繁调用可能触发风控
4. **并发安全**: 多个工具同时运行时,`login.py` 独享标准 profile 目录,`sniffer.py` / `generator.py` 会自动复制 profile 到临时目录启动,避免 Chromium `SingletonLock` 冲突
---
## 依赖
```
requests
cloakbrowser (用于登录和浏览器自动化)
```
---
## 法律声明
本项目仅供学习和研究使用。使用本代码需遵守腾讯混元3D的服务条款。请勿用于商业用途或大规模自动化调用。