mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
Rework JB finalization: drop dropbear, auto-bootstrap on first boot (#141)
* fix: build
* fix: remove [trusted=yes] from Havoc apt source
The inline [trusted=yes] option can cause issues with Sileo's
source parser. The apt-get calls already use AllowUnauthenticated
flags, making it redundant.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: main actor crash in VPhoneControl + IPA extraction failures
VPhoneControl: pending request handlers are @MainActor-isolated closures
but were called from DispatchQueue.global() in the read loop and timeout
handler, causing dispatch_assert_queue_fail crashes. Wrap all
pending.handler() calls in DispatchQueue.main.async.
unarchive: the recent ARCHIVE_EXTRACT_SECURE_* hardening (ef02d50) broke
IPA extraction on iOS because:
- SECURE_NOABSOLUTEPATHS: we set absolute output paths on entries
- SECURE_SYMLINKS: iOS system paths (/var, /tmp) are symlinks
- archive_write_header failures were silently swallowed due to if/else if
structure, making extraction report success with no files extracted
Fix by keeping only SECURE_NODOTDOT, resolving symlinks in extraction
path, fixing header error handling, removing unnecessary ACL/FFLAGS
flags, and surfacing libarchive errors in the install response.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* remove dropbear SSH daemon from guest
Drop all dropbear setup: LaunchDaemon plist injection, host key
generation, daemon deployment, and SSH availability messages.
Guest communication is handled by vphoned over vsock.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: liblaunch compat stub + automatic JB first-boot setup
liblaunch_compat.dylib: stub exporting _launch_active_user_switch
(missing from PCC VM's libSystem.B.dylib) so procursus binaries
like launchctl can load. Deployed to /cores/, loaded via
DYLD_INSERT_LIBRARIES in LaunchDaemon environment and JB profile.
vphone_jb_setup.sh: first-boot script replacing the SSH-based
cfw_install_jb_post.sh. Runs as a LaunchDaemon on first normal
boot and performs all JB finalization: /var/jb symlink,
prep_bootstrap, markers, Sileo, apt setup, TrollStore Lite.
Idempotent with done marker. Logs to /var/log/vphone_jb_setup.log.
Removes the cfw_install_jb_finalize make target and the entire
SSH/iproxy/sshpass-based post-boot flow from setup_machine.sh.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore: update AGENTS.md firmware table, gitignore build artifacts
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: launchctl wrapper uses absolute path + timeout to prevent hangs
- Use absolute path to launchctl.real instead of relative dirname,
fixing "not found" when called via /var/jb/bin/launchctl symlink
- Add 5s timeout so launchctl doesn't hang when launchd is
unresponsive on PCC VMs — always exits 0 for dpkg postinst compat
- Symlink /var/jb/bin/launchctl -> /var/jb/usr/bin/launchctl so both
paths work (openssh postinst uses the /bin/ path)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: replace liblaunch_compat dylib stub with iosbinpack64 launchctl symlink
Procursus launchctl crashes on PCC VMs due to missing
_launch_active_user_switch symbol. Rather than a custom dylib stub,
simply symlink iosbinpack64's launchctl into /var/jb — it talks to
launchd fine and always exits 0, which is all dpkg scripts need.
- Remove liblaunch_compat.c, its build target, signing, and deployment
- Remove DYLD_INSERT_LIBRARIES from setup script and plist
- Replace launchctl wrapper with symlinks to /iosbinpack64/bin/launchctl
- Both /var/jb/usr/bin/launchctl and /var/jb/bin/launchctl are covered
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5921cba2ba
commit
32b73cd50b
@@ -319,6 +319,7 @@ __marimo__/
|
||||
TODO.md
|
||||
/references/
|
||||
scripts/vphoned/vphoned
|
||||
/cfw_input/
|
||||
sources/vphone-cli/VPhoneBuildInfo.swift
|
||||
setup_logs/
|
||||
/testing_results
|
||||
|
||||
@@ -33,9 +33,9 @@ For any changes applying new patches, also update research/0_binary_patch_compar
|
||||
| --------------- | :---------: | :-------: | ------------------------------------------------------------ |
|
||||
| **Regular** | 51 patches | 10 phases | `fw_patch` + `cfw_install` |
|
||||
| **Development** | 64 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **Jailbreak** | 126 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` + `cfw_install_jb_finalize` |
|
||||
| **Jailbreak** | 126 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |
|
||||
|
||||
> `cfw_install_jb_finalize` requires booting into the full system, not the ramdisk.
|
||||
> JB finalization (symlinks, Sileo, apt, TrollStore) runs automatically on first boot via `/cores/vphone_jb_setup.sh` LaunchDaemon. Monitor progress: `/var/log/vphone_jb_setup.log`.
|
||||
|
||||
See `research/` for detailed firmware pipeline, component origins, patch breakdowns, and boot flow documentation.
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ help:
|
||||
@echo " make cfw_install Install CFW mods via SSH"
|
||||
@echo " make cfw_install_dev Install CFW mods via SSH (dev mode)"
|
||||
@echo " make cfw_install_jb Install CFW + JB extensions (jetsam/procursus/basebin)"
|
||||
@echo " make cfw_install_jb_finalize Finalize JB bootstrap on live device (symlinks/sileo/apt)"
|
||||
@echo ""
|
||||
@echo "Variables: VM_DIR=$(VM_DIR) CPU=$(CPU) MEMORY=$(MEMORY) DISK_SIZE=$(DISK_SIZE)"
|
||||
|
||||
@@ -244,7 +243,7 @@ ramdisk_send:
|
||||
# CFW
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
.PHONY: cfw_install cfw_install_dev cfw_install_jb cfw_install_jb_finalize
|
||||
.PHONY: cfw_install cfw_install_dev cfw_install_jb
|
||||
|
||||
cfw_install:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
|
||||
@@ -254,6 +253,3 @@ cfw_install_dev:
|
||||
|
||||
cfw_install_jb:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .
|
||||
|
||||
cfw_install_jb_finalize:
|
||||
$(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") $(if $(SSH_PASS),SSH_PASS="$(SSH_PASS)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb_post.sh"
|
||||
|
||||
@@ -478,7 +478,7 @@ cp "$TEMP_DIR/vphoned" "$VM_DIR/.vphoned.signed"
|
||||
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
||||
|
||||
# Send daemon plists (overwrite on re-run)
|
||||
for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do
|
||||
for plist in bash.plist trollvnc.plist rpcserver_ios.plist; do
|
||||
scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
||||
done
|
||||
@@ -518,7 +518,6 @@ rm -f "$TEMP_DIR/seputil" \
|
||||
echo ""
|
||||
echo "[+] CFW installation complete!"
|
||||
echo " Reboot the device for changes to take effect."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
||||
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
||||
|
||||
@@ -477,7 +477,7 @@ cp "$TEMP_DIR/vphoned" "$VM_DIR/.vphoned.signed"
|
||||
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
||||
|
||||
# Send daemon plists (overwrite on re-run)
|
||||
for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do
|
||||
for plist in bash.plist trollvnc.plist rpcserver_ios.plist; do
|
||||
scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
||||
done
|
||||
@@ -517,7 +517,6 @@ rm -f "$TEMP_DIR/seputil" \
|
||||
echo ""
|
||||
echo "[+] CFW installation complete!"
|
||||
echo " Reboot the device for changes to take effect."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
||||
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
||||
|
||||
@@ -277,6 +277,40 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
echo " [+] BaseBin hooks deployed"
|
||||
fi
|
||||
|
||||
# ═══════════ JB-5 DEPLOY FIRST-BOOT SETUP ══════════════════════
|
||||
echo ""
|
||||
echo "[JB-5] Deploying first-boot setup..."
|
||||
|
||||
# Deploy first-boot JB setup script + LaunchDaemon
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/vphone_jb_setup.sh"
|
||||
SETUP_PLIST="$SCRIPT_DIR/vphone_jb_setup.plist"
|
||||
if [[ -f "$SETUP_SCRIPT" ]]; then
|
||||
scp_to "$SETUP_SCRIPT" "/mnt1/cores/vphone_jb_setup.sh"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores/vphone_jb_setup.sh"
|
||||
echo " [+] vphone_jb_setup.sh -> /cores/"
|
||||
fi
|
||||
if [[ -f "$SETUP_PLIST" ]]; then
|
||||
scp_to "$SETUP_PLIST" "/mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
|
||||
# Inject into launchd.plist so launchd starts it at boot
|
||||
echo " Injecting com.vphone.jb-setup into launchd.plist..."
|
||||
scp_from "/mnt1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
||||
python3 -c "
|
||||
import plistlib, sys
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
target = plistlib.load(f)
|
||||
with open(sys.argv[2], 'rb') as f:
|
||||
daemon = plistlib.load(f)
|
||||
target.setdefault('LaunchDaemons', {})['/System/Library/LaunchDaemons/com.vphone.jb-setup.plist'] = daemon
|
||||
with open(sys.argv[1], 'wb') as f:
|
||||
plistlib.dump(target, f, sort_keys=False)
|
||||
" "$TEMP_DIR/launchd.plist" "$SETUP_PLIST"
|
||||
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"
|
||||
echo " [+] com.vphone.jb-setup.plist injected into launchd.plist"
|
||||
fi
|
||||
|
||||
# ═══════════ CLEANUP ═════════════════════════════════════════
|
||||
echo ""
|
||||
echo "[*] Unmounting device filesystems..."
|
||||
@@ -291,6 +325,5 @@ rm -f "$TEMP_DIR/launchd" \
|
||||
echo ""
|
||||
echo "[+] CFW + JB installation complete!"
|
||||
echo " Reboot the device for changes to take effect."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
ssh_cmd "/sbin/halt" || true
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
#!/bin/zsh
|
||||
# cfw_install_jb_post.sh — Finalize JB bootstrap on a normally-booted vphone.
|
||||
#
|
||||
# Runs after `cfw_install_jb` + first normal boot. Connects to the live device
|
||||
# via SSH and sets up procursus symlinks, markers, Sileo, and apt packages.
|
||||
#
|
||||
# Every step is idempotent — safe to re-run at any point.
|
||||
# All binary paths are discovered dynamically (no hardcoded /bin, /sbin, etc.).
|
||||
#
|
||||
# Usage: make cfw_install_jb_finalize [SSH_PORT=22222] [SSH_PASS=alpine]
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="${0:a:h}"
|
||||
|
||||
# ── Configuration ───────────────────────────────────────────────
|
||||
SSH_PORT="${SSH_PORT:-22222}"
|
||||
SSH_PASS="${SSH_PASS:-alpine}"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
SSH_RETRY="${SSH_RETRY:-3}"
|
||||
SSHPASS_BIN=""
|
||||
SSH_OPTS=(
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o PreferredAuthentications=password
|
||||
-o ConnectTimeout=30
|
||||
-q
|
||||
)
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────
|
||||
die() {
|
||||
echo "[-] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
_sshpass() {
|
||||
"$SSHPASS_BIN" -p "$SSH_PASS" "$@"
|
||||
}
|
||||
|
||||
_ssh_retry() {
|
||||
local attempt rc label
|
||||
label=${2:-cmd}
|
||||
for ((attempt = 1; attempt <= SSH_RETRY; attempt++)); do
|
||||
"$@" && return 0
|
||||
rc=$?
|
||||
[[ $rc -ne 255 ]] && return $rc
|
||||
echo " [${label}] connection lost (attempt $attempt/$SSH_RETRY), retrying in 3s..." >&2
|
||||
sleep 3
|
||||
done
|
||||
return 255
|
||||
}
|
||||
|
||||
# Raw ssh — no PATH prefix
|
||||
ssh_raw() {
|
||||
_ssh_retry _sshpass ssh "${SSH_OPTS[@]}" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "$@"
|
||||
}
|
||||
|
||||
# ssh with discovered PATH prepended
|
||||
ssh_cmd() {
|
||||
ssh_raw "$RENV $*"
|
||||
}
|
||||
|
||||
# ── Prerequisites ──────────────────────────────────────────────
|
||||
command -v sshpass &>/dev/null || die "Missing sshpass. Run: make setup_tools"
|
||||
SSHPASS_BIN="$(command -v sshpass)"
|
||||
|
||||
echo "[*] cfw_install_jb_post.sh — Finalizing JB bootstrap..."
|
||||
echo " Target: ${SSH_USER}@${SSH_HOST}:${SSH_PORT}"
|
||||
echo ""
|
||||
|
||||
# ── Verify SSH connectivity ────────────────────────────────────
|
||||
echo "[*] Checking SSH connectivity..."
|
||||
ssh_raw "echo ready" >/dev/null 2>&1 || die "Cannot reach device on ${SSH_HOST}:${SSH_PORT}. Is the VM booted normally?"
|
||||
echo "[+] Device reachable"
|
||||
|
||||
# ── Discover remote PATH ──────────────────────────────────────
|
||||
# Uses only shell builtins (test -d, echo) — works with empty PATH.
|
||||
echo "[*] Discovering remote binary directories..."
|
||||
DISCOVERED_PATH=$(ssh_raw 'P=""; \
|
||||
for d in \
|
||||
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
||||
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
||||
/usr/bin /usr/sbin /bin /sbin; do \
|
||||
[ -d "$d" ] && P="$P:$d"; \
|
||||
done; \
|
||||
echo "${P#:}"')
|
||||
|
||||
[[ -n "$DISCOVERED_PATH" ]] || die "Could not discover any binary directories on device"
|
||||
echo " PATH=$DISCOVERED_PATH"
|
||||
|
||||
# This gets prepended to every ssh_cmd call
|
||||
RENV="export PATH='$DISCOVERED_PATH' TERM='xterm-256color';"
|
||||
|
||||
# Quick sanity: verify we can run ls now
|
||||
ssh_cmd "ls / >/dev/null" || die "PATH discovery succeeded but 'ls' still not found"
|
||||
echo "[+] Remote environment ready"
|
||||
|
||||
# ═══════════ 1/6 SYMLINK /var/jb ══════════════════════════════
|
||||
echo ""
|
||||
echo "[1/6] Creating /private/var/jb symlink..."
|
||||
|
||||
# Find 96-char boot manifest hash — use shell glob (no ls dependency)
|
||||
BOOT_HASH=$(ssh_cmd 'for d in /private/preboot/*/; do \
|
||||
b="${d%/}"; b="${b##*/}"; \
|
||||
[ "${#b}" = 96 ] && echo "$b" && break; \
|
||||
done')
|
||||
[[ -n "$BOOT_HASH" ]] || die "Could not find 96-char boot manifest hash in /private/preboot"
|
||||
echo " Boot manifest hash: $BOOT_HASH"
|
||||
|
||||
JB_TARGET="/private/preboot/$BOOT_HASH/jb-vphone/procursus"
|
||||
ssh_cmd "test -d '$JB_TARGET'" || die "Procursus directory not found at $JB_TARGET. Run cfw_install_jb first."
|
||||
|
||||
CURRENT_LINK=$(ssh_cmd "readlink /private/var/jb 2>/dev/null || true")
|
||||
if [[ "$CURRENT_LINK" == "$JB_TARGET" ]]; then
|
||||
echo " [*] Symlink already correct, skipping"
|
||||
else
|
||||
ssh_cmd "ln -sf '$JB_TARGET' /private/var/jb"
|
||||
echo " [+] /private/var/jb -> $JB_TARGET"
|
||||
fi
|
||||
|
||||
# ═══════════ 2/6 FIX OWNERSHIP / PERMISSIONS ═════════════════
|
||||
echo ""
|
||||
echo "[2/6] Fixing mobile Library ownership..."
|
||||
|
||||
ssh_cmd "mkdir -p /var/jb/var/mobile/Library/Preferences"
|
||||
ssh_cmd "chown -R 501:501 /var/jb/var/mobile/Library"
|
||||
ssh_cmd "chmod 0755 /var/jb/var/mobile/Library"
|
||||
ssh_cmd "chown -R 501:501 /var/jb/var/mobile/Library/Preferences"
|
||||
ssh_cmd "chmod 0755 /var/jb/var/mobile/Library/Preferences"
|
||||
|
||||
echo " [+] Ownership set"
|
||||
|
||||
# ═══════════ 3/6 RUN prep_bootstrap.sh ════════════════════════
|
||||
echo ""
|
||||
echo "[3/6] Running prep_bootstrap.sh..."
|
||||
|
||||
if ssh_cmd "test -f /var/jb/prep_bootstrap.sh"; then
|
||||
# Skip interactive password prompt (uses uialert GUI — not usable over SSH)
|
||||
ssh_cmd "NO_PASSWORD_PROMPT=1 /var/jb/prep_bootstrap.sh"
|
||||
echo " [+] prep_bootstrap.sh completed"
|
||||
echo " [!] Terminal password was NOT set (automated mode)."
|
||||
echo " To set it manually: ssh in and run: passwd"
|
||||
else
|
||||
echo " [*] prep_bootstrap.sh already ran (deleted itself), skipping"
|
||||
fi
|
||||
|
||||
# Re-discover PATH after prep_bootstrap.sh may have changed the login shell.
|
||||
# The shell switch (chsh) can alter which profile scripts run on subsequent SSH
|
||||
# sessions, so we must refresh RENV to ensure dpkg/apt/uicache are reachable.
|
||||
echo "[*] Re-discovering remote PATH after bootstrap prep..."
|
||||
DISCOVERED_PATH=$(ssh_raw 'P=""; \
|
||||
for d in \
|
||||
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
||||
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
||||
/usr/bin /usr/sbin /bin /sbin; do \
|
||||
[ -d "$d" ] && P="$P:$d"; \
|
||||
done; \
|
||||
echo "${P#:}"')
|
||||
RENV="export PATH='$DISCOVERED_PATH' TERM='xterm-256color';"
|
||||
echo " PATH=$DISCOVERED_PATH"
|
||||
|
||||
# Fix interactive SSH environment.
|
||||
# dropbear uses --shell /iosbinpack64/bin/bash and ignores /etc/passwd.
|
||||
# /iosbinpack64/etc/profile spawns a non-login subshell that only reads ~/.bashrc.
|
||||
# prep_bootstrap.sh's chsh has no effect because dropbear doesn't consult passwd.
|
||||
# Create ~/.bashrc so the interactive subshell sources /var/jb/etc/profile (full PATH).
|
||||
echo "[*] Setting up shell profile for interactive SSH..."
|
||||
if ! ssh_cmd "test -f /var/root/.bashrc"; then
|
||||
ssh_cmd "printf '%s\n' '# Source JB environment' '[ -r /var/jb/etc/profile ] && . /var/jb/etc/profile' > /var/root/.bashrc"
|
||||
echo " [+] /var/root/.bashrc created"
|
||||
else
|
||||
echo " [*] /var/root/.bashrc already exists, skipping"
|
||||
fi
|
||||
|
||||
# ═══════════ 4/6 CREATE MARKER FILES ═════════════════════════
|
||||
echo ""
|
||||
echo "[4/6] Creating marker files..."
|
||||
|
||||
for marker in .procursus_strapped .installed_dopamine; do
|
||||
if ssh_cmd "test -f /var/jb/$marker"; then
|
||||
echo " [*] $marker already exists, skipping"
|
||||
else
|
||||
ssh_cmd ": > /var/jb/$marker && chown 0:0 /var/jb/$marker && chmod 0644 /var/jb/$marker"
|
||||
echo " [+] $marker created"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════ 5/6 INSTALL SILEO ══════════════════════════════
|
||||
echo ""
|
||||
echo "[5/6] Installing Sileo..."
|
||||
|
||||
SILEO_DEB_PATH="/private/preboot/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
|
||||
if ssh_cmd "dpkg -s org.coolstar.sileo >/dev/null 2>&1"; then
|
||||
echo " [*] Sileo already installed, skipping"
|
||||
else
|
||||
ssh_cmd "test -f '$SILEO_DEB_PATH'" || die "Sileo deb not found at $SILEO_DEB_PATH. Was it uploaded by cfw_install_jb?"
|
||||
ssh_cmd "dpkg -i '$SILEO_DEB_PATH'"
|
||||
echo " [+] Sileo installed"
|
||||
fi
|
||||
|
||||
ssh_cmd "uicache -a 2>/dev/null || true"
|
||||
echo " [+] uicache refreshed"
|
||||
|
||||
# ═══════════ 6/7 APT SETUP ═════════════════════════════════
|
||||
echo ""
|
||||
echo "[6/7] Running apt setup..."
|
||||
|
||||
HAVOC_LIST="/var/jb/etc/apt/sources.list.d/havoc.list"
|
||||
if ssh_cmd "test -d /etc/apt/sources.list.d && test ! -d /var/jb/etc/apt/sources.list.d"; then
|
||||
HAVOC_LIST="/etc/apt/sources.list.d/havoc.list"
|
||||
fi
|
||||
|
||||
HAVOC_SOURCES="$(ssh_cmd "grep -RIl 'havoc.app' /etc/apt /var/jb/etc/apt 2>/dev/null || true")"
|
||||
if [[ -n "$HAVOC_SOURCES" ]]; then
|
||||
echo " [*] Havoc source already present:"
|
||||
echo "$HAVOC_SOURCES" | sed 's/^/ - /'
|
||||
|
||||
OTHER_HAVOC_SOURCES="$(printf '%s\n' "$HAVOC_SOURCES" | grep -Fvx "$HAVOC_LIST" || true)"
|
||||
if [[ -n "$OTHER_HAVOC_SOURCES" ]] && ssh_cmd "test -f '$HAVOC_LIST' && grep -q 'https://havoc.app/' '$HAVOC_LIST'"; then
|
||||
ssh_cmd "rm -f '$HAVOC_LIST'"
|
||||
echo " [+] Removed duplicate autogenerated Havoc source: $HAVOC_LIST"
|
||||
fi
|
||||
else
|
||||
ssh_cmd "mkdir -p '${HAVOC_LIST:h}'"
|
||||
ssh_cmd "printf '%s\n' 'deb [trusted=yes] https://havoc.app/ ./' > '$HAVOC_LIST'"
|
||||
echo " [+] Havoc source added: $HAVOC_LIST"
|
||||
fi
|
||||
|
||||
echo " [*] Allowing unsigned third-party repos during automated apt refresh"
|
||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o Acquire::AllowInsecureRepositories=true -o Acquire::AllowDowngradeToInsecureRepositories=true update -qq"
|
||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::AllowUnauthenticated=true install -y -qq libkrw0-tfp0 2>/dev/null || true"
|
||||
echo " [+] apt update + libkrw0-tfp0 done"
|
||||
|
||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::AllowUnauthenticated=true upgrade -y -qq 2>/dev/null || true"
|
||||
echo " [+] apt upgrade done"
|
||||
|
||||
# ═══════════ 7/7 INSTALL TROLLSTORE LITE ═══════════════════
|
||||
echo ""
|
||||
echo "[7/7] Installing TrollStore Lite..."
|
||||
|
||||
if ssh_cmd "dpkg -s com.opa334.trollstorelite >/dev/null 2>&1"; then
|
||||
echo " [*] TrollStore Lite already installed, skipping"
|
||||
else
|
||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::AllowUnauthenticated=true install -y -qq com.opa334.trollstorelite"
|
||||
echo " [+] TrollStore Lite installed"
|
||||
fi
|
||||
|
||||
ssh_cmd "uicache -a 2>/dev/null || true"
|
||||
echo " [+] uicache refreshed"
|
||||
|
||||
# ═══════════ DONE ═══════════════════════════════════════════
|
||||
echo ""
|
||||
echo "[+] JB finalization complete!"
|
||||
echo " TrollStore Lite is installed automatically during finalization."
|
||||
echo " Next: open Sileo on device, add source https://ellekit.space, install ElleKit"
|
||||
echo " Then reboot the device for full JB environment."
|
||||
@@ -24,7 +24,7 @@ Commands:
|
||||
Patch launchd jetsam panic guard to avoid initproc crash loop.
|
||||
|
||||
inject-daemons <launchd.plist> <daemon_dir>
|
||||
Inject bash/dropbear/trollvnc into launchd.plist.
|
||||
Inject bash/trollvnc into launchd.plist.
|
||||
|
||||
inject-dylib <binary> <dylib_path>
|
||||
Inject LC_LOAD_DYLIB into Mach-O binary (thin or universal).
|
||||
|
||||
@@ -25,7 +25,7 @@ Commands:
|
||||
Patch launchd jetsam panic guard to avoid initproc crash loop.
|
||||
|
||||
inject-daemons <launchd.plist> <daemon_dir>
|
||||
Inject bash/dropbear/trollvnc into launchd.plist.
|
||||
Inject bash/trollvnc into launchd.plist.
|
||||
|
||||
inject-dylib <binary> <dylib_path>
|
||||
Inject LC_LOAD_DYLIB into Mach-O binary (thin or universal).
|
||||
|
||||
@@ -37,14 +37,14 @@ def parse_cryptex_paths(manifest_path):
|
||||
|
||||
|
||||
def inject_daemons(plist_path, daemon_dir):
|
||||
"""Inject bash/dropbear/trollvnc entries into launchd.plist."""
|
||||
"""Inject bash/trollvnc entries into launchd.plist."""
|
||||
# Convert to XML first (macOS binary plist -> XML)
|
||||
subprocess.run(["plutil", "-convert", "xml1", plist_path], capture_output=True)
|
||||
|
||||
with open(plist_path, "rb") as f:
|
||||
target = plistlib.load(f)
|
||||
|
||||
for name in ("bash", "dropbear", "trollvnc", "vphoned", "rpcserver_ios"):
|
||||
for name in ("bash", "trollvnc", "vphoned", "rpcserver_ios"):
|
||||
src = os.path.join(daemon_dir, f"{name}.plist")
|
||||
if not os.path.exists(src):
|
||||
print(f" [!] Missing {src}, skipping")
|
||||
|
||||
@@ -486,11 +486,8 @@ send_first_boot_commands() {
|
||||
|
||||
local commands=(
|
||||
"export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/games:/iosbinpack64/usr/local/sbin:/iosbinpack64/usr/local/bin:/iosbinpack64/usr/sbin:/iosbinpack64/usr/bin:/iosbinpack64/sbin:/iosbinpack64/bin'"
|
||||
"mkdir -p /var/dropbear"
|
||||
"cp /iosbinpack64/etc/profile /var/profile"
|
||||
"cp /iosbinpack64/etc/motd /var/motd"
|
||||
"dropbearkey -t rsa -f /var/dropbear/dropbear_rsa_host_key"
|
||||
"dropbearkey -t ecdsa -f /var/dropbear/dropbear_ecdsa_host_key"
|
||||
"shutdown -h now"
|
||||
)
|
||||
|
||||
@@ -1033,53 +1030,9 @@ main() {
|
||||
if [[ "$JB_MODE" -eq 1 ]]; then
|
||||
echo ""
|
||||
echo "=== JB Finalize ==="
|
||||
echo "[*] Booting VM normally for JB bootstrap finalization..."
|
||||
|
||||
check_vm_storage_locks
|
||||
mkdir -p "$LOG_DIR"
|
||||
: > "$BOOT_LOG"
|
||||
(make boot >"$BOOT_LOG" 2>&1) &
|
||||
BOOT_PID=$!
|
||||
|
||||
sleep 2
|
||||
if ! kill -0 "$BOOT_PID" 2>/dev/null; then
|
||||
echo "[-] make boot exited early during JB finalize stage."
|
||||
tail -n 40 "$BOOT_LOG" 2>/dev/null || true
|
||||
die "JB finalize boot failed."
|
||||
fi
|
||||
|
||||
local jb_ssh_port jb_iproxy_pid jb_iproxy_log
|
||||
local iproxy_bin="${PROJECT_ROOT}/.limd/bin/iproxy"
|
||||
|
||||
jb_ssh_port="$(pick_random_ssh_port)" \
|
||||
|| die "Failed to allocate a random local SSH port for JB finalize"
|
||||
|
||||
jb_iproxy_log="${LOG_DIR}/iproxy_jb_${jb_ssh_port}.log"
|
||||
: > "$jb_iproxy_log"
|
||||
|
||||
echo "[*] Waiting for device UDID=${DEVICE_UDID} on USBMux..."
|
||||
wait_for_iproxy_target_udid
|
||||
|
||||
echo "[*] Starting iproxy ${jb_ssh_port} -> 22222 (target_udid=${IPROXY_TARGET_UDID})..."
|
||||
("$iproxy_bin" -u "$IPROXY_TARGET_UDID" "$jb_ssh_port" 22222 >"$jb_iproxy_log" 2>&1) &
|
||||
jb_iproxy_pid=$!
|
||||
sleep 1
|
||||
if ! kill -0 "$jb_iproxy_pid" 2>/dev/null; then
|
||||
echo "[-] iproxy exited early. Log:"
|
||||
tail -n 40 "$jb_iproxy_log" || true
|
||||
die "iproxy for JB finalize failed to start."
|
||||
fi
|
||||
echo "[+] iproxy running (pid=$jb_iproxy_pid, log=$jb_iproxy_log)"
|
||||
|
||||
wait_for_device_ssh "$jb_ssh_port" 120
|
||||
|
||||
run_make "JB finalize" cfw_install_jb_finalize SSH_PORT="$jb_ssh_port"
|
||||
|
||||
halt_device_ssh "$jb_ssh_port"
|
||||
stop_process_tree "$jb_iproxy_pid" 2>/dev/null || true
|
||||
echo "[*] Waiting for VM shutdown..."
|
||||
wait "$BOOT_PID" || true
|
||||
BOOT_PID=""
|
||||
echo "[*] JB finalization will run automatically on first normal boot"
|
||||
echo " via /cores/vphone_jb_setup.sh (LaunchDaemon)."
|
||||
echo " Monitor progress via vphoned file browser: /var/log/vphone_jb_setup.log"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.vphone.jb-setup</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/iosbinpack64/bin/bash</string>
|
||||
<string>/cores/vphone_jb_setup.sh</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/vphone_jb_setup.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/vphone_jb_setup.log</string>
|
||||
<key>UserName</key>
|
||||
<string>root</string>
|
||||
<key>EnablePressuredExit</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable
+198
@@ -0,0 +1,198 @@
|
||||
#!/bin/bash
|
||||
# vphone_jb_setup.sh — First-boot JB finalization script.
|
||||
#
|
||||
# Deployed to /cores/ during cfw_install_jb.sh (ramdisk phase).
|
||||
# Runs automatically via LaunchDaemon on first normal boot.
|
||||
# Idempotent — safe to re-run on subsequent boots.
|
||||
#
|
||||
# Logs to /var/log/vphone_jb_setup.log for host-side monitoring
|
||||
# via vphoned file browser.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
LOG="/var/log/vphone_jb_setup.log"
|
||||
DONE_MARKER="/var/mobile/.vphone_jb_setup_done"
|
||||
|
||||
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG"; }
|
||||
die() { log "FATAL: $*"; exit 1; }
|
||||
|
||||
# Redirect all output to log
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
log "=== vphone_jb_setup.sh starting ==="
|
||||
|
||||
# ── Check done marker ────────────────────────────────────────
|
||||
if [ -f "$DONE_MARKER" ]; then
|
||||
log "Already completed (marker exists), exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Environment ──────────────────────────────────────────────
|
||||
export TERM=xterm-256color
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Discover PATH dynamically
|
||||
P=""
|
||||
for d in \
|
||||
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
||||
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
||||
/usr/bin /usr/sbin /bin /sbin; do
|
||||
[ -d "$d" ] && P="$P:$d"
|
||||
done
|
||||
export PATH="${P#:}"
|
||||
log "PATH=$PATH"
|
||||
|
||||
# ── Find boot manifest hash ─────────────────────────────────
|
||||
BOOT_HASH=""
|
||||
for d in /private/preboot/*/; do
|
||||
b="${d%/}"; b="${b##*/}"
|
||||
if [ "${#b}" = 96 ]; then
|
||||
BOOT_HASH="$b"
|
||||
break
|
||||
fi
|
||||
done
|
||||
[ -n "$BOOT_HASH" ] || die "Could not find 96-char boot manifest hash"
|
||||
log "Boot hash: $BOOT_HASH"
|
||||
|
||||
JB_TARGET="/private/preboot/$BOOT_HASH/jb-vphone/procursus"
|
||||
[ -d "$JB_TARGET" ] || die "Procursus not found at $JB_TARGET"
|
||||
|
||||
# ═══════════ 0/7 REPLACE LAUNCHCTL ═════════════════════════════
|
||||
# Procursus launchctl crashes (missing _launch_active_user_switch symbol).
|
||||
# iosbinpack64's launchctl talks to launchd fine and always exits 0,
|
||||
# which is enough for dpkg postinst/prerm script compatibility.
|
||||
log "[0/7] Linking iosbinpack64 launchctl into procursus..."
|
||||
IOSBINPACK_LAUNCHCTL=""
|
||||
for p in /iosbinpack64/bin/launchctl /iosbinpack64/usr/bin/launchctl; do
|
||||
[ -f "$p" ] && IOSBINPACK_LAUNCHCTL="$p" && break
|
||||
done
|
||||
|
||||
if [ -n "$IOSBINPACK_LAUNCHCTL" ]; then
|
||||
if [ -f "$JB_TARGET/usr/bin/launchctl" ] && [ ! -L "$JB_TARGET/usr/bin/launchctl" ] && [ ! -f "$JB_TARGET/usr/bin/launchctl.procursus" ]; then
|
||||
mv "$JB_TARGET/usr/bin/launchctl" "$JB_TARGET/usr/bin/launchctl.procursus"
|
||||
log " procursus original saved as launchctl.procursus"
|
||||
fi
|
||||
ln -sf "$IOSBINPACK_LAUNCHCTL" "$JB_TARGET/usr/bin/launchctl"
|
||||
mkdir -p "$JB_TARGET/bin"
|
||||
ln -sf "$IOSBINPACK_LAUNCHCTL" "$JB_TARGET/bin/launchctl"
|
||||
log " linked usr/bin/launchctl + bin/launchctl -> $IOSBINPACK_LAUNCHCTL"
|
||||
else
|
||||
log " WARNING: iosbinpack64 launchctl not found"
|
||||
fi
|
||||
|
||||
# ═══════════ 1/7 SYMLINK /var/jb ═════════════════════════════
|
||||
log "[1/7] Creating /private/var/jb symlink..."
|
||||
CURRENT_LINK=$(readlink /private/var/jb 2>/dev/null || true)
|
||||
if [ "$CURRENT_LINK" = "$JB_TARGET" ]; then
|
||||
log " Symlink already correct"
|
||||
else
|
||||
ln -sf "$JB_TARGET" /private/var/jb
|
||||
log " /var/jb -> $JB_TARGET"
|
||||
fi
|
||||
|
||||
# ═══════════ 2/7 FIX OWNERSHIP / PERMISSIONS ═════════════════
|
||||
log "[2/7] Fixing mobile Library ownership..."
|
||||
mkdir -p /var/jb/var/mobile/Library/Preferences
|
||||
chown -R 501:501 /var/jb/var/mobile/Library
|
||||
chmod 0755 /var/jb/var/mobile/Library
|
||||
chown -R 501:501 /var/jb/var/mobile/Library/Preferences
|
||||
chmod 0755 /var/jb/var/mobile/Library/Preferences
|
||||
log " Ownership set"
|
||||
|
||||
# ═══════════ 3/7 RUN prep_bootstrap.sh ═══════════════════════
|
||||
log "[3/7] Running prep_bootstrap.sh..."
|
||||
if [ -f /var/jb/prep_bootstrap.sh ]; then
|
||||
NO_PASSWORD_PROMPT=1 /var/jb/prep_bootstrap.sh || log " prep_bootstrap.sh exited with $?"
|
||||
log " prep_bootstrap.sh completed"
|
||||
else
|
||||
log " prep_bootstrap.sh already ran (deleted itself), skipping"
|
||||
fi
|
||||
|
||||
# Re-discover PATH after prep_bootstrap
|
||||
P=""
|
||||
for d in \
|
||||
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
||||
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
||||
/usr/bin /usr/sbin /bin /sbin; do
|
||||
[ -d "$d" ] && P="$P:$d"
|
||||
done
|
||||
export PATH="${P#:}"
|
||||
log " PATH=$PATH"
|
||||
|
||||
# ═══════════ 4/7 CREATE MARKER FILES ═════════════════════════
|
||||
log "[4/7] Creating marker files..."
|
||||
for marker in .procursus_strapped .installed_dopamine; do
|
||||
if [ -f "/var/jb/$marker" ]; then
|
||||
log " $marker already exists"
|
||||
else
|
||||
: > "/var/jb/$marker"
|
||||
chown 0:0 "/var/jb/$marker"
|
||||
chmod 0644 "/var/jb/$marker"
|
||||
log " $marker created"
|
||||
fi
|
||||
done
|
||||
|
||||
# ═══════════ 5/7 INSTALL SILEO ═══════════════════════════════
|
||||
log "[5/7] Installing Sileo..."
|
||||
SILEO_DEB_PATH="/private/preboot/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
|
||||
if dpkg -s org.coolstar.sileo >/dev/null 2>&1; then
|
||||
log " Sileo already installed"
|
||||
else
|
||||
if [ -f "$SILEO_DEB_PATH" ]; then
|
||||
dpkg -i "$SILEO_DEB_PATH" || log " dpkg -i sileo exited with $?"
|
||||
log " Sileo installed"
|
||||
else
|
||||
log " WARNING: Sileo deb not found at $SILEO_DEB_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
uicache -a 2>/dev/null || true
|
||||
log " uicache refreshed"
|
||||
|
||||
# ═══════════ 6/7 APT SETUP ══════════════════════════════════
|
||||
log "[6/7] Running apt setup..."
|
||||
|
||||
# Determine apt sources directory
|
||||
HAVOC_LIST="/var/jb/etc/apt/sources.list.d/havoc.list"
|
||||
if [ -d /etc/apt/sources.list.d ] && [ ! -d /var/jb/etc/apt/sources.list.d ]; then
|
||||
HAVOC_LIST="/etc/apt/sources.list.d/havoc.list"
|
||||
fi
|
||||
|
||||
if ! grep -rIl 'havoc.app' /etc/apt /var/jb/etc/apt 2>/dev/null | grep -q .; then
|
||||
mkdir -p "$(dirname "$HAVOC_LIST")"
|
||||
printf '%s\n' 'deb https://havoc.app/ ./' > "$HAVOC_LIST"
|
||||
log " Havoc source added: $HAVOC_LIST"
|
||||
else
|
||||
log " Havoc source already present"
|
||||
fi
|
||||
|
||||
apt-get -o Acquire::AllowInsecureRepositories=true \
|
||||
-o Acquire::AllowDowngradeToInsecureRepositories=true \
|
||||
update -qq 2>&1 || log " apt update exited with $?"
|
||||
log " apt update done"
|
||||
|
||||
apt-get -o APT::Get::AllowUnauthenticated=true \
|
||||
install -y -qq libkrw0-tfp0 2>/dev/null || true
|
||||
log " libkrw0-tfp0 done"
|
||||
|
||||
apt-get -o APT::Get::AllowUnauthenticated=true \
|
||||
upgrade -y -qq 2>/dev/null || true
|
||||
log " apt upgrade done"
|
||||
|
||||
# ═══════════ 7/7 INSTALL TROLLSTORE LITE ═════════════════════
|
||||
log "[7/7] Installing TrollStore Lite..."
|
||||
if dpkg -s com.opa334.trollstorelite >/dev/null 2>&1; then
|
||||
log " TrollStore Lite already installed"
|
||||
else
|
||||
apt-get -o APT::Get::AllowUnauthenticated=true \
|
||||
install -y -qq com.opa334.trollstorelite 2>&1 || log " TrollStore install exited with $?"
|
||||
log " TrollStore Lite installed"
|
||||
fi
|
||||
|
||||
uicache -a 2>/dev/null || true
|
||||
log " uicache refreshed"
|
||||
|
||||
# ═══════════ DONE ════════════════════════════════════════════
|
||||
: > "$DONE_MARKER"
|
||||
log "=== vphone_jb_setup.sh complete ==="
|
||||
@@ -1,3 +1,3 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern int vp_extract_archive(NSString *archivePath, NSString *extractionPath);
|
||||
extern int vp_extract_archive(NSString *archivePath, NSString *extractionPath, NSString **errorOutput);
|
||||
|
||||
+32
-15
@@ -20,14 +20,15 @@ static int copy_data(struct archive *ar, struct archive *aw) {
|
||||
}
|
||||
}
|
||||
|
||||
int vp_extract_archive(NSString *archivePath, NSString *extractionPath) {
|
||||
int vp_extract_archive(NSString *archivePath, NSString *extractionPath, NSString **errorOutput) {
|
||||
int flags = ARCHIVE_EXTRACT_TIME
|
||||
| ARCHIVE_EXTRACT_PERM
|
||||
| ARCHIVE_EXTRACT_ACL
|
||||
| ARCHIVE_EXTRACT_FFLAGS
|
||||
| ARCHIVE_EXTRACT_SECURE_SYMLINKS
|
||||
| ARCHIVE_EXTRACT_SECURE_NODOTDOT
|
||||
| ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS;
|
||||
| ARCHIVE_EXTRACT_SECURE_NODOTDOT;
|
||||
|
||||
// Resolve symlinks in extractionPath (e.g. /tmp -> /private/tmp on iOS)
|
||||
// so ARCHIVE_EXTRACT_SECURE_SYMLINKS doesn't reject trusted system symlinks.
|
||||
NSString *resolvedPath = [extractionPath stringByResolvingSymlinksInPath];
|
||||
NSLog(@"vphoned: extract %@ -> %@ (resolved: %@)", archivePath, extractionPath, resolvedPath);
|
||||
|
||||
struct archive *a = archive_read_new();
|
||||
archive_read_support_format_all(a);
|
||||
@@ -39,6 +40,9 @@ int vp_extract_archive(NSString *archivePath, NSString *extractionPath) {
|
||||
|
||||
int ret = 0;
|
||||
if (archive_read_open_filename(a, archivePath.fileSystemRepresentation, 10240) != ARCHIVE_OK) {
|
||||
NSString *err = [NSString stringWithFormat:@"archive_read_open failed: %s", archive_error_string(a)];
|
||||
NSLog(@"vphoned: %@", err);
|
||||
if (errorOutput) *errorOutput = err;
|
||||
ret = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -48,30 +52,43 @@ int vp_extract_archive(NSString *archivePath, NSString *extractionPath) {
|
||||
int r = archive_read_next_header(a, &entry);
|
||||
if (r == ARCHIVE_EOF) break;
|
||||
if (r < ARCHIVE_OK)
|
||||
fprintf(stderr, "%s\n", archive_error_string(a));
|
||||
if (r < ARCHIVE_WARN) { ret = 1; goto cleanup; }
|
||||
NSLog(@"vphoned: archive_read_next_header: %s", archive_error_string(a));
|
||||
if (r < ARCHIVE_WARN) {
|
||||
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"archive_read_next_header failed: %s", archive_error_string(a)];
|
||||
ret = 1; goto cleanup;
|
||||
}
|
||||
|
||||
const char *entryPath = archive_entry_pathname(entry);
|
||||
if (!entryPath) { ret = 1; goto cleanup; }
|
||||
NSString *currentFile = [NSString stringWithUTF8String:entryPath];
|
||||
if (!currentFile) { ret = 1; goto cleanup; }
|
||||
NSString *fullOutputPath = [extractionPath stringByAppendingPathComponent:currentFile];
|
||||
NSString *fullOutputPath = [resolvedPath stringByAppendingPathComponent:currentFile];
|
||||
archive_entry_set_pathname(entry, fullOutputPath.fileSystemRepresentation);
|
||||
|
||||
r = archive_write_header(ext, entry);
|
||||
if (r < ARCHIVE_OK)
|
||||
fprintf(stderr, "%s\n", archive_error_string(ext));
|
||||
else if (archive_entry_size(entry) > 0) {
|
||||
NSLog(@"vphoned: archive_write_header(%@): %s (r=%d)", currentFile, archive_error_string(ext), r);
|
||||
if (r < ARCHIVE_WARN) {
|
||||
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"archive_write_header failed for %@: %s", currentFile, archive_error_string(ext)];
|
||||
ret = 1; goto cleanup;
|
||||
}
|
||||
if (archive_entry_size(entry) > 0) {
|
||||
r = copy_data(a, ext);
|
||||
if (r < ARCHIVE_OK)
|
||||
fprintf(stderr, "%s\n", archive_error_string(ext));
|
||||
if (r < ARCHIVE_WARN) { ret = 1; goto cleanup; }
|
||||
NSLog(@"vphoned: copy_data(%@): %s (r=%d)", currentFile, archive_error_string(ext), r);
|
||||
if (r < ARCHIVE_WARN) {
|
||||
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"copy_data failed for %@: %s", currentFile, archive_error_string(ext)];
|
||||
ret = 1; goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
r = archive_write_finish_entry(ext);
|
||||
if (r < ARCHIVE_OK)
|
||||
fprintf(stderr, "%s\n", archive_error_string(ext));
|
||||
if (r < ARCHIVE_WARN) { ret = 1; goto cleanup; }
|
||||
NSLog(@"vphoned: archive_write_finish_entry(%@): %s (r=%d)", currentFile, archive_error_string(ext), r);
|
||||
if (r < ARCHIVE_WARN) {
|
||||
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"archive_write_finish_entry failed for %@: %s", currentFile, archive_error_string(ext)];
|
||||
ret = 1; goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
|
||||
@@ -745,9 +745,10 @@ static int vp_extract_package_to_directory(
|
||||
NSString *extractionPath,
|
||||
NSString **detailOutput
|
||||
) {
|
||||
int ret = vp_extract_archive(fileToExtract, extractionPath);
|
||||
NSString *archiveError = nil;
|
||||
int ret = vp_extract_archive(fileToExtract, extractionPath, &archiveError);
|
||||
if (ret != 0) {
|
||||
if (detailOutput) *detailOutput = @"libarchive extraction failed";
|
||||
if (detailOutput) *detailOutput = archiveError ?: @"libarchive extraction failed";
|
||||
return 168;
|
||||
}
|
||||
return 0;
|
||||
@@ -796,7 +797,7 @@ NSDictionary *vp_handle_custom_install(NSDictionary *msg) {
|
||||
certPath = nil;
|
||||
}
|
||||
|
||||
NSString *tmpPackagePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||
NSString *tmpPackagePath = [[NSTemporaryDirectory() stringByResolvingSymlinksInPath] stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:tmpPackagePath withIntermediateDirectories:NO attributes:nil error:nil]) {
|
||||
NSMutableDictionary *response = vp_make_response(@"err", reqId);
|
||||
response[@"msg"] = @"failed to create temporary extraction directory";
|
||||
|
||||
@@ -531,7 +531,7 @@ class VPhoneControl {
|
||||
if let reqId, let pending = removePending(id: reqId) {
|
||||
if type == "err" {
|
||||
let detail = msg["msg"] as? String ?? "unknown error"
|
||||
pending.handler(.failure(ControlError.guestError(detail)))
|
||||
DispatchQueue.main.async { pending.handler(.failure(ControlError.guestError(detail))) }
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -543,19 +543,19 @@ class VPhoneControl {
|
||||
if Self.readFully(fd: fd, buf: buf, count: size) {
|
||||
let data = Data(bytes: buf, count: size)
|
||||
buf.deallocate()
|
||||
pending.handler(.success((msg, data)))
|
||||
DispatchQueue.main.async { pending.handler(.success((msg, data))) }
|
||||
} else {
|
||||
buf.deallocate()
|
||||
pending.handler(.failure(ControlError.protocolError("failed to read file data")))
|
||||
DispatchQueue.main.async { pending.handler(.failure(ControlError.protocolError("failed to read file data"))) }
|
||||
}
|
||||
} else {
|
||||
pending.handler(.success((msg, Data())))
|
||||
DispatchQueue.main.async { pending.handler(.success((msg, Data()))) }
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Normal response (ok, pong, etc.)
|
||||
pending.handler(.success((msg, nil)))
|
||||
DispatchQueue.main.async { pending.handler(.success((msg, nil))) }
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ class VPhoneControl {
|
||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + timeout) { [weak self] in
|
||||
guard let self else { return }
|
||||
guard let pending = removePending(id: id) else { return }
|
||||
pending.handler(.failure(ControlError.requestTimedOut(type: type, seconds: timeoutSeconds)))
|
||||
DispatchQueue.main.async { pending.handler(.failure(ControlError.requestTimedOut(type: type, seconds: timeoutSeconds))) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@preconcurrency import Foundation
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user