198 Commits
Author SHA1 Message Date
Gilbo aae31f4448 Fix the bash prompt regex. After first booth the JB interactive shell sources the procursus profile which overrides PS1. 2026-06-22 12:15:06 +03:00
Gilbo 3b7cf92137 Fix typo NONE_INTERACTIVE --> NON_INTERACTIVE 2026-06-22 12:15:06 +03:00
Gilbo 6b6f3b1504 fix vphone_jb_setup.sh script.
The PATH variable needs to be constructed first before defining functions that use binaries such as date, tee, etc..
2026-06-22 12:15:06 +03:00
zqxwceandClaude Opus 4.7 44a69c0e9a camera: libvcamcaptured 26.x version-agnostic patches
The 26.5 implementation embedded multiple build-specific values:
  - a hardcoded byte offset 1056 to find `_sSourceList`
  - a hardcoded stack-frame offset (#576) in the per-source filter scan
  - hardcoded ivar offsets 0x08/0x18/0x10/0x18/0x48 on
    BWFigCaptureDevice / BWFigCaptureStream
  - hardcoded image VMAs 0x1ae6ff05c, 0x1ae2bd414, 0x1ae2c353c for
    three error-suppression byte-patches
  - two `#if 0` blocks pinning more 0x1ae* VMAs

Refactor every site to a runtime-resolved equivalent.

1. _sSourceList: structural ARM64 anchor chain rooted at the exported
   FigCaptureSourceServerStart symbol — every link is a stable pattern
   that survives DSC byte-offset shifts, stub-call layout changes, and
   LC_SYMTAB local-symbol stripping:

     FigCaptureSourceServerStart   (exported, retained on every build)
       walk for `cmn x?, #0x1 ; b.ne <wrapper>`     (onceToken check)
     wrapper                       (single-insn `bl <cold.1>` site)
     cold.1                        (static helper; 5-6 instructions)
       `adrp x1, ... ; add x1, x1, #imm`            (block-constant addr)
     block constant                (struct __Block_literal in __DATA_CONST)
       +0x10  = invoke pointer (PAC-stripped) = dispatch_once body
     init block-invoke
       walk for `bl <X> ; adrp + str x0, [Xn, #imm]` pairs
                                                    (each "store fn result
                                                     into a static global")
       pick the first slot whose stored value is a heap CFArray
                                                    (filters the lock-store
                                                     at #0 — that's a void*
                                                     mutex handle, not an
                                                     array)

   LC_SYMTAB is still consulted first as a fast deterministic path for
   builds that happen to retain `_sSourceList` as a regular nlist entry;
   the structural chain is what actually fires on stock 26.1/26.3.1/26.5
   DSCs (which strip static data symbols).

2. Per-source filter LDR x2 anchor: mask the imm12, accepting any
   sp-relative 64-bit load into x2 regardless of the compiler-chosen
   stack-frame slot.

3. BWFigCaptureDevice / BWFigCaptureStream ivar offsets: resolved at
   synth-class init via class_getInstanceVariable + ivar_getOffset on
   the parent class. Required ivars (deviceID, portType, uniqueID)
   abort class registration on miss; the streaming BOOL is optional
   (skip the YES poke instead of aborting). New vcc_resolve_ivar
   helper walks a NULL-terminated candidate-name list to tolerate
   underscore-prefix convention differences.

4. -[FigCaptureCameraSourcePipeline requiresMasterClock] prologue:
   resolved via LC_SYMTAB by name (two underscore-prefix variants),
   PAC-stripped, gated on a `pacibsp` insn1 sanity anchor before
   rewriting to `mov w0, #0 ; ret`. The function isn't in the ObjC
   method table on observed builds (so class_replaceMethod won't
   intercept) — the byte-patch is the only working path.

5. _cs_addObjectToStreamsAttributes and -[BWFigVideoCaptureStream
   initWithCaptureStream:…] -12783 bail sites: both prepare the
   OSStatus via MOVN encodings (0x12863dd4 for w20, 0x12863dc8 for
   w8). The new vcc_scan_and_patch helper finds every occurrence of
   each encoding in __text and rewrites it to MOVZ #0. -12783 is a
   capture-specific OSStatus and the daemon's only consumer in the
   VM is the synth source, so over-application is benign.

Validator fixes (kept from the original 26.1/26.5 work):
  - arm64e ISA class-pointer mask: 0x00007FFFFFFFFFF8 (44-bit class
    field, bits 3-46) per libobjc's ISA_MASK. The previous mask
    captured bit 47 (magic-signature region), so two pointers to the
    same class produced different masked values when bit 47 differed.
  - Pointer dereferences during slot validation gated by
    `malloc_zone_from_ptr` so a stale/bogus heap pointer in a
    candidate slot can't trap the daemon during init. (vm_read /
    vm_read_overwrite were considered but cameracaptured's sandbox
    returns KERN_DENIED on intra-task vm_read on iOS 26.x.)

Helpers in scripts/vcamcaptured/libvcamcaptured.m:
  vcc_safe_read_ptr                    pointer-read wrapper
  vcc_slot_value_is_cfarray            malloc_zone + ISA-class check
  vcc_collect_call_then_store_globals  walk a function body for
                                       "BL <X>; adrp + str x0,
                                       [Xn, #imm]" pairs
  vcc_resolve_ivar                     class_getInstanceVariable
                                       wrapper with candidate-name list
  vcc_scan_and_patch                   __text scan + per-occurrence
                                       vcc_patch_word wrapper
  VCC_ISA_CLASS_MASK                   arm64e 44-bit class-pointer mask

The two dead `#if 0` byte-patch blocks (referencing 0x1ae2b5284 and
0x1ae2b4c90, with the captureSession_buildGraphWithConfiguration
thumbnail / preview-sink bail-bypass commentary) are removed along
with their explanatory comments. scripts/cfw_install_exp.sh's
comment that mistakenly described a non-existent "Patch #6" inside
_captureSourceServer_handleCopySourcesMessage is rewritten to
describe the actual DSC patches (NU short-circuit + AVF authorization,
both already version-agnostic via `ipsw dyld symaddr`).

Validated end-to-end on:
  iOS 26.1   build 23B85
  iOS 26.3.1
  iOS 26.5   build 23F77

All three return the same `vphone:vcam:0` synthetic camera as the
default video device and deliver real JPEG frames through the modern
AVCapturePhoto delegate path in continuitycaptured / Camera.app.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-20 01:12:16 +03:00
zqxwceandClaude Opus 4.7 1aab0d6e25 camera: discover AVCapturePhoto + resolvedSettings selectors at runtime
Replace the two giant hardcoded selectors (the 27-arg AVCapturePhoto
init and the 32-arg +resolvedSettingsWithUniqueID:… factory) with a
prefix-lookup + label-driven NSInvocation builder. This survives
Apple adding or removing args between iOS releases without code
changes: the discovered selector decides arg count and order, the
resolver block fills the args we care about by label
("timestamp", "photoSurface", "uniqueID", "photoDimensions", etc.),
and unknown labels get nil/zero from the runtime type encoding.

  cfx_find_selector_by_prefix(cls, prefix, classMethod)
      Walks class_copyMethodList on cls (or its metaclass for class
      methods), returns the matching selector with the most colons.
      Highest-arg-count match wins so a future Apple revision that
      adds a new arg in the middle is still found.

  cfx_normalize_first_label(NSString *)
      Strips "initWith" / "resolvedSettingsWith" and lowercases the
      first char of the remainder so the leading component matches
      the same label convention as the rest of the selector.

  cfx_invoke_with_labeled_args(target, selector, resolver)
      Builds the NSInvocation, iterates selector components, calls
      the resolver block once per arg with (label, typeEnc, outBuf).
      Block writes the value via the appropriate cast (CMTime,
      IOSurfaceRef, __unsafe_unretained id, NSInteger, etc.) or
      leaves outBuf zeroed.

Builders refactored:
  - cfx_build_resolved_settings now fills only uniqueID +
    photoDimensions + previewDimensions; everything else stays
    zero/nil (Apple's impl tolerates that on builds where the
    factory itself works at all).
  - cfx_build_avcapturephoto_with_request fills timestamp,
    photoSurface, photoSurfaceSize, processedFileType, metadata,
    captureRequest, sequenceCount, photoCount, sourceDeviceType.
    Every other surface/dictionary arg defaults to nil.

End state: net +169/-96 lines, zero hardcoded full selectors,
photo synthesis remains functionally identical on 26.5 and is
prepared for arg-list drift on future iOS revisions.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-20 01:12:16 +03:00
zqxwceandClaude Opus 4.7 35a7bd44d8 camera: load libcamfix into every AVFoundation client via TweakLoader Filter.Frameworks
Universal injection mechanism: any process where AVFoundation is
loaded (Camera.app, continuitycaptured, third-party apps, system
daemons — anything dyld pulls AVF into) automatically gets libcamfix
via TweakLoader. No per-bundle plist filter, no allowlist entries.

scripts/tweakloader/TweakLoader.m

  New Filter.Frameworks key. A tweak's plist may list framework
  names; TweakLoader matches the path containing
  "/<name>.framework/". Already-loaded frameworks trigger an immediate
  dlopen; not-yet-loaded frameworks register a
  _dyld_register_func_for_add_image callback that fires when the
  named framework appears.

  Two-tier engagement:
    - Framework-filtered tweaks scan + schedule in EVERY process,
      self-limiting at runtime. Cost in non-AVF processes is one
      dir scan + a few plist parses + one callback registration.
    - Non-framework tweaks (Bundles/Executables or no filter) keep
      the existing .app/+kVPhoneAllowedDaemonPaths gate so we don't
      drop arbitrary tweaks into launch-critical daemons.

  CRITICAL safety: dyld invokes add-image callbacks SYNCHRONOUSLY
  inside its loader lock. dlopen from within that callback recurses
  and can deadlock or crash early daemons. The actual dlopen is
  handed off to a background queue (dispatch_async) so it runs after
  dyld is idle.

  Defensive: each per-tweak block is @try/@catch wrapped so a
  malformed plist or Foundation quirk in an early-boot daemon can't
  crash the process and trigger a launchd respawn loop.

scripts/camfix/libcamfix.m

  Constructor no longer eagerly installs hooks. Instead registers a
  _dyld_register_func_for_add_image callback and installs hooks the
  first time AVFCapture's mach header is observed (idempotent via
  dispatch_once). Whether libcamfix loads before or after AVFCapture,
  hooks land exactly once.

  cfx_capturePhoto_hook now drives the MODERN
  -[<AVCapturePhotoCaptureDelegate> captureOutput:
  didFinishProcessingPhoto:error:] path in addition to the deprecated
  CMSampleBuffer one. The synthesized AVCapturePhoto uses nil
  captureRequest (there's no CAMCaptureEngine outside Camera.app —
  msgSend to nil during init returns 0 safely). Photos tagged with
  associated JPEG/CGImage so fileDataRepresentation /
  CGImageRepresentation return our bytes regardless of which delegate
  protocol the client implements.

scripts/camfix/libcamfix.plist

  Filter.Frameworks = ["AVFoundation"]. Replaces the previous
  Bundles=["com.apple.camera"] filter.

scripts/cfw_install_exp.sh

  build_libcamfix install_name reverted to /var/jb/Library/
  MobileSubstrate/DynamicLibraries/libcamfix.dylib (TweakLoader
  location). [JB-4.2] deploys dylib + plist together.

Verified on fresh `make setup_machine` install of 26.5:

  - 373+ distinct AVF-using processes auto-load libcamfix at boot,
    including watchdogd / amfid / backboardd / SpringBoard /
    cameracaptured / continuitycaptured.
  - Camera.app: preview live, photos save, shutter works past
    many consecutive captures.
  - continuitycaptured: a vanilla AVCapturePhotoCaptureDelegate
    using the documented capturePhotoWithSettings:delegate: API gets
    a real 1280x720 JFIF JPEG via the modern delegate path.
  - Full reboot cycle stable.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-20 01:12:16 +03:00
zqxwceandClaude Opus 4.7 ba70b6f523 camera: libcamfix substrate plugin + Camera.app photo delivery via CAMCaptureEngine
libcamfix.dylib is loaded into Camera.app (com.apple.camera) by
TweakLoader (the .app/-rule covers Camera.app automatically; the plist
filters to Bundles=[com.apple.camera]). It bridges the vphone shm
frames from libvcamcaptured into Camera.app's normal photo + preview
pipeline so the user can take real photos via the standard shutter.

Hooks (only fire for connections backed by AVCaptureDevice uid
"vphone:vcam:0"):

  - _setActiveFormat:    substitute device.formats.firstObject when
                         the session-preset->format lookup hands us a
                         nil format (would otherwise throw at launch).
  - capturePhoto         deliver a CMSampleBuffer (built from shm) via
                         the deprecated didFinishProcessingPhotoSample
                         delegate path — kept for test harnesses that
                         use the documented AVCapturePhotoOutput API.
  - beginMomentCapture / commitMomentCaptureToPhotoWithUniqueID:
                         Camera.app's actual shutter path. Skip orig
                         (would throw), stash the delegate at begin,
                         drive a synthesized AVCapturePhoto at commit.
  - cancelMomentCaptureWithUniqueID:    no-op (orig would throw).
  - AVCaptureSession _setRunning: / _setInterrupted: setters swallowed
                         for vcam-bound sessions, and isRunning /
                         isInterrupted getters force YES / NO so
                         Camera.app's "preview live" poll keeps the
                         viewfinder visible past ~4-5 s.
  - AVCaptureVideoPreviewLayer:  scan UIApplication.windows at 1 Hz
                         for layers bound to a vcam session and pump
                         CGImage frames into layer.contents at 30 Hz.
  - AVCapturePhoto fileDataRepresentation / CGImageRepresentation:
                         when the photo we synthesized is the receiver,
                         return the JPEG / CGImage we built from shm
                         instead of asking the (non-existent) photo
                         surface to encode itself.
  - CAMStillImageCaptureRequest: dynamically add three stubs
                         (resolvedSettings, unresolvedSettings,
                         lensStabilizationSupported) so AVCapturePhoto's
                         private 27-arg init does not throw on the
                         CAM-internal request we pass in.

Synthesized AVCapturePhoto construction:
  - extract the real CAMStillImageCaptureRequest for the current uid
    from CAMCaptureEngine._resultsQueueRegisteredStillImageRequests
    (Camera.app's pending-photo dict),
  - hand-build a minimal AVCaptureResolvedPhotoSettings via
    class_createInstance + ivar writes for uniqueID + dimensions +
    empty NSArray ivars (CFRetained so the dealloc chain stays valid),
  - feed both into AVCapturePhoto's documented 27-arg
    initWithTimestamp:photoSurface:... via NSInvocation,
  - tag the photo with the JPEG bytes via objc_setAssociatedObject
    so the fileDataRepresentation hook returns them.

Full AVF + CAM internal delegate sequence fired at commit time:
willBeginCaptureBeforeResolvingSettingsForUniqueID,
willBeginCaptureForResolvedSettings, willCapturePhotoForResolvedSettings,
didCapturePhotoForResolvedSettings, didFinishProcessingPhoto:error:,
didFinishCaptureForResolvedSettings:error:,
_didFinishStillImageCaptureForUniqueID:error:, and crucially
captureOutput:readyForResponsiveRequestAfterResolvedSettings:.
Without that last "responsive ready" signal AVF's 2-deep pipeline
never frees its slots and Camera.app's shutter stops accepting
input after the 2nd capture.

Install wiring in scripts/cfw_install_exp.sh:
  - build_libcamfix() — clang -arch arm64e -fobjc-arc -Os, frameworks
    AVFoundation / CoreImage / CoreMedia / CoreVideo / Foundation /
    ImageIO / IOSurface / MobileCoreServices / Photos / QuartzCore /
    UIKit, ldid-signed.
  - [JB-4.2] scp the dylib + plist into procursus/Library/MobileSubstrate/
    DynamicLibraries (same location as libvcamcaptured) and chmod /
    chown so TweakLoader picks them up on next boot.

End state: Camera.app on EXP shows live preview from the host-supplied
vcam frames, the shutter takes real photos that get saved into Photos
via Camera.app's own pipeline (no PHPhotoLibrary back-channel), and
the shutter button keeps working across many consecutive captures.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-20 01:12:16 +03:00
zqxwceandClaude Opus 4.7 9b4e25569b camera: libvcamcaptured + cameracaptured TweakLoader allowlist + install wiring
libvcamcaptured.dylib is loaded into /usr/libexec/cameracaptured via
TweakLoader and registers a synthetic FigCaptureSource backed by the
vphone shm region. From AVF's point of view there is now a normal
"vphone:vcam:0" camera device that streams BGRA frames at the
session's requested width/height.

  scripts/vcamcaptured/                .gitignore (drop built .dylib),
                                       Makefile, libvcamcaptured.m,
                                       libvcamcaptured.plist (filter:
                                       Executables=["cameracaptured"]).

  scripts/tweakloader/TweakLoader.m    add /usr/libexec/cameracaptured
                                       to kVPhoneAllowedDaemonPaths so
                                       TweakLoader engages in a daemon
                                       (not just .app/) processes.

  scripts/cfw_install_exp.sh           build_libvcamcaptured() helper
                                       (clang + CoreMedia/CoreVideo/
                                       Foundation, ldid-signed) and
                                       new [JB-4.1] section that
                                       scp's the dylib + plist into
                                       procursus/Library/MobileSubstrate/
                                       DynamicLibraries.

Pairs with the host vphone-cli camera server + vphoned vcam vsock
listener already in this branch.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-20 01:12:16 +03:00
zqxwceandClaude Opus 4.7 c8de9c9cb7 camera: vphoned guest-side vcam vsock listener + shm writer
Guest-side counterpart to VPhoneCameraServer. vphoned runs inside
the VM and now hosts an extra vsock listener on port 1338 that
receives BGRA frames from the host and writes them into a memory-
mapped shm region at
/var/jb/var/mobile/Library/vphone-vcam-frame.shm with a packed
header (seq monotonic, width/height, bytes_per_row, pixel_format,
timestamp_ns, frame_index, pixels_length).

  vphoned_vcam.h        packed shm header layout + filename const.
  vphoned_vcam.m        VPVcamServer: accept loop, header parse,
                        seq-bump write, file-based debug log for
                        first-boot post-mortem.
  vphoned.m             Boot the vcam server alongside the existing
                        port-1337 control daemon.

Pairs with the host VPhoneCameraServer (1338 client) and the guest
libvcamcaptured (shm reader) added in the following commits.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-20 01:12:16 +03:00
zqxwceandClaude Opus 4.7 9bb5b87279 camera: Make Camera.app launch on EXP firmware
Three pieces of plumbing that together let Camera.app reach the
viewfinder UI on the EXP build:

1. DeviceTree: synthesize a /product/camera node so AVCaptureDevice
   discovery + permission gating finds a "front" camera slot for
   subsequent vcam injection (FirmwarePatcher/DeviceTreePatcher.swift).

2. DSC patch family in scripts/patchers/cfw_patch_camera_dsc.py:
     - NeutrinoCore short-circuit: rewrite the five
       +[_NUStyleTransfer*Processor processWithInputs:arguments:output:
       error:] class methods to `mov w0, #0; ret`. Without this Camera.app
       crashes inside NeutrinoCore the moment it tries to render the
       style picker.
     - AVCaptureDevice auth always-authorized:
       +[AVCaptureDevice authorizationStatusForMediaType:] -> `mov w0, #3;
       ret` (AVAuthorizationStatusAuthorized=3). Any process that probes
       camera authorization gets "Authorized" without going through TCC.

3. scripts/patch_camera_userland.sh + cfw.py registration so the install
   pipeline applies the two patches above against the chunked DSC during
   `make cfw_install_exp`.

Camera.app now launches and shows preview UI on EXP, even though the
actual vcam pipeline is wired up by the libvcamcaptured / libcamfix
commits that follow.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-20 01:12:16 +03:00
Maximilian Paßandzqxwce 3b08751029 Fix Patchless for cloudOS 26.4 (#336)
* Fix Patchless for cloudOS 26.4

by dynamically matching and downloading the required apfs_sealvolume version.

* patchless: Add `seal.log` for `apfs_sealvolume` command

---------

Co-authored-by: zqxwce <[email protected]>
2026-05-28 13:55:05 +03:00
zqxwce d15d218fe2 ramdisk: Remove trollstore from RAMDISK_REMOVE list 2026-05-25 15:12:59 +03:00
zqxwce 9eea357142 ramdisk_build: Remove usr/trollstore from ramdisk to free needed space 2026-05-24 18:18:52 +03:00
zqxwce 045d8050a3 install: Add opt-in ProductBuildVersion rewrite via SPOOF_BUILD (EXP-JB-7)
Adds the final EXP-only step: rewrite the userland-visible
`ProductBuildVersion` in `SystemVersion.plist` to a chosen build
identifier. Gated on the `SPOOF_BUILD` env var — when unset/empty, the
step is skipped entirely and the build identifier stays at whatever the
IPSW shipped.

The iPhone IPSW we install from ships with build identifier `23B85`
(iOS 26.1). iOS displays this string in Settings -> General -> About ->
"Build" and exposes it through `MGCopyAnswer("BuildVersion")`,
CoreFoundation's `_CFCopyServerVersionDictionary`, App Store telemetry,
and every other framework path that reads
`/System/Library/CoreServices/SystemVersion.plist`.

The build identifier lives in exactly two on-device plist files. Both
are plain XML/binary plists (no Apple-side per-file signature), and
both live on volumes that are writable at install time:

  /System/Library/CoreServices/SystemVersion.plist                                  (rootfs)
  /private/preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist     (preboot)

EXP-JB-7 rewrites the `ProductBuildVersion` key in both plists to the
target value (typical: `23F77`). `ProductVersion` (`26.1`),
`ProductName` (`iPhone OS`), `BuildID`, `SystemImageID`, and
`ProductCopyright` are left untouched.

Order of operations: EXP-JB-7 runs AFTER EXP-JB-6 (post-restore DT
rewrite) so the post-restore identity work on `/mnt5` has completed
before the Cryptex's SystemVersion.plist (same volume) is touched.

Does NOT flip:
  - `sysctl kern.osversion` — comes from a kernel global initialized
    from boot args at boot time, not from this plist. To change it
    would require rebuilding the kernelcache with a different
    `OS_BUILD_VERSION` or patching the boot-args path — out of scope.
  - `SystemVersionCompat.plist` — carries a legacy iOS-19 marker for
    MacCatalyst-style queries; not user-visible, deliberately untouched.

- `scripts/patchers/cfw_patch_build_version.py` — host-side
  plistlib-based rewriter. Auto-detects XML vs binary plist format
  and preserves it on write. Idempotent — a re-run on an
  already-patched plist exits without rewriting.
- `scripts/cfw_install_exp.sh` — EXP-JB-7 phase: gated on
  `SPOOF_BUILD`; for each of the two plist paths it scp_from's the
  file to host, runs the patcher with the target id, scp_to's the
  file back. Tolerates missing-on-device with warn+continue.

Invocation:
  make setup_machine EXP=1 SPOOF_BUILD=23F77
  make cfw_install_exp SPOOF_BUILD=23F77

JB and DEV install scripts do NOT carry this step.
2026-05-18 16:14:55 +03:00
zqxwce 8bc903644f devicetree: Add post-restore identity rewrite for restore-fatal properties (EXP-JB-6)
The three restore-fatal DT root properties (root `model`, root
`target-type`, root `compatible[0]`) cannot be edited at fw_patch
time because `restored_external` / iBoot's restore mode cross-checks
them against the BuildManifest's signed `SupportedProductTypes`.
Editing them in the IPSW's `devicetree.im4p` causes the restore to
fail partway through.

But the cross-check fires ONLY during installation. After restore
completes, subsequent boots validate IM4P contents against the IM4M
only, which the existing iBSS/iBEC/LLB `image4_validate_property_callback`
bypass patches accept regardless. So an additional DT edit applied
AFTER restore but BEFORE the device reboots into the rootfs is
in-policy with the project's existing trust-chain bypass.

EXP-JB-6 exploits this. While the install pipeline still has `/mnt5`
(the preboot volume) mounted on the ramdisk, `cfw_install_exp.sh`:

  1. scp_from's `/mnt5/<boot-hash>/usr/standalone/firmware/devicetree.img4`
     to the host.
  2. Runs `scripts/patchers/cfw_patch_post_restore_dt.py`, which:
       - Unwraps IMG4 -> IM4P -> LZFSE-decompresses -> DT flat-binary
         blob (via pyimg4).
       - Rewrites three root properties:
           root `model`:       iPhone99,11        -> iPhone17,3
           root `target-type`: VPHONE600          -> D47
           root `compatible`:  reorder so D47AP is first, VPHONE600AP
                                second (IOKit's AppleVMApple1IO platform
                                bind still resolves via the second
                                entry; userland reads only the first
                                for `hw.model`).
       - Re-compresses LZFSE -> repacks IM4P -> repacks IMG4 with the
         ORIGINAL IM4M (the per-board ticket survives — the image4
         bypass already accepts any payload contents).
  3. scp_to's the modified img4 back to the same path.
  4. The device reboots out of ramdisk; iBoot loads the modified DT;
     kernel populates `machine_info` from the new property values.

Idempotent: the patcher detects target-state-already-met and exits
without rewriting.

Userland effects on next boot:
  - `sysctl hw.machine` -> "iPhone17,3" (was "iPhone99,11")
  - `sysctl hw.product` -> "iPhone17,3" (was "iPhone99,11")
  - `sysctl hw.model`   -> "D47AP"      (was "VPHONE600AP")
  - Settings -> General -> About -> Model Identifier picks up the new
    ProductType after the gestalt cache rebuilds.

- `scripts/patchers/cfw_patch_post_restore_dt.py` — host-side
  img4 <-> IM4P <-> DT flat-binary round-trip via pyimg4. Mirrors the
  DT format parser/serializer from `DeviceTreePatcher.swift`.
- `scripts/cfw_install_exp.sh` — EXP-JB-6 phase: discovers the
  boot-manifest-hash via the same `get_boot_manifest_hash` helper used
  by earlier install steps; tolerates a missing devicetree.img4 with
  warn-and-skip. JB and DEV install scripts do NOT carry this step.
2026-05-18 16:14:55 +03:00
zqxwce 674a86bfd4 watchdogd: Add surgical hv_vmm_present cache patch (EXP-JB-3.5)
After the kernel-side OID rename (`KernelEXPPatchHvVmmRename`),
`sysctlbyname("kern.hv_vmm_present", ...)` returns ENOENT on this image.
`/usr/libexec/watchdogd` caches that answer at startup. On ENOENT the
cached byte stays at its BSS-zero default (`0`) and the downstream
`cbz w0, ...` at the IOWatchdog-lookup site takes a branch into
`_os_crash` -> `brk #1`; launchd's `_PanicOnCrash =
PanicOnConsecutiveCrash = true` flag in `com.apple.watchdogd.plist`
escalates the SIGTRAP to a kernel panic.

The cstring-mangle approach used for DSC dylibs doesn't apply here: we
want this binary to behave as if the sysctl returned 1, not as if it
returned ENOENT. Solution is a surgical 2-instruction patch that forces
the cached "am I a VM?" byte to 1 regardless of the sysctl result.

- `scripts/patchers/cfw_patch_watchdogd.py` — capstone-anchored pattern
  matcher + Keystone-assembled 2-insn patch. Two functions match the
  canonical caching shape on iPhone17,3 / iOS 26.1; both are patched.
  Net effect: `cbnz w0, skip` -> NOP and `cset wN, ne` -> `mov wN, #1`,
  forcing the cached byte to 1. watchdogd's pre-existing "detected
  virtual machine environment, exiting..." clean-exit branch runs
  instead of the trap path. Idempotent.
- `scripts/patchers/cfw_macho_codesign.py` — generic standalone-Mach-O
  page-hash re-attestation. Parses `LC_CODE_SIGNATURE` directly, reads
  page size from each `CS_CodeDirectory` header (4 KiB on watchdogd —
  not the DSC's 16 KiB), handles short tail slot length
  (`codeLimit - (n-1)*pageSize`), and updates every present CD. The
  resulting cdHash change is accepted by the JB `patch_amfi_cdhash_in_trustcache`
  kernel patch which accepts any cdHash; the patcher does NOT re-sign
  with ldid (preserving the original Apple-issued code-signing
  identifier is required for launchd's boot-task identity validation).
- `scripts/patchers/cfw.py` — adds `patch-watchdogd` subcommand.
- `scripts/patch_hv_vmm_userland.sh` — adds `watchdogd <binary>` op.
- `scripts/cfw_install_exp.sh` — invokes the patcher at step
  `[EXP-JB-3.5]` on the live `/mnt1/usr/libexec/watchdogd` (scp-down,
  patch, scp-up, chmod 0755). JB and DEV install scripts do NOT run
  this step.
2026-05-18 16:14:55 +03:00
zqxwce af90c9a903 userland: Add DSC hv_vmm_present byte-5 mangle with sign-in blacklist and slot reattest (EXP only)
Companion user-mode patches to the kernel-side OID rename. Mangles
byte 5 of every `kern.hv_vmm_present` cstring inside DSC dylibs EXCEPT
those in `DONT_PATCH_INSTALL_NAMES` (sign-in / device-likeness consumers,
~15 entries). Patched dylibs query the renamed OID and get the truthful
1 (graphics + accel passthrough); blacklisted dylibs keep the original
cstring, hit ENOENT on the renamed kernel, defensively cache 0 ("not
running on a VM") for sign-in / device-attestation surfaces.

- `scripts/patchers/cfw_patch_hv_vmm_dsc.py` — DSC orchestrator. Walks
  every `kern.hv_vmm_present\\0` cstring in any executable mapping,
  resolves the containing dylib via Mach-O-header walk-back +
  LC_ID_DYLIB, applies the byte-5 mangle to non-blacklisted dylibs, and
  drives slot-hash re-attestation in the chunk's `CS_CodeDirectory`.
- `scripts/patchers/cfw_dsc_chunks.py` — pure-Python helper for the
  chunked DSC layout: vmaddr<->chunk-fileoff mapping, install-name
  walk-back, byte-level read/write at a vmaddr.
- `scripts/patchers/cfw_dsc_codesign.py` — per-page SHA-256 slot-hash
  re-attestation for DSC chunks (16 KiB pages). Required on
  `codeSigningMonitor == 2` (TXM) hardware where per-page hash checks
  would otherwise SIGKILL the patched dylibs at first demand-page-in.
- `scripts/patchers/cfw_patch_hv_vmm.py` — standalone Mach-O variant of
  the cstring mangle (kept for completeness; the historical
  standalone-binary loop step was removed in favor of the
  blacklist-flip-via-kernel-rename design).
- `scripts/patchers/cfw_patch_hv_vmm_rootfs.py` — rootfs-path inventory
  shared with the install scripts (former JB-3.5 / 6.5/7 loop input).
- `scripts/patch_hv_vmm_userland.sh` — thin wrapper used by the install
  script (`dsc` and `standalone` operations; `watchdogd` is added by
  the next commit).
- `scripts/patchers/cfw.py` — new subcommands: `patch-hv-vmm`,
  `patch-hv-vmm-dsc`, `list-hv-vmm-rootfs-paths`.
- `scripts/cfw_install_exp.sh` — adds a pre-step that decrypts the
  SystemOS Cryptex AEA into the cache location `cfw_install.sh` already
  uses, mounts it, applies the DSC patch, and unmounts. The unmodified
  base `cfw_install.sh` then picks up the cached (patched) DMG.

Research docs in this commit describe the full EXP variant comprehensively
(`research/0_binary_patch_comparison.md` top-of-doc note, EXP-Only
Kernel Methods section, DSC userland subsection, plus forward references
to EXP-JB-3.5 / EXP-JB-6 / EXP-JB-7 subsections wired up by the following
commits; `research/firmware_manifest_and_origins.md` sections 9-11
similarly forward-describe the DT and build-version pieces).

JB and DEV variants are NOT affected: their install scripts don't invoke
any of this.
2026-05-18 16:14:55 +03:00
zqxwce ee5e7d0fa2 kernel: Introduce EXP variant and hv_vmm_present OID rename (KernelEXPPatcher)
Introduces a new `exp` firmware variant on top of JB and ports the
`hv_vmm_present` sysctl rename to it.

Variant infrastructure
----------------------
- Adds `case exp` to `FirmwarePipeline.Variant`,
  `VPhoneCLI.PatchFirmwareCLI.VariantOption`, and
  `VPhoneVirtualMachine.Variant`. Every `switch variant` block in
  `FirmwarePipeline.buildComponentList` includes the new case.
- Adds `make fw_patch_exp` + `make cfw_install_exp` Makefile targets
  and an `EXP=1` flag for `make setup_machine`, mutually exclusive with
  `JB=1` / `DEV=1` / `LESS=1`. `SPOOF_BUILD` env var is threaded
  through for later use by the build-version step.
- Adds `--exp` to `scripts/setup_machine.sh` alongside `--jb` /
  `--dev` / `--less`. The post-install JB-Finalize block also fires
  for EXP (since EXP inherits the JB rootfs deployment).
- Adds `scripts/cfw_install_exp.sh` as the EXP install script
  starting point: identical phase set to `cfw_install_jb.sh` (JB-1..JB-5)
  with banner/header/footer updated for EXP. Subsequent commits in this
  branch add the EXP-only experimental phases on top.
- Updates the variants table in `AGENTS.md` / `README.md` and the
  three translated READMEs to include the new `Experimental (EXP)`
  row, plus a paragraph describing what EXP adds on top of JB.

Kernel patch
------------
- New `KernelEXPPatcher` orchestrator in
  `sources/FirmwarePatcher/Kernel/`, chained after `KernelPatcher` +
  `KernelJBPatcher` for the `.exp` variant only. Inherits
  `KernelJBPatcherBase` to reuse the JB symbol-table / ADRP-BL index /
  branch-encoder infrastructure.
- New `KernelEXPPatchHvVmmRename` patch in
  `sources/FirmwarePatcher/Kernel/EXPPatches/`. Part A flips byte 0 of
  the NUL-delimited `\\0hv_vmm_present\\0` cstring (the sysctl OID's
  `oid_name` value) — `'h'` -> `'X'` — so the kernel resolves
  `sysctlbyname("kern.hv_vmm_present")` as ENOENT and
  `sysctlbyname("kern.Xv_vmm_present")` to the OID's real int value.
  Part B mangles byte 5 of every kernel-internal occurrence of
  `kern.hv_vmm_present` so callers keep hitting the renamed OID; two
  byte-aligned forms are covered (NUL-delimited cstring + sandbox-profile
  name-token with trailing `\\x0f`).
- Patch IDs are `kernelcache_exp.hv_vmm_oid_rename` and
  `kernelcache_exp.hv_vmm_internal_caller_mangle`. Idempotent.
- `KernelJBPatcher` is unchanged at the call-site level (no
  `patchHvVmmRename` call); its docstring is updated to point at
  `KernelEXPPatcher` for the EXP-only patch.

JB and other variants are NOT affected: `cfw_install_jb.sh` and
`cfw_install_dev.sh` are untouched in this commit.
2026-05-18 16:14:55 +03:00
Joshua Seltzer f283d83e25 Add zstd dep to other README files and the setup_tools script 2026-05-07 16:01:37 +03:00
zqxwce 77d4a04c55 window: Show guest IP in subtitle once vphoned connects
vphoned now reports its primary non-loopback IPv4 address in the hello
response (preferring en*/pdp_ip* interfaces).
2026-04-30 12:27:59 +03:00
renegadelink 6f3b16397b restore: add offline mode (cached SHSH + in-place AEA decryption) 2026-04-26 18:54:23 +03:00
Maximilian Paß bde1ff3a83 [Patchless] Fix Panic Regex 2026-04-24 00:19:28 +03:00
Maximilian Paß a6f0bf4326 [Patchless] Add noVphone Flag 2026-04-24 00:19:28 +03:00
Maximilian Paß bca4450a6c [Patchless] Add Binpack (incl SSH) 2026-04-24 00:19:28 +03:00
zqxwce 120d6f9862 venv: Fix python3 locating for enviornments using uv 2026-04-20 17:23:32 +03:00
zqxwce 3b48ce6cf3 iosbinpack64: dev_overlay: Move dev overlay to before install 2026-04-20 17:23:32 +03:00
zqxwce 367209a1e4 amfidont: Simplify start_amfidont_for_vphone.sh 2026-04-15 16:41:07 +03:00
zqxwce 7d09a1bb0e patchless: Add support for non SIP/AMFI disabled systems 2026-04-15 16:41:07 +03:00
zqxwce 45c3df7609 setup_tools: Limit apfs_sealvolume download to patchless variant only 2026-04-08 18:31:14 +03:00
Maximilian Paßandzqxwce 8188761e24 [Patchless] Add AMFI preflight test
Co-authored-by: zqxwce <[email protected]>
2026-04-08 18:29:01 +03:00
Maximilian Paß ecf1c327fc [Patchless] Add vphoned 2026-04-08 18:29:01 +03:00
Maximilian Paß b81fa62a11 Introduce Patchless Variant 2026-04-08 18:29:01 +03:00
zqxwce 20d3f1a217 pymobiledevice3: Replace most external tools with pymobiledevice3 2026-04-03 13:47:09 +03:00
pluginslab 981f2cfcc9 setup_machine: fix ECID mismatch race in DFU recovery wait (#260) 2026-03-31 07:59:36 +08:00
TastyHeadphones 5ab5e5b6f8 setup: install and document aria2c (#237) 2026-03-19 03:24:01 +09:00
TastyHeadphones e0ad9e87ed boot_preflight: skip /dev/tty fallback without a tty (#225) 2026-03-17 18:31:34 +09:00
Brandon Lekai 9c90286b70 Implement a battery sync between the VM and the host (#230)
* Implement battery sync with host

* Clean up the previous sync implementation

* Enable the battery sync functionality by default

* Re-sync the VM's battery state when vphoned reconnects
2026-03-17 18:31:13 +09:00
Mustafa Dur 78e4c0cb6d Prevent script exit when nvram boot-args is missing (#219) 2026-03-16 01:40:17 +09:00
zqxwce 30fcc05ca5 refactor: Move all manual clones to be submodules (#218) 2026-03-16 01:40:05 +09:00
Adam McNight 5484151149 fix: handle multi-volume csrutil prompt in boot preflight (#209) 2026-03-15 16:59:42 +09:00
TastyHeadphones 71b8f8e53b vm: validate restore and switch backup names (#210) 2026-03-15 16:59:16 +09:00
maybe developer fea8d88513 feat: add aria2c support instead of shitty wget/curl (#207) 2026-03-15 01:39:57 +09:00
matteo zappia 624ed4de31 add: VM backup, restore, and switch support (#206)
* fix: prefer project venv Python for patchers

* add: VM backup, restore, and switch support

Named backups via rsync --sparse for efficient sparse disk handling.
- vm_backup.sh: save current VM as a named backup to vm.backups/
- vm_restore.sh: restore a named backup into vm/
- vm_switch.sh: save current + restore target in one step
- Makefile targets: vm_backup, vm_restore, vm_switch, vm_list
- Documentation added to all READMEs (EN, ZH, KO, JA)

Closes #204

Made-with: Cursor
2026-03-15 01:39:10 +09:00
TastyHeadphones c67de21483 setup_machine: avoid errexit-sensitive arithmetic increments (#201) 2026-03-13 22:16:40 +09:00
zqxwce 4b052cc1ca setup_machine: Fix (( waited++ )) causing exit on first iteration (#199)
In the first iteration, waited would be 0 and cause the expression to be evaluated to `(( 0 ))`, which exists as it returns 1.
2026-03-13 01:11:13 +08:00
Robert H 0320c9142c Update vm_manifest.py (#198)
fix issue with older python versions erroring out on line 20 (formerly 19)
2026-03-12 13:52:08 +08:00
TastyHeadphones 6cc5a11b09 fw_prepare: avoid cloudOS cache key collisions for extensionless sources (#197) 2026-03-12 13:51:57 +08:00
Xin Huang 08c9cb78ee Nix PATH preservation, amfidont boot, and preflight stability (#196)
* fix: preserve caller PATH through Nix zshenv reset in cfw scripts

Nix darwin's /etc/zshenv resets PATH on every zsh subprocess,
discarding the Makefile's carefully constructed PATH (which includes
.venv/bin and /opt/homebrew/bin). This caused 'Missing Python deps'
and ldid PKCS12_parse errors during cfw_install.

Pass the Makefile PATH through _VPHONE_PATH env var (which zshenv
won't touch), and restore it at the top of each cfw_install script.

* fix(cfw_install_dev): add python resolver, use glob for vphoned sources

- Add _resolve_python3() matching cfw_install.sh so the venv python
  is used instead of Nix system python (which lacks capstone/keystone).
- Replace hardcoded VPHONED_SRCS list with glob pattern to auto-pick
  up new .m files (was missing 5 files: accessibility, apps, clipboard,
  settings, url — causing linker errors).

* fix: amfidont uses bundle binary CDHash and .build path

make boot launches the bundle binary (.build/vphone-cli.app/Contents/
MacOS/vphone-cli), not the release binary. amfidont's --path must
cover the .app bundle location.

- amfidont_allow_vphone depends on bundle (not build)
- start_amfidont_for_vphone.sh extracts CDHash from bundle binary
- --path points to .build/ so amfidont covers .app bundle contents

* fix(preflight): prevent run_capture errexit on non-zero return

zsh set -e is global scope — set -e inside run_capture then
return 137 triggers errexit and kills the script before reaching
the assert-bootable check. Use '|| rc=$?' instead to capture
the exit code without modifying errexit state.
2026-03-12 13:51:45 +08:00
Felipe Cavalcanti 5da047bddd Add firmware listing and selectable IPSW resolution (#188) 2026-03-11 15:32:03 +08:00
matteo zappia e8c29f3a82 fix: prefer project venv Python for patchers (#187) 2026-03-11 15:31:44 +08:00