mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-10-02 08:04:32 +08:00
Remove --force-dsc-maxslide from every command and from Launchpad
The flag could not do anything any more. `dsc_maxslide.zero` is pinned to iOS 27, and on a 27 base the installer's force branch was never reached: it sat below the `27.` and `26.0`/`18.` cases. Issue #531 credited it with a fix it could not have made. The self-gate needs no force on 27. The pristine 24A435 cache reads size 0x17D504000 + maxSlide 0x20000000 = 0x19D504000, over the 6 GiB region, and `patch-dsc-maxslide --dry-run` reports overflow. XNU's shared_region_map_and_slide_2_np picks a 16 KiB-aligned slide below maxSlide and maps each range FIXED in the 0x180000000 submap, so size + maxSlide <= region is a sufficient test. Removed from vm create, restore, cfw install and install-root, the create options, the Launchpad new-machine sheet and control verbs, and the helper's installCustomFirmware XPC signature. A helper built before this has a different hash and shows as outdated, so Launchpad reinstalls it first. `patch-dsc-maxslide --force` stays on the standalone verb. Docs: troubleshooting no longer offers the long-gone --force-exc-guard, and FORCE_DSC_MAXSLIDE is gone from the patcher, verb help, research notes and the patch-set skill. Row 10 of the patch comparison records the 24A435 numbers and the source check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ Use the VM window's **Keys → Home** action. The current JB workflow does not i
|
||||
|
||||
## App exits with `EXC_GUARD` / `GUARD_TYPE_MACH_PORT`
|
||||
|
||||
The optional `vphone-cli fw patch <name> --force-exc-guard` patch can address this class of crash. Repatching firmware alone does not alter an already restored guest: restore and reinstall CFW after changing the patch set. See [issue #291](https://github.com/Lakr233/vphone-cli/issues/291).
|
||||
The kernel patch `kernel.thread_guard_violation` stops fatal Mach port guard delivery. It applies only on an iOS 18 base, where it is needed to boot, and every preset turns it on there. Other bases cannot select it, so an app that trips this guard on a 26.x base still exits. See [issue #291](https://github.com/Lakr233/vphone-cli/issues/291).
|
||||
|
||||
## Restore or first boot fails
|
||||
|
||||
|
||||
@@ -100,11 +100,14 @@
|
||||
>
|
||||
> Two of the three flags are gone from the surface as well: `--force-exc-guard` no
|
||||
> longer exists, and `--frida` survives only on the `patch-component` developer
|
||||
> subcommand, not on `vm create` or `fw patch`. `--force-dsc-maxslide` is **still
|
||||
> plumbed** through `vm create`, `VPhoneVirtualMachineCreateOptions`, the Launchpad
|
||||
> helper protocol and the Launchpad new-machine sheet, where it now reaches an
|
||||
> installer branch the plan has already decided; removing that chain is outstanding
|
||||
> and touches the privileged helper's XPC signature, so it is its own change.
|
||||
> subcommand, not on `vm create` or `fw patch`. `--force-dsc-maxslide` is gone too
|
||||
> (2026-09-30): it was removed from `vm create`, `restore`, `cfw install`,
|
||||
> `VPhoneVirtualMachineCreateOptions`, the Launchpad new-machine sheet, the
|
||||
> Launchpad control verbs and the helper's `installCustomFirmware` XPC signature.
|
||||
> On a 27 base it never reached the patcher at all: the installer passed `--force`
|
||||
> only in a branch below the `27.` and `26.0`/`18.` cases, which the plan had
|
||||
> already closed. A helper built before this change has a different hash and shows
|
||||
> as outdated, so Launchpad reinstalls it before it is called with the new signature.
|
||||
>
|
||||
> The `iosBaseIs18` / `iosBaseIs27` booleans are gone from the pipeline too: it now
|
||||
> carries a parsed `VPhoneVersion` (major, minor, patch). Two things still branch
|
||||
@@ -388,9 +391,9 @@
|
||||
| 15 | `mov w0,#0` | `_handle_fsioc_graft` | Allow fsioc graft | Y | Y | Y |
|
||||
| 16 | NOP (3x) | `handle_get_dev_by_role` | Bypass APFS role-lookup deny gates for boot mounts | Y | Y | Y |
|
||||
| 17-26 | `mov x0,#0; ret` (5 hooks) | Sandbox MACF ops table | Stub 5 sandbox hooks | Y | Y | Y |
|
||||
| 27 | `PACIBSP→RET` | `_thread_guard_violation` | Disable fatal EXC_GUARD (Mach port guard) delivery. Dev variant + iOS 18 bases always; regular/jb/exp otherwise opt-in via `--force-exc-guard` (see note below). | Y* | Y | Y* |
|
||||
| 27 | `PACIBSP→RET` | `_thread_guard_violation` | Disable fatal EXC_GUARD (Mach port guard) delivery. Dev variant + iOS 18 bases always. The old `--force-exc-guard` opt-in for other bases is gone; `kernel.thread_guard_violation` is pinned to `iOSBase: .major(18)` (see note below). | Y* | Y | Y* |
|
||||
|
||||
† Always required on iOS 18 bases (18.6.2's runningboardd/SpringBoard trip `GUARD_TYPE_MACH_PORT` "flavor 10", crash-looping the UI — the VM won't boot without it there). On other bases this is opt-in, not always-on: some third-party apps shipping crash-reporting/RASP SDKs (Bugly, Crashlytics, KSCrash, ...) call `task_swap_exception_ports()`, which the research kernel can enforce as a fatal `GUARD_TYPE_MACH_PORT`/`KOBJECT_REPLY_PORT_SEMANTICS` violation (see [upstream issue #291](https://github.com/Lakr233/vphone-cli/issues/291), reproduced and confirmed via crash logs against a `kernelcache.research.vphone600 (26.1/23B85)` build) — but it's not required for the VM itself to boot on 26.x, so it stays opt-in rather than always-on for regular/jb/exp. `applyExcGuard` in `FirmwarePipeline`/`KernelPatcher` is `iosBaseIs18 || forceExcGuard`; pass `--force-exc-guard` to `patch-firmware` (or `FORCE_EXC_GUARD=1` to the relevant `make fw_patch*` target) to enable it on a base that needs it. `iosBaseIs18` (from `iPhone-BuildManifest.plist`'s `ProductVersion`) also still gates the unrelated 18.x skywalk-netagent boot-arg workaround.
|
||||
† Always required on iOS 18 bases (18.6.2's runningboardd/SpringBoard trip `GUARD_TYPE_MACH_PORT` "flavor 10", crash-looping the UI — the VM won't boot without it there). On other bases this is opt-in, not always-on: some third-party apps shipping crash-reporting/RASP SDKs (Bugly, Crashlytics, KSCrash, ...) call `task_swap_exception_ports()`, which the research kernel can enforce as a fatal `GUARD_TYPE_MACH_PORT`/`KOBJECT_REPLY_PORT_SEMANTICS` violation (see [upstream issue #291](https://github.com/Lakr233/vphone-cli/issues/291), reproduced and confirmed via crash logs against a `kernelcache.research.vphone600 (26.1/23B85)` build) — but it's not required for the VM itself to boot on 26.x, so it stays opt-in rather than always-on for regular/jb/exp. `applyExcGuard` in `FirmwarePipeline`/`KernelPatcher` is `iosBaseIs18 || forceExcGuard`; pass `--force-exc-guard` to `patch-firmware` (or `FORCE_EXC_GUARD=1` to the relevant `make fw_patch*` target) to enable it on a base that needs it. `iosBaseIs18` (from `iPhone-BuildManifest.plist`'s `ProductVersion`) also still gates the unrelated 18.x skywalk-netagent boot-arg workaround. **Superseded:** the `--force-exc-guard` / `FORCE_EXC_GUARD=1` opt-in and the `iosBaseIs18` boolean no longer exist. The patch is declared with `iOSBase: .major(18)`, and no preset or VM selection can widen that gate, so it cannot be applied on 26.x.
|
||||
|
||||
### JB-Only Kernel Methods (Reference List)
|
||||
|
||||
@@ -460,7 +463,7 @@ do NOT execute these).
|
||||
| 7 | cstring byte 5 mangle `'h' → 'X'` (`"kern.hv_vmm_present"` → `"kern.Xv_vmm_present"`) + per-page slot-hash re-attestation, BLACKLIST semantics — **EXP only** | DSC dylibs | Companion to EXP kernel rename (`KernelEXPPatcher.patchHvVmmRename`). The mangle is applied to every DSC dylib EXCEPT those in `DONT_PATCH_INSTALL_NAMES` (sign-in / device-likeness consumers, ~15 entries). Patched dylibs query `kern.Xv_vmm_present` and get the truthful 1 (graphics / accel passthrough). Blacklisted dylibs keep the original cstring, hit ENOENT on the renamed kernel, cache 0, lie about VM presence. On `codeSigningMonitor == 2` hardware the byte-mangle alone causes `CODESIGNING/Invalid Page` SIGKILL because TXM enforces per-page hashes; the re-attestation pass recomputes the SHA-256 slot in the chunk's `CS_CodeDirectory` for every modified 16 KiB page. See `scripts/patchers/cfw_dsc_codesign.py` and `cfw_patch_hv_vmm_dsc.py`. | - | - | - |
|
||||
| 8 | (removed — was: standalone-binary mangle in 6 rootfs Mach-Os via SSH) | n/a | Removed in the blacklist-flip redesign. With the EXP kernel rename in place, the 6 rootfs binaries (MobileActivationMigrator, CheckerBoard, StoreKitUISceneService, storekitd, appstored, CorePrescriptionService) get the desired "cache 0 / not in a VM" behavior for free: they keep their original cstring, hit ENOENT on the renamed kernel sysctl, defensive `cbnz w0, skip` leaves the cached byte at BSS-zero. No SSH-time standalone patch needed. | - | - | - |
|
||||
| 9 | `mov w3,#<size>` -> `mov w3,#<base-size>` in `_kern_SwapEnd` — **26.0/26.0.1 and 18.x** | DSC `IOMobileFramebuffer` | Fixes host VZ GUI black-screen with the available PCC vphone600 userclient: the userclient does an exact `checkStructureInputSize` check on external-method-5 (SwapEnd) input, so a userland whose `_kern_SwapEnd` sends a different-sized state gets `kIOReturnBadArgument` and the host display stays black (guest still renders — the Apple logo is visible over VNC, just not in the vphone-cli view). **The accepted size is a property of the base kernel, not the userland**: 26.1 base -> **0x560**, 26.4 base (xnu-12377) -> **0x588**. The 0x588 value is confirmed two ways: the sole dispatch-shaped entry in `kernelcache.*.vphone600` with `checkStructureInputSize==0x588` (scalarIn=0, scalarOut=0, structOut=0, preceded by a ptrauth code ptr, at decompressed file offset 0x9c7228), and empirically — native 26.5 userland sends 0x588 and displays correctly on this stack. Source (userland-sent) sizes observed: 18.6.2 = 0x514, 26.0/26.0.1 = 0x548, 27.0 (24A5380h) = 0x6e0. The patcher is semantic (anchors on `mov w1,#5` -> `mov w3,#imm` -> `mov x4,#0`/`mov x5,#0` -> `bl` inside `_kern_SwapEnd`) and idempotent — rewrites the size to `--target-size` regardless of source and re-attests the modified DSC page. Install gate: `26.0*` / `18.*` -> 0x560 (26.1 base). Validated after host install on `17,3_26.0_23A341`, `17,3_26.0.1_23A355`, and `17,3_18.6.2_22G100` (Apple logo renders) against the 26.1 base. **CORRECTION (2026-07-15): iOS 27.0 is NO LONGER handled here.** 27 presents the paravirt display via IOMFB's `_virt_*` callback path — external method 5 is NEVER called — so no SwapEnd *size* change can help 27 (confirmed by kernel trace + live AppleParavirtGPU idle scheduler). iOS 27 now uses **force-kern (item 11)** to route present back onto method 5; this row applies to 26.0/26.0.1/18.x only. | Y | Y | Y |
|
||||
| 10 | Zero `maxSlide` in `dyld_cache_header` (`@0xF0`) — **iOS 27.0 / any userland whose cache overflows the 6 GiB region** | DSC `dyld_shared_cache_arm64e` header | Fixes pid-1 `launchd` panic at boot on the vphone600 26.x kernel. The kernel reserves `SHARED_REGION_SIZE_ARM64 = 0x180000000` (6 GiB) and, at map time, needs room for the cache's mapped span **plus** the header `maxSlide` (ASLR range). iOS 27.0's cache (span `0x17c830000` ≈ 5.95 GiB) + `maxSlide 0x20000000` = `0x19c830000` > 6 GiB, so `_shared_region_map_and_slide` returns `ENOMEM`, dyld cannot map `libSystem.B.dylib`, and `launchd` panics (`initproc failed to start`). Zeroing `maxSlide` (LE u64) in the main chunk maps the cache at slide 0 (fits with ~58 MiB spare). Install gate: **`27.*`** (hard-gated in `cfw_install.sh` as of 2026-07-20 — an 18.x/26.x base skips it entirely). The patcher additionally self-gates (`patch-dsc-maxslide`): no-op unless span + maxSlide > `0x180000000`, kept as defense-in-depth so 26.x / 18.x are untouched even if the install gate were removed. **Opt-in for non-27 bases** (added 2026-07-23): `FORCE_DSC_MAXSLIDE=1` in the `cfw_install.sh` environment runs `patch-dsc-maxslide --force` on 18.x/26.x too; `--force` bypasses the fits-check and zeroes `maxSlide` unconditionally (still idempotent — no-op if already 0). Default off, so non-27 bases keep their native ASLR slide unless explicitly requested; the `27.*` path is unchanged. **No** page re-attestation (header metadata, not a `cs_validate`'d code page — confirmed empirically). Validated on `17,3_27.0_24A5380h` + cloudOS 26.4 (`c0ecdb4b…`): `dyld cache mapped system-wide`, launchd reaches first unlock, vphoned connects as iOS 27.0.0, 0 panics. See `scripts/patchers/cfw_patch_dsc_maxslide.py`. | Y | Y | Y |
|
||||
| 10 | Zero `maxSlide` in `dyld_cache_header` (`@0xF0`) — **iOS 27.0 / any userland whose cache overflows the 6 GiB region** | DSC `dyld_shared_cache_arm64e` header | Fixes pid-1 `launchd` panic at boot on the vphone600 26.x kernel. The kernel reserves `SHARED_REGION_SIZE_ARM64 = 0x180000000` (6 GiB) and, at map time, needs room for the cache's mapped span **plus** the header `maxSlide` (ASLR range). iOS 27.0's cache (span `0x17c830000` ≈ 5.95 GiB) + `maxSlide 0x20000000` = `0x19c830000` > 6 GiB, so `_shared_region_map_and_slide` returns `ENOMEM`, dyld cannot map `libSystem.B.dylib`, and `launchd` panics (`initproc failed to start`). Zeroing `maxSlide` (LE u64) in the main chunk maps the cache at slide 0 (fits with ~58 MiB spare). Install gate: **`27.*`** (hard-gated in `cfw_install.sh` as of 2026-07-20 — an 18.x/26.x base skips it entirely). The patcher additionally self-gates (`patch-dsc-maxslide`): no-op unless span + maxSlide > `0x180000000`, kept as defense-in-depth so 26.x / 18.x are untouched even if the install gate were removed. **The non-27 opt-in is gone** (2026-09-30): `FORCE_DSC_MAXSLIDE=1`, later `--force-dsc-maxslide`, no longer exists on any command or in Launchpad. `dsc_maxslide.zero` is declared `iOSBase: .major(27)`, so non-27 bases keep their native slide. `patch-dsc-maxslide --force` remains on the standalone verb for hand use; `cfw install` never passes it. **24A435 (27.0 RC) checked 2026-09-30** for issue #531: the pristine SystemOS cryptex header reads `sharedRegionStart 0x180000000`, `sharedRegionSize 0x17D504000`, `maxSlide 0x20000000`, so span + slide is `0x19D504000`. `vphone-cli cfw patch-dsc-maxslide --dry-run` (2.1.6) reports `overflow` and would zero it; an installed 24A435 guest reads `maxSlide 0x0` and boots. The self-gate therefore patches 24A435 without force. The 512 MiB slide is the cache builder's fixed iOS arm64 value, so every 27 cache overflows this region. Source check (xnu-12377, `shared_region_map_and_slide_2_np`, `vm_shared_region_map_file_setup`, `vm_map_locate_space_fixed`): the kernel picks a 16 KiB-aligned slide strictly below `maxSlide` and maps each range FIXED inside the `0x180000000` submap. With no twig rounding and no reserved area, `size + maxSlide <= 0x180000000` is a sufficient test, one 16 KiB page stricter than needed. The #531 panic therefore came from a guest whose cache was never patched. Most likely its CFW install had not completed, because `cfw install` swaps in its clone only on success. It did not come from the gate. **No** page re-attestation (header metadata, not a `cs_validate`'d code page — confirmed empirically). Validated on `17,3_27.0_24A5380h` + cloudOS 26.4 (`c0ecdb4b…`): `dyld cache mapped system-wide`, launchd reaches first unlock, vphoned connects as iOS 27.0.0, 0 panics. See `scripts/patchers/cfw_patch_dsc_maxslide.py`. | Y | Y | Y |
|
||||
| 11 | Retarget public `_IOMobileFramebufferSwap*` trampolines -> `b _kern_Swap*` (force-kern) — **iOS 27.0** | DSC `IOMobileFramebuffer` | **iOS-27 VZ-view (host paravirt-GPU scanout) fix, userland half.** The host `VZVirtualMachineView` is fed by the guest `AppleParavirtGPU` scanout, which the 26.4 kernel drives ONLY from the IOMFB userclient SwapEnd (external method 5) — the `_kern_Swap*` path. iOS 27 defaults the paravirt display's present to IOMFB's parallel `_virt_Swap*` path (`_virt_SwapEnd` does no userclient call — it invokes an in-process callback `blraaz [conn+0xe68]` and hands the IOSurface to a virtual-display consumer), so the paravirt GPU never scans out → host VZ window black (guest still composites; GUI visible over in-guest TrollVNC; AppleParavirtGPU `SchedulerState` idle). The public `_IOMobileFramebufferSwap*` entrypoints are thin trampolines (`cbz x0; ldr xN,[x0,#slot]; cbz xN; braaz xN`) that tail-call the per-connection swap fp (kern or virt impl). This patch rewrites each trampoline's first insn to `b _kern_Swap<Name>`, forcing present onto method 5 regardless of how 27 classified the display (tail-call, args intact → behaviourally identical to selecting the kern fp). Fully dynamic: public + `_kern_` addrs resolved by name via `ipsw dyld symaddr`, trampoline shape verified by Capstone, branch bytes from Keystone `asm_at()`, modified DSC code pages re-attested. Requires ≥{SwapBegin,SwapEnd,SwapSetLayer} or raises (dry-run retargets 31 entrypoints on 24A5380h, skips 4 non-trampolines). **Pairs with the JB kernel patches (`patchIomfbSwapEndVariableSize` + `patchIomfbSwapEndHandlerSize`)** which relax the 26.4 userclient's two exact `0x588` size gates to accept 27's native `0x6e0` IOMFBSwapRec (prefix matches 26.x, so the paravirt swap handler reads valid fields). Install gate: `27.*`. See `scripts/patchers/cfw_patch_iomfb_force_kern.py`. **VALIDATED on-device (2026-07-15, `17,3_27.0_24A5380h` + cloudOS 26.4 `c0ecdb4b…`, JB): iOS 27 userland renders AND is interactive in the native VZ view (not just TrollVNC); clean boot — no `kIOReturnBadArgument`/SwapEnd rejection/panic.** Runtime confirmed 31 entrypoints retargeted (4 non-trampoline setters left on virt). | Y | Y | Y |
|
||||
| 12 | NOP `-[_LSDModifyClient clientIsEntitledForEmbeddedRegistrationOperations]` entitlement gate + per-page re-attest — **iOS 27.0** | DSC `CoreServices` (LaunchServices) | **iOS-27 app-registration fix.** lsd gates `-[_LSDModifyClient performPostInstallationRegistration:operationUUID:reply:]` (and the containerized/rebuild registration paths) behind `clientIsEntitledForEmbeddedRegistrationOperations`, which does `xpc_connection_copy_entitlement_value` on the XPC peer for any of `com.apple.private.coreservices.lsaw` / `com.apple.private.installcoordinationd.daemon` / `com.apple.private.coreservices.can-register-install-results`. A client without one gets `NSOSStatusErrorDomain -54` (permErr, `LSDModifyService.mm:1639`), so `registerApplicationDictionary:` / `registerContainerizedApplicationWithInfoDictionaries:` fail and no app can (re)register — blocking vphoned's installer, TrollStore, and uicache/Sileo alike. The entitlement route is a dead end even for a launchd platform daemon (vphoned) whose validated csblob (`csops CS_OPS_ENTITLEMENTS_BLOB`) contains all three: LS registration is proxied, so the XPC peer lsd inspects is not the registering process. Fix: NOP the final `cbz w0, <not_entitled>` (the conditional branch whose fall-through sets the `mov w<reg>,#1` result) so the method always returns YES. Fully dynamic: method resolved via the DSC's own `.symbols` in-image local-symbol table (ipsw `symaddr -a`/`a2s` time out on this cache), gate located by control-flow shape in Capstone, NOP from Keystone `asm("nop")`, modified 16 KiB page re-attested (`cfw_dsc_codesign.py`; TXM enforces per-page). The resulting CDHash change is accepted by the JB always-true AMFI cdhash-trust patch. Install gate: **`27.*`** (hard-gated in `cfw_install.sh` as of 2026-07-20 — an 18.x/26.x base does not apply it). The patcher additionally self-gates (`patch-lsd-embedded-reg`): no-op on pre-iOS-27 userlands where the method is absent. **Pairs with vphoned's `vp_register_path` containerized-registration fallback** (`registerContainerizedApplicationWithInfoDictionaries:...:registrationError:`, treating a nil `registrationError` as success since it returns NO even when it registers). Also paired with **`/cores/vpregister`** (built + deployed by `cfw_install_jb.sh` / `cfw_install_exp.sh`, invoked by `vphone_jb_setup.sh` at first boot — 27-gated by DEPLOYMENT: `cfw_install_jb.sh`/`cfw_install_exp.sh` copy `/cores/vpregister` only when the mounted rootfs `SystemVersion.plist` is `27.*`, and the setup script's `[ -x /cores/vpregister ]` presence check is the runtime gate. Do NOT gate the invocation on a guest `sw_vers` check — the hybrid guest does not reliably report the 27 userland version at first boot, which silently skipped registration): it registers JB app bundles (Sileo) via the same containerized API, because `uicache -a`'s `registerApplicationDictionary:` is a deprecated no-op on iOS 27 (lsd logs *"you cannot use ... to register applications anymore. These interfaces have been deprecated for years."*). **VALIDATED (2026-07-17, `17,3_27.0_24A5380h` + cloudOS 26.4, JB): -54 gone; Sileo registers (`uicache -l` 0→1) via `vpregister`; vphoned installs+registers a test IPA (`com.vphone.vptest`) to `/var/containers/Bundle/Application/` end-to-end. Clean boot (re-attest correct; no CoreServices page rejection).** See `scripts/patchers/cfw_patch_lsd_embedded_reg.py` and `Siblings/VPRegister/vpregister.m`. **FIX (2026-08-10):** `_find_gate` only matched the live `cbz`/`cbnz` branch shape, so re-running `cfw install` (host-mount flow, `myphone` VM, `17,3_27.0_24A5390f`) against a cache where this gate was already NOP'd from a prior pass raised `ValueError: ... entitled-result gate ... not found` instead of recognizing the idempotent state (unlike the Cryptex/IOMFB steps, which log `already ... idempotent` and skip cleanly). Confirmed live via host-mount disassembly: the third check's `bl <check3>` is followed by a bare `nop` at the gate site (exact match against `asm("nop")` bytes) immediately before `mov w20, #1` — i.e. already patched. `_find_gate` now also matches `nop` immediately preceding `mov w<reg>,#1` as an already-patched gate, so a re-run just re-attests the page instead of erroring. | Y | Y | Y |
|
||||
| 13 | `mov x0,#1; ret` on `-[DIDiskArb isMountCompleteWithExpectedCount:diskTracker:]` — **iOS 27.0** | `diskimagesiod` | **iOS-27 DDI (`/System/Developer`) auto-mount — mount-gate.** After the personalized DDI attaches (kernel side: JB-28 + JB-09), MobileStorageMounter waits on diskimagesiod's `-[DIDiskArb waitForDAMountWithExpectedCount:diskTracker:]` before it does the real (nobrowse) mount at `/System/Developer`. That wait loops until `isMountComplete` (= `callbackReached \|\| (appearedDiskCount>=expectedCount && mountedDiskCount>=mountableDiskCount)`) is YES; on the 26.4-kernel / 27-userland hybrid it never becomes true (not all of the DMG's IOMedia "appear" to diskimagesiod's DiskArbitration session, and diskarbitrationd never auto-mounts the volume), so the wait hangs and pmd3 times out. diskimagesiod itself does NOT mount the DDI (its `-[DIDiskArb mountWithDeviceName:...]` is dead code) — it only gates MobileStorageMounter. Forcing `isMountComplete` → YES lets the wait return so MobileStorageMounter proceeds. IMP resolved via LC_SYMTAB or ObjC metadata (selector → `__objc_selrefs` → `__TEXT,__objc_methlist` relative method list → IMP); prologue overwritten `mov x0,#1 ; ret` (safe — the method returns to the caller's unsigned LR without pushing a frame). **Gated to 27.\*** in `cfw_install.sh` (same `$IOS_VERSION` as the DSC patches): on a version-matched userland the native wait completes correctly and forcing it early could race the real mount, so it is NOT applied there. Embedded sandbox profile + private DA/apfs entitlements preserved on re-sign (`ldid_sign_ent`). **Validated on `c0ecdb4b` 26.4 + 27.0 userland: `pmd3 mounter auto-mount` → rc=0, DDI at `/System/Developer`, idempotent across fresh boots.** See `scripts/patchers/cfw_patch_diskimagesiod.py`. | Y | Y | Y |
|
||||
@@ -1196,7 +1199,7 @@ cache rebuild.
|
||||
| BaseBin hook deployment (`*.dylib` -> `/mnt1/cores`) | - | - | Y (JB-3) | Y (JB-3) |
|
||||
| First-boot JB finalization (`vphone_jb_setup.sh`) | - | - | Y (post-boot) | Y (post-boot) |
|
||||
| IOMobileFramebuffer SwapEnd payload-size patch (install-gated `26.0*`/`18.*` -> 0x560 / 26.1 base; **27.0 does NOT use this — it uses force-kern, next section**) | Y | Y | Y (inherited from base run) | Y (inherited from base run) |
|
||||
| dyld cache `maxSlide` zero (`patch-dsc-maxslide`; **install-gated `27.*`** as of 2026-07-20 — iOS 27's cache overflows the 6 GiB region; 18.x/26.x skip it, or opt in via `FORCE_DSC_MAXSLIDE=1` → `--force`) | Y | Y | Y (inherited from base run) | Y (inherited from base run) |
|
||||
| dyld cache `maxSlide` zero (`patch-dsc-maxslide`; **install-gated `27.*`** as of 2026-07-20 — iOS 27's cache overflows the 6 GiB region; 18.x/26.x skip it; the old `FORCE_DSC_MAXSLIDE=1` / `--force-dsc-maxslide` opt-in was removed 2026-09-30) | Y | Y | Y (inherited from base run) | Y (inherited from base run) |
|
||||
| DSC pre-patch (`kern.hv_vmm_present` byte-5 mangle + slot reattest) | - | - | - | Y (pre-step, before base CFW) |
|
||||
| DSC camera patches (12 patches across CMCapture / CoreMediaIO / AVFCapture / libMobileGestalt) | - | - | - | Y (pre-step, same cryptex mount as hv_vmm) |
|
||||
| `watchdogd` surgical 2-insn patch + slot reattest | - | - | - | Y (EXP-JB-3.5) |
|
||||
|
||||
@@ -80,7 +80,8 @@ so it is a superset of regular, and `dev` adds nothing exp does not already have
|
||||
| … on an **iOS 27** base | + `dsc_maxslide`, `lsd_embedded_reg`, `xpc_lwcr`, `lockdown_mode`, `iomfb_force_kern`, `diskimagesiod` |
|
||||
|
||||
`dsc_maxslide` self-gates to a no-op on a base whose cache already fits. To
|
||||
capture it on a non-27 base anyway, add `FORCE_DSC_MAXSLIDE=1`.
|
||||
capture it on a non-27 base anyway, run `vphone-cli cfw patch-dsc-maxslide
|
||||
<dyld dir> --force` by hand; `cfw install` never forces it.
|
||||
|
||||
Capture the same variant on more iOS builds than these two — the reference is
|
||||
per build, and a patcher that silently finds nothing on a new build is exactly
|
||||
|
||||
@@ -107,10 +107,8 @@ Two rules follow, and they are the ones people get wrong:
|
||||
|
||||
Do **not** add a boolean flag to `FirmwarePipeline` or a `--force-something` CLI
|
||||
flag for this. The patches behind `--frida`, `--force-exc-guard` and
|
||||
`--force-dsc-maxslide` are declarations now, and adding another flag is a
|
||||
regression. (`--force-dsc-maxslide` itself is still plumbed from `vm create`
|
||||
through the Launchpad helper; that chain is dead weight awaiting removal, not a
|
||||
pattern to copy.)
|
||||
`--force-dsc-maxslide` are declarations now, and all three flags are gone.
|
||||
Adding another is a regression.
|
||||
|
||||
## Boot-Essential Patches
|
||||
|
||||
|
||||
+9
-2
@@ -18,6 +18,13 @@
|
||||
// iOS 27.0 (24A5380h): span 0x17C830000 (~5.95 GiB)
|
||||
// + maxSlide 0x20000000 (512 MiB)
|
||||
// = 0x19C830000 (~6.46 GiB) > 0x180000000 (6 GiB)
|
||||
// iOS 27.0 (24A435): size 0x17D504000 + maxSlide 0x20000000
|
||||
// = 0x19D504000 > 0x180000000
|
||||
//
|
||||
// The 512 MiB is not a per-build accident: the cache builder gives every iOS
|
||||
// arm64 cache a fixed 512 MiB slide and sizes it to fit the iOS 27 SDK's own,
|
||||
// larger region. So every 27 cache read so far overflows this kernel's region,
|
||||
// and the gate below patches it.
|
||||
//
|
||||
// `_shared_region_map_and_slide` then returns ENOMEM, dyld cannot map
|
||||
// libSystem, and launchd (pid 1) panics: "initproc failed to start -- Library
|
||||
@@ -181,8 +188,8 @@ public enum DyldSharedCacheMaxSlidePatcher {
|
||||
/// this project targets.
|
||||
/// - kernelRegionSize: the guest kernel's `SHARED_REGION_SIZE_ARM64`.
|
||||
/// - dryRun: decide and report, but write nothing.
|
||||
/// - force: clamp even when the cache already fits — the `--force` flag
|
||||
/// behind `FORCE_DSC_MAXSLIDE=1`.
|
||||
/// - force: clamp even when the cache already fits — the verb's `--force`,
|
||||
/// for use by hand. `cfw install` never sets it.
|
||||
/// - verbose: print the Python's log lines.
|
||||
@discardableResult
|
||||
public static func patch(
|
||||
|
||||
+2
-2
@@ -230,8 +230,8 @@ struct VPhoneCustomFirmwarePatchDyldSharedCacheMaxSlideCommand: ParsableCommand
|
||||
with full slide is left alone and a cache already at maxSlide 0 is left
|
||||
alone either way. Both are reported and exit 0.
|
||||
|
||||
--force zeroes maxSlide even when the cache fits — the opt-in behind
|
||||
FORCE_DSC_MAXSLIDE=1 for a non-27 base.
|
||||
--force zeroes maxSlide even when the cache fits. It is for running this
|
||||
verb by hand; `cfw install` never passes it.
|
||||
|
||||
No re-attestation: maxSlide lives in the cache header, which is not one
|
||||
of the cs_validate'd code pages.
|
||||
|
||||
+2
-19
@@ -23,7 +23,6 @@ import VPhoneSign
|
||||
struct VPhoneCustomFirmwareInstaller {
|
||||
let bundle: URL
|
||||
let resources: VPhoneResources
|
||||
let forceDyldSharedCacheMaxSlide: Bool
|
||||
|
||||
/// Guest system files belong to root:wheel.
|
||||
private static let guestOwner: (uid: uid_t, gid: gid_t) = (0, 0)
|
||||
@@ -47,17 +46,9 @@ struct VPhoneCustomFirmwareInstaller {
|
||||
return build
|
||||
}
|
||||
|
||||
static func elevate(
|
||||
bundle: URL,
|
||||
resources: VPhoneResources,
|
||||
forceDyldSharedCacheMaxSlide: Bool,
|
||||
) throws -> Int32 {
|
||||
static func elevate(bundle: URL, resources: VPhoneResources) throws -> Int32 {
|
||||
if geteuid() == 0 {
|
||||
try VPhoneCustomFirmwareInstaller(
|
||||
bundle: bundle,
|
||||
resources: resources,
|
||||
forceDyldSharedCacheMaxSlide: forceDyldSharedCacheMaxSlide,
|
||||
).run()
|
||||
try VPhoneCustomFirmwareInstaller(bundle: bundle, resources: resources).run()
|
||||
return 0
|
||||
}
|
||||
throw ValidationError("CFW installation needs root. Run this command with sudo.")
|
||||
@@ -446,12 +437,6 @@ struct VPhoneCustomFirmwareInstaller {
|
||||
if on("dsc.iomfb_swapend") {
|
||||
try patch("patch-iomfb-swapend", [dsc, "--target-size", "0x560"])
|
||||
}
|
||||
} else if forceDyldSharedCacheMaxSlide {
|
||||
// Superseded by the plan: `dsc_maxslide.zero` is pinned to iOS 27, so
|
||||
// a VM with a plan cannot force it onto a 26.x base any more.
|
||||
if on("dsc_maxslide.zero") {
|
||||
try patch("patch-dsc-maxslide", [dsc, "--force"])
|
||||
}
|
||||
}
|
||||
// Version-agnostic: the guest is hacktivated on every base, so the
|
||||
// profile check this opens fails on every base too.
|
||||
@@ -1106,13 +1091,11 @@ struct VPhoneCustomFirmwareInstallRootCommand: ParsableCommand {
|
||||
|
||||
@Argument(help: "VM bundle path") var bundle: String
|
||||
@Option(help: "Resource base") var resources: String
|
||||
@Flag(name: .customLong("force-dsc-maxslide")) var forceDyldSharedCacheMaxSlide = false
|
||||
|
||||
func run() throws {
|
||||
try VPhoneCustomFirmwareInstaller(
|
||||
bundle: URL(fileURLWithPath: bundle),
|
||||
resources: VPhoneResources(base: URL(fileURLWithPath: resources)),
|
||||
forceDyldSharedCacheMaxSlide: forceDyldSharedCacheMaxSlide,
|
||||
).run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,11 +230,6 @@ struct VPhoneCustomFirmwareInstallCommand: ParsableCommand {
|
||||
|
||||
@OptionGroup var lib: VPhoneLibraryOption
|
||||
@Argument(help: "VM name") var name: String?
|
||||
@Flag(
|
||||
name: .customLong("force-dsc-maxslide"),
|
||||
help: "Zero the dyld cache maxSlide on non-27 bases (opt-in DSC-map fit)",
|
||||
)
|
||||
var forceDyldSharedCacheMaxSlide = false
|
||||
@Flag(
|
||||
name: .customLong("keep-artifacts"),
|
||||
help: "Keep the extracted firmware after install (default: removed to save space)",
|
||||
@@ -249,7 +244,6 @@ struct VPhoneCustomFirmwareInstallCommand: ParsableCommand {
|
||||
let code = try VPhoneCustomFirmwareInstaller.elevate(
|
||||
bundle: bundle.url,
|
||||
resources: resources,
|
||||
forceDyldSharedCacheMaxSlide: forceDyldSharedCacheMaxSlide,
|
||||
)
|
||||
if code == 0 {
|
||||
try recordInstall(in: bundle)
|
||||
|
||||
-6
@@ -19,11 +19,6 @@ struct VPhoneVirtualMachineCreateCommand: ParsableCommand {
|
||||
@Option(help: "Directory for downloaded IPSWs, shared by every VM (default: ~/.vphone/ipsws or $VPHONE_ROOT/ipsws)")
|
||||
var ipswCache: String?
|
||||
@Option(name: .shortAndLong, help: "Disk size (GB)") var diskSize: UInt64 = 64
|
||||
@Flag(
|
||||
name: .customLong("force-dsc-maxslide"),
|
||||
help: "Zero the dyld cache maxSlide on non-27 bases (opt-in DSC-map fit)",
|
||||
)
|
||||
var forceDyldSharedCacheMaxSlide = false
|
||||
@Option(
|
||||
name: .customLong("preset"),
|
||||
help: "Patch preset for the new VM. Defaults to standard; run `fw patches` to see what each one applies.",
|
||||
@@ -61,7 +56,6 @@ struct VPhoneVirtualMachineCreateCommand: ParsableCommand {
|
||||
ipswCacheDirectory: ipswCache.map {
|
||||
URL(fileURLWithPath: ($0 as NSString).expandingTildeInPath, isDirectory: true)
|
||||
} ?? VPhoneResources.ipswCacheDirectory(),
|
||||
forceDyldSharedCacheMaxSlide: forceDyldSharedCacheMaxSlide,
|
||||
patchPreset: preset,
|
||||
diskSizeGB: diskSize,
|
||||
verbosity: VPhoneVerbosity(count: verboseCount),
|
||||
|
||||
-3
@@ -11,7 +11,6 @@ public extension VPhoneVirtualMachineCreator {
|
||||
public var cloudosSource: String?
|
||||
public var gpuDriverBundle: URL?
|
||||
public var ipswCacheDirectory: URL
|
||||
public var forceDyldSharedCacheMaxSlide: Bool
|
||||
/// Which patch preset the new VM is built with. Individual patches are
|
||||
/// turned on or off per VM afterwards, through its patch selection.
|
||||
public var patchPreset: String
|
||||
@@ -27,7 +26,6 @@ public extension VPhoneVirtualMachineCreator {
|
||||
cloudosSource: String? = nil,
|
||||
gpuDriverBundle: URL? = nil,
|
||||
ipswCacheDirectory: URL = VPhoneResources.ipswCacheDirectory(),
|
||||
forceDyldSharedCacheMaxSlide: Bool = false,
|
||||
patchPreset: String = VPhonePatchPreset.standardIdentifier,
|
||||
cpuCount: UInt = 8,
|
||||
memoryMB: UInt64 = 8192,
|
||||
@@ -40,7 +38,6 @@ public extension VPhoneVirtualMachineCreator {
|
||||
self.cloudosSource = cloudosSource
|
||||
self.gpuDriverBundle = gpuDriverBundle
|
||||
self.ipswCacheDirectory = ipswCacheDirectory
|
||||
self.forceDyldSharedCacheMaxSlide = forceDyldSharedCacheMaxSlide
|
||||
self.patchPreset = patchPreset
|
||||
self.cpuCount = cpuCount
|
||||
self.memoryMB = memoryMB
|
||||
|
||||
+1
-4
@@ -426,10 +426,7 @@ public struct VPhoneVirtualMachineCreator {
|
||||
) throws {
|
||||
let v = options.verbosity
|
||||
trace("native JB CFW install for \(bundleURL.path)", v)
|
||||
let code = try VPhoneCustomFirmwareInstaller.elevate(
|
||||
bundle: bundleURL, resources: resources,
|
||||
forceDyldSharedCacheMaxSlide: options.forceDyldSharedCacheMaxSlide,
|
||||
)
|
||||
let code = try VPhoneCustomFirmwareInstaller.elevate(bundle: bundleURL, resources: resources)
|
||||
guard code == 0 else { throw VPhoneVirtualMachineCreationError.cfwInstallFailed(code) }
|
||||
print("[+] JB CFW installed.")
|
||||
if let bundle = try? VPhoneBundle.load(at: bundleURL),
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
VPhoneLaunchpadNewMachineAdvancedView(
|
||||
network: .constant("nat"),
|
||||
patches: .constant(VPhoneLaunchpadPatchSelection()),
|
||||
forceMaxSlide: .constant(false),
|
||||
keepArtifacts: .constant(false),
|
||||
patchCatalog: nil,
|
||||
patchCatalogError: nil,
|
||||
@@ -280,7 +279,6 @@
|
||||
diskSizeGB: 128,
|
||||
network: "nat",
|
||||
patches: VPhoneLaunchpadPatchSelection(),
|
||||
forceDyldSharedCacheMaxSlide: false,
|
||||
keepArtifacts: false,
|
||||
)
|
||||
|
||||
|
||||
@@ -524,7 +524,6 @@ struct VPhoneLaunchpadControlCommands {
|
||||
diskSizeGB: number("disk-size", 64),
|
||||
network: request.option("network") ?? "nat",
|
||||
patches: patches,
|
||||
forceDyldSharedCacheMaxSlide: request.flag("force-dsc-maxslide"),
|
||||
keepArtifacts: request.flag("keep-artifacts"),
|
||||
)
|
||||
return library.create(options)
|
||||
@@ -576,7 +575,6 @@ struct VPhoneLaunchpadControlCommands {
|
||||
bundleVersion: version,
|
||||
machineName: machine.name,
|
||||
libraryRoot: machine.libraryRoot,
|
||||
forceDyldSharedCacheMaxSlide: request.flag("force-dsc-maxslide"),
|
||||
keepArtifacts: request.flag("keep-artifacts"),
|
||||
onLine: emit,
|
||||
)
|
||||
|
||||
@@ -250,7 +250,6 @@ final class VPhoneLaunchpadHelperClient {
|
||||
bundleVersion: String,
|
||||
machineName: String,
|
||||
libraryRoot: String,
|
||||
forceDyldSharedCacheMaxSlide: Bool,
|
||||
keepArtifacts: Bool,
|
||||
onLine: @escaping @Sendable (String) -> Void,
|
||||
) async throws -> Int32 {
|
||||
@@ -264,7 +263,6 @@ final class VPhoneLaunchpadHelperClient {
|
||||
bundleVersion: bundleVersion,
|
||||
machineName: machineName,
|
||||
libraryRoot: libraryRoot,
|
||||
forceDyldSharedCacheMaxSlide: forceDyldSharedCacheMaxSlide,
|
||||
keepArtifacts: keepArtifacts,
|
||||
) { status, message in
|
||||
if let message {
|
||||
|
||||
@@ -26,7 +26,6 @@ final class VPhoneLaunchpadCreationPipeline {
|
||||
var network: String
|
||||
/// The preset and per-patch overrides the boot chain is built with.
|
||||
var patches: VPhoneLaunchpadPatchSelection
|
||||
var forceDyldSharedCacheMaxSlide: Bool
|
||||
var keepArtifacts: Bool
|
||||
|
||||
var machine: VPhoneLaunchpadMachinePath {
|
||||
@@ -347,7 +346,6 @@ final class VPhoneLaunchpadCreationPipeline {
|
||||
bundleVersion: version,
|
||||
machineName: name,
|
||||
libraryRoot: Self.canonicalPath(URL(fileURLWithPath: options.libraryRoot, isDirectory: true)),
|
||||
forceDyldSharedCacheMaxSlide: options.forceDyldSharedCacheMaxSlide,
|
||||
// The restore tree stays until first boot succeeds, so a
|
||||
// failed boot can still be restored again without preparing
|
||||
// the firmware anew. `removeRestoreFiles()` reclaims it then.
|
||||
|
||||
@@ -5,7 +5,6 @@ import SwiftUI
|
||||
struct VPhoneLaunchpadNewMachineAdvancedView: View {
|
||||
@Binding var network: String
|
||||
@Binding var patches: VPhoneLaunchpadPatchSelection
|
||||
@Binding var forceMaxSlide: Bool
|
||||
@Binding var keepArtifacts: Bool
|
||||
let patchCatalog: VPhoneLaunchpadPatchCatalog?
|
||||
let patchCatalogError: String?
|
||||
@@ -38,7 +37,6 @@ struct VPhoneLaunchpadNewMachineAdvancedView: View {
|
||||
patchSection
|
||||
|
||||
Section("Options") {
|
||||
Toggle("Disable dyld shared cache randomization", isOn: $forceMaxSlide)
|
||||
Toggle("Keep prepared restore files", isOn: $keepArtifacts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ struct VPhoneLaunchpadNewMachineView: View {
|
||||
@State private var patchCatalog: VPhoneLaunchpadPatchCatalog?
|
||||
@State private var patchCatalogError: String?
|
||||
@State private var showsAdvanced = false
|
||||
@State private var forceMaxSlide = false
|
||||
@State private var keepArtifacts = false
|
||||
|
||||
private var selectedPairing: VPhoneLaunchpadFirmwareCatalog.Pairing? {
|
||||
@@ -128,7 +127,6 @@ struct VPhoneLaunchpadNewMachineView: View {
|
||||
VPhoneLaunchpadNewMachineAdvancedView(
|
||||
network: $network,
|
||||
patches: $patches,
|
||||
forceMaxSlide: $forceMaxSlide,
|
||||
keepArtifacts: $keepArtifacts,
|
||||
patchCatalog: patchCatalog,
|
||||
patchCatalogError: patchCatalogError,
|
||||
@@ -392,7 +390,6 @@ struct VPhoneLaunchpadNewMachineView: View {
|
||||
diskSizeGB: diskSizeGB,
|
||||
network: network,
|
||||
patches: patches,
|
||||
forceDyldSharedCacheMaxSlide: forceMaxSlide,
|
||||
keepArtifacts: keepArtifacts,
|
||||
)
|
||||
let pipeline = model.machines.create(options)
|
||||
|
||||
@@ -14,7 +14,6 @@ struct VPhoneLaunchpadHelperFirmwareRequest {
|
||||
bundleVersion: String,
|
||||
machineName: String,
|
||||
libraryRoot: String,
|
||||
forceDyldSharedCacheMaxSlide: Bool,
|
||||
keepArtifacts: Bool,
|
||||
callerUID: uid_t,
|
||||
callerGID: gid_t,
|
||||
@@ -47,9 +46,6 @@ struct VPhoneLaunchpadHelperFirmwareRequest {
|
||||
let home = String(cString: account.pointee.pw_dir)
|
||||
|
||||
var arguments = ["cfw", "install", machineName, "--library-root", libraryRoot]
|
||||
if forceDyldSharedCacheMaxSlide {
|
||||
arguments.append("--force-dsc-maxslide")
|
||||
}
|
||||
if keepArtifacts {
|
||||
arguments.append("--keep-artifacts")
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ final class VPhoneLaunchpadHelperService: NSObject, VPhoneLaunchpadHelperProtoco
|
||||
bundleVersion: String,
|
||||
machineName: String,
|
||||
libraryRoot: String,
|
||||
forceDyldSharedCacheMaxSlide: Bool,
|
||||
keepArtifacts: Bool,
|
||||
reply: @escaping @Sendable (Int32, String?) -> Void,
|
||||
) {
|
||||
@@ -92,7 +91,6 @@ final class VPhoneLaunchpadHelperService: NSObject, VPhoneLaunchpadHelperProtoco
|
||||
bundleVersion: bundleVersion,
|
||||
machineName: machineName,
|
||||
libraryRoot: libraryRoot,
|
||||
forceDyldSharedCacheMaxSlide: forceDyldSharedCacheMaxSlide,
|
||||
keepArtifacts: keepArtifacts,
|
||||
callerUID: callerUID,
|
||||
callerGID: callerGID,
|
||||
|
||||
@@ -166,10 +166,10 @@ nonisolated struct VPhoneLaunchpadControlCommand: Sendable {
|
||||
summary: "The last lines of the console log (--kind create, dfu or patch for those logs)."),
|
||||
Self(name: "vm.create", arguments: ["name"], options: [
|
||||
"root", "iphone-source", "cloudos-source", "cpu", "memory", "disk-size", "network", "preset", "from",
|
||||
], flags: ["force-dsc-maxslide", "keep-artifacts", "no-wait"],
|
||||
], flags: ["keep-artifacts", "no-wait"],
|
||||
summary: "Create a machine through every step, as New Machine does. --from <step> retries a failed creation from that step."),
|
||||
|
||||
Self(name: "cfw.install", arguments: ["name"], options: ["root"], flags: ["force-dsc-maxslide", "keep-artifacts"],
|
||||
Self(name: "cfw.install", arguments: ["name"], options: ["root"], flags: ["keep-artifacts"],
|
||||
summary: "Install CFW into a stopped machine through the root helper."),
|
||||
|
||||
Self(name: "guest.send", arguments: ["name", "json"], options: ["root"], flags: [],
|
||||
|
||||
@@ -54,7 +54,6 @@ nonisolated protocol VPhoneLaunchpadHelperProtocol {
|
||||
bundleVersion: String,
|
||||
machineName: String,
|
||||
libraryRoot: String,
|
||||
forceDyldSharedCacheMaxSlide: Bool,
|
||||
keepArtifacts: Bool,
|
||||
reply: @escaping @Sendable (Int32, String?) -> Void,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user