atx_reader
Parser and decoder for Apple .atx texture archives — an AAPL-container
format that wraps ASTC-compressed image data, used by Apple internally and
surfaced in third-party forensic exports such as Cellebrite UFED.
Quick start
[]
= "0.1"
let bytes = read?;
// High-level: get an RGBA8 buffer at the original (width, height).
let img = decode?;
assert_eq!;
// With the `image` feature (default), save directly to disk.
let rgba = decode_to_image?;
rgba.save?;
CLI examples:
cargo run --example inspect -- path/to/file.atx
cargo run --example decode_to_png -- path/to/file.atx out.png
Cargo features
| Feature | Default | Pulls in | Effect |
|---|---|---|---|
decode |
yes | astc-decode |
Enables decode / decode_with / AtxDecoder |
image |
yes | image (PNG) |
Enables decode_to_image returning RgbaImage |
lzfse |
yes | lzfse_rust |
Adds support for the LZFS chunk (compressed variant) |
Disable defaults if you only need parsing:
= { = "0.1", = false }
API surface
AtxContainer::parse(&bytes)— validate theAAPL\r\n\x1a\nsignature and obtain a zero-copy view.container.chunks()— iterate over well-formed chunks.container.header()— parse theHEADchunk intoAtxHeader.container.astc_payload()— slice the raw ASTC blocks, robust to alignment padding between the last walked chunk and theastcmarker.decode(&bytes)/decode_with(&bytes, &opts)— full pipeline.AtxDecoder::new(header, payload)— when you already have the parts.
DecodeOptions lets you pick the [AstcFootprint], the
[PayloadLayout], and override the padded image size if your sample
uses a different scheme than the default.
Format notes
The crate was reverse-engineered against real .atx samples. The verified
layout is documented below; see src/parser.rs and
src/decode.rs for the authoritative reference.
Container
- 8-byte signature:
41 41 50 4C 0D 0A 1A 0A="AAPL\r\n\x1a\n"(PNG-style trailer for transport-corruption detection). - Chunks follow immediately, in
[size:u32 LE][tag:4 ASCII][payload:size bytes]layout.sizecovers the payload only.
HEAD chunk
Payload offsets observed in the verified sample (relative to the start of the chunk's payload):
| Offset | Type | Field |
|---|---|---|
0x00 |
u32 | flags / version |
0x18 |
u32 | width |
0x1C |
u32 | height |
0x20 |
u32 | depth |
0x28 |
u32 | array layers |
0x2C |
u32 | mipmap count |
0x3C |
16 B | per-texture UUID |
0x4C |
u32 | pixel-format discriminator A |
0x50 |
u32 | pixel-format discriminator B |
The exact (A, B) → ASTC-footprint mapping is proprietary. Only (3, 5) → Astc4x4
is confirmed today; contributions of new samples welcome.
FILL chunk
All-zero padding for GPU alignment. Skipped by the decoder.
astc chunk
[size][astc][inner_size:u32 LE][raw ASTC blocks]. The 4-byte inner_size
field sits between the tag and the actual block data — astc_payload()
already accounts for this. Used by snapshot variants (iOS lock-screen and
snapshot-switcher captures, ~3 MB files).
LZFS chunk
[size][LZFS][inner_size:u32 LE][LZFSE stream bvx2/bvxn...]. The
decompressed output is a linear row-major sequence of raw ASTC 4×4 blocks
at the unpadded block grid (ceil(W/4) × ceil(H/4) blocks). Used by camera
thumbnails and camera-grid icons (kilobyte-scale files).
Decode pipeline
The high-level decode / decode_to_image entry points auto-detect the
chunk type and apply the right layout:
| Chunk | Decompress? | Default layout | Default padding |
|---|---|---|---|
astc |
no | macro-tiled Morton, macro_blocks=32 |
next multiple of 32 × 4 = 128 px |
LZFS |
LZFSE | linear row-major | next multiple of 4 px |
For the canonical astc-chunk layout:
- Read
width × heightfromHEAD. - Pad each dimension up to a multiple of
128px (e.g.1170 × 2532 → 1280 × 2560). - Iterate the payload as
32 × 32-block macro-tiles in row-major order; inside each macro-tile, blocks are in Morton (Z-order) order. - Re-linearize the block grid, ASTC-decode at the padded size, crop to
(width, height).
For the LZFS-chunk layout:
- Decompress the
bvx2LZFSE stream — the result is exactlyceil(W/4) × ceil(H/4) × 16bytes of raw ASTC 4×4 blocks. - ASTC-decode at the block-aligned size and crop to
(width, height).
To target a sample that does not match this layout (e.g. plain linear ASTC), build a decoder explicitly:
use ;
let c = parse?;
let img = new
.footprint
.layout
.decode?;
License
Licensed under either of MIT or Apache-2.0 at your option.