Support launching directly with the deep link, instead of just launching while already running.

This commit is contained in:
Henrik Rydgård
2026-04-28 22:26:53 +02:00
parent 1288070f95
commit b4698eabfe
4 changed files with 76 additions and 47 deletions
+5 -3
View File
@@ -624,12 +624,14 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
boot_filename = Path(str);
skipLogo = true;
}
// This is needed on iOS, to fixup the path to match the current app directory, if it's stored in it.
TryUpdateSavedPath(&boot_filename);
if (okToLoad && okToCheck) {
std::unique_ptr<FileLoader> fileLoader(ConstructFileLoader(boot_filename));
if (!fileLoader->Exists()) {
fprintf(stderr, "File not found: %s\n", boot_filename.c_str());
#if defined(_WIN32) || defined(__ANDROID__)
// Ignore and proceed.
#if defined(_WIN32) || defined(__ANDROID__) || PPSSPP_PLATFORM(IOS)
boot_filename.clear();
#else
// Bail.
@@ -639,7 +641,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
}
} else {
fprintf(stderr, "Syntax error: Can only boot one file.\nNote: Many command line args need a =, like --appendconfig=FILENAME.ini.\n");
#if defined(_WIN32) || defined(__ANDROID__)
#if defined(_WIN32) || defined(__ANDROID__) || PPSSPP_PLATFORM(IOS)
// Ignore and proceed.
#else
// Bail.
-26
View File
@@ -121,32 +121,6 @@ __attribute__((used)) static Class _forceLinkSceneDelegate = [SceneDelegate clas
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, gamePath.ToString());
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
// 1. Check the scheme
if (![[url scheme] isEqualToString:@"ppsspp"]) {
NSLog(@"PPSSPPUIApplication.openURL called with invalid URL: %@", url);
return NO;
}
NSLog(@"PPSSPPUIApplication.openURL called with valid URL: %@", url);
// 2. Parse the URL (using NSURLComponents is safer than manual string parsing)
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSArray *queryItems = [components queryItems];
NSLog(@"PPSSPPUIApplication.openURL query items: %@", queryItems);
for (NSURLQueryItem *item in queryItems) {
if ([item.name isEqualToString:@"path"]) {
NSString *receivedPath = item.value;
[self processFilePath:receivedPath];
} else {
NSLog(@"PPSSPPUIApplication.openURL: Unrecognized query item: %@=%@", item.name, item.value);
}
}
return YES;
}
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
switch (g_Config.iScreenRotation) {
case ROTATION_LOCKED_HORIZONTAL:
+67 -18
View File
@@ -13,8 +13,31 @@
#import "Core/Config.h"
#import "Common/Log.h"
#import "IAPManager.h"
#include "Core/Util/PathUtil.h"
#import <AVFoundation/AVFoundation.h>
#include <string>
static std::string gStartupArgStorage;
static NSString *ExtractDeepLinkPath(NSURL *url) {
if (![[url scheme] isEqualToString:@"ppsspp"]) {
return nil;
}
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
for (NSURLQueryItem *item in components.queryItems) {
if ([item.name isEqualToString:@"path"] && item.value.length > 0) {
return item.value;
}
}
return nil;
}
@interface SceneDelegate ()
@property (nonatomic, strong) NSString *pendingLaunchPath;
@end
@implementation SceneDelegate
@@ -22,11 +45,37 @@
NSLog(@"✅ SceneDelegate class was loaded!");
}
- (void)capturePendingPathFromURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
for (UIOpenURLContext *context in URLContexts) {
NSString *path = ExtractDeepLinkPath(context.URL);
if (path.length > 0) {
self.pendingLaunchPath = path;
NSLog(@"SceneDelegate: captured cold-start deep link path: %@", self.pendingLaunchPath);
break;
}
}
}
- (void)handleURLContextsWhileRunning:(NSSet<UIOpenURLContext *> *)URLContexts {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
for (UIOpenURLContext *context in URLContexts) {
NSLog(@"SceneDelegate: openURLContexts called with URL: %@", context.URL);
NSString *path = ExtractDeepLinkPath(context.URL);
if (path.length > 0) {
[appDelegate processFilePath:path];
} else {
NSLog(@"SceneDelegate: ignoring deep link URL (invalid scheme or missing path): %@", context.URL);
}
}
}
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
if (![scene isKindOfClass:[UIWindowScene class]]) {
return;
}
[self capturePendingPathFromURLContexts:connectionOptions.URLContexts];
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (self.window) {
NSLog(@"✅ Window already exists, not creating again!");
@@ -43,12 +92,26 @@
int argc = 1;
char *argv[5]{};
NSString *startupPath = nil;
NSURL *nsUrl = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey];
if (nsUrl != nullptr && nsUrl.isFileURL) {
if (self.pendingLaunchPath.length > 0) {
startupPath = self.pendingLaunchPath;
self.pendingLaunchPath = nil;
}
if (startupPath == nil && nsUrl != nullptr && nsUrl.isFileURL) {
NSString *nsString = nsUrl.path;
const char *string = nsString.UTF8String;
argv[argc++] = (char*)string;
startupPath = nsString;
}
if (startupPath.length > 0) {
// Keep cold-start argv behavior aligned with processFilePath().
std::string startupPathUtf8(startupPath.UTF8String);
Path gamePath(startupPathUtf8);
gStartupArgStorage = gamePath.ToString();
argv[argc++] = (char *)gStartupArgStorage.c_str();
NSLog(@"SceneDelegate: startup path passed to argv: %s", gStartupArgStorage.c_str());
}
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
@@ -96,21 +159,7 @@
}
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
for (UIOpenURLContext *context in URLContexts) {
NSLog(@"SceneDelegate: openURLContexts called with URL: %@", context.URL);
NSURL *url = context.URL;
if (![[url scheme] isEqualToString:@"ppsspp"]) {
NSLog(@"SceneDelegate: ignoring URL with unknown scheme: %@", url);
continue;
}
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
for (NSURLQueryItem *item in components.queryItems) {
if ([item.name isEqualToString:@"path"]) {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate processFilePath:item.value];
}
}
}
[self handleURLContextsWhileRunning:URLContexts];
}
- (void)sceneWillResignActive:(UIScene *)scene {
+4
View File
@@ -699,6 +699,10 @@ int main(int argc, char *argv[]) {
}
PROFILE_INIT();
#if PPSSPP_PLATFORM(IOS_APP_STORE) && defined(_DEBUG)
g_logManager.SetOutputsEnabled(LogOutput::Stdio);
#endif
@autoreleasepool {
return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), NSStringFromClass([AppDelegate class]));
}