Skip to content

fix: prevent crash in apps that weak-link MarketplaceKit on macOS 27 - #238

Open
NickStrupat wants to merge 1 commit into
PlayCover:masterfrom
NickStrupat:fix/marketplacekit-macos27
Open

NickStrupat wants to merge 1 commit into
PlayCover:masterfrom
NickStrupat:fix/marketplacekit-macos27

Conversation

@NickStrupat

@NickStrupat NickStrupat commented Sep 23, 2026 •

Copy link
Copy Markdown

Problem

On macOS 27, Rocket League Sideswipe (1.12.5) opens for about a second, then closes without an error message. macOS writes no .ips crash report because Unreal's own crash handler catches the signal and calls _exit.

The game's PLCrashReporter dumps (Documents/Engine/Saved/Crashes/*/minidump.dmp) all show the same crash:

  • SIGSEGV with pc = 0x0
  • lr = MarketplaceKitHelper +0x4300, on a Swift concurrency thread

The app bundles MarketplaceKitHelper.framework, which weak-links /System/Library/Frameworks/MarketplaceKit.framework and calls AppDistributor.current behind #available(iOS 17.4, *) (__isPlatformVersionAtLeast(2, 17, 4, 0)).

For Mac Catalyst, MarketplaceKit.framework exists only as an empty stub in /System/iOSSupport. The binary entry is a dangling symlink, and the framework isn't in the dyld cache. So every weak AppDistributor symbol resolves to NULL. On macOS 27 the iOS 17.4 availability check now passes, and the helper calls the NULL type metadata accessor ($s14MarketplaceKit14AppDistributorOMa) at +0x42fc. The same game build worked on macOS 15.

Fix

Interpose _availability_version_check (the OS function behind #available / __isPlatformVersionAtLeast) in PlayLoader.m:

  • A _dyld_register_func_for_add_image callback records the __TEXT range of every image that has an LC_LOAD_WEAK_DYLIB for MarketplaceKit.
  • The interposer returns false only when all three of these hold:
    • the caller is one of those images;
    • the query asks for iOS ≥ 17.4;
    • MarketplaceKit isn't loaded (checked once with dlopen(..., RTLD_NOLOAD)).
  • Every other call is forwarded to the real function unchanged. If a future macOS ships a working MarketplaceKit for Catalyst, the workaround switches itself off.

The fix doesn't depend on the bundle ID, so it should also cover other games that bundle the same helper (EU alternative-marketplace builds).

Testing

  • macOS 27 (26A428), Rocket League Sideswipe 1.12.5, PlayTools built from master with this change, converted to Mac Catalyst with vtool and installed in ~/Library/Frameworks.
  • Before: crashed at about 2s on every launch (10 identical crash dumps).
  • After: the game stays running and reaches EOS login. Its log shows the helper taking the fallback path:
    LogMarketplaceKitHelper_RL: Warning: App Distribution not available on system.
    LogMarketplaceKitHelper_RL: Display: Received App Distribution info: 5 - IOS version below 17.4.
    
  • Not tested on macOS 15/26. The crash didn't happen there (the same game build worked on macOS 15); presumably the iOS 17.4 check came out false, but I haven't verified that. Either way, the interposer only changes results in the narrow case above.
  • Built locally with FASTLANE=1 because SwiftLint wasn't installed; this change doesn't touch any Swift code.

🤖 Generated with Claude Code

MarketplaceKit is only an empty stub for Mac Catalyst, so its symbols
resolve to NULL in apps that weak-link it. On macOS 27 the
`#available(iOS 17.4, *)` checks guarding those calls now pass, and apps
such as Rocket League Sideswipe (via MarketplaceKitHelper) jump to a NULL
AppDistributor function and crash with SIGSEGV on launch.

Interpose _availability_version_check so that images weak-linking
MarketplaceKit are told iOS 17.4+ is unavailable while MarketplaceKit is
not loaded. All other availability checks are passed through unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TheMoonThatRises TheMoonThatRises added the ai-pr Pull request generated with AI (with or without assistance) label Sep 23, 2026
@TheMoonThatRises

Copy link
Copy Markdown
Member

Have you looked at user plugins (#236) for PlayTools? These changes would be best if it was a plugin that can be installed for individual apps, as MarketplaceKit is unlikely to be used by most applications. Claude should be able to quickly create a custom plugin.

@NickStrupat

Copy link
Copy Markdown
Author

Thanks for the pointer to user plugins! I did look at that route, but I think this one belongs in core. Here's my reasoning:

  • Users would have no way to find the fix. As far as I can tell, the plugin system loads a .dylib the user already has, picked from Settings → Custom Plugins, after a warning that plugins are arbitrary code to add only from people you trust. There's no catalog or prompt. And this crash is silent: the game opens for about a second and disappears, with no error and no macOS crash report (Unreal's handler catches the SIGSEGV and calls _exit). To fix it themselves, a user would have to work out that MarketplaceKit is the cause, find a prebuilt dylib somewhere, and install it despite that warning. In practice it will just look like "PlayCover doesn't work on macOS 27".
  • It only affects apps that use MarketplaceKit. The workaround only changes results for images that have an LC_LOAD_WEAK_DYLIB on MarketplaceKit, only for iOS ≥ 17.4 queries, and only while MarketplaceKit isn't loaded. For every other app, the interposer does a range check over an empty list and forwards to the real function. If a future macOS ships a working MarketplaceKit for Catalyst, it switches itself off.
  • It's not specific to this game. The crashing helper here (Psyonix.MarketplaceKitHelper) is Rocket League's own, but it does exactly what Apple's documentation describes: weak-link MarketplaceKit and call AppDistributor.current behind #available(iOS 17.4, *). Any app that adopts MarketplaceKit that way will crash the same way on macOS 27.
  • A plugin would need a private dyld API. Plugins are dlopened, so dyld ignores their __interpose sections. A plugin version would have to use dyld_dynamic_interpose, which is less proven than the DYLD_INTERPOSE mechanism PlayLoader already uses for sysctl, uname and SecItem*.

If you'd still prefer it as a plugin, I'm happy to build and test one on Sideswipe. Just let me know.

@TheMoonThatRises

Copy link
Copy Markdown
Member

Can you first test it was a user plugin? This would be a good test flight of user plugins, after all fixing specific games is one of its main purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-pr Pull request generated with AI (with or without assistance)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants