patterns: Add support for Unity Asset Bundle (#461)

This commit is contained in:
Khoo Hao Yit
2025-12-06 04:14:08 +08:00
committed by GitHub
parent 53384a4a54
commit cc7eb7d764
2 changed files with 178 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
#pragma author Khoo Hao Yit
#pragma description Unity Asset Bundle
#pragma magic [ "UnityWeb\0" ] @ 0x00
#pragma magic [ "UnityRaw\0" ] @ 0x00
#pragma magic [ "UnityArchive\0" ] @ 0x00
#pragma magic [ "UnityFS\0" ] @ 0x00
#pragma endian big
// Reference:
// https://archive.vg-resource.com/thread-43269.html
// https://github.com/K0lb3/UnityPy
// https://github.com/Perfare/AssetStudio
// https://docs.unity3d.com/550/Documentation/Manual/AssetBundleInternalStructure.html
import std.core;
import std.sys;
import hex.dec;
enum Compression: u8 {
None = 0,
Lzma = 1,
Lz4 = 2,
Lz4hc = 3,
Lzham = 4,
};
enum Flag : u32 {
CompressionMask = 0b0000111111,
BlocksAndDirectoryInfoCombined = 0b0001000000,
BlockInfoAtTheEnd = 0b0010000000,
OldWebPluginCompatibility = 0b0100000000,
BlockInfoNeedPaddingAtStart = 0b1000000000,
};
std::mem::Section blockData;
auto cursor = -1;
namespace v1 {
struct Level {
u32 compressedSize;
u32 decompressedSize;
};
struct DirectoryRecord {
char path[];
u32 offset;
u32 size;
std::mem::Bytes<size> data @ offset in blockData;
};
struct Header {
u32 nodeCount;
v1::DirectoryRecord nodes[nodeCount];
};
}
namespace v2 {
struct BlockInfo {
u32 decompressedSize;
u32 compressedSize;
u16 flags;
u8 compressedData[compressedSize] @ cursor in 0 [[sealed, name(std::format("blockInfo{:d}", std::core::array_index()))]];
cursor += compressedSize;
std::mem::Section decompressedData = std::mem::create_section("decompressedData");
match (flags & Flag::CompressionMask) {
(_): std::unimplemented();
(Compression::None): std::mem::copy_value_to_section(compressedData, decompressedData, 0);
(Compression::Lzma): hex::dec::lzma_decompress(compressedData, decompressedData);
(Compression::Lz4): hex::dec::lz4_decompress(compressedData, decompressedData, false);
(Compression::Lz4hc): hex::dec::lz4_decompress(compressedData, decompressedData, false);
(Compression::Lzham): std::unimplemented();
}
std::mem::copy_section_to_section(
decompressedData,
0,
blockData,
std::mem::get_section_size(blockData),
std::mem::get_section_size(decompressedData)
);
std::mem::delete_section(decompressedData);
};
struct DirectoryRecord {
s64 offset;
s64 size;
u32 flags;
char path[];
std::mem::Bytes<size> data @ offset in blockData;
};
struct Header {
blockData = std::mem::create_section("BlockData");
std::mem::Bytes<16> dataHash;
s32 blockInfoCount;
v2::BlockInfo blockInfos[blockInfoCount];
u32 nodeCount;
v2::DirectoryRecord nodes[nodeCount];
};
}
struct AssetBundle {
char signature[];
u32 version;
char unityVersion[];
char unityRevision[];
if (signature == "UnityArchive\0") {
std::unimplemented();
}
if (signature != "UnityFS\0" && version != 6) {
if (version >= 4) {
std::mem::Bytes<16> hash;
u32 crc;
}
u32 minimumStreamedBytes;
u32 size;
u32 numberOfLevelsToDownloadBeforeStreaming;
s32 levelCount;
v1::Level levels[levelCount];
if (version >= 2) {
u32 completeFileSize;
}
if (version >= 3) {
u32 fileInfoHeaderSize;
}
$ = size;
u8 compressedBlockInfo[levels[std::core::member_count(levels) - 1].compressedSize] [[sealed]];
blockData = std::mem::create_section("BlockInfoAndData");
if (signature == "UnityWeb\0") {
hex::dec::lzma_decompress(compressedBlockInfo, blockData);
} else {
std::mem::copy_value_to_section(compressedBlockInfo, blockData, 0);
}
v1::Header header @ 0 in blockData;
return;
}
s64 size;
u32 compressedBlockInfoSize;
u32 decompressedBlockInfoSize;
u32 flags;
if (signature != "UnityFS\0") {
$ += 1;
}
if (version >= 7) {
$ += -$ & 0b1111;
}
if (flags & Flag::BlockInfoAtTheEnd) {
u8 compressedBlockInfo[compressedBlockInfoSize] @ std::mem::size() - compressedBlockInfo [[sealed]];
} else {
std::assert_warn(flags & Flag::BlocksAndDirectoryInfoCombined, "Expected BlocksAndDirectoryInfoCombined to be true");
u8 compressedBlockInfo[compressedBlockInfoSize] [[sealed]];
}
if (flags & Flag::BlockInfoNeedPaddingAtStart) {
$ += -$ & 0b1111;
}
std::mem::Section blockInfo = std::mem::create_section("BlockInfo");
match (flags & Flag::CompressionMask) {
(_): std::unimplemented();
(Compression::None): std::mem::copy_value_to_section(compressedBlockInfo, blockInfo, 0);
(Compression::Lzma): hex::dec::lzma_decompress(compressedBlockInfo, blockInfo);
(Compression::Lz4): hex::dec::lz4_decompress(compressedBlockInfo, blockInfo, false);
(Compression::Lz4hc): hex::dec::lz4_decompress(compressedBlockInfo, blockInfo, false);
(Compression::Lzham): std::unimplemented();
}
cursor = $;
v2::Header header @ 0 in blockInfo;
};
AssetBundle assetBundle @ 0;