Skip to content

Commit 77db5ce

Browse files
committed
patterns: Add support for Unity Asset Bundle
1 parent a350046 commit 77db5ce

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
188188
| UEFI Boot Entry | | [`patterns/uefi_boot_entry.hexpat`](patterns/uefi_boot_entry.hexpat) | UEFI Boot Entry (Load option) |
189189
| UEFI Variable Store | | [`patterns/uefi_fv_varstore.hexpat`](patterns/uefi_fv_varstore.hexpat) | UEFI Firmware Volume Variable Store |
190190
| UF2 | | [`patterns/uf2.hexpat`](patterns/uf2.hexpat) | [USB Flashing Format](https://github.com/microsoft/uf2) |
191+
| Unity Asset Bundle | | [`patterns/unity-asset-bundle.hexpat`](patterns/unity-asset-bundle.hexpat) | Unity Asset Bundle |
191192
| Valve VPK | | [`patterns/valve_vpk.hexpat`](valve_vpk.hexpat) | Valve Package File |
192193
| VBMeta | | [`patterns/vbmeta.hexpat`](patterns/vbmeta.hexpat) | Android VBMeta image |
193194
| VDF | | [`patterns/vdf.hexpat`](patterns/vdf.hexpat) | Binary Value Data Format (.vdf) files |

patterns/unity-asset-bundle.hexpat

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#pragma author Khoo Hao Yit
2+
#pragma description Unity Asset Bundle
3+
4+
#pragma magic [ "UnityWeb\0" ] @ 0x00
5+
#pragma magic [ "UnityRaw\0" ] @ 0x00
6+
#pragma magic [ "UnityArchive\0" ] @ 0x00
7+
#pragma magic [ "UnityFS\0" ] @ 0x00
8+
#pragma endian big
9+
10+
// Reference:
11+
// https://archive.vg-resource.com/thread-43269.html
12+
// https://github.com/K0lb3/UnityPy
13+
// https://github.com/Perfare/AssetStudio
14+
// https://docs.unity3d.com/550/Documentation/Manual/AssetBundleInternalStructure.html
15+
16+
import std.core;
17+
import std.sys;
18+
import hex.dec;
19+
20+
enum Compression: u8 {
21+
None = 0,
22+
Lzma = 1,
23+
Lz4 = 2,
24+
Lz4hc = 3,
25+
Lzham = 4,
26+
};
27+
28+
enum Flag : u32 {
29+
CompressionMask = 0b0000111111,
30+
BlocksAndDirectoryInfoCombined = 0b0001000000,
31+
BlockInfoAtTheEnd = 0b0010000000,
32+
OldWebPluginCompatibility = 0b0100000000,
33+
BlockInfoNeedPaddingAtStart = 0b1000000000,
34+
};
35+
36+
std::mem::Section blockData;
37+
auto cursor = -1;
38+
39+
namespace v1 {
40+
struct Level {
41+
u32 compressedSize;
42+
u32 decompressedSize;
43+
};
44+
45+
struct DirectoryRecord {
46+
char path[];
47+
u32 offset;
48+
u32 size;
49+
50+
std::mem::Bytes<size> data @ offset in blockData;
51+
};
52+
53+
struct Header {
54+
u32 nodeCount;
55+
v1::DirectoryRecord nodes[nodeCount];
56+
};
57+
}
58+
59+
namespace v2 {
60+
struct BlockInfo {
61+
u32 decompressedSize;
62+
u32 compressedSize;
63+
u16 flags;
64+
65+
u8 compressedData[compressedSize] @ cursor in 0 [[sealed, name(std::format("blockInfo{:d}", std::core::array_index()))]];
66+
cursor += compressedSize;
67+
68+
std::mem::Section decompressedData = std::mem::create_section("decompressedData");
69+
match (flags & Flag::CompressionMask) {
70+
(_): std::unimplemented();
71+
(Compression::None): std::mem::copy_value_to_section(compressedData, decompressedData, 0);
72+
(Compression::Lzma): hex::dec::lzma_decompress(compressedData, decompressedData);
73+
(Compression::Lz4): hex::dec::lz4_decompress(compressedData, decompressedData, false);
74+
(Compression::Lz4hc): hex::dec::lz4_decompress(compressedData, decompressedData, false);
75+
(Compression::Lzham): std::unimplemented();
76+
}
77+
std::mem::copy_section_to_section(
78+
decompressedData,
79+
0,
80+
blockData,
81+
std::mem::get_section_size(blockData),
82+
std::mem::get_section_size(decompressedData)
83+
);
84+
std::mem::delete_section(decompressedData);
85+
};
86+
87+
struct DirectoryRecord {
88+
s64 offset;
89+
s64 size;
90+
u32 flags;
91+
char path[];
92+
93+
std::mem::Bytes<size> data @ offset in blockData;
94+
};
95+
96+
struct Header {
97+
blockData = std::mem::create_section("BlockData");
98+
std::mem::Bytes<16> dataHash;
99+
s32 blockInfoCount;
100+
v2::BlockInfo blockInfos[blockInfoCount];
101+
u32 nodeCount;
102+
v2::DirectoryRecord nodes[nodeCount];
103+
};
104+
}
105+
106+
struct AssetBundle {
107+
char signature[];
108+
u32 version;
109+
char unityVersion[];
110+
char unityRevision[];
111+
if (signature == "UnityArchive\0") {
112+
std::unimplemented();
113+
}
114+
if (signature != "UnityFS\0" && version != 6) {
115+
if (version >= 4) {
116+
std::mem::Bytes<16> hash;
117+
u32 crc;
118+
}
119+
u32 minimumStreamedBytes;
120+
u32 size;
121+
u32 numberOfLevelsToDownloadBeforeStreaming;
122+
s32 levelCount;
123+
v1::Level levels[levelCount];
124+
if (version >= 2) {
125+
u32 completeFileSize;
126+
}
127+
if (version >= 3) {
128+
u32 fileInfoHeaderSize;
129+
}
130+
$ = size;
131+
u8 compressedBlockInfo[levels[std::core::member_count(levels) - 1].compressedSize] [[sealed]];
132+
blockData = std::mem::create_section("BlockInfoAndData");
133+
if (signature == "UnityWeb\0") {
134+
hex::dec::lzma_decompress(compressedBlockInfo, blockData);
135+
} else {
136+
std::mem::copy_value_to_section(compressedBlockInfo, blockData, 0);
137+
}
138+
v1::Header header @ 0 in blockData;
139+
return;
140+
}
141+
s64 size;
142+
u32 compressedBlockInfoSize;
143+
u32 decompressedBlockInfoSize;
144+
u32 flags;
145+
if (signature != "UnityFS\0") {
146+
$ += 1;
147+
}
148+
if (version >= 7) {
149+
$ += $ % 16 ? 16 - $ % 16 : 0;
150+
}
151+
152+
if (flags & Flag::BlockInfoAtTheEnd) {
153+
u8 compressedBlockInfo[compressedBlockInfoSize] @ std::mem::size() - compressedBlockInfo [[sealed]];
154+
} else {
155+
std::assert_warn(flags & Flag::BlocksAndDirectoryInfoCombined, "Expected BlocksAndDirectoryInfoCombined to be true");
156+
u8 compressedBlockInfo[compressedBlockInfoSize] [[sealed]];
157+
}
158+
159+
if (flags & Flag::BlockInfoNeedPaddingAtStart) {
160+
$ += $ % 16 ? 16 - $ % 16 : 0;
161+
}
162+
163+
std::mem::Section blockInfo = std::mem::create_section("BlockInfo");
164+
match (flags & Flag::CompressionMask) {
165+
(_): std::unimplemented();
166+
(Compression::None): std::mem::copy_value_to_section(compressedBlockInfo, blockInfo, 0);
167+
(Compression::Lzma): hex::dec::lzma_decompress(compressedBlockInfo, blockInfo);
168+
(Compression::Lz4): hex::dec::lz4_decompress(compressedBlockInfo, blockInfo, false);
169+
(Compression::Lz4hc): hex::dec::lz4_decompress(compressedBlockInfo, blockInfo, false);
170+
(Compression::Lzham): std::unimplemented();
171+
}
172+
173+
cursor = $;
174+
v2::Header header @ 0 in blockInfo;
175+
};
176+
177+
AssetBundle assetBundle @ 0;

0 commit comments

Comments
 (0)