Skip to content

Publish Cryptex1 DDI assets, and a script to refresh them - #27

Merged
doronz88 merged 4 commits into
mainfrom
feature/cryptex-ddi-variant
Aug 7, 2026
Merged

Publish Cryptex1 DDI assets, and a script to refresh them#27
doronz88 merged 4 commits into
mainfrom
feature/cryptex-ddi-variant

Conversation

@doronz88

@doronz88 doronz88 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Why

iOS 17+ can have the DeveloperDiskImage installed as a cryptex through cryptexd, rather than mounted through the image mounter. That path needs four Cryptex1,* payloads which exist only inside Xcode's own bundle — the personalized variant published here ships PersonalizedDMG, which has no cryptex info plist or volume root hash. So today a host without Xcode (notably Windows) cannot install a DDI that way at all.

This publishes those assets as their own variant, and adds a script so refreshing every variant is reproducible instead of a manual copy out of /Library/Developer.

What's here

  • update_ddi.py — a PEP 723 script (no third-party deps, uv run --script update_ddi.py). It locates the platform's Restore directory, attaching CoreDevice/CandidateDDIs/<platform>_DDI.dmg when Xcode has not expanded one, verifies every payload against the SHA-384 digests in the build manifest, and removes stale files left by a previous build. Has --dry-run, --platform, --variant.
  • PersonalizedImages/Xcode_iOS_DDI_Cryptex/ — the new variant.
  • get_cryptex_disk_image() — returns the four payloads plus the build manifest.
  • DDIs updated to 27A5228h (personalized was 27A5194q).
  • Payloads now come from raw.githubusercontent.com instead of the REST API.

Predictable names

Every payload is published under a fixed name (Image.dmg, Image.dmg.trustcache, Image.dmg.cryptex_info, Image.dmg.root_hash), so download URLs are stable across releases. Apple's own paths embed a build number (022-22107-072.dmg), so the published BuildManifest.plist is rewritten to declare the fixed names. Only Info.Path is adjusted — the digests personalization relies on are untouched — which keeps a downloaded cryptex directory usable as a drop-in Restore directory.

No more rate limiting

Reading blobs through the REST API cost a recursive tree listing of a repository full of disk images on every create(), and burned the anonymous 60-per-hour quota — which is why the tests were xfailed on GithubRateLimitExceededError instead of simply expected to pass. raw.githubusercontent.com is not under that quota, needs no token, and serves any ref. Since every payload now sits at a known fixed path, the tree listing bought nothing.

Consequently create() performs no request at all and takes a ref, so the tests check the revision under test rather than main — previously a PR adding payloads could not see its own files and could only go green after merging. API change: DeveloperDiskImageRepository.__init__ no longer takes a tree.

Verification

The published directory was used to install the DDI on a physical iPhone 11 (iOS 27) end to end, over cryptexd alone, via pymobiledevice3 cryptex auto-install --restore-dir <this directory>. Digest verification passes for every payload; the script is idempotent (a second run reports zero changes) and each commit is flake8/isort clean.

Follow-up

LATEST_DDI_BUILD_ID in pymobiledevice3 needs bumping to 27A5228h, and cryptex auto-install can drop its Xcode requirement once a developer_disk_image release carries get_cryptex_disk_image().

Publishing a new DDI was a manual copy out of `/Library/Developer`. This
script does it reproducibly: it locates the platform's `Restore` directory
(attaching `CoreDevice/CandidateDDIs/<platform>_DDI.dmg` when Xcode has not
expanded one), verifies every payload against the SHA-384 digests its build
manifest declares, and republishes it under fixed file names so download URLs
stay predictable across releases.

Apple's own paths embed a build number, so the published `BuildManifest.plist`
is rewritten to declare the fixed names instead. Only `Info.Path` is adjusted;
the digests personalization relies on are untouched.

It is a PEP 723 script with no third-party dependencies -- run it with
`uv run --script update_ddi.py`.
iOS 17+ can have the DeveloperDiskImage installed as a cryptex through
`cryptexd` instead of mounted through the image mounter. That path needs four
`Cryptex1,*` payloads which exist only inside Xcode's own bundle -- the
personalized variant published here ships `PersonalizedDMG`, which has no
cryptex info plist or volume root hash -- so hosts without Xcode, notably
Windows, cannot install a DDI that way at all today.

Publish them as their own variant alongside the personalized one, and add
`get_cryptex_disk_image()` to reach it. Written back out under their published
names, the result is a directory `cryptexd` clients accept as-is, since the
build manifest shipped with them declares those same names.

Note `test_cryptex_disk_image` reads the `main` tree, so it only goes green
once this is merged -- the same chicken-and-egg the personalized test had.
Refreshed with `update_ddi.py`. The personalized payloads move from 27A5194q,
and their `BuildManifest.plist` now declares the published file names.

Bump `LATEST_DDI_BUILD_ID` in pymobiledevice3 to 27A5228h to match.
Reading blobs through the REST API cost a recursive tree listing of a
repository full of disk images on every `create()`, and burned the anonymous
60-requests-per-hour quota -- which is why the tests are `xfail`ed on
`GithubRateLimitExceededError` rather than simply expected to pass.

`raw.githubusercontent.com` is not under that quota, needs no token, and serves
any ref. Since every payload now lives at a known fixed path, the tree listing
bought nothing: resolve paths directly, and treat 404 as "not published".

`create()` therefore performs no request at all, and takes a `ref` so a caller
can read a revision that is not merged yet. The tests use it to check the
revision under test instead of `main`, which they could not see their own
payloads from.

`DeveloperDiskImageRepository.__init__` no longer takes a tree.
@doronz88
doronz88 merged commit 1daa8aa into main Aug 7, 2026
15 checks passed
@doronz88
doronz88 deleted the feature/cryptex-ddi-variant branch August 7, 2026 21:04
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.

1 participant