72 lines
4.2 KiB
Python
72 lines
4.2 KiB
Python
"""Verify a thin ARM64 iPhone IPA, independently of the macOS build environment."""
|
|
import pathlib
|
|
import plistlib
|
|
import struct
|
|
import sys
|
|
import zipfile
|
|
|
|
path = pathlib.Path(sys.argv[1])
|
|
with zipfile.ZipFile(path) as archive:
|
|
assert archive.testzip() is None, "Corrupt ZIP member"
|
|
prefix = "Payload/MusicBridge.app/"
|
|
info = plistlib.loads(archive.read(prefix + "Info.plist"))
|
|
assert info["CFBundleSupportedPlatforms"] == ["iPhoneOS"], "Not an iPhone device build"
|
|
assert info["CFBundleIdentifier"] == "ru.yukinoki.musicbridge", "Unexpected bundle ID"
|
|
assert info["MinimumOSVersion"] == "17.0", "Unexpected minimum iOS version"
|
|
executable = info["CFBundleExecutable"]
|
|
assert executable == "MusicBridge", "Unexpected executable"
|
|
binary = archive.read(prefix + executable)
|
|
magic, cpu = struct.unpack_from("<II", binary)
|
|
assert magic == 0xFEEDFACF and cpu == 0x0100000C, "Expected ARM64 Mach-O executable"
|
|
# ARM64 can also be a simulator build: check LC_BUILD_VERSION's platform field.
|
|
command_count = struct.unpack_from("<I", binary, 16)[0]
|
|
offset = 32
|
|
platforms = []
|
|
for _ in range(command_count):
|
|
command, size = struct.unpack_from("<II", binary, offset)
|
|
assert size >= 8 and offset + size <= len(binary), "Invalid Mach-O load command"
|
|
if command == 0x32:
|
|
platforms.append(struct.unpack_from("<I", binary, offset + 8)[0])
|
|
offset += size
|
|
assert platforms == [2], "Mach-O is not built for iOS devices"
|
|
assert info["NSSupportsLiveActivities"] is True
|
|
extension = prefix + "PlugIns/MusicBridgeWidgets.appex/"
|
|
widget = plistlib.loads(archive.read(extension + "Info.plist"))
|
|
assert widget["CFBundleIdentifier"] == info["CFBundleIdentifier"] + ".widgets"
|
|
assert widget["CFBundleShortVersionString"] == info["CFBundleShortVersionString"]
|
|
assert widget["CFBundleVersion"] == info["CFBundleVersion"]
|
|
assert widget["NSExtension"]["NSExtensionPointIdentifier"] == "com.apple.widgetkit-extension"
|
|
assert widget["CFBundleSupportedPlatforms"] == ["iPhoneOS"]
|
|
binary = archive.read(extension + widget["CFBundleExecutable"])
|
|
assert struct.unpack_from("<II", binary) == (0xFEEDFACF, 0x0100000C)
|
|
offset, platforms = 32, []
|
|
for _ in range(struct.unpack_from("<I", binary, 16)[0]):
|
|
command, size = struct.unpack_from("<II", binary, offset)
|
|
assert size >= 8 and offset + size <= len(binary)
|
|
if command == 0x32:
|
|
platforms.append(struct.unpack_from("<I", binary, offset + 8)[0])
|
|
offset += size
|
|
assert platforms == [2], "Widget is not built for iOS devices"
|
|
remote_path = prefix + "Extensions/MusicBridgeRemote.appex/"
|
|
remote = plistlib.loads(archive.read(remote_path + "Info.plist"))
|
|
assert remote["CFBundleIdentifier"] == info["CFBundleIdentifier"] + ".remote"
|
|
assert remote["CFBundleShortVersionString"] == info["CFBundleShortVersionString"]
|
|
assert remote["CFBundleVersion"] == info["CFBundleVersion"]
|
|
assert remote["MinimumOSVersion"] == "27.0"
|
|
assert remote["EXAppExtensionAttributes"]["EXExtensionPointIdentifier"] == "com.apple.nowplaying.remote-media"
|
|
assert remote["MusicBridgeSharedKeychainGroup"] == info["MusicBridgeSharedKeychainGroup"] == "ru.yukinoki.musicbridge.remote-control"
|
|
binary = archive.read(remote_path + remote["CFBundleExecutable"])
|
|
assert struct.unpack_from("<II", binary) == (0xFEEDFACF, 0x0100000C)
|
|
offset, platforms = 32, []
|
|
for _ in range(struct.unpack_from("<I", binary, 16)[0]):
|
|
command, size = struct.unpack_from("<II", binary, offset)
|
|
assert size >= 8 and offset + size <= len(binary)
|
|
if command == 0x32:
|
|
platforms.append(struct.unpack_from("<I", binary, offset + 8)[0])
|
|
offset += size
|
|
assert platforms == [2], "Remote extension is not built for iOS devices"
|
|
print(f"PASS: {path.name}: valid IPA, iPhoneOS ARM64, iOS 17+, ru.yukinoki.musicbridge.")
|
|
print("PASS: embedded WidgetKit extension, matching version, ARM64 iPhoneOS and Live Activities support.")
|
|
print("PASS: embedded iOS 27 NowPlaying ExtensionKit extension and matching shared Keychain metadata.")
|
|
print("Requires signing all extensions and shared Keychain entitlements before installation.")
|