Read storage block offsets from the archive BlocksInfo (format version 9) - #128
Draft
SkowronskiAndrew wants to merge 2 commits into
Draft
Read storage block offsets from the archive BlocksInfo (format version 9)#128SkowronskiAndrew wants to merge 2 commits into
SkowronskiAndrew wants to merge 2 commits into
Conversation
Unity PR 119906 bumps the Unity Archive format to version 9 and introduces the kArchiveBlockPaddingBetweenChunks flag: each chunk-based (non-streamed) storage block is followed by zero padding up to the next 16-byte boundary of the data section, so that a size change in one chunk no longer shifts the position of every chunk after it (this aids binary patching). Block positions are not stored in the block list, so a reader has to accumulate them from the block sizes. Align that running offset after each non-streamed block when the flag is set, matching ArchiveStorageReader in the Unity runtime. Also report the new flag in `archive header` and the padding total in `archive info`, and add LZ4 reference bundles from the LeadingEdge project as new-format test data.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds proactive support for Unity Archive format v9’s BlockPaddingBetweenChunks flag by updating the C# archive parser to correctly skip 16-byte alignment padding between non-streamed blocks, and extends CLI reporting + test data/tests to validate the new layout.
Changes:
- Update archive block offset accumulation to align after each non-streamed block when
BlockPaddingBetweenChunksis set, and consolidate header flag constants into a sharedArchiveFlagsdefinition. - Improve reporting: recognize the new flag in
archive headeroutput and report total block padding size inarchive info. - Add LeadingEdge LZ4 (chunk-based) AssetBundle build output and new tests that validate padding bytes and alignment behavior.
Reviewed changes
Copilot reviewed 14 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| UnityBinaryFormat/ArchiveDetector.cs | Adds v9 padding-aware block offset accumulation and introduces shared ArchiveFlags. |
| Archive/ArchiveTool.cs | Reports BlockPaddingBetweenChunks as a known flag and adds blockPaddingSize reporting in archive info. |
| UnityDataTool.Tests/ArchiveTests.cs | Adds tests for v9 flag reporting, block alignment/padding verification, and native extraction on padded archives. |
| UnityProjects/LeadingEdge/Assets/Editor/BuildAssetBundles.cs | Adds a second build entry point to generate LZ4 chunk-based bundles into a separate folder. |
| UnityProjects/LeadingEdge/AGENTS.md | Documents the new LZ4 build entry point and its purpose. |
| TestCommon/Data/LeadingEdgeBuilds/AGENTS.md | Documents the new AssetBundlesLz4/ test-data folder and why it exists. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/AssetBundlesLz4.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/a.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/6.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/assetbundleroot.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/directaudioclipreference.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/serializationdemo.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/scenes.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
| TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/singleaudioclipdirectreference.manifest | Adds new LeadingEdge LZ4 bundle manifest test data. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The v9 format design changed: instead of a kArchiveBlockPaddingBetweenChunks flag telling readers to round each block's offset up to a 16 byte boundary, each StorageBlock now serializes a UInt64 offset giving where its stored bytes start, relative to the start of the data section. The writer is then free to change its padding policy without another format bump, and the reader no longer needs the "streamed blocks are not padded, chunk blocks are" special case. Parse that offset as the fourth field of each block record, after Flags, when the signature is UnityFS and the version is 9 or later. Version 8 and earlier store their blocks contiguously, so their offsets are still accumulated from the compressed sizes. A v9 archive whose blocks are out of order or overlapping is rejected, matching the native reader. The archive flag is gone from the format, so it is gone from the flag table too, and `archive info` now derives the padding total from the stored offsets. Regenerates the LZ4 reference bundles in the new format. Their SerializedFiles are version 26, which the C# metadata parser does not read yet (issue #130), so `analyze` and `serialized-file` cannot inspect this data; the `archive` commands and `dump` can. The mp3 .meta files gain explicit importer settings, which the project needs in order to build with a 6.7 editor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Proactive support for an upcoming Unity Archive format change, so we find out whether it
causes UnityDataTools any trouble before the format change lands. The archive work is on
unity/unity#119906.
The design of that change was revised, and this PR now reflects the revision. The original
approach bumped the format to version 9 and added a header flag,
kArchiveBlockPaddingBetweenChunks, telling readers to round each block's start offset up tothe next 16-byte boundary. The current approach instead has each
StorageBlockrecord its ownoffset, and the flag is gone.
That is the better trade for a reader. Block positions are no longer reconstructed from a
padding rule that the reader has to know and the writer has to keep, so the writer can change
its alignment policy later (4 KB for DirectStorage, none at all) with no further format bump,
and the "streamed blocks are not padded, chunk blocks are" special case disappears.
The offset is relative to the start of the data section, not the file. It has to be: in the
BlocksInfo-at-the-start layout that AssetBundle builds use, the absolute data offset depends on
compressedBlocksInfoSize, which is not known until every block has been written — and puttingabsolute offsets in the blocks would change the bytes that get compressed, which changes that
size, which changes the offsets. Readers add
GetDataOffset(header).Changes
Parsing —
UnityBinaryFormat/ArchiveDetector.csStorageBlock.Offset(UInt64, big-endian) is parsed as the fourth field of each blockrecord, after
Flags. Note the serialized order differs from the C++ struct layout, whereoffsetis declared first.Signature == "UnityFS" && Version >= 9, mirroringHeader::HasStorageBlockOffsets().Version 8 and earlier store blocks contiguously, so those offsets are still accumulated from
the compressed sizes — the pre-existing behaviour, now the explicit legacy path.
kErrorin
ReadBlocksInfo. These offsets are corruption-controlled input rather than derived fromsizes, so this is the one new failure mode the format change introduces.
HasBlockPaddingBetweenChunksand the0x400flag constant; addedHasStorageBlockOffsets.Reporting —
Archive/ArchiveTool.csBlockPaddingBetweenChunksremoved from the flag name table, since the bit is no longer partof the format.
archive infokeeps theBlock Padding Size/blockPaddingSizefield but now derives itfrom the stored offsets rather than an assumed alignment rule. Without it,
Data Sizesilentlystops accounting for the whole data section.
Test data
UnityProjects/LeadingEdge/Assets/Editor/BuildAssetBundles.cshas aBuildLz4entry point(
ChunkBasedCompression) writing the same bundle layout to a separate folder. The existingLZMA reference bundles are deliberately untouched — both the v8 contiguous layout and the v9
offset layout are wanted as reference data.
TestCommon/Data/LeadingEdgeBuilds/AssetBundlesLz4/regenerated in the new format. Thescenesbundle is the interesting one: 11 blocks, 80 bytes of padding, withBuildPlayer-Scene1.sharedAssetsspanning the first eight..metafiles gain explicitAudioImportersettings (GUIDs unchanged), which theLeadingEdge project needs in order to build with a 6.7 editor at all.
Testing
dotnet test— 790 passed, 10 skipped, 0 failed.New coverage in
UnityDataTool.Tests/ArchiveTests.cs:ArchiveBlocks_Version9_OffsetsComeFromTheBlockList— checks the parsed offsets against theactual file: blocks in order, every byte of every gap between them zero, and a non-zero total
gap. That last assertion matters: without it the test would still pass on a contiguous archive
even if the stored offsets were being ignored entirely.
ArchiveBlocks_LegacyVersion8_BlocksAreContiguous— the accumulate fallback, againstPlayerDataCompressed/data.unity3d, a real v8 8-block Lz4 archive.ArchiveHeader_Version9_AllFlagsRecognized— version 9, and no flag bit reported as raw hex(which would mean a flag we don't know about).
ArchiveInfo_Version9_ReportsPaddingSize,ArchiveExtract_Version9_FilesExtractedSuccessfully.Validating that the writer is correct
This branch is also meant to be evidence that the v9 writer on the Unity side is right, so the
checks were aimed at the produced bytes rather than at self-consistency. Against a debug build
of
buildpipeline/archive_alignmentat44a19c46:GetBlocksInfoSizetherefore agrees with the bytes written, which the writer asserts.compressedSizeBlockPaddingBetweenChunksabsent from every regenerated header.StoreStreampath (LZMA / uncompressed), not justStoreChunkUFS_CreateArchive.Two writer paths were exercised: the Editor's AssetBundle build, and
UFS_CreateArchivevia thenative library. Both use the BlocksInfo-at-the-start layout.
Residual gaps, neither reachable from here: multiple streamed blocks in one archive (needs
Draft: what is still needed before this lands
The native library is not included. Validation used a local debug build of
buildpipeline/archive_alignment; committing it would mean a 10 MB debug DLL covering Windowsonly. The checked-in
UnityFileSystemApiis still v8-era and rejects these v9 archives cleanly(
Invalid file format) rather than misreading them — which is the version bump doing its job —so the tests that mount an archive will fail on CI until official builds for all three platforms
are dropped in.
The regenerated bundles contain SerializedFile version 26, which the C# metadata parser does
not read yet — tracked separately in #130, and true of all new Unity 6.7 content, not something
this change introduces. So
analyzeandserialized-filecannot inspect this data; thearchivecommands anddump(which go through the native library) can. Worth knowing:analyzesurfaces this as
Sequence contains no elements, via the C# parse inSerializedFileSQLiteWriter.WriteSerializedFile, whereserialized-filegives the correct"version 26 is not supported" message.