The 117 bundled patch identifiers had grown five naming schemes
(kernel.x, jb.x, kernelcache_jb.x, txm_dev.x, bare names). Each one is now
{component}-{effect}-{name}:
- component: avpbooter, ibss, ibec, llb, txm, kernel, devicetree, dyld,
preboot, or system-<binary> for a guest binary or file.
- effect: boot when the patch is boot-essential, exp when the standard
preset leaves it off, cfw otherwise. A catalog test enforces this.
- name: snake_case, no hyphen, so the identifier splits from the right.
Record sites are now always <identifier>.<site>. The underscore-prefix
rule in covers(recordIdentifier:) and in the gate is gone: the new names
contain underscores, so kernel-boot-post_validation would otherwise have
covered kernel-boot-post_validation_unsigned. The 25 records that relied
on it (amfi_trustcache_1, launch_constraints_mov, sandbox_ext_N, ...) now
use a dot.
Old identifiers are not migrated. A VM whose PatchPlan or PatchSelection
names one must be patched again. The bundle becomes 2.2.0 and Launchpad
requires 2.2.0, so it never meets an old identifier from a bundle.
Launchpad's patch table shows Component, Effect and Name columns in place
of Identifier and Patch Set; the set moves to the detail line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Xcode could not install anything onto a guest unless it was signed with an
Apple leaf certificate, even though the guest runs unsigned code perfectly
well. Measured on a booted 26.6.2 guest: unsigned and ldid-shaped bundles
fail at 0xE800801C, a `codesign --sign -` bundle at 0xE8008014, all from
+[MICodeSigningVerifier _validateSignatureAndCopyInfoForURL:withOptions:error:].
A bundle pushed in through vphoned's apps.install, signed with nothing but
this project's own signature, installs and reaches the foreground — so the
kernel, lsd and SpringBoard already accept it and installd's check is the
only gate.
libmis accepts an ad-hoc signature outright when the caller passes the
AllowAdHocSigning option, which installd never does, and it fills the whole
info dictionary itself. So libmisfix.dylib interposes
MISValidateSignatureAndCopyInfo and adds the option, and cfw install attaches
it to installd with an LC_LOAD_WEAK_DYLIB. Nothing in the dyld shared cache
is touched, deliberately: writing a cache code page is what leaves a 27.0
guest unable to boot in #532.
The same dylib answers the other refusal. A paid team's profile fails at
0xE8008012 because the VM's UDID is in nobody's ProvisionedDevices, and only
a free personal team gets auto-registration. misagent obtains that UDID from
MGCopyAnswer -- its other source, an emulated UDID in the kernel's
codesigning configuration, is unreachable here: the sysctl is a four-byte
flags word and amfi_emulate_device_udid is in neither the kernelcache nor
TXM. Interposing MGCopyAnswer lets libmisfix.plist name a device the team has
already registered. Off until a UDID is set there.
What Xcode and lockdown report is unchanged and still the guest's own UDID;
TXM builds that one from the device tree before the kernel runs. The two
answers disagree on purpose, because agreeing would mean a re-restore for a
UDID that still could not match a real device's.
Also fixes#532's second defect: DyldSharedCacheMISTrustAuthPatcher now
recognises its own output, so a second cfw install reports alreadyPatched
instead of aborting the install.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
`input.touch2` accepted a `normalized` parameter and never read it, while
`input.touch` beside it honours one. `vp_hid_touch2` takes 0..1 only, so a
caller passing screen points the way `input.touch` allows would have had both
fingers read as fractions and land in the corner, silently. Refuse the flag
instead.
The view's `normalizeCoordinate(allowOutside:)` had reimplemented
`VPhoneDisplayGeometry.normalizedPoint` minus the clamp, leaving the mapping
in two places while only one of them is tested. The geometry type takes a
`clamped` parameter now, with a test for the path the scroll gesture uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two ways to drive the guest from the host, both landing on the same touch injection.
Trackpad: a two-finger scroll becomes a synthetic finger that presses under the pointer and follows the physical direction, with edge re-anchoring so a long swipe is not bounded by where the pointer sits, and a Home-strip snap so an unlock gesture starts inside the indicator strip. A pinch becomes two fingers spreading or closing around the pointer. The two never interleave.
Esc: iOS has no back key, and a forwarded Escape only reads as cancel (or stop-loading in Safari), so Esc and a new Device -> Back item replay the system back gesture instead. It is taken in VPhoneApplication.sendEvent, before the menu lookup, because AppKit does not reliably match a modifier-less Esc key equivalent.
Both land on the view existing touch path, so the guest-side half is shared: VPhoneGuestControl gains supportsMultiTouch and sendTouch2 for the new input.touch2 request, and VPhoneDaemon builds the two-finger hand event itself because icli carries one digitizer point per event and cannot express a pinch. A guest that predates touch2 degrades to a one-finger move.
Bundle 2.1.7 (build 18). vphoned now installs IPAs and reads debs whose
entries have UTF-8 names, through icli 0.7.5 (#530), and vphone-cli's
archive commands read and write such names too. Launchpad is unchanged:
the app stays 2.1.2 and still accepts VPhone.bundle 2.1.0 or later.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundle 2.1.6 (build 17). It carries the MIS trust/authorization patch, so
an app signed with a free personal-team certificate now launches on a
guest this project restored, and the macOS 27 CFW disk selection and
debugserver seatbelt fix. Launchpad is unchanged: the app stays 2.1.2 and
still accepts VPhone.bundle 2.1.0 or later. This release's Launchpad zip
is signed but not notarized; the notarized 2.1.2 download still works.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A guest restored by this project is hacktivated, so it never receives an
activation record and online-auth-agent can never obtain the device
identity an authorization request is signed with. libmis's
checkTrustAndAuthorization therefore returns 0xE8008026 and the profile
stays in "Profile Needs Network Validation" for good: an app signed with
a free personal-team Apple Development certificate installs, then
refuses to launch, and Settings' "Verify App" cannot clear it because
the network step it offers is the step that cannot complete. A paid
team's profile is not marked as needing online authorization, which is
why this was never seen before.
DyldSharedCacheMISTrustAuthPatcher short-circuits the function to return
success, writing mov x0, #0; retab after the prologue's pacibsp so the
PAC pair stays balanced. The function is static and carries no symbol,
so it is anchored on the log string that names it outright and then
required to seed 0xE8008026 in its prologue before anything is written:
two independent routes that must agree. Replacement bytes are the
existing keystone-checked ARM64.movX0_0 and ARM64.retab constants, and
the modified page is re-attested. A cache whose libmis lacks the string
reports absent and exits 0, an already-patched cache is a no-op, and a
cache with the string but no seeding prologue is an error rather than a
guess.
The declaration mis_trust_auth carries no applicability and is not boot
essential: the guest is hacktivated on every base, so the failure exists
on every base. cfw install applies it unconditionally.
Validated on a fresh iPhone17,3 26.6.2 (23G90) + cloudOS 26.4 guest: the
patcher reached the same site through the DSC chunk path that was derived
statically from the extracted library, the guest booted normally, and a
free-team app now installs, verifies, launches and accepts an Xcode
attach.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cfw): select image disk on macOS 27
Select the APFS store's parent disk when hdiutil lists the synthesized container first.
Use the selected disk for both CFW installation and PCC GPU recovery.
* fix(debugserver): replace entitlements without seatbelt profile
Sign with the complete edited entitlement dictionary instead of merging it with the original signature.
A merge restores seatbelt-profiles, so removing that key did not take effect.
Bundle 2.1.5 (build 16). Launchpad is unchanged: it stays 2.1.2, still
accepts VPhone.bundle 2.1.0 or later, and the READMEs keep pointing at
the 2.1.2 notarized Launchpad.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window used to wait for the guest, then jump to its new shape with
a black frame between. The container now drives one 0.35 s ease-in-out
turn from a display link, interpolating the panel's angle and the window
frame together. Mid-turn the panel keeps its aspect ratio and shrinks
to fit inside the window, so there are no bars or black frames; full
screen turns the panel alone.
Rotations started from the menu set the orientation before the guest is
asked, so the window turns alongside the guest instead of after it. A
refused orientation moves straight on to the next candidate, and only a
full refusal turns back. The poll pauses while a menu rotation is
pending, so a read from before the guest turned cannot undo it, and a
repeated ⌘← or ⌘→ turns on from the orientation already being turned
to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundle 2.1.4 (build 15). Launchpad is unchanged: it stays 2.1.2, still
accepts VPhone.bundle 2.1.0 or later, and the READMEs keep pointing at
the 2.1.2 notarized Launchpad.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vphoned gains display.orientation, which asks SpringBoard's
activeInterfaceOrientation through AXSpringBoardServer instead of
capturing the screen, so the host can poll it every second. Guests that
report display_orientation are polled; others stay portrait.
The window's content view now holds the VM view turned to that
orientation. The VM view keeps its portrait bounds, so touch mapping is
unchanged: AppKit's conversion undoes the rotation. A windowed VM swaps
its sides around its center and shrinks to fit the screen; full screen
letterboxes the turned panel. A frame saved while sideways is turned
back to portrait at launch.
The Device menu gains Rotate Left (⌘←), Rotate Right (⌘→) and an
Orientation submenu checked by the current orientation, enabled only
while the agent reports display, so the keys otherwise reach the guest.
An orientation the front app refuses is skipped by the rotate keys and
reported by the submenu. New strings are translated for ja, ko, vi and
zh-Hans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Edit menu's Copy, Cut and Paste now reach the VM view, which carries
the clipboard through vphoned: paste sends a changed Mac clipboard to the
guest before the guest pastes, and copy and cut bring the guest clipboard
back once the guest has written it. Only small text and images are
synced (VPhoneClipboardTransfer); anything else stays on its own side.
Without a connected agent the items are disabled and the keys reach the
guest unchanged.
capturesSystemKeys makes VZVirtualMachineView swallow every key in a
local event monitor it re-adds on each focus change, so a monitor of ours
always ran after it. VPhoneApplication asks the main menu in
sendEvent(_:), which runs before any local monitor, and drops the key-up
of a key a menu item took.
A full-screen VM letterboxes the guest display, so touches measured
against the view mapped the bars onto the guest screen. They are now
measured against the drawn display (VPhoneDisplayGeometry): points on a
bar clamp to the nearest edge and count as near it for edge swipes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Device keeps the phone's buttons, input and Restart Guest; a new Features
menu holds the Location, Battery and Camera overrides. Guest becomes Data:
browsers, preferences and every clipboard action, including typing the Mac
clipboard. Bootstrap install and uninstall move to Apps, and Ping and the
agent hash go into a Guest Agent submenu under Diagnostics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundle 2.1.3 (build 14). Launchpad is unchanged: it stays 2.1.2, still
accepts VPhone.bundle 2.1.0 or later, and the READMEs keep pointing at
the 2.1.2 notarized Launchpad.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window buttons, the title and the Home button now share one gap of
12 pt: from the window edge to the close button, between the three
buttons (AppKit uses 9 pt), from the zoom button to the title and from
the Home button to the edge. AppKit puts the buttons back at its own
inset whenever it lays out the title bar and may replace them when the
window is shown, so they are placed again after each frame change and
after resize, full screen, key, main and screen changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dropping files on the VM window used to take only the first .ipa or
.tipa. Every dropped file now goes through vphoned: a package is
installed as before, and any other file is uploaded and moved into the
Files app's On My iPhone › vphone-drop. Folders are not taken, and one
alert sums up the drop.
On My iPhone is `File Provider Storage` in the
group.com.apple.FileProvider.LocalStorage app group, whose container
UUID differs per device, so vphoned finds it by the container metadata
(files.save_to_files_app, capability files_app_drop). The Files app shows
an item placed there at once when it looks like one the app creates:
owned by mobile, folders 755, files 644, no extended attributes, which
is what a folder made in the Files app has. A name already taken is kept
and the new file is numbered, as in "notes 2.txt".
Tested on a RootHide guest (iOS 26.6.2): a screenshot dropped on the
window and two files saved under one name through the RPC appear in
Files as vphone-drop with three items.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unified toolbar leaves a wider gap after the zoom button than the
close button's inset from the window edge. The window now hides its own
title and draws the name and subtitle in the titlebar, with the gap after
the zoom button held equal to that inset by two layout guides. The title
truncates before the Home button. window.title and window.subtitle are
still set for the Window menu and accessibility.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once vphoned connects, the VM window's subtitle reads
`iOS <version> - <address>`, and it is empty while vphoned is away.
vphoned's health report took the first en* address, which sorts to en0's
fe80:: link-local address. It now picks a 192.x IPv4 address first, then
any other IPv4 that is not loopback or 169.254, then a routable IPv6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The accessory container had no frame width, so the button collapsed to
zero and showed an empty glass shape. Size the container explicitly and
keep the button square. On macOS 26 it is a circular glass button.
The disconnected icon is now circle.circle with a slash drawn across it,
since SF Symbols has no circle.circle.slash, instead of circle.slash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window title is the VM's folder name and the subtitle is gone; the
connection state no longer appears in the title. The Home button moves
from the toolbar to a trailing titlebar accessory so a narrow window
truncates the title instead of hiding the button in overflow. While
vphoned is not connected the button shows circle.slash and is disabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundle 2.1.2 (build 13) and Launchpad 2.1.2. No new CLI surface or
helper change since 2.1.0, so the minimum bundle version stays 2.1.0 and
the helper build stays 8. The READMEs point at the 2.1.2 notarized
Launchpad.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VPhoneEscalator.c keeps the header comment and main(). Escalator.h holds
the shared paths, keys, globals and declarations; AMFIDTask.c attaches to
amfid and reads and writes its memory; Requirements.c builds the cdhash
requirement; Preferences.c owns the coderequirements preference; and
Commands.c holds the status, allow and off verbs. No behavior change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundle 2.1.1 (build 12) and Launchpad 2.1.1. No new CLI surface or
helper change since 2.1.0, so the minimum bundle version stays 2.1.0 and
the helper build stays 8. The READMEs point at the 2.1.1 notarized
Launchpad.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--disk-size and Launchpad's stepper say GB, but Disk.img was truncated
to GiB, so a 128 GB machine showed 137 GB inside iOS. Disks are now
created at N × 10^9 bytes, and vm list and Launchpad divide by 10^9,
matching iOS. Existing machines keep their size and now show it in the
same unit iOS does (a former "64 GB" disk reads 68 GB).
Fixes#523
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 53 keys only the removed inspector used, with their ja, ko, vi and
zh-Hans translations. Each was checked against the remaining sources;
"Source" survives in Launchpad, which has its own catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its accessibility tab never works in the VM: every AX query returns
-25215 because no app registers com.apple.iphone.axserver, even with
Settings in front ("AX Lookup problem ... Unknown service name" in
vphoned's log). The panel, its Diagnostics menu item (⌥⌘U) and the
unused accessibilityTree call are gone.
vphoned keeps its ui.* methods and the ui_inspection capability; ui.ocr
still works. The catalog keeps the inspector's strings for now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IcliKit found the frontmost app only by a FrontBoard focal assertion.
Setup Assistant drops its launch assertion once it is up and never takes
Workspace-ForegroundFocal, so apps.foreground reported "unavailable" and
every ui.* call failed with "frontmost application could not be
verified". 0.7.2 falls back to the one app whose RunningBoard role is
UserInteractiveFocal; 0.7.3 also removes screen describe/ocr temporary
JPEGs on every exit path.
Checked on research-01 (iOS 26.4) with the 0.7.2 build: once unlocked,
apps.foreground reports Setup and then Settings as verified, source
runningboard. A locked or dark screen still has no frontmost app.
The requirement is now upToNextMajor from 0.7.3, as for the other
packages, instead of an exact pin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XNU's vsock answers close() and shutdown() with an immediate RESET or
SHUTDOWN and drops bytes still queued for host credit, which cut replies
of about 8 to 16 KiB. After its last write vphoned now waits for the host
to close (30 s fallback), echoes a WebSocket close instead of closing, and
the host HTTP client closes its side once it has read the reply.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue #438: guests built after the former EXP patches joined the JB flow
lost location. standard is now the JB baseline plus the virtual camera.
- watchdogd.hv_vmm_cache joins the hv_vmm_present concealment group and
loses bootEssential: watchdogd only panics once the OID is renamed.
- The eight DeviceTree identity rewrites and the Preboot DeviceTree
rewrite, newly declared as preboot_devicetree.identity, are blocked in
standard. cfw install now asks the plan before the Preboot rewrite.
- Camera DT nodes, cam offsets and camera_dsc stay on.
- libvlocation.dylib and its SystemHook load are removed. location.set,
clear and current call IcliKit directly again, which confirms a request
by reading back a fresh, software-simulated fix at that coordinate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`fw patch --preset standard` spent 28.8 s of 49 s inside one patch.
Sampling a Release run (8623 samples) put 93% in the vm_map_protect
Shape C scan and 72% of that inside `cs_disasm` — not decoding, but in
Capstone's printer: printInst / printAliasInstr / matchAliasPatterns,
vsnprintf / SStream_concat, and map_set_alias_id -> name2id's strcmp
chain. The real decode, AArch64_LLVM_getInstruction, was 6%.
The scan is deliberately unscoped, so it walks all 8.4 MB of kernel text
and was decoding five instructions at each of ~2.1M offsets only to
reject nearly all of them on the first one.
Gate it on the raw instruction word first, the way buildADRPIndex and
the vm_map_delete scan already do. The gate only ever rejects: a word
that survives takes exactly the decode and the checks it always did, and
every positive determination stays Capstone's.
Both encodings of `mov wD, #6` are accepted even though only MOVZ can
reach the existing `mnemonic == "mov"` check — the MOV-bitmask alias
applies only when the immediate is not MOVZ-encodable, so
`orr w9, wzr, #6` (0x321F07E9) prints as `orr`. Letting it through
anyway keeps the gate independent of that aliasing rule, which is the
one way a cheap prefilter could silently narrow the match. ARM64InstTests
pins both words and the printer's answer for each.
patchThreadSetStateEntitlementFlag has the same shape (extended only) and
gets the same treatment via isBorBL.
Release, 17,3_26.4 + cloudOS 26.4: the patch step 28.81 s -> 1.28 s,
whole run 48.98 s -> 21.68 s standard, ~61 s -> ~28 s extended,
instructions retired 1.006e12 -> 5.38e11.
Verified byte-identical: four bases (26.1 / 26.4 / 26.6.2 / 27.0) x both
presets, HEAD vs HEAD+prefilter, every file in each restore tree hashed —
12/12 identical, same 172 standard / 183 extended counts, same 0x1DC6EA0
rewrite, no undeclared-patch warnings. FirmwarePatcherTests: 410 tests,
132 issues, unchanged from HEAD and all missing local reference samples.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The toolbar splits into the library and the selection: New Machine is a
menu holding New Machine and Import, and Start or Stop shares a capsule
with the actions menu.
With several machines selected the actions menu offers one Settings edit
that writes only the fields changed, an Export that writes <name>.tzst
for each into a folder, and Delete, followed by start and stop.
Exports show a progress bar in the State column and the inspector, run
one at a time, and can be cancelled from the context menu while one runs
or waits. A cancelled export's partial archive is removed.
vphone-cli's progress bar prints `progress <done> <total>` lines when
VPHONE_PROGRESS=lines is set and stderr is not a terminal. Launchpad sets
it for piped commands and keeps those lines out of logs and error details.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundle 2.1.0 (build 11) and Launchpad 2.1.0. Launchpad now drives patch
presets through `fw patches`, `fw set-patches` and `fw patch --preset`,
which older bundles lack, so the minimum bundle version rises to 2.1.0.
The helper shares that check, so its build goes to 8 and Launchpad
blesses it again. The READMEs point at the 2.1.0 notarized Launchpad.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host control socket handled one client at a time: the accept loop
ran each request inline and blocked on a semaphore until the main-actor
work (including the 500 ms settle before the compact screenshot)
finished. It was also created only with a window, since tap, swipe and
the compact image went through the VM view.
- Each connection is read and written on a concurrent queue; the
command runs on the main actor without holding a thread. Requests
from different clients may interleave; input stays ordered per client.
- The socket starts for every launch. Without a window, tap and swipe
go to vphoned's input.touch (normalized coordinates, through the
ordered input queue) and the compact image comes from the guest's
screen.screenshot. The screenshot command no longer needs a window.
- A windowed swipe now replies after the gesture ends, as headless does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It bricks a freshly restored 26.4 guest. bluetoothd caches its
sysctlbyname("kern.hv_vmm_present") answer in a dispatch_once (23E246:
the call at 0x1004ac7b4, dispatch_once at 0x1004ac798, flag at
0x100b51bc0); with the OID renamed it gets ENOENT and caches 0, so its
chip-selection singleton (0x10042fa40) picks a transport from the
MGIsDeviceOneOfType table instead. Nothing there matches a virtual
iPhone, the transport singleton at 0x100b50bd0 stays NULL, and it faults
on `ldr w23, [x0, #0x31c]` at +0x402864 and crash-loops until launchd
throttles com.apple.bluetoothd. locationd's CLSeparationAlertsServiceSilo
then blocks on a synchronous call to that throttled service, the
com.apple.locationd.migrator plugin hangs for over an hour, and
SpringBoard waits on migration: black screen, no panic, nothing in the
log naming the cause.
The two halves — kernelcache_exp.hv_vmm (kernel OID rename plus the
kernel-internal cstring mangle) and hv_vmm_dsc (the shared-cache mangle
the system installer runs) — are one behaviour in everything but name,
and neither is useful alone: the rename without the mangle breaks the
graphics and ML paths, the mangle without the rename does nothing. They
are now hypervisorConcealmentPatches, blocked by standard and present in
extended, and a catalogue test refuses any shipped preset that enables
one without the other. Both lose bootEssential, which they never were:
a shipped preset that drops a boot-essential patch warns on every run.
buildComponentList no longer builds KernelExperimentalPatcher when the
patch is off, rather than building it and letting the gate refuse the
write — a patcher constructed to write nothing still logs as if it might.
The DSC half needed no code change; the installer already runs
patch-hv-vmm-dsc only when the plan enables it. A VM with no recorded
plan still gets both, deliberately: that is what its guest was restored
with.
Everything else the former EXP integration brought over stays on:
DeviceTree identity and camera, camera_dsc, the watchdogd cache patch,
and the post-restore Preboot DeviceTree rewrite.
The user-mode xref inventory is corrected too. It listed bluetoothd as
carrying the string with no reference to it, which was measured on 26.1
(23B85) and is not true of 23E246 — so the line is marked per-build
rather than deleted, the 26.4 call sites are written down, and the
"standalone binaries fall into unpatched → ENOENT → cache 0
automatically" step in the shipping design is flagged as the bug it is.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Patches were scattered across the patchers that applied them: nothing listed
what a run would do, and nothing could turn one off. This adds the manifest
layer that names them and the preset layer that chooses.
VPhonePatchKit is a distribution framework (library evolution on) holding the
model: VPhoneVersion, VPhoneVersionRequirement, VPhonePatchDeclaration,
VPhonePatchSetManifest, VPhonePatchPreset, VPhonePatchPlan and the gate a
patcher consults before each write. Capstone and the ARM64 encoder moved in
behind an internal import, so nothing downstream sees the package.
Ten bundled sets in FirmwarePatcher/PatchSets declare every existing patch,
checked against the patchers by FirmwarePatchSetCatalogTests. Two presets ship
in VPhone.bundle: standard, and extended for the experimental sets. A
version-pinned patch is present in the manifest but off unless a preset or a
per-VM checkmark asks for it, and neither can widen its version gate.
Patch sets also load from outside the tool. A .vphonepatchset is a macOS
loadable bundle whose Contents/Resources/Manifest.plist is read before any of
its code is mapped, and whose executable exports one symbol,
vphone_patch_set_principal, returning a VPhonePatchSetPrincipal that hands the
pipeline one BufferedPatcher per component the plan enabled.
Not NSPrincipalClass, which is how a loadable bundle normally names its entry
point: library evolution makes VPhonePatchSetPrincipal a resilient superclass,
so a subclass of it needs runtime metadata initialization and is not registered
with the ObjC runtime when the image is mapped. NSClassFromString cannot find
it, and Bundle.principalClass then silently returns whichever class was
registered — the example set's patcher rather than its principal. A @_cdecl
symbol found with dlsym has none of that.
External sets are boot-chain only, because root cfw install loads no external
set; a preset that names one lives in ~/.vphone/patches_presets and cannot
shadow a shipped identifier. `vphone-cli patchset import` copies a set into
~/.vphone/patchsets and ad hoc signs it if it arrived unsigned, so a bundle
straight out of Xcode loads: the linker signs its Mach-O but seals no
resources, which codesign rejects until one pass over the bundle fixes it. The
signature is re-checked from disk at every load, and PatchSetLoaderTests proves
that over the example set — inspect, validate, tamper, load, patch, gate off.
BufferedPatcher replaces the nine concrete downcasts the pipeline used to read
patched bytes back with, which is what lets an out-of-tree patcher return any.
Launchpad gains a patch table per machine, `vphone-cli fw set-patches` writes
the selection, and Skills/authoring-patch-sets documents the whole flow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bundle 2.0.9 (build 10) and Launchpad 2.0.9. The helper is unchanged
since build 7, and Launchpad still accepts bundles from 2.0.8, so the
minimum bundle version stays. The READMEs point at the 2.0.9 notarized
Launchpad.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Post Darwin notifications from the Controls panel
vphoned gains notify.post {name, state?} and notify.state {name}, thin
calls into IcliKit 0.7.0's postDarwinNotification and
darwinNotificationState. The state is a full UInt64, so it is accepted
as a decimal string as well as a number.
The Controls panel gets a Darwin Notification section: a name field with
presets, an optional state, and Post and Read State buttons. This
replaces #248, which targeted the removed ObjC daemon and sources/ tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refuse a JSON boolean as a notification state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Remote IPSWs were cached inside each machine (<machine>/.ipsw-cache), so
every new machine downloaded both IPSWs again. Launchpad runs the same
fw prepare with catalog URLs and had the same problem. Fixes#513.
- Remote IPSWs go to ~/.vphone/ipsws (follows VPHONE_ROOT); vm create
and fw prepare take --ipsw-cache to put it elsewhere.
- The GPU driver recovered from cloudOS is cached per build in
~/.vphone/gpu-drivers, so later machines on the same cloudOS skip the
temporary cloudOS restore.
- Two prepares finishing the same URL no longer fail on the rename, the
cache directory is made writable before a download can fail, and
partial downloads abandoned for an hour are removed.
- vm export leaves out .ipsw-cache from older machines and staging
directories left by a failed detach, and its progress total prunes
excluded directories the same way the writer does.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Launch no longer waits on the helper XPC call, the network probe, bundle
preflight and the GitHub lists before listing machines. The local host
checks, the helper state (from the installed files) and the installed
bundles are read at init; a bundle whose last check passed shows as ready
while the launch check confirms it quietly. Machines list alongside the
checks, and "No Machines" waits for the first answer.
- Spawn vphone.bundle detached (new session, responsibility disclaimed),
so a VM outlives Launchpad and is not attributed to it.
- vphone-vm shows the VM name in the Dock.
- Keep in Menu Bar: closing the window leaves a menu bar item that starts
and stops machines; the Dock icon shows only while a window or the menu
is open.
- File panels open as sheets on the window; toolbar actions are grouped.
- Copy and translations for today's strings, with unused keys removed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The host control socket only knew ping, tap, swipe, four hardware keys and
a clipboard write, so keyboard input such as cmd+v or return failed with
"unknown key" even though vphoned serves it through IcliKit.
- {"t":"rpc","method":...,"params":{...}} calls any vphoned method and
returns its result object.
- "key" sends names other than home/power/volup/voldown to input.key.
- Both wait for queued input first, so they cannot overtake a tap.
- A request line may be up to 1 MiB, vphoned's JSON body limit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cfw install on iOS 18.6.2 fails at the final launchd re-sign:
no room for the signature load command: content starts at 3208,
3224 bytes of commands
launchd ships with only 16 bytes of header padding. inject-dylib spends
the stripped LC_CODE_SIGNATURE slot plus that padding on the 32-byte
LC_LOAD_WEAK_DYLIB for /vh, so when signed() rebuilds the command area
there is nowhere left for the fresh LC_CODE_SIGNATURE.
When the rebuilt area would overrun the first section, drop
LC_SOURCE_VERSION — 16 informational bytes nothing loads — and lay the
area out again; only fail if it still does not fit.
Verified against the shipping iOS 18.6.2 (22G100) launchd: with the
command removed, inject-dylib + sign succeed and cfw install completes
end to end.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>