# 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
```toml
[dependencies]
atx_reader = "0.1"
```
```rust
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:
```text
cargo run --example inspect -- path/to/file.atx
cargo run --example decode_to_png -- path/to/file.atx out.png
```
## Cargo features
| `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:
```toml
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`](src/parser.rs) and
[`src/decode.rs`](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):
| `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:
| `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:
```rust
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](LICENSE-MIT) or
[Apache-2.0](LICENSE-APACHE) at your option.