|
| 1 | +# AssetBundle Format |
| 2 | + |
| 3 | +This topic digs into the internal layout of AssetBundles, with a focus on the parts that are useful |
| 4 | +when inspecting an AssetBundle with UnityDataTool. It complements the higher-level |
| 5 | +[Overview of Unity Content](unity-content-format.md), which introduces SerializedFiles and Unity |
| 6 | +Archives. |
| 7 | + |
| 8 | +For Unity's official reference on the container format, see |
| 9 | +[AssetBundle file format](https://docs.unity3d.com/Manual/assetbundles-file-format.html). |
| 10 | +This page builds on that information with more technical information and examples of how |
| 11 | +these data structures can be examined. |
| 12 | + |
| 13 | +## AssetBundles are Unity Archives |
| 14 | + |
| 15 | +An AssetBundle is a [Unity Archive](unity-content-format.md#unity-archive) with some conventions for |
| 16 | +what lives inside. The [Addressables](https://docs.unity3d.com/Manual/com.unity.addressables.html) |
| 17 | +package builds its content as AssetBundles too, so the same layout applies there. |
| 18 | + |
| 19 | +An AssetBundle always contains at least one SerializedFile, and may contain auxiliary files such as |
| 20 | +`.resS` (Textures and Meshes) and `.resource` (audio/video). |
| 21 | + |
| 22 | +### SerializedFile names inside a bundle |
| 23 | + |
| 24 | +The names of the SerializedFiles inside a bundle are technical and hash-based. You do not need to |
| 25 | +understand them for normal use, but they show up throughout UnityDataTool output: |
| 26 | + |
| 27 | +- **Regular (non-scene) bundles** contain one SerializedFile named `CAB-<hash>`, where the hash is |
| 28 | + the **MD4** hash of the AssetBundle name (not the `Hash128` / spooky hash exposed in the C# API). |
| 29 | +- **Scene bundles** name their scene files differently depending on the build pipeline: |
| 30 | + - `BuildPipeline.BuildAssetBundles` uses `BuildPlayer-<SceneName>`. |
| 31 | + - The Scriptable Build Pipeline / Addressables uses `CAB-<hash of the scene path>`. |
| 32 | + - The Multi-Process Build Pipeline (2023.1+) uses `CAB-<scene GUID>`. |
| 33 | + |
| 34 | +## Inspecting a bundle with UnityDataTool |
| 35 | + |
| 36 | +The [`archive`](command-archive.md) command lists or extracts the files inside a bundle, and |
| 37 | +[`dump`](command-dump.md) / [`serialized-file`](command-serialized-file.md) inspect the |
| 38 | +SerializedFiles. A typical workflow is to extract the bundle into a folder and then dump specific |
| 39 | +objects: |
| 40 | + |
| 41 | +``` |
| 42 | +UnityDataTool archive extract mybundle.bundle -o extracted |
| 43 | +cd extracted |
| 44 | +UnityDataTool sf objectlist CAB-<hash> |
| 45 | +UnityDataTool dump --stdout CAB-<hash> --type AssetBundle |
| 46 | +``` |
| 47 | + |
| 48 | +## The AssetBundle object |
| 49 | + |
| 50 | +Every bundle has one **AssetBundle** object (ClassID 142). It records which assets the bundle |
| 51 | +exposes and what each of them needs preloaded. Its important fields are: |
| 52 | + |
| 53 | +- `m_Container` - a map from an asset's address/path to the object it maps to, plus the |
| 54 | + `preloadIndex`/`preloadSize` range describing that asset's dependencies (see below). |
| 55 | +- `m_PreloadTable` - a flat list of `PPtr`s. Each container entry references a contiguous slice of |
| 56 | + this list. |
| 57 | +- `m_Dependencies` - the names of other bundles this bundle depends on. |
| 58 | +- `m_IsStreamedSceneAssetBundle` - true for scene bundles. |
| 59 | +- `m_SceneHashes` - for scene bundles, a map from each scene path to the SerializedFile that holds |
| 60 | + it (populated by SBP/Addressables only; see [Scenes in AssetBundles](#scenes-in-assetbundles)). |
| 61 | + |
| 62 | +In a regular bundle the AssetBundle object is typically at local file id (LFID) 1. In a scene bundle |
| 63 | +it is at LFID 2, because the PreloadData object takes LFID 1. |
| 64 | + |
| 65 | +### Preload for assets (non-scene bundles) |
| 66 | + |
| 67 | +For a regular asset, `m_Container` names the asset and points at its main object, and |
| 68 | +`preloadIndex` + `preloadSize` select the slice of `m_PreloadTable` listing every object that must |
| 69 | +be loaded for that asset (including objects found in SerializedFiles in other dependent AssetBundles). This is how Unity knows the full set of objects to load in order to fully load an asset. |
| 70 | + |
| 71 | +For example, a bundle whose single ScriptableObject references an AudioClip stored in another |
| 72 | +bundle: |
| 73 | + |
| 74 | +``` |
| 75 | +ID: 1 (ClassID: 142) AssetBundle |
| 76 | + m_Name (string) directaudioclipreference |
| 77 | + m_PreloadTable (vector) |
| 78 | + Array<PPtr<Object>>[4] |
| 79 | + data[0] (PPtr<Object>) |
| 80 | + m_FileID (int) 1 |
| 81 | + m_PathID (SInt64) -895285400835485219 |
| 82 | + data[1] (PPtr<Object>) |
| 83 | + m_FileID (int) 0 |
| 84 | + m_PathID (SInt64) -602721743313719314 |
| 85 | + data[2] (PPtr<Object>) |
| 86 | + m_FileID (int) 0 |
| 87 | + m_PathID (SInt64) 8127315791103748070 |
| 88 | + data[3] (PPtr<Object>) |
| 89 | + m_FileID (int) 2 |
| 90 | + m_PathID (SInt64) -5151917721481642524 |
| 91 | + m_Container (map) |
| 92 | + Array<pair>[1] |
| 93 | + data[0] (pair) |
| 94 | + first (string) assets/scriptableobjects/directaudioclipreference.asset |
| 95 | + second (AssetInfo) |
| 96 | + preloadIndex (int) 0 |
| 97 | + preloadSize (int) 4 |
| 98 | + asset (PPtr<Object>) |
| 99 | + m_FileID (int) 0 |
| 100 | + m_PathID (SInt64) -602721743313719314 |
| 101 | + m_Dependencies (vector) |
| 102 | + Array<string>[2] |
| 103 | + data[0] (string) 6 |
| 104 | + data[1] (string) a |
| 105 | + m_IsStreamedSceneAssetBundle (bool) False |
| 106 | +``` |
| 107 | + |
| 108 | +Here the single asset `directaudioclipreference.asset` has `preloadIndex 0` and `preloadSize 4`, so |
| 109 | +its dependencies are `m_PreloadTable[0..3]`. Those `PPtr`s use `m_FileID` to say which file each |
| 110 | +object lives in: `0` is this file, and `1`/`2` are entries in the file's external reference table - |
| 111 | +in this case objects in the dependency bundles `6` and `a` listed in `m_Dependencies`. |
| 112 | + |
| 113 | +> [!NOTE] |
| 114 | +> `m_Container` only records the assets that were **explicitly** added to the bundle. Assets that |
| 115 | +> are pulled in implicitly (because an explicit asset references them) appear in `m_PreloadTable` |
| 116 | +> and the dependency bundles, but are not listed as their own container entries. |
| 117 | +
|
| 118 | +## Scenes in AssetBundles |
| 119 | + |
| 120 | +Scenes are a special case, where the component hierarchy requires a dedicated serialized file that is entirely |
| 121 | +loaded each time a scene is loaded. Unity's build pipeline emits **two SerializedFiles |
| 122 | +per scene**: |
| 123 | + |
| 124 | +- `<scene>` - the scene's own contents (the GameObject/Transform hierarchy, RenderSettings, |
| 125 | + LightmapSettings, and so on). |
| 126 | +- `<scene>.sharedAssets` - the objects referenced by the scene (Materials, Meshes, MonoScripts, |
| 127 | + etc.). To avoid duplication, a scene's files may reference objects in *other* scenes' `.sharedAssets` |
| 128 | + files, but there is never an external reference **into** a scene's own contents file. Note: in some cases a scene will have no additional external references apart from the references already saved in other .sharedAssets files. So in that case there will be no sharedAssets file for that scene. |
| 129 | + |
| 130 | +Extracting a simple single-scene bundle shows the pair: |
| 131 | + |
| 132 | +``` |
| 133 | +UnityDataTool archive list scene1.bundle |
| 134 | +``` |
| 135 | +``` |
| 136 | +BuildPlayer-Scene1.sharedAssets |
| 137 | + ... |
| 138 | + Flags: SerializedFile |
| 139 | +
|
| 140 | +BuildPlayer-Scene1 |
| 141 | + ... |
| 142 | + Flags: SerializedFile |
| 143 | +``` |
| 144 | + |
| 145 | +A scene bundle contains exactly **one AssetBundle object**, in one of the `.sharedAssets` files. |
| 146 | +Its `m_Container` lists every scene in the bundle by `.unity` path, and each scene's container entry |
| 147 | +has a null `asset` PPtr (`m_FileID 0`, `m_PathID 0`) because there is no object to point at: |
| 148 | + |
| 149 | +``` |
| 150 | +UnityDataTool dump --stdout BuildPlayer-Scene1.sharedAssets --type AssetBundle |
| 151 | +``` |
| 152 | +``` |
| 153 | +ID: 2 (ClassID: 142) AssetBundle |
| 154 | + m_Name (string) scene1.bundle |
| 155 | + m_PreloadTable (vector) |
| 156 | + Array<PPtr<Object>>[0] |
| 157 | + m_Container (map) |
| 158 | + Array<pair>[1] |
| 159 | + data[0] (pair) |
| 160 | + first (string) Assets/AssetDuplication/Scene1.unity |
| 161 | + second (AssetInfo) |
| 162 | + preloadIndex (int) 0 |
| 163 | + preloadSize (int) 0 |
| 164 | + asset (PPtr<Object>) |
| 165 | + m_FileID (int) 0 |
| 166 | + m_PathID (SInt64) 0 |
| 167 | + m_IsStreamedSceneAssetBundle (bool) True |
| 168 | + m_SceneHashes (map) |
| 169 | + Array<pair>[0] |
| 170 | +``` |
| 171 | + |
| 172 | +Note: the layout for scenes inside an AssetBundle is basically the same as how a Player build builds scenes (except different naming convention for the serialized files). In fact BuildPipeline.BuildAssetBundles and BuildPipeline.BuildPlayer() share much of the same code for processing scenes. |
| 173 | + |
| 174 | +### m_SceneHashes: mapping a scene to its SerializedFile |
| 175 | + |
| 176 | +Because SBP/Addressables name their scene files `CAB-<hash>`, the file name alone does not reveal |
| 177 | +which scene it holds. The AssetBundle object records the mapping in `m_SceneHashes`, from the scene |
| 178 | +path to the scene's SerializedFile name: |
| 179 | + |
| 180 | +``` |
| 181 | + m_SceneHashes (map) |
| 182 | + Array<pair>[145] |
| 183 | + data[0] (pair) |
| 184 | + first (string) Assets/Scenes/Dungeons/BaneChamber/DUN_BaneChamber_DESIGN.unity |
| 185 | + second (string) CAB-256771f7b55e388852b84baa22aeb5b2 |
| 186 | +``` |
| 187 | + |
| 188 | +`BuildPipeline.BuildAssetBundles` leaves `m_SceneHashes` **empty** (as in the single-scene example |
| 189 | +above) and instead encodes the scene name directly in the file name (`BuildPlayer-<SceneName>`). |
| 190 | + |
| 191 | +### PreloadData |
| 192 | + |
| 193 | +Each scene's `.sharedAssets` file contains a **PreloadData** object (ClassID 150) at LFID 1. Its |
| 194 | +`m_Assets` vector lists the `PPtr`s of everything the scene depends on; at runtime Unity walks this |
| 195 | +list to load all required objects before the scene loads. |
| 196 | + |
| 197 | +``` |
| 198 | +UnityDataTool dump --stdout BuildPlayer-Scene1.sharedAssets --type PreloadData |
| 199 | +``` |
| 200 | +``` |
| 201 | +ID: 1 (ClassID: 150) PreloadData |
| 202 | + m_Name (string) |
| 203 | + m_Assets (vector) |
| 204 | + Array<PPtr<Object>>[2] |
| 205 | + data[0] (PPtr<Object>) |
| 206 | + m_FileID (int) 1 |
| 207 | + m_PathID (SInt64) 10001 |
| 208 | + data[1] (PPtr<Object>) |
| 209 | + m_FileID (int) 2 |
| 210 | + m_PathID (SInt64) 4900368479417156912 |
| 211 | + m_Dependencies (vector) |
| 212 | + Array<string>[1] |
| 213 | + data[0] (string) imagelist.bundle |
| 214 | +``` |
| 215 | + |
| 216 | +As with the AssetBundle preload table, each `m_Assets` entry's `m_FileID` selects the file: `0` is |
| 217 | +this file and `1`, `2`, ... are entries in the file's external reference table (other bundles, or |
| 218 | +Unity's built-in resource files). Here `data[1]` points into another bundle (`imagelist.bundle`). |
| 219 | + |
| 220 | +Because PreloadData maps out the whole dependency graph of the objects referenced from a scene, its |
| 221 | +size grows with the number of dependencies. Projects with very large or numerous hard references |
| 222 | +(for example a MonoBehaviour in a scene directly referencing thousands of prefabs) can end up with |
| 223 | +PreloadData tables containing huge numbers of entries, adding significant metadata overhead to the |
| 224 | +build. If PreloadData is contributing excessive size or load time, the usual fix is to break up |
| 225 | +large dependency graphs by loading some assets on demand (via Addressables, AssetBundles, or |
| 226 | +`Resources.Load`) instead of using direct hard references. |
| 227 | + |
| 228 | +## How `analyze` represents this |
| 229 | + |
| 230 | +The [`analyze`](analyzer.md) command turns the above into queryable tables: |
| 231 | + |
| 232 | +- Each explicit `m_Container` asset becomes a row in `assetbundle_assets`, and its preload slice |
| 233 | + becomes rows in `preload_dependencies`. |
| 234 | +- Because a scene has no Unity object, `analyze` synthesizes a "Scene" object per scene to stand in |
| 235 | + for it, so scenes appear in `assetbundle_asset_view` and their PreloadData dependencies appear in |
| 236 | + `preload_dependencies_view`. |
| 237 | + |
| 238 | +See [Analyzer](analyzer.md) for the full schema and the exact behaviour of those views. |
0 commit comments