Mechanism
File Description
Arcaea content bundles consist of two types of files:
- Content bundle files: store actual resource data, usually with the
.cbsuffix. - Metadata files: store versions, hashes, file paths, offsets, partition information, and other data, usually with the
.jsonsuffix.
These two types of files must be used together. A .cb file itself does not store the directory structure or file names. All of this information comes from metadata. Therefore, without metadata, it is impossible to reliably debundle; without .cb files, metadata cannot complete resource download either.
Content Bundle
A content bundle is not a compressed package or a standard archive format. Its core structure can be understood as directly concatenating the binary content of several resource files in order, and then using metadata to record each file's part index, offset, and length.
For example, if a file is recorded in metadata as follows:
{
"path": "songs/example/base.ogg",
"byteOffset": 1024,
"length": 4096,
"partIndex": 0,
"sha256HashBase64Encoded": "..."
}This means that the client or debundler should read 4096 bytes from the 0th .cb part starting at byte 1024, and save this data as songs/example/base.ogg. After reading, it will also use sha256HashBase64Encoded to verify the file content.
When a content bundle is large, it will be split into multiple .cb files. Taking 6.14.0 as an example, the server can recognize the following form:
6.14.0.json
6.14.0_0.cb
6.14.0_1.cb
6.14.0_2.cb
...The old single-file form can also be recognized by Arcaea-Server:
6.14.0.json
6.14.0.cbArcaea-Bundler generates the first multipart naming format by default.
Metadata
Metadata is a JSON file that describes the version relationship and file index of a content bundle. A simplified structure is as follows:
{
"versionNumber": "6.14.1.1",
"previousVersionNumber": "6.14.1",
"applicationVersionNumber": "6.14.1",
"uuid": "abcdef123",
"removed": [
"songs/example/base.ogg"
],
"added": [
{
"path": "songs/example/base.ogg",
"byteOffset": 0,
"length": 4096,
"partIndex": 0,
"sha256HashBase64Encoded": "..."
}
],
"pathToHash": {
"songs/example/base.ogg": "..."
},
"pathToDetails": {
"songs/unlocks": "...",
"songs/packlist": "...",
"songs/songlist": "..."
},
"generatedUnixTimestamp": 1777355565,
"totalPartitions": 1
}The fields mean:
versionNumber: the current content bundle version number.previousVersionNumber: the parent content bundle version number. A full package usesnull.applicationVersionNumber: the corresponding client version number. In strict mode of Arcaea-Server, the server groups content bundles by this field. In graph path mode, it is mainly used to determine the target latest content bundle version for the current client version.uuid: the random identifier of this metadata. Arcaea-Bundler generates a 9-digit hexadecimal string.removed: the list of file paths that the client needs to delete when applying this update. When file content changes, Arcaea-Bundler adds the path to bothremovedandadded, meaning the old file should be removed first and then the new file should be written.added: the list of files actually written into the.cbfiles in this update. Each item contains the path, offset, length, part index, and SHA-256 verification value.pathToHash: a mapping from all file paths in the complete resource state of the current version to SHA-256 Base64 values. It is mainly used to determine which files are added, changed, or removed during the next bundle.pathToDetails: detail hashes of the three key filessongs/unlocks,songs/packlist, andsongs/songlist. Arcaea-Bundler calculates HMAC-SHA256 with a fixed key and then Base64-encodes the result.generatedUnixTimestamp: the Unix timestamp when the metadata was generated.totalPartitions: the number of parts. If this field is missing, the debundler will process it as an old single-file.cb.
Old Metadata
Old metadata is an auxiliary file used by Arcaea-Bundler itself. By default, it is located at metadata.oldjson under the input folder. It is not a file downloaded directly by the client, and it does not need to be placed in the database/bundle folder of Arcaea-Server.
Its content is a JSON array. Each item stores the complete resource state after a bundle operation. To reduce size, added and removed are removed from the record, and only information needed for later incremental update detection is retained, for example:
[
{
"versionNumber": "6.14.1",
"previousVersionNumber": null,
"applicationVersionNumber": "6.14.1",
"uuid": "abcdef123",
"pathToHash": {
"songs/songlist": "..."
},
"pathToDetails": {
"songs/unlocks": "...",
"songs/packlist": "...",
"songs/songlist": "..."
},
"generatedUnixTimestamp": 1777355565,
"totalPartitions": 1
}
]The next time the same input folder is bundled, the program reads the record with the largest version number in old metadata:
- Reuses its
applicationVersionNumber. - Uses its
versionNumberas the defaultpreviousVersionNumber. - Generates the next
versionNumberaccording to the default rule. - Compares its
pathToHashwith the current input folder item by item to generate newaddedandremoved.
Deleting metadata.oldjson resets the incremental record. After resetting, the program can no longer know the file state of past versions, so the current input folder will be treated as a full bundle.
Warning
You can modify metadata.oldjson manually, but you must keep the version relationship, pathToHash, and actual resource state consistent. Otherwise, the generated incremental package may fail to update correctly from its parent version to the target version.
assets Folder
The input folder of the bundler is equivalent to the assets folder in the client resource directory.
The current official structure example:
input_dir/
├─ songs/
│ ├─ unlocks
│ ├─ packlist
│ ├─ songlist
│ └─ example_song/
│ ├─ 0.aff
│ ├─ 1.aff
│ ├─ 2.aff
│ ├─ base.ogg
│ ├─ base.jpg
│ └─ base_256.jpg
├─ img/
│ └─ bg/
└─ tl/The following three files are required:
songs/unlockssongs/packlistsongs/songlist
Arcaea-Bundler processes these three files first in the order above, and then recursively traverses other files in the input folder. The default old metadata file metadata.oldjson and other .oldjson files are skipped and will not be written into the content bundle.
Other than the required files above, the bundler does not check whether resources comply with client business rules. In other words, as long as it is a regular file in the input folder, it will be treated as a bundleable resource. Whether it can be correctly used by the client depends on songlist, packlist, unlocks, and the client's own requirements for resource paths and formats.
Incremental Update Mechanism
Arcaea-Bundler determines incremental updates based on file paths and SHA-256:
- Reads
pathToHashfrom the latest version in old metadata. - Traverses the current input folder and calculates SHA-256 for each file.
- A path that exists currently but not in the old record is added to
added. - A path that exists currently and has a changed hash is added to both
removedandadded. - A path that exists in the old record but not currently is added to
removed. - Files whose paths and hashes are unchanged are not written into the new
.cb, but they continue to be retained in the new metadata'spathToHash.
Therefore, the .cb file in an incremental package only stores newly added or changed files, and does not contain complete resources. The client needs to already be in the resource state corresponding to previousVersionNumber to correctly apply this incremental package.
If you want to generate a full package, delete or change the old metadata record, and explicitly specify new -av and -bv.
Server Mechanism
Arcaea-Server reads content bundles from the database/bundle folder. Each metadata JSON needs to have a .cb file with the same name, or part files with the same prefix. For example:
database/bundle/
├─ 6.14.1.json
├─ 6.14.1_0.cb
├─ 6.14.1_1.cb
└─ 6.14.1_2.cbWhen the server starts or refreshes the content bundle cache, it recursively scans database/bundle:
- Reads each
.jsonmetadata file. - Builds version indexes according to
versionNumber,previousVersionNumber, andapplicationVersionNumber. - Looks for a
.cbfile with the same name, or looks for*_0.cb,*_1.cb, and other parts according tototalPartitions. - Calculates the sizes of metadata and each part file, which are returned to the client.
When the client requests hot updates, it accesses the /game/content_bundle endpoint of the game API and provides the following request headers:
AppVersion: client version.ContentBundle: current content bundle version of the client.DeviceId: device identifier, used to generate download token records.
Then the server chooses different update strategies according to BUNDLE_STRICT_MODE.
Strict Mode
BUNDLE_STRICT_MODE defaults to True. In strict mode, the server only selects updates from content bundles whose applicationVersionNumber is equal to the request header AppVersion.
The specific process is:
- When scanning the cache, the server first groups content bundles by
applicationVersionNumberand sorts them byversionNumberfrom small to large. - When the client requests, the server takes the whole group corresponding to
AppVersion. - When generating the response, it filters out items whose
versionNumberis less than or equal to the client'sContentBundle.
In other words, strict mode does not calculate paths according to previousVersionNumber. As long as a content bundle belongs to the same applicationVersionNumber group and its version number is greater than the client's current ContentBundle, it may appear in the response list. Therefore, in this mode, it is recommended to keep content bundle versions under the same client version linearly increasing, and to ensure each incremental package can correctly update from the previous version.
Graph Path Mode
When BUNDLE_STRICT_MODE is set to False, the server uses an update method based on a version relationship graph.
The server treats each content bundle as a directed edge:
previousVersionNumber -> versionNumberWhen the previousVersionNumber of a full package is null, the server internally treats it as 0.0.0. When the client requests:
- The server still uses the request header
AppVersionto find the largestversionNumberunder that client version as the target version. - The start point is the request header
ContentBundle. If the client does not provide a content bundle version, the start point is treated as0.0.0. - The server performs breadth-first search in the graph composed of
previousVersionNumber -> versionNumber, looking for the shortest path from the start point to the target version. - The response only contains content bundles on this path, and the order is the order in which the client should apply updates.
Here, applicationVersionNumber is only used to determine the "target version", and is not used to restrict every edge in the graph. In other words, intermediate update packages in graph path mode are determined by version relationships. If different client versions reuse the same versionNumber and previousVersionNumber, or if the version graph is accidentally connected, path selection may be affected. Therefore, in graph path mode, avoid sharing the same version edge between incompatible update packages.
For example, assume the following content bundles exist:
0.0.0 -> 6.14.1
6.14.1 -> 6.14.1.1
6.14.1.1 -> 6.14.1.2
6.14.1 -> 6.14.1.2When the client's current ContentBundle is 6.14.1 and the target version is 6.14.1.2, graph path mode will choose the shorter path 6.14.1 -> 6.14.1.2, instead of returning 6.14.1.1 and then 6.14.1.2.
Warning
Graph path mode depends on the version graph composed of previousVersionNumber and versionNumber. If no reachable path exists, the server cannot find an update package for that client. If multiple paths have the same length, the final choice depends on the server's scan and cache order. Since the server uses (versionNumber, previousVersionNumber) as the content bundle index, the same version edge should not be reused by multiple incompatible packages.
The response normally looks like:
{
"orderedResults": [
{
"contentBundleVersion": "6.14.1.1",
"appVersion": "6.14.1",
"jsonSize": 12345,
"jsonUrl": "https://example.com/bundle_download/...",
"bundleParts": [
{
"bundleSize": 104857600,
"bundleUrl": "https://example.com/bundle_download/..."
}
]
}
]
}The client downloads metadata and each part file in the order of orderedResults, and applies the update according to removed, added, hashes, and part indexes in metadata.
Tips
After modifying files in database/bundle, restart the server or refresh the content bundle cache on the Web management page. Otherwise, the server will still use the old cache.