Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,15 @@ jobs:
needs: [check]
strategy:
matrix:
builder: [ghcr.io/nyamisty/altserver_builder_alpine_armv7, ghcr.io/nyamisty/altserver_builder_alpine_aarch64, ghcr.io/nyamisty/altserver_builder_alpine_amd64, ghcr.io/nyamisty/altserver_builder_alpine_i386]
include:
- builder: ghcr.io/nyamisty/altserver_builder_alpine_armv7
rust_target: armv7-unknown-linux-musleabihf
- builder: ghcr.io/nyamisty/altserver_builder_alpine_aarch64
rust_target: aarch64-unknown-linux-musl
- builder: ghcr.io/nyamisty/altserver_builder_alpine_amd64
rust_target: x86_64-unknown-linux-musl
- builder: ghcr.io/nyamisty/altserver_builder_alpine_i386
rust_target: i686-unknown-linux-musl
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -92,11 +100,15 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare build
run: mkdir /tmp/build_output
- name: Build idevice
run: |
lib=$(makefiles/idevice-build/build-idevice-cross.sh ${{ matrix.rust_target }})
echo "IDEVICE_FFI_LIB=/workdir/${lib#$PWD/}" >> "$GITHUB_ENV"
- name: Build
run: |
image=${{ matrix.builder }}
docker pull $image
docker run -v ${PWD}:/workdir -w /workdir $image bash -c 'mkdir build; cd build; make -f ../Makefile -j3'
docker run -v ${PWD}:/workdir -w /workdir $image bash -c "mkdir build; cd build; make -f ../Makefile -j3 IDEVICE_FFI_LIB=$IDEVICE_FFI_LIB"
cp build/AltServer-* /tmp/build_output; chmod +x /tmp/build_output/*
sudo rm -rf build
git clean -fdX
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@
[submodule "libraries/libimobiledevice-glue"]
path = libraries/libimobiledevice-glue
url = https://github.com/libimobiledevice/libimobiledevice-glue
[submodule "libraries/idevice"]
path = libraries/idevice
url = https://github.com/jkcoxson/idevice
15 changes: 12 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,18 @@ lib_dnssd_loader_clean :
$(MAKE) -f $(ROOT_DIR)/makefiles/dnssd_loader-build/dnssd_loader.mak clean
.PHONY: $(BUILD_DIR)/dnssd_loader.a lib_dnssd_loader lib_dnssd_loader_clean

$(BUILD_DIR)/idevice_ffi.o:
$(MAKE) -f $(ROOT_DIR)/makefiles/idevice-build/idevice.mak
lib_idevice : $(BUILD_DIR)/idevice_ffi.o
lib_idevice_clean :
$(MAKE) -f $(ROOT_DIR)/makefiles/idevice-build/idevice.mak clean
.PHONY: $(BUILD_DIR)/idevice_ffi.o lib_idevice lib_idevice_clean


include $(ROOT_DIR)/makefiles/libimobiledevice-build/libimobiledevice-files.mak
include $(ROOT_DIR)/makefiles/AltSign-build/AltSign-files.mak
include $(ROOT_DIR)/makefiles/dnssd_loader-build/dnssd_loader-files.mak
include $(ROOT_DIR)/makefiles/idevice-build/idevice-files.mak

#libimobiledevice_include := -I$(LIB_DIR)/libimobiledevice/include -I$(LIB_DIR)/libimobiledevice -I$(LIB_DIR)/libusbmuxd/include
#libplist_include := -I$(LIB_DIR)/libplist/include
Expand All @@ -56,6 +64,7 @@ INC_CFLAGS += $(libimobiledevice_include)
INC_CFLAGS += $(libplist_include)
INC_CFLAGS += $(altsign_include)
INC_CFLAGS += $(dnssd_loader_include)
INC_CFLAGS += $(idevice_include)

include $(ROOT_DIR)/makefiles/AltWindowsShim.mak

Expand Down Expand Up @@ -113,17 +122,17 @@ main_newsrc := $(main_orisrc:$(main_srcroot)/%=$(main_patched_root)/%)

main_objs = $(main_newsrc:$(BUILD_DIR)/%=$(BUILD_DIR)/objs/%.o) $(main_override_src:$(ROOT_DIR)/%=$(BUILD_DIR)/objs/%.o) $(shim_src:$(MAIN_DIR)/%=$(BUILD_DIR)/objs/%.o)

$(main_objs) : lib_AltSign lib_libimobiledevice lib_dnssd_loader
$(main_objs) : lib_AltSign lib_libimobiledevice lib_dnssd_loader lib_idevice

$(main_objs) : EXTRA_FLAGS := -I$(main_patched_root) -I$(ROOT_DIR)/src -fpermissive -include "common.h" $(INC_CFLAGS)

LDFLAGS = -static -lssl -lcrypto -lpthread -lcorecrypto_static -lzip -lm -lz -lcpprest -lboost_system -lboost_filesystem -lstdc++ -lssl -lcrypto -luuid

$(BUILD_DIR)/$(PROGRAM):: $(main_objs) $(BUILD_DIR)/libimobiledevice.a $(BUILD_DIR)/AltSign.a $(BUILD_DIR)/libplist.a $(BUILD_DIR)/dnssd_loader.a
$(BUILD_DIR)/$(PROGRAM):: $(main_objs) $(BUILD_DIR)/libimobiledevice.a $(BUILD_DIR)/AltSign.a $(BUILD_DIR)/libplist.a $(BUILD_DIR)/dnssd_loader.a $(BUILD_DIR)/idevice_ffi.o
$(CC) -o $@ $^ $(LDFLAGS)

.PHONY: clean all
clean:: lib_libimobiledevice_clean lib_AltSign_clean lib_dnssd_loader_clean
clean:: lib_libimobiledevice_clean lib_AltSign_clean lib_dnssd_loader_clean lib_idevice_clean
rm -f $(main_objs) $(BUILD_DIR)/$(PROGRAM)

all:: preprocess $(BUILD_DIR)/$(PROGRAM)
Expand Down
51 changes: 48 additions & 3 deletions README-NG.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,42 @@ assumed the BSD layout (byte 0 = `sa_len`, byte 1 = family) and failed with
`libraries/libimobiledevice/src/idevice.c` now detects both layouts, so `USBMUXD_SOCKET_ADDRESS`
can point straight at netmuxd. A BSD-format proxy in front of netmuxd keeps working as well.

## Fix 4: Remote AltServer setup (`PairingFileRequest`)

### Symptom

In AltStore Classic 2.3, **Set up Remote AltServer…** fails with "unable to pair" and AltServer
logs `Failed to handle request:AltServer does not support this request.`

### Cause

AltStore Classic 2.3 asks AltServer for a RemotePairing (RPPairing) file, the iOS 17+ pairing
record with a `private_key`. AltStore keeps it in its keychain and uses it to install and refresh
apps on the device itself over LocalDevVPN. AltServer for macOS creates it in
`DevicePairingManager.swift` with idevice's `tunnel_pair_usb()`; AltServer-Linux had no handler for
the request.

### Fix

- `upstream_repo/AltServer/ClientConnection.cpp` handles `PairingFileRequest` and answers with a
`PairingFileResponse` carrying the file. `upstream_repo/AltServer/DevicePairingManager.h` declares
the pairing interface; the `DevicePairingManager.cpp` next to it is the Windows version, which
throws `UnknownRequest`, and the Linux build replaces it with `src/DevicePairingManager.cpp`.
- `src/DevicePairingManager.cpp` is a C++ port of `DevicePairingManager.swift`: it looks up the
device by UDID in usbmuxd, calls `tunnel_pair_usb()` (the device shows a Trust prompt for
"AltServer on <hostname>") and serializes the pairing file. It reads `USBMUXD_SOCKET_ADDRESS` by
libusbmuxd's rules (`UNIX:<path>`, `<host>:<port>`, otherwise `/var/run/usbmuxd`), so the device
lookup and the pairing connection use the same usbmuxd as the rest of AltServer-Linux. It pairs
only over the device's USB connection, not a network entry for the same UDID. The pairing
runs on its own thread with an 8 MiB stack: `tunnel_pair_usb()` polls idevice's pairing future on
the calling thread, and that overflows the 128 KiB stack musl gives cpprestsdk's pool threads.
- `libraries/idevice` is [idevice](https://github.com/jkcoxson/idevice) v0.1.68, built with the
features `usbmuxd,tunnel_tcp_stack,ring` (`makefiles/idevice-build/idevice-features.txt`).
idevice's static library also exports libplist-compatible `plist_*` functions and the Rust standard
library, which would clash with the vendored libplist. `makefiles/idevice-build/idevice.mak`
therefore prelinks it into one object (`ld -r`) and keeps only the functions in
`idevice-api.txt` global (`objcopy --keep-global-symbols`).

## Prebuilt binaries

Static binaries for x86_64, aarch64, armv7 and i586 are attached to the
Expand All @@ -109,24 +145,33 @@ produces them as GitHub Actions artifacts, which expire after 90 days.
Builds run in the prebuilt upstream Alpine image. On an Apple Silicon Mac, Rosetta runs the amd64
container at native speed and a clean build takes about 30 seconds.

The builder images have no Rust toolchain, so idevice (Fix 4) is built first with
`makefiles/idevice-build/build-idevice-cross.sh`, which runs cargo-zigbuild in the
`ghcr.io/rust-cross/cargo-zigbuild` image and prints the path of the static library:

```bash
git clone --recursive -b ng https://github.com/jaakkopalvaila/AltServer-Linux
cd AltServer-Linux
lib=$(makefiles/idevice-build/build-idevice-cross.sh x86_64-unknown-linux-musl)
mkdir -p build
docker run --rm --platform linux/amd64 -v "$PWD:/workdir" -w /workdir \
ghcr.io/nyamisty/altserver_builder_alpine_amd64:latest \
bash -c 'cd build && make -f ../Makefile -j8'
bash -c "cd build && make -f ../Makefile -j8 IDEVICE_FFI_LIB=/workdir/${lib#$PWD/}"
```

The result is a statically linked `build/AltServer-x86_64`. Other architectures use the
`altserver_builder_alpine_{aarch64,armv7,i386}` images.
`altserver_builder_alpine_{aarch64,armv7,i386}` images with the Rust targets
`aarch64-unknown-linux-musl`, `armv7-unknown-linux-musleabihf` and `i686-unknown-linux-musl`.
Without `IDEVICE_FFI_LIB`, the Makefile runs `cargo build` itself, which needs cargo in the build environment
(tested with Rust 1.93 and 1.98).

Sanity-check that all three fixes are present:
Sanity-check that the fixes are present:

```bash
strings build/AltServer-x86_64 | grep -c "missing path separator" # Fix 1, new ldid -> 1
strings build/AltServer-x86_64 | grep -c "Sanitized client info" # Fix 2, sign-in -> 1
grep -c idevice_sockaddr_len libraries/libimobiledevice/src/idevice.c # Fix 3, netmuxd -> >= 1
strings build/AltServer-x86_64 | grep -c "Generated pairing file" # Fix 4, pairing file -> 1
```

## Running
Expand Down
1 change: 1 addition & 0 deletions libraries/idevice
Submodule idevice added at d32c81
18 changes: 18 additions & 0 deletions makefiles/idevice-build/build-idevice-cross.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh
# Cross-compiles idevice's static library for a musl Rust target with cargo-zigbuild, for
# architectures whose builder image has no musl-hosted Rust toolchain (i686, armv7).
# zig provides the C compiler and musl headers for ring's C and assembly code.
# Pass the printed path to make as IDEVICE_FFI_LIB. The output goes under libraries/idevice/target,
# which idevice's .gitignore already covers, so the submodule stays clean.
#
# Usage: makefiles/idevice-build/build-idevice-cross.sh <rust-target>
set -eu
target="$1"
root="$(cd "$(dirname "$0")/../.." && pwd)"
features="$(cat "$root/makefiles/idevice-build/idevice-features.txt")"

docker run --rm -v "$root/libraries/idevice:/src" -w /src/ffi ghcr.io/rust-cross/cargo-zigbuild:0.23.4 sh -c "
rustup target add $target >/dev/null &&
cargo zigbuild --release --locked --target $target --target-dir /src/target/cross --no-default-features --features $features" >&2

echo "$root/libraries/idevice/target/cross/$target/release/libidevice_ffi.a"
19 changes: 19 additions & 0 deletions makefiles/idevice-build/idevice-api.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
idevice_data_free
idevice_error_free
idevice_provider_free
idevice_string_free
idevice_usbmuxd_addr_free
idevice_usbmuxd_connection_free
idevice_usbmuxd_device_get_connection_type
idevice_usbmuxd_device_get_device_id
idevice_usbmuxd_device_get_udid
idevice_usbmuxd_device_list_free
idevice_usbmuxd_get_devices
idevice_usbmuxd_new_tcp_connection
idevice_usbmuxd_new_unix_socket_connection
idevice_usbmuxd_tcp_addr_new
idevice_usbmuxd_unix_addr_new
rp_pairing_file_free
rp_pairing_file_to_bytes
tunnel_pair_usb
usbmuxd_provider_new
1 change: 1 addition & 0 deletions makefiles/idevice-build/idevice-features.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
usbmuxd,tunnel_tcp_stack,ring
1 change: 1 addition & 0 deletions makefiles/idevice-build/idevice-files.mak
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
idevice_include := -I$(LIB_DIR)/idevice
44 changes: 44 additions & 0 deletions makefiles/idevice-build/idevice.mak
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Builds idevice's C API (https://github.com/jkcoxson/idevice) for DevicePairingManager.
#
# idevice's static library also exports libplist-compatible plist_* functions and the Rust standard
# library, which would clash with the vendored libplist. So the library is prelinked into one object
# (ld -r) whose only global symbols are the functions in idevice-api.txt.
#
# Set IDEVICE_FFI_LIB to a libidevice_ffi.a built elsewhere to skip the cargo build, for example
# one from build-idevice-cross.sh for architectures without a musl-hosted Rust toolchain.
# Either cargo build also generates the header, libraries/idevice/ffi/idevice.h.
ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
include $(ROOT_DIR)/../main.mak

# A failed ld or objcopy must not leave a partial idevice_ffi.o that the next make treats as built.
.DELETE_ON_ERROR:

IDEVICE_FEATURES := $(shell cat $(ROOT_DIR)/idevice-features.txt)
IDEVICE_TARGET_DIR := $(BUILD_DIR)/idevice-target
IDEVICE_FFI_LIB ?= $(IDEVICE_TARGET_DIR)/release/libidevice_ffi.a

idevice_api := $(ROOT_DIR)/idevice-api.txt

# cargo runs on every make, because only cargo knows when the library is out of date: a submodule
# bump, a change to idevice-features.txt or a new toolchain. When nothing changed it leaves the
# library untouched, and make then does not prelink it again.
$(IDEVICE_TARGET_DIR)/release/libidevice_ffi.a: FORCE
cd $(LIB_DIR)/idevice/ffi && cargo build --release --locked --target-dir $(IDEVICE_TARGET_DIR) --no-default-features --features $(IDEVICE_FEATURES)

$(BUILD_DIR)/idevice_ffi.o: $(IDEVICE_FFI_LIB) $(idevice_api)
ld -r $(addprefix -u ,$(shell cat $(idevice_api))) -o $@.tmp $(IDEVICE_FFI_LIB)
objcopy --keep-global-symbols=$(idevice_api) $@.tmp $@
rm -f $@.tmp

FORCE:
.PHONY : FORCE

clean::
rm -f $(BUILD_DIR)/idevice_ffi.o
rm -rf $(IDEVICE_TARGET_DIR)
.PHONY : clean

all :: $(BUILD_DIR)/idevice_ffi.o
.PHONY : all

.DEFAULT_GOAL := all
Loading