Skip to content

Read storage block offsets from the archive BlocksInfo (format version 9) - #128

Draft
SkowronskiAndrew wants to merge 2 commits into
mainfrom
chunk-alignment-change
Draft

Read storage block offsets from the archive BlocksInfo (format version 9)#128
SkowronskiAndrew wants to merge 2 commits into
mainfrom
chunk-alignment-change

Conversation

@SkowronskiAndrew

@SkowronskiAndrew SkowronskiAndrew commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

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 to
the next 16-byte boundary. The current approach instead has each StorageBlock record its own
offset, 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 putting
absolute 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.cs

  • StorageBlock.Offset (UInt64, big-endian) is parsed as the fourth field of each block
    record, after Flags. Note the serialized order differs from the C++ struct layout, where
    offset is declared first.
  • Gated on Signature == "UnityFS" && Version >= 9, mirroring Header::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.
  • A v9 archive whose blocks are out of order or overlapping is rejected, matching the kError
    in ReadBlocksInfo. These offsets are corruption-controlled input rather than derived from
    sizes, so this is the one new failure mode the format change introduces.
  • Removed HasBlockPaddingBetweenChunks and the 0x400 flag constant; added
    HasStorageBlockOffsets.

Reporting — Archive/ArchiveTool.cs

  • BlockPaddingBetweenChunks removed from the flag name table, since the bit is no longer part
    of the format.
  • archive info keeps the Block Padding Size / blockPaddingSize field but now derives it
    from the stored offsets rather than an assumed alignment rule. Without it, Data Size silently
    stops accounting for the whole data section.

Test data

  • UnityProjects/LeadingEdge/Assets/Editor/BuildAssetBundles.cs has a BuildLz4 entry point
    (ChunkBasedCompression) writing the same bundle layout to a separate folder. The existing
    LZMA 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. The
    scenes bundle is the interesting one: 11 blocks, 80 bytes of padding, with
    BuildPlayer-Scene1.sharedAssets spanning the first eight.
  • The two mp3 .meta files gain explicit AudioImporter settings (GUIDs unchanged), which the
    LeadingEdge project needs in order to build with a 6.7 editor at all.

Testing

dotnet test790 passed, 10 skipped, 0 failed.

New coverage in UnityDataTool.Tests/ArchiveTests.cs:

  • ArchiveBlocks_Version9_OffsetsComeFromTheBlockList — checks the parsed offsets against the
    actual 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, against
    PlayerDataCompressed/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_alignment at 44a19c46:

Check Result
Offset field is really serialized, and sized right Uncompressed metadata is 404 bytes = 16 + 4 + 11×18 + 186. At the old 10-byte record it was 316. GetBlocksInfoSize therefore agrees with the bytes written, which the writer asserts.
Offsets point at the real block positions An independent C# parse and the native reader agree on all 11 offsets, and each block decompresses from its recorded position.
Padding is excluded from compressedSize Every byte in every inter-block gap is zero, across all 8 bundles.
Blocks ordered and non-overlapping The invariants hold on every bundle; the parser validates and accepts them.
Flag removal is complete in the writer BlockPaddingBetweenChunks absent from every regenerated header.
Content survives the round trip Extraction is byte-identical to the source files, including a file spanning 8 non-contiguous blocks.
StoreStream path (LZMA / uncompressed), not just StoreChunk Both produce a single block at offset 0 with no padding, and round-trip byte-identically. Driven through UFS_CreateArchive.
v8 archives unaffected The v8 bundles in the test data still parse and extract; the legacy test asserts contiguity.

Two writer paths were exercised: the Editor's AssetBundle build, and UFS_CreateArchive via the
native library. Both use the BlocksInfo-at-the-start layout.

Residual gaps, neither reachable from here: multiple streamed blocks in one archive (needs

4 GB to trigger the 32-bit size overflow) and a mixed streamed + chunk archive. Both are covered
by the native ArchiveStorage tests on the Unity side.

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 Windows
only. The checked-in UnityFileSystemApi is 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 analyze and serialized-file cannot inspect this data; the
archive commands and dump (which go through the native library) can. Worth knowing: analyze
surfaces this as Sequence contains no elements, via the C# parse in
SerializedFileSQLiteWriter.WriteSerializedFile, where serialized-file gives the correct
"version 26 is not supported" message.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 BlockPaddingBetweenChunks is set, and consolidate header flag constants into a shared ArchiveFlags definition.
  • Improve reporting: recognize the new flag in archive header output and report total block padding size in archive 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.

Comment thread UnityDataTool.Tests/ArchiveTests.cs
Comment thread UnityDataTool.Tests/ArchiveTests.cs
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.
@SkowronskiAndrew SkowronskiAndrew changed the title Support archive format version 9 chunk alignment padding Read storage block offsets from the archive BlocksInfo (format version 9) Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants