Skip to content

Commit a1c152f

Browse files
committed
feat: Added urlscheme export functionality
1 parent ebdae45 commit a1c152f

3 files changed

Lines changed: 117 additions & 1 deletion

File tree

ui/drivers/cocoa/RetroArchPlaylistManager.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN
1818
@property (nonatomic, strong) NSString *fullPath;
1919
@property (nonatomic, strong, nullable) NSString *corePath;
2020
@property (nonatomic, strong, nullable) NSString *coreName;
21+
// Display name of the playlist/system this game belongs to
22+
@property (nonatomic, strong, nullable) NSString *system;
2123
@end
2224

2325
// Unified playlist management class
@@ -27,6 +29,13 @@ NS_ASSUME_NONNULL_BEGIN
2729
+ (NSArray<RetroArchPlaylistGame *> *)getAllGames;
2830
+ (nullable RetroArchPlaylistGame *)findGameByFilename:(NSString *)filename;
2931

32+
// Library sharing: serialize the whole library so another app can fetch it.
33+
// Returns a JSON array of game dictionaries.
34+
+ (nullable NSData *)exportAllGamesAsJSONData;
35+
// Same, encoded as a URL-safe base64 string (base64url, no padding) suitable
36+
// for embedding in a callback URL query parameter.
37+
+ (nullable NSString *)exportAllGamesAsBase64URLString;
38+
3039
// History and favorites support for App Intents suggestions
3140
+ (NSArray<RetroArchPlaylistGame *> *)getHistoryGames;
3241
+ (NSArray<RetroArchPlaylistGame *> *)getFavoriteGames;

ui/drivers/cocoa/RetroArchPlaylistManager.m

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ + (void)enumerateAllPlaylistEntries:(PlaylistEntryBlock _Nonnull)block
168168
game.title = [NSString stringWithUTF8String:entry->label];
169169
game.fullPath = [NSString stringWithUTF8String:entry->path];
170170

171+
/* System/playlist display name (strip trailing ".lpl") */
172+
if ([playlistName.pathExtension.lowercaseString isEqualToString:@"lpl"])
173+
game.system = playlistName.stringByDeletingPathExtension;
174+
else
175+
game.system = playlistName;
176+
171177
/* Extract filename from path */
172178
const char *filename = path_basename(entry->path);
173179
game.filename = [NSString stringWithUTF8String:filename];
@@ -211,6 +217,59 @@ + (nullable RetroArchPlaylistGame *)findGameByFilename:(NSString * _Nonnull)file
211217
return nil;
212218
}
213219

220+
+ (nullable NSData *)exportAllGamesAsJSONData
221+
{
222+
NSArray<RetroArchPlaylistGame *> *allGames = [self getAllGames];
223+
NSMutableArray<NSDictionary *> *serialized =
224+
[[NSMutableArray alloc] initWithCapacity:allGames.count];
225+
226+
for (RetroArchPlaylistGame *game in allGames) {
227+
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
228+
229+
// "titleId" is the same <filename> used in the retroarch://game/<filename> launch scheme
230+
dict[@"titleId"] = game.filename ?: @"";
231+
dict[@"titleName"] = game.title ?: @"";
232+
dict[@"filename"] = game.filename ?: @"";
233+
dict[@"gameId"] = game.gameId ?: @"";
234+
dict[@"developer"] = @"";
235+
dict[@"version"] = @"";
236+
if (game.system)
237+
dict[@"system"] = game.system;
238+
if (game.coreName)
239+
dict[@"coreName"] = game.coreName;
240+
241+
[serialized addObject:dict];
242+
}
243+
244+
NSError *error = nil;
245+
NSData *json = [NSJSONSerialization dataWithJSONObject:serialized
246+
options:0
247+
error:&error];
248+
if (!json) {
249+
RARCH_WARN("Failed to serialize game library: %s\n",
250+
[[error localizedDescription] UTF8String]);
251+
return nil;
252+
}
253+
254+
return json;
255+
}
256+
257+
+ (nullable NSString *)exportAllGamesAsBase64URLString
258+
{
259+
NSData *json = [self exportAllGamesAsJSONData];
260+
if (!json)
261+
return nil;
262+
263+
/* URL-safe base64 (base64url) without padding, matching the encoding other
264+
* front-ends use for their library callbacks. */
265+
NSString *encoded = [json base64EncodedStringWithOptions:0];
266+
encoded = [encoded stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
267+
encoded = [encoded stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
268+
encoded = [encoded stringByReplacingOccurrencesOfString:@"=" withString:@""];
269+
270+
return encoded;
271+
}
272+
214273
// Private helper method to extract games from a playlist
215274
+ (NSArray<RetroArchPlaylistGame *> *)getGamesFromPlaylist:(playlist_t *)playlist
216275
withIdPrefix:(NSString *)idPrefix

ui/drivers/ui_cocoatouch.m

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
#include "cocoa/cocoa_common.h"
3030
#include "cocoa/apple_platform.h"
31+
#ifdef HAVE_RETROARCH_PLAYLIST_MANAGER
32+
#import "cocoa/RetroArchPlaylistManager.h"
33+
#endif
3134

3235
#if defined(HAVE_COCOA_METAL)
3336
#include "../../gfx/common/metal_view.h"
@@ -1147,7 +1150,10 @@ -(BOOL)openRetroArchURL:(NSURL *)url
11471150
return YES; // Just bring app to foreground
11481151
}
11491152

1150-
// Handle game launch URL: retroarch://game/filename
1153+
// Handle game launch URL: retroarch://game/<filename>
1154+
// The <filename> matches the "titleId" exported by the library query below,
1155+
// so a frontend app can round-trip a fetched game straight back into a
1156+
// launch URL.
11511157
if ([url.host isEqualToString:@"game"])
11521158
{
11531159
NSString *filename = [url.path hasPrefix:@"/"] ? [url.path substringFromIndex:1] : url.path;
@@ -1158,6 +1164,48 @@ -(BOOL)openRetroArchURL:(NSURL *)url
11581164
}
11591165
}
11601166

1167+
#ifdef HAVE_RETROARCH_PLAYLIST_MANAGER
1168+
// Handle library query URL: retroarch://library?scheme=<callerScheme>
1169+
// Serializes the whole game library and hands it back to the requesting app
1170+
// by opening <callerScheme>://retroarch?games=<base64url-encoded-JSON>.
1171+
if ([url.host isEqualToString:@"library"])
1172+
{
1173+
NSURLComponents *comp = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
1174+
RARCH_AUTORELEASE(comp);
1175+
NSString *caller_scheme = nil;
1176+
for (NSURLQueryItem *q in comp.queryItems)
1177+
{
1178+
if ([q.name isEqualToString:@"scheme"])
1179+
caller_scheme = q.value;
1180+
}
1181+
1182+
if (!caller_scheme || caller_scheme.length == 0)
1183+
{
1184+
RARCH_WARN("Library query missing 'scheme' parameter: %s\n", [[url absoluteString] UTF8String]);
1185+
return NO;
1186+
}
1187+
1188+
NSString *encoded = [RetroArchPlaylistManager exportAllGamesAsBase64URLString];
1189+
if (!encoded)
1190+
{
1191+
RARCH_WARN("Failed to export game library for '%s'\n", [caller_scheme UTF8String]);
1192+
return NO;
1193+
}
1194+
1195+
NSString *reply = [NSString stringWithFormat:@"%@://retroarch?games=%@", caller_scheme, encoded];
1196+
NSURL *replyURL = [NSURL URLWithString:reply];
1197+
if (!replyURL)
1198+
{
1199+
RARCH_WARN("Could not build reply URL for scheme '%s'\n", [caller_scheme UTF8String]);
1200+
return NO;
1201+
}
1202+
1203+
RARCH_LOG("Returning game library to '%s'\n", [caller_scheme UTF8String]);
1204+
[[UIApplication sharedApplication] openURL:replyURL options:@{} completionHandler:nil];
1205+
return YES;
1206+
}
1207+
#endif
1208+
11611209
RARCH_LOG("Unknown RetroArch URL format: %s\n", [[url absoluteString] UTF8String]);
11621210
return NO;
11631211
}

0 commit comments

Comments
 (0)