atx_reader 0.1.0

Parser and decoder for Apple .atx texture archives (AAPL container with ASTC payload), as produced by tools like Cellebrite UFED iOS exports.
Documentation

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

[dependencies]
atx_reader = "0.1"
let bytes = std::fs::read("snapshot.atx")?;

// High-level: get an RGBA8 buffer at the original (width, height).
let img = atx_reader::decode(&bytes)?;
assert_eq!(img.pixels.len(), (img.width * img.height * 4) as usize);

// With the `image` feature (default), save directly to disk.
let rgba = atx_reader::decode_to_image(&bytes)?;
rgba.save("snapshot.png")?;

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:

atx_reader = { version = "0.1", default-features = false }

API surface

  • AtxContainer::parse(&bytes) — validate the AAPL\r\n\x1a\n signature and obtain a zero-copy view.
  • container.chunks() — iterate over well-formed chunks.
  • container.header() — parse the HEAD chunk into AtxHeader.
  • container.astc_payload() — slice the raw ASTC blocks, robust to alignment padding between the last walked chunk and the astc marker.
  • 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. size covers 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:

  1. Read width × height from HEAD.
  2. Pad each dimension up to a multiple of 128 px (e.g. 1170 × 2532 → 1280 × 2560).
  3. Iterate the payload as 32 × 32-block macro-tiles in row-major order; inside each macro-tile, blocks are in Morton (Z-order) order.
  4. Re-linearize the block grid, ASTC-decode at the padded size, crop to (width, height).

For the LZFS-chunk layout:

  1. Decompress the bvx2 LZFSE stream — the result is exactly ceil(W/4) × ceil(H/4) × 16 bytes of raw ASTC 4×4 blocks.
  2. 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 atx_reader::{AtxContainer, AtxDecoder, AstcFootprint, PayloadLayout};

let c = AtxContainer::parse(&bytes)?;
let img = AtxDecoder::new(c.header()?, c.astc_payload()?)
    .footprint(AstcFootprint::Astc8x8)
    .layout(PayloadLayout::Linear)
    .decode()?;

License

Licensed under either of MIT or Apache-2.0 at your option.