4.5 版本,原神启动器开始灰度测试全新的 Chunk 下载模式

该下载模式可以解决长期困扰玩家的 “安装/更新游戏需要预留两倍安装/更新包空间” 的问题,4.5版本游戏本体+单语音包已经达到 84G 的大小,传统下载模式需要用户预留接近 170G 的硬盘空间

开启 Chunk 下载模式

新下载模式疑似已在 4.6 版本全量启用

新下载模式正处于灰度测试中,启动器会请求以下 API 判断是否开启

https://abtest-api-data.mihoyo.com/data_abtest_api/config/experiment/list

使用 Fiddler 等工具将其中一个请求返回的 downloadMode 字段由 file 修改为 chunk 即可强行启用新下载模式

{
    ...
    "data": [
        {
            ...
            "configs": {
                "downloadMode": "chunk"
            },
            ...
        }
    ]
}

开启 Chunk 下载模式后的启动器:

获取 Manifest

启动器使用以下 API 获取游戏包体信息

国服 main 分支(已停止更新)
https://api-takumi.mihoyo.com/downloader/sophon_chunk/api/getBuild?branch=main&package_id=s8m3Yf3j6G&password=QlYzM79uF6va&plat_app=cxgf44wie1a8

国服 predownload 分支(已停止更新)
https://api-takumi.mihoyo.com/downloader/sophon_chunk/api/getBuild?branch=predownload&package_id=s8m3Yf3j6G&password=izObT6iTHAqq&plat_app=cxgf44wie1a8

国服 main 分支(HYP)
https://api-takumi.mihoyo.com/downloader/sophon_chunk/api/getBuild?branch=main&package_id=8xfMve0uwQ&password=CW8GbLNU8f&plat_app=ddxf5qt290cg

国服 predownload 分支(HYP)
https://api-takumi.mihoyo.com/downloader/sophon_chunk/api/getBuild?branch=predownload&package_id=8xfMve0uwQ&password=EPq5oNru9q&plat_app=ddxf5qt290cg

此 API 返回数据中包含了 tag(游戏版本)和游戏包及语音包的 manifest 信息

4.5.0 版本游戏本体 manifest 信息示例:

{
    "category_id": "10017",
    "category_name": "游戏资源-外网",
    "manifest": {
        "id": "manifest_233f3acd5276c84e_890ab337d4ec8edf6c98c4dcf702b8bf",
        "checksum": "890ab337d4ec8edf6c98c4dcf702b8bf",
        "compressed_size": "4736513",
        "uncompressed_size": "9818860"
    },
    "chunk_download": {
        "encryption": 0,
        "password": "",
        "compression": 1,
        "url_prefix": "https://autopatchcn.yuanshen.com/client_app/sophon/chunks/cxgf44wie1a8/62nfHL6ocpNF",
        "url_suffix": ""
    },
    "manifest_download": {
        "encryption": 0,
        "password": "",
        "compression": 1,
        "url_prefix": "https://autopatchcn.yuanshen.com/client_app/sophon/manifests/cxgf44wie1a8/62nfHL6ocpNF",
        "url_suffix": ""
    },
    "matching_field": "game",
    "stats": {
        "compressed_size": "75506162967",
        "uncompressed_size": "76752839912",
        "file_count": "16115",
        "chunk_count": "74156"
    },
    "deduplicated_stats": {
        "compressed_size": "75443433225",
        "uncompressed_size": "76650339773",
        "file_count": "16115",
        "chunk_count": "74077"
    }
}

使用 manifest_download.url_prefixmanifest.id 进行拼接即可获取 manifest 数据

此 API 返回的 download 信息中包含暂未使用的 encryptionpassword 字段,这意味着后续版本中 manifest 与 chunk 文件可能会被加密,或者也可能是仅用于测试服的加密

解析 Manifest

manifest 是一个使用 zstd 算法压缩的 protobuf 数据包,其中包含了文件基础信息以及文件的分块信息,由 Blackbox Protobuf 工具解析后得到以下结构

{
    "1": {
        "type": "message",
        "message_typedef": {
            "1": {
                "type": "string",
                "example_value_ignored": "pkg_version"
            },
            "2": {
                "type": "message",
                "message_typedef": {
                    "1": {
                        "type": "string",
                        "example_value_ignored": "6ed042e749b0c6b1_84c069f03b872025728b592e7448e031"
                    },
                    "2": {
                        "type": "string",
                        "example_value_ignored": "84c069f03b872025728b592e7448e031"
                    },
                    "3": {
                        "type": "int",
                        "example_value_ignored": 2621003
                    },
                    "4": {
                        "type": "int",
                        "example_value_ignored": 50321
                    },
                    "5": {
                        "type": "int",
                        "example_value_ignored": 232573
                    },
                    "6": {
                        "type": "int",
                        "example_value_ignored": -5162654371156066304
                    }
                },
                "seen_repeated": true
            },
            "3": {
                "type": "int",
                "example_value_ignored": 64
            },
            "4": {
                "type": "int",
                "example_value_ignored": 2853576
            },
            "5": {
                "type": "string",
                "example_value_ignored": "069a8b21002eee2aaa2c5a9ecc64615e"
            }
        },
        "seen_repeated": true
    }
}

根据以上文件结构编写 proto 文件对 manifest 进行反序列化以获得可读数据

proto 文件示例:

Chunk 字段6 的作用暂不清楚,记为 Unknown
syntax = "proto3";

package manifest;

message Chunk {
    string Id = 1;
    string Checksum = 2;
    int32 Offset = 3;
    int32 CompressedSize = 4;
    int32 UncompressedSize = 5;
    int32 Unknown = 6;
}

message File {
    string Path = 1;
    repeated Chunk Chunks = 2;
    bool IsFolder = 3;
    int32 Size = 4;
    string Checksum = 5;
}

message Manifest {
    repeated File Files = 1;
}

反序列化结果示例(File HoYoKProtect.sys):

{
    "Path": "HoYoKProtect.sys",
    "Chunks": [
        {
            "Id": "d9b860120f8ccdb8_5599af11843b5088088aa9d40e75a8b2",
            "Checksum": "5599af11843b5088088aa9d40e75a8b2",
            "CompressedSize": 1115436,
            "UncompressedSize": 1247585,
            "Unknown": 346030080,
            "Offset": 0
        },
        {
            "Id": "e3328979a868c6e8_7c5efd948e2a5bed89cf195b20f74282",
            "Checksum": "7c5efd948e2a5bed89cf195b20f74282",
            "Offset": 1247585,
            "CompressedSize": 1177009,
            "UncompressedSize": 1188402,
            "Unknown": 1256456192
        },
        {
            "Id": "4da30f28403f5f1c_470a3eb15ace803c3d934f3e2245f779",
            "Checksum": "470a3eb15ace803c3d934f3e2245f779",
            "Offset": 2435987,
            "CompressedSize": 755378,
            "UncompressedSize": 770792,
            "Unknown": 742391808
        },
        {
            "Id": "d2ee63fcff6f7529_02a72d7f9b17024d19d1527bbae1bda8",
            "Checksum": "02a72d7f9b17024d19d1527bbae1bda8",
            "Offset": 3206779,
            "CompressedSize": 422551,
            "UncompressedSize": 491973,
            "Unknown": -1927368755
        }
    ],
    "Size": 3698752,
    "Checksum": "3336116e64580ac8b7def9e584be87eb",
    "IsFolder": false
}

Chunk.Id压缩后数据的xxhash64 拼接 未压缩数据的md5manifest.id 也为类似的格式

下载与合成文件

使用 chunk_download.url_prefixChunk.Id 进行拼接即可获取 chunk 数据

将文件的所有 chunk 使用 zstd 算法进行解压后根据 Offset 进行拼接即可合成完整的文件数据

在下载文件时,下载器会将 chunk 数据下载至 游戏安装目录/chunk

chunk 下载完成后会在 游戏安装目录/staging 中根据 Offset 信息进行拼接,staging 的目录结构与游戏文件目录结构一致

当某文件的所有 chunk 都合并完成并校验通过后就会被从 staging 目录中移至游戏安装目录下

参考

[Document] 原神启动器的 Chunk 下载模式分析 · Issue #725 · Scighost/Starward (github.com)