Syncing with the App Store branch (iCloud support)

This commit is contained in:
Lior Halphon 2024-09-14 23:58:58 +03:00
parent 8cb94e7a8b
commit 1931c2830f
14 changed files with 218 additions and 152 deletions

View File

@ -231,6 +231,7 @@
self.navigationController.toolbar.disableCompactLayout = true; self.navigationController.toolbar.disableCompactLayout = true;
} }
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
{ {
[url startAccessingSecurityScopedResource]; [url startAccessingSecurityScopedResource];

View File

@ -218,7 +218,6 @@
if ([GBROMManager.sharedManager romFileForROM:_game.title]) { if ([GBROMManager.sharedManager romFileForROM:_game.title]) {
[GBROMManager sharedManager].currentROM = _game.title; [GBROMManager sharedManager].currentROM = _game.title;
[self.navigationController dismissViewControllerAnimated:true completion:nil]; [self.navigationController dismissViewControllerAnimated:true completion:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBROMChanged" object:nil];
} }
else { else {
UIActivityIndicatorViewStyle style = UIActivityIndicatorViewStyleWhite; UIActivityIndicatorViewStyle style = UIActivityIndicatorViewStyleWhite;
@ -237,9 +236,9 @@
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Could not download ROM" UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Could not download ROM"
message:@"Could not download this ROM from Homebrew Hub. Please try again later." message:@"Could not download this ROM from Homebrew Hub. Please try again later."
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" [alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:nil]]; handler:nil]];
[self presentViewController:alert animated:true completion:nil]; [self presentViewController:alert animated:true completion:nil];
self.navigationItem.rightBarButtonItem.customView = nil; self.navigationItem.rightBarButtonItem.customView = nil;
}); });

View File

@ -1,6 +1,6 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface GBLibraryViewController : UITabBarController @interface GBLibraryViewController : UITabBarController
@end @end

View File

@ -1,7 +1,9 @@
#import "GBLibraryViewController.h" #import "GBLibraryViewController.h"
#import "GBLoadROMTableViewController.h" #import "GBROMViewController.h"
#import "GBHubViewController.h" #import "GBHubViewController.h"
#import "GBViewController.h" #import "GBViewController.h"
#import "GBROMManager.h"
@implementation GBLibraryViewController @implementation GBLibraryViewController
@ -19,8 +21,9 @@
- (void)viewDidLoad - (void)viewDidLoad
{ {
[super viewDidLoad]; [super viewDidLoad];
self.viewControllers = @[ self.viewControllers = @[
[self.class wrapViewController:[[GBLoadROMTableViewController alloc] init]], [self.class wrapViewController:[[GBROMViewController alloc] init]],
[self.class wrapViewController:[[GBHubViewController alloc] init]], [self.class wrapViewController:[[GBHubViewController alloc] init]],
]; ];
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
@ -42,7 +45,7 @@
} }
} }
self.viewControllers[0].tabBarItem.image = [UIImage systemImageNamed:symbol] ?: [UIImage systemImageNamed:@"folder.fill"]; self.viewControllers[0].tabBarItem.image = [UIImage systemImageNamed:symbol] ?: [UIImage systemImageNamed:@"folder.fill"];
self.viewControllers[1].tabBarItem.image = [UIImage systemImageNamed:@"globe"]; self.viewControllers.lastObject.tabBarItem.image = [UIImage systemImageNamed:@"globe"];
} }
else { else {
self.viewControllers[0].tabBarItem.image = [UIImage imageNamed:@"FolderTemplate"]; self.viewControllers[0].tabBarItem.image = [UIImage imageNamed:@"FolderTemplate"];
@ -50,4 +53,5 @@
} }
} }
@end @end

View File

@ -1,5 +0,0 @@
#import <UIKit/UIKit.h>
@interface GBLoadROMTableViewController : UITableViewController
@end

View File

@ -10,6 +10,8 @@
@property (readonly) NSString *batterySaveFile; @property (readonly) NSString *batterySaveFile;
@property (readonly) NSString *autosaveStateFile; @property (readonly) NSString *autosaveStateFile;
@property (readonly) NSString *cheatsFile; @property (readonly) NSString *cheatsFile;
@property (readonly) NSString *localRoot;
- (NSString *)stateFile:(unsigned)index; - (NSString *)stateFile:(unsigned)index;
- (NSString *)romFileForROM:(NSString *)rom; - (NSString *)romFileForROM:(NSString *)rom;
@ -21,4 +23,5 @@
- (NSString *)renameROM:(NSString *)rom toName:(NSString *)newName; - (NSString *)renameROM:(NSString *)rom toName:(NSString *)newName;
- (NSString *)duplicateROM:(NSString *)rom; - (NSString *)duplicateROM:(NSString *)rom;
- (void)deleteROM:(NSString *)rom; - (void)deleteROM:(NSString *)rom;
@end @end

View File

@ -4,6 +4,8 @@
@implementation GBROMManager @implementation GBROMManager
{ {
NSString *_romFile; NSString *_romFile;
NSMutableDictionary<NSString *,NSString *> *_cloudNameToFile;
bool _doneInitializing;
} }
+ (instancetype)sharedManager + (instancetype)sharedManager
@ -21,6 +23,7 @@
self = [super init]; self = [super init];
if (!self) return nil; if (!self) return nil;
self.currentROM = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBLastROM"]; self.currentROM = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBLastROM"];
_doneInitializing = true;
return self; return self;
} }
@ -28,11 +31,16 @@
{ {
_romFile = nil; _romFile = nil;
_currentROM = currentROM; _currentROM = currentROM;
if (currentROM && !self.romFile) { bool foundROM = self.romFile;
if (currentROM && !foundROM) {
_currentROM = nil; _currentROM = nil;
} }
[[NSUserDefaults standardUserDefaults] setObject:_currentROM forKey:@"GBLastROM"]; [[NSUserDefaults standardUserDefaults] setObject:_currentROM forKey:@"GBLastROM"];
if (_doneInitializing) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBROMChanged" object:nil];
}
} }
- (NSString *)romFileForDirectory:(NSString *)romDirectory - (NSString *)romFileForDirectory:(NSString *)romDirectory
@ -47,13 +55,17 @@
return nil; return nil;
} }
- (NSString *)romDirectoryForROM:(NSString *)romFile
{
return [self.localRoot stringByAppendingPathComponent:romFile];
}
- (NSString *)romFile - (NSString *)romFile
{ {
if (_romFile) return _romFile; if (_romFile) return _romFile;
if (!_currentROM) return nil; if (!_currentROM) return nil;
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; return _romFile = [self romFileForDirectory:[self romDirectoryForROM:_currentROM]];
NSString *romDirectory = [root stringByAppendingPathComponent:_currentROM];
return _romFile = [self romFileForDirectory:romDirectory];
} }
- (NSString *)romFileForROM:(NSString *)rom - (NSString *)romFileForROM:(NSString *)rom
@ -63,9 +75,9 @@
if (rom == _currentROM) { if (rom == _currentROM) {
return self.romFile; return self.romFile;
} }
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
NSString *romDirectory = [root stringByAppendingPathComponent:rom];
return [self romFileForDirectory:romDirectory]; return [self romFileForDirectory:[self romDirectoryForROM:rom]];
} }
- (NSString *)auxilaryFileForROM:(NSString *)rom withExtension:(NSString *)extension - (NSString *)auxilaryFileForROM:(NSString *)rom withExtension:(NSString *)extension
@ -117,7 +129,7 @@
- (NSArray<NSString *> *)allROMs - (NSArray<NSString *> *)allROMs
{ {
NSMutableArray<NSString *> *ret = [NSMutableArray array]; NSMutableArray<NSString *> *ret = [NSMutableArray array];
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; NSString *root = self.localRoot;
for (NSString *romDirectory in [NSFileManager.defaultManager contentsOfDirectoryAtPath:root for (NSString *romDirectory in [NSFileManager.defaultManager contentsOfDirectoryAtPath:root
error:nil]) { error:nil]) {
if ([romDirectory hasPrefix:@"."] || [romDirectory isEqualToString:@"Inbox"]) continue; if ([romDirectory hasPrefix:@"."] || [romDirectory isEqualToString:@"Inbox"]) continue;
@ -130,7 +142,7 @@
- (NSString *)makeNameUnique:(NSString *)name - (NSString *)makeNameUnique:(NSString *)name
{ {
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; NSString *root = self.localRoot;
if (![[NSFileManager defaultManager] fileExistsAtPath:[root stringByAppendingPathComponent:name]]) return name; if (![[NSFileManager defaultManager] fileExistsAtPath:[root stringByAppendingPathComponent:name]]) return name;
unsigned i = 2; unsigned i = 2;
@ -150,27 +162,14 @@
NSString *friendlyName = [[romFile lastPathComponent] stringByDeletingPathExtension]; NSString *friendlyName = [[romFile lastPathComponent] stringByDeletingPathExtension];
friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""]; friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""];
friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
friendlyName = [self makeNameUnique:friendlyName];
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
if ([[NSFileManager defaultManager] fileExistsAtPath:[root stringByAppendingPathComponent:friendlyName]]) {
unsigned i = 2;
while (true) {
NSString *attempt = [friendlyName stringByAppendingFormat:@" %u", i];
if ([[NSFileManager defaultManager] fileExistsAtPath:[root stringByAppendingPathComponent:attempt]]) {
i++;
continue;
}
friendlyName = attempt;
break;
}
}
return [self importROM:romFile withName:friendlyName keepOriginal:keep]; return [self importROM:romFile withName:friendlyName keepOriginal:keep];
} }
- (NSString *)importROM:(NSString *)romFile withName:(NSString *)friendlyName keepOriginal:(bool)keep - (NSString *)importROM:(NSString *)romFile withName:(NSString *)friendlyName keepOriginal:(bool)keep
{ {
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; NSString *root = self.localRoot;
NSString *romFolder = [root stringByAppendingPathComponent:friendlyName]; NSString *romFolder = [root stringByAppendingPathComponent:friendlyName];
[[NSFileManager defaultManager] createDirectoryAtPath:romFolder [[NSFileManager defaultManager] createDirectoryAtPath:romFolder
withIntermediateDirectories:false withIntermediateDirectories:false
@ -205,9 +204,9 @@
{ {
newName = [self makeNameUnique:newName]; newName = [self makeNameUnique:newName];
if ([rom isEqualToString:_currentROM]) { if ([rom isEqualToString:_currentROM]) {
_currentROM = newName; self.currentROM = newName;
} }
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; NSString *root = self.localRoot;
[[NSFileManager defaultManager] moveItemAtPath:[root stringByAppendingPathComponent:rom] [[NSFileManager defaultManager] moveItemAtPath:[root stringByAppendingPathComponent:rom]
toPath:[root stringByAppendingPathComponent:newName] error:nil]; toPath:[root stringByAppendingPathComponent:newName] error:nil];
@ -224,9 +223,14 @@
- (void)deleteROM:(NSString *)rom - (void)deleteROM:(NSString *)rom
{ {
NSString *root = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; NSString *root = self.localRoot;
NSString *romDirectory = [root stringByAppendingPathComponent:rom]; NSString *romDirectory = [root stringByAppendingPathComponent:rom];
[[NSFileManager defaultManager] removeItemAtPath:romDirectory error:nil]; [[NSFileManager defaultManager] removeItemAtPath:romDirectory error:nil];
} }
- (NSString *)localRoot
{
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
}
@end @end

14
iOS/GBROMViewController.h Normal file
View File

@ -0,0 +1,14 @@
#import <UIKit/UIKit.h>
@interface GBROMViewController : UITableViewController<UIDocumentPickerDelegate>
/* For inheritance */
- (void)romSelectedAtIndex:(unsigned)index;
- (void)deleteROMAtIndex:(unsigned)index;
- (void)renameROM:(NSString *)oldName toName:(NSString *)newName;
- (void)duplicateROMAtIndex:(unsigned)index;
- (NSString *)rootPath;
/* To be used by subclasses */
- (UITableViewCell *)cellForROM:(NSString *)rom;
@end

View File

@ -1,13 +1,11 @@
#import "GBLoadROMTableViewController.h" #import "GBROMViewController.h"
#import "GBROMManager.h" #import "GBROMManager.h"
#import "GBViewController.h" #import "GBViewController.h"
#import "GBLibraryViewController.h"
#import <CoreServices/CoreServices.h> #import <CoreServices/CoreServices.h>
#import <objc/runtime.h> #import <objc/runtime.h>
@interface GBLoadROMTableViewController() <UIDocumentPickerDelegate> @implementation GBROMViewController
@end
@implementation GBLoadROMTableViewController
{ {
NSIndexPath *_renamingPath; NSIndexPath *_renamingPath;
} }
@ -36,19 +34,10 @@
return [GBROMManager sharedManager].allROMs.count; return [GBROMManager sharedManager].allROMs.count;
} }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath - (UITableViewCell *)cellForROM:(NSString *)rom
{ {
if (indexPath.section == 1) {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
switch (indexPath.item) {
case 0: cell.textLabel.text = @"Import ROM files"; break;
case 1: cell.textLabel.text = @"Show Library in Files"; break;
}
return cell;
}
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
NSString *rom = [GBROMManager sharedManager].allROMs[[indexPath indexAtPosition:1]]; cell.textLabel.text = rom.lastPathComponent;
cell.textLabel.text = rom;
cell.accessoryType = [rom isEqualToString:[GBROMManager sharedManager].currentROM]? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; cell.accessoryType = [rom isEqualToString:[GBROMManager sharedManager].currentROM]? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
NSString *pngPath = [[[GBROMManager sharedManager] autosaveStateFileForROM:rom] stringByAppendingPathExtension:@"png"]; NSString *pngPath = [[[GBROMManager sharedManager] autosaveStateFileForROM:rom] stringByAppendingPathExtension:@"png"];
@ -68,6 +57,20 @@
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
return cell; return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1) {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
switch (indexPath.item) {
case 0: cell.textLabel.text = @"Import ROM files"; break;
case 1: cell.textLabel.text = @"Show Library in Files"; break;
}
return cell;
}
return [self cellForROM:[GBROMManager sharedManager].allROMs[[indexPath indexAtPosition:1]]];
} }
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
@ -89,6 +92,12 @@
return @"You can also import ROM files by opening them in SameBoy using the Files app or a web browser, or by sending them over with AirDrop."; return @"You can also import ROM files by opening them in SameBoy using the Files app or a web browser, or by sending them over with AirDrop.";
} }
- (void)romSelectedAtIndex:(unsigned)index
{
[GBROMManager sharedManager].currentROM = [GBROMManager sharedManager].allROMs[index];
[self.presentingViewController dismissViewControllerAnimated:true completion:nil];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{ {
if (indexPath.section == 1) { if (indexPath.section == 1) {
@ -110,9 +119,9 @@
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"File Association Conflict" UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"File Association Conflict"
message:@"Due to a limitation in iOS, the file picker will allow you to select files not supported by SameBoy. SameBoy will only import GB, GBC and ISX files.\n\nIf you have a multi-system emulator installed, updating it could fix this problem." message:@"Due to a limitation in iOS, the file picker will allow you to select files not supported by SameBoy. SameBoy will only import GB, GBC and ISX files.\n\nIf you have a multi-system emulator installed, updating it could fix this problem."
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" [alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBShownUTIWarning"]; [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBShownUTIWarning"];
[self tableView:tableView didSelectRowAtIndexPath:indexPath]; [self tableView:tableView didSelectRowAtIndexPath:indexPath];
}]]; }]];
@ -141,22 +150,22 @@
return; return;
} }
case 1: { case 1: {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"shareddocuments://%@", NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject]] NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"shareddocuments://%@",
[self.rootPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]]];
[[UIApplication sharedApplication] openURL:url
options:nil options:nil
completionHandler:nil]; completionHandler:nil];
return; return;
} }
} }
} }
[GBROMManager sharedManager].currentROM = [GBROMManager sharedManager].allROMs[[indexPath indexAtPosition:1]]; [self romSelectedAtIndex:indexPath.row];
[self.presentingViewController dismissViewControllerAnimated:true completion:^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBROMChanged" object:nil];
}];
} }
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls
{ {
[(GBViewController *)[UIApplication sharedApplication] handleOpenURLs:urls openInPlace:false]; [(GBViewController *)[UIApplication sharedApplication].delegate handleOpenURLs:urls
openInPlace:false];
} }
- (UIModalPresentationStyle)modalPresentationStyle - (UIModalPresentationStyle)modalPresentationStyle
@ -164,28 +173,34 @@
return UIModalPresentationOverFullScreen; return UIModalPresentationOverFullScreen;
} }
- (void)deleteROMAtIndex:(unsigned)index
{
NSString *rom = [GBROMManager sharedManager].allROMs[index];
[[GBROMManager sharedManager] deleteROM:rom];
[self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
if ([[GBROMManager sharedManager].currentROM isEqualToString:rom]) {
[GBROMManager sharedManager].currentROM = nil;
}
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{ {
if (indexPath.section == 1) return; if (indexPath.section == 1) return;
if (editingStyle != UITableViewCellEditingStyleDelete) return; if (editingStyle != UITableViewCellEditingStyleDelete) return;
NSString *rom = [GBROMManager sharedManager].allROMs[[indexPath indexAtPosition:1]]; NSString *rom = [self.tableView cellForRowAtIndexPath:indexPath].textLabel.text;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Delete ROM “%@”?", rom] UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Delete “%@”?", rom]
message: @"Save data for this ROM will also be deleted." message: @"Save data for this ROM will also be deleted."
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Delete" [alert addAction:[UIAlertAction actionWithTitle:@"Delete"
style:UIAlertActionStyleDestructive style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
[[GBROMManager sharedManager] deleteROM:rom]; [self deleteROMAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
if ([[GBROMManager sharedManager].currentROM isEqualToString:rom]) {
[GBROMManager sharedManager].currentROM = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBROMChanged" object:nil];
}
}]]; }]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" [alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:nil]]; handler:nil]];
[self presentViewController:alert animated:true completion:nil]; [self presentViewController:alert animated:true completion:nil];
} }
@ -197,7 +212,7 @@
UITextField *field = [[UITextField alloc] initWithFrame:cell.textLabel.frame]; UITextField *field = [[UITextField alloc] initWithFrame:cell.textLabel.frame];
field.font = cell.textLabel.font; field.font = cell.textLabel.font;
field.text = cell.textLabel.text; field.text = cell.textLabel.text;
cell.textLabel.text = @""; cell.textLabel.textColor = [UIColor clearColor];
[[cell.textLabel superview] addSubview:field]; [[cell.textLabel superview] addSubview:field];
[field becomeFirstResponder]; [field becomeFirstResponder];
[field selectAll:nil]; [field selectAll:nil];
@ -205,29 +220,36 @@
[field addTarget:self action:@selector(doneRename:) forControlEvents:UIControlEventEditingDidEnd | UIControlEventEditingDidEndOnExit]; [field addTarget:self action:@selector(doneRename:) forControlEvents:UIControlEventEditingDidEnd | UIControlEventEditingDidEndOnExit];
} }
- (void)renameROM:(NSString *)oldName toName:(NSString *)newName
{
[[GBROMManager sharedManager] renameROM:oldName toName:newName];
[self.tableView reloadData];
}
- (void)doneRename:(UITextField *)sender - (void)doneRename:(UITextField *)sender
{ {
if (!_renamingPath) return; if (!_renamingPath) return;
NSString *newName = sender.text; NSString *newName = sender.text;
NSString *oldName = [GBROMManager sharedManager].allROMs[[_renamingPath indexAtPosition:1]]; NSString *oldName = [self.tableView cellForRowAtIndexPath:_renamingPath].textLabel.text;
_renamingPath = nil; _renamingPath = nil;
if ([newName isEqualToString:oldName]) { if ([newName isEqualToString:oldName]) {
[self.tableView reloadData]; [self.tableView reloadData];
return; return;
} }
if ([newName containsString:@"/"]) { if ([newName containsString:@"/"]) {
[self.tableView reloadData]; [self.tableView reloadData];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"You can't use a name that contains “/”. Please choose another name." UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"You can't use a name that contains “/”. Please choose another name."
message:nil message:nil
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" [alert addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:nil]]; handler:nil]];
[self presentViewController:alert animated:true completion:nil]; [self presentViewController:alert animated:true completion:nil];
return; return;
} }
[[GBROMManager sharedManager] renameROM:oldName toName:newName]; [self renameROM:oldName toName:newName];
[self.tableView reloadData];
_renamingPath = nil; _renamingPath = nil;
} }
@ -236,6 +258,13 @@
return indexPath.section == 0; return indexPath.section == 0;
} }
- (void)duplicateROMAtIndex:(unsigned)index
{
[[GBROMManager sharedManager] duplicateROM:[GBROMManager sharedManager].allROMs[index]];
[self.tableView reloadData];
}
// Leave these ROM management to iOS 13.0 and up for now // Leave these ROM management to iOS 13.0 and up for now
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
@ -266,8 +295,7 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
image:[UIImage systemImageNamed:@"plus.square.on.square"] image:[UIImage systemImageNamed:@"plus.square.on.square"]
identifier:nil identifier:nil
handler:^(__kindof UIAction *action) { handler:^(__kindof UIAction *action) {
[[GBROMManager sharedManager] duplicateROM:[GBROMManager sharedManager].allROMs[[indexPath indexAtPosition:1]]]; [self duplicateROMAtIndex:indexPath.row];
[self.tableView reloadData];
}], }],
deleteAction, deleteAction,
]]; ]];
@ -286,4 +314,9 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
[self.tableView reloadData]; [self.tableView reloadData];
} }
- (NSString *)rootPath
{
return [GBROMManager sharedManager].localRoot;
}
@end @end

View File

@ -696,9 +696,9 @@ static NSString *LocalizedNameForElement(GCControllerElement *element, GBControl
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"No Controllers Connected" UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"No Controllers Connected"
message:@"There are no connected game controllers to configure" message:@"There are no connected game controllers to configure"
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" [alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:nil]]; handler:nil]];
[self presentViewController:alert animated:true completion:nil]; [self presentViewController:alert animated:true completion:nil];
return false; return false;

View File

@ -35,6 +35,7 @@
} }
} }
- (void)viewDidLoad - (void)viewDidLoad
{ {
[super viewDidLoad]; [super viewDidLoad];

View File

@ -57,16 +57,16 @@
// No supporter-only themes outside the App Store release // No supporter-only themes outside the App Store release
} }
else { else {
[alert addAction:[UIAlertAction actionWithTitle:@"Apply Theme" [alert addAction:[UIAlertAction actionWithTitle:@"Apply Theme"
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
[[NSUserDefaults standardUserDefaults] setObject:_verticalLayout.theme.name forKey:@"GBInterfaceTheme"]; [[NSUserDefaults standardUserDefaults] setObject:_verticalLayout.theme.name forKey:@"GBInterfaceTheme"];
[[self presentingViewController] dismissViewControllerAnimated:true completion:nil]; [[self presentingViewController] dismissViewControllerAnimated:true completion:nil];
}]]; }]];
} }
[alert addAction:[UIAlertAction actionWithTitle:@"Exit Preview" [alert addAction:[UIAlertAction actionWithTitle:@"Exit Preview"
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
[[self presentingViewController] dismissViewControllerAnimated:true completion:nil]; [[self presentingViewController] dismissViewControllerAnimated:true completion:nil];
}]]; }]];
[self presentViewController:alert animated:true completion:^{ [self presentViewController:alert animated:true completion:^{

View File

@ -27,7 +27,8 @@ typedef enum {
- (void)emptyPrinterFeed; - (void)emptyPrinterFeed;
- (void)saveStateToFile:(NSString *)file; - (void)saveStateToFile:(NSString *)file;
- (bool)loadStateFromFile:(NSString *)file; - (bool)loadStateFromFile:(NSString *)file;
- (bool)handleOpenURLs:(NSArray <NSURL *> *)urls openInPlace:(bool)inPlace; - (bool)handleOpenURLs:(NSArray <NSURL *> *)urls
openInPlace:(bool)inPlace;
- (void)dismissViewController; - (void)dismissViewController;
@property (nonatomic) GBRunMode runMode; @property (nonatomic) GBRunMode runMode;
@end @end

View File

@ -32,6 +32,7 @@
bool _rewindOver; bool _rewindOver;
bool _romLoaded; bool _romLoaded;
bool _swappingROM; bool _swappingROM;
bool _loadingState;
UIInterfaceOrientation _orientation; UIInterfaceOrientation _orientation;
GBHorizontalLayout *_horizontalLayout; GBHorizontalLayout *_horizontalLayout;
@ -265,12 +266,12 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
_audioLock = [[NSCondition alloc] init]; _audioLock = [[NSCondition alloc] init];
[self loadROM];
[[NSNotificationCenter defaultCenter] addObserverForName:@"GBROMChanged" [[NSNotificationCenter defaultCenter] addObserverForName:@"GBROMChanged"
object:nil object:nil
queue:nil queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self loadROM]; _swappingROM = true;
[self stop];
[self start]; [self start];
}]; }];
@ -628,9 +629,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"SameBoy is not properly signed and might not be able to open ROMs" UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"SameBoy is not properly signed and might not be able to open ROMs"
message:[NSString stringWithFormat:@"The bundle identifier in the Info.plist file (“%@”) does not match the one in the entitlements (“%@”)", plistIdentifier, entIdentifier] message:[NSString stringWithFormat:@"The bundle identifier in the Info.plist file (“%@”) does not match the one in the entitlements (“%@”)", plistIdentifier, entIdentifier]
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" [alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:nil]]; handler:nil]];
[self presentViewController:alert animated:true completion:nil]; [self presentViewController:alert animated:true completion:nil];
} }
} }
@ -648,7 +649,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (bool)loadStateFromFile:(NSString *)file - (bool)loadStateFromFile:(NSString *)file
{ {
[self stop]; _loadingState = true;
GB_model_t model; GB_model_t model;
if (!GB_get_state_model(file.fileSystemRepresentation, &model)) { if (!GB_get_state_model(file.fileSystemRepresentation, &model)) {
if (GB_get_model(&_gb) != model) { if (GB_get_model(&_gb) != model) {
@ -656,53 +657,56 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
} }
return GB_load_state(&_gb, file.fileSystemRepresentation) == 0; return GB_load_state(&_gb, file.fileSystemRepresentation) == 0;
} }
return false; return false;
} }
- (void)loadROM - (void)loadROM
{ {
_swappingROM = true;
[self stop];
GBROMManager *romManager = [GBROMManager sharedManager]; GBROMManager *romManager = [GBROMManager sharedManager];
if (romManager.romFile) { if (romManager.romFile) {
// Todo: display errors and warnings if (!_loadingState) {
if ([romManager.romFile.pathExtension.lowercaseString isEqualToString:@"isx"]) { // Todo: display errors and warnings
_romLoaded = GB_load_isx(&_gb, romManager.romFile.fileSystemRepresentation) == 0; if ([romManager.romFile.pathExtension.lowercaseString isEqualToString:@"isx"]) {
} _romLoaded = GB_load_isx(&_gb, romManager.romFile.fileSystemRepresentation) == 0;
else { }
_romLoaded = GB_load_rom(&_gb, romManager.romFile.fileSystemRepresentation) == 0; else {
} _romLoaded = GB_load_rom(&_gb, romManager.romFile.fileSystemRepresentation) == 0;
if (_romLoaded) { }
GB_reset(&_gb); if (_romLoaded) {
GB_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation); GB_reset(&_gb);
GB_remove_all_cheats(&_gb); GB_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation);
GB_load_cheats(&_gb, [GBROMManager sharedManager].cheatsFile.UTF8String, false); GB_remove_all_cheats(&_gb);
if (![self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]) { GB_load_cheats(&_gb, [GBROMManager sharedManager].cheatsFile.UTF8String, false);
// Newly played ROM, pick the best model if (![self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]) {
uint8_t *rom = GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, NULL, NULL); // Newly played ROM, pick the best model
uint8_t *rom = GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, NULL, NULL);
if ((rom[0x143] & 0x80)) {
if (!GB_is_cgb(&_gb)) { if ((rom[0x143] & 0x80)) {
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]); if (!GB_is_cgb(&_gb)) {
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]);
}
}
else if ((rom[0x146] == 3) && !GB_is_sgb(&_gb)) {
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]);
} }
} }
else if ((rom[0x146] == 3) && !GB_is_sgb(&_gb)) {
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]);
}
} }
GB_rewind_reset(&_gb);
} }
GB_rewind_reset(&_gb);
} }
else { else {
_romLoaded = false; _romLoaded = false;
} }
_gbView.hidden = !_romLoaded; dispatch_async(dispatch_get_main_queue(), ^{
_gbView.hidden = !_romLoaded;
});
_swappingROM = false; _swappingROM = false;
_loadingState = false;
} }
- (void)applicationDidBecomeActive:(UIApplication *)application - (void)applicationDidBecomeActive:(UIApplication *)application
{ {
if (self.presentedViewController) return;
[self start]; [self start];
} }
@ -758,9 +762,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"SameBoy is not a Game Boy Advance Emulator" UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"SameBoy is not a Game Boy Advance Emulator"
message:@"SameBoy cannot play GBA games. Changing the model to Game Boy Advance lets you play Game Boy games as if on a Game Boy Advance in Game Boy Color mode." message:@"SameBoy cannot play GBA games. Changing the model to Game Boy Advance lets you play Game Boy games as if on a Game Boy Advance in Game Boy Color mode."
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" [alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
[self start]; [self start];
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBShownGBAWarning"]; [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBShownGBAWarning"];
}]]; }]];
@ -836,9 +840,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
if (completion) { if (completion) {
completion(); completion();
} }
if (!self.presentedViewController) { dispatch_async(dispatch_get_main_queue(), ^{
[self start]; [self start];
} });
}]; }];
} }
@ -846,9 +850,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{ {
/* Hack. Some view controllers dismiss without calling the method above. */ /* Hack. Some view controllers dismiss without calling the method above. */
[super setNeedsUpdateOfSupportedInterfaceOrientations]; [super setNeedsUpdateOfSupportedInterfaceOrientations];
if (!self.presentedViewController) { dispatch_async(dispatch_get_main_queue(), ^{
[self start]; [self start];
} });
} }
- (void)dismissViewController - (void)dismissViewController
@ -1047,6 +1051,12 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)run - (void)run
{ {
[self loadROM];
if (!_romLoaded) {
_running = false;
_stopping = false;
return;
}
[self preRun]; [self preRun];
while (_running) { while (_running) {
if (_rewind) { if (_rewind) {
@ -1168,8 +1178,8 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
- (void)start - (void)start
{ {
if (!_romLoaded) return;
if (_running) return; if (_running) return;
if (self.presentedViewController) return;
_running = true; _running = true;
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
} }
@ -1289,7 +1299,8 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
GB_set_palette(&_gb, &_palette); GB_set_palette(&_gb, &_palette);
} }
- (bool)handleOpenURLs:(NSArray <NSURL *> *)urls openInPlace:(bool)inPlace - (bool)handleOpenURLs:(NSArray <NSURL *> *)urls
openInPlace:(bool)inPlace
{ {
NSMutableArray<NSURL *> *validURLs = [NSMutableArray array]; NSMutableArray<NSURL *> *validURLs = [NSMutableArray array];
NSMutableArray<NSString *> *skippedBasenames = [NSMutableArray array]; NSMutableArray<NSString *> *skippedBasenames = [NSMutableArray array];
@ -1335,10 +1346,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
message:[NSString stringWithFormat:@"Could not find any Game Boy ROM files in the following archives:\n%@", message:[NSString stringWithFormat:@"Could not find any Game Boy ROM files in the following archives:\n%@",
[unusedZips componentsJoinedByString:@"\n"]] [unusedZips componentsJoinedByString:@"\n"]]
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" [alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:nil]]; handler:nil]];
[self stop];
[self presentViewController:alert animated:true completion:nil]; [self presentViewController:alert animated:true completion:nil];
} }
@ -1347,15 +1357,15 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
message:[NSString stringWithFormat:@"Could not import the following files because they're not supported:\n%@", message:[NSString stringWithFormat:@"Could not import the following files because they're not supported:\n%@",
[skippedBasenames componentsJoinedByString:@"\n"]] [skippedBasenames componentsJoinedByString:@"\n"]]
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close" [alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
[[NSUserDefaults standardUserDefaults] setBool:false forKey:@"GBShownUTIWarning"]; // Somebody might need a reminder [[NSUserDefaults standardUserDefaults] setBool:false forKey:@"GBShownUTIWarning"]; // Somebody might need a reminder
}]]; }]];
[self stop];
[self presentViewController:alert animated:true completion:nil]; [self presentViewController:alert animated:true completion:nil];
} }
if (validURLs.count == 1 && urls.count == 1) { if (validURLs.count == 1 && urls.count == 1) {
NSURL *url = validURLs.firstObject; NSURL *url = validURLs.firstObject;
NSString *potentialROM = [[url.path stringByDeletingLastPathComponent] lastPathComponent]; NSString *potentialROM = [[url.path stringByDeletingLastPathComponent] lastPathComponent];
@ -1369,7 +1379,6 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
keepOriginal:![url.path hasPrefix:tempDir] && !inPlace]; keepOriginal:![url.path hasPrefix:tempDir] && !inPlace];
[url stopAccessingSecurityScopedResource]; [url stopAccessingSecurityScopedResource];
} }
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBROMChanged" object:nil];
return true; return true;
} }
for (NSURL *url in validURLs) { for (NSURL *url in validURLs) {
@ -1383,7 +1392,6 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
keepOriginal:![url.path hasPrefix:tempDir] && !inPlace]; keepOriginal:![url.path hasPrefix:tempDir] && !inPlace];
[url stopAccessingSecurityScopedResource]; [url stopAccessingSecurityScopedResource];
} }
[self stop];
[self openLibrary]; [self openLibrary];
return validURLs.count; return validURLs.count;
@ -1391,15 +1399,18 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{ {
if (self.presentedViewController && ![self.presentedViewController isKindOfClass:[UIAlertController class]]) {
[self dismissViewController];
}
NSString *potentialROM = [[url.path stringByDeletingLastPathComponent] lastPathComponent]; NSString *potentialROM = [[url.path stringByDeletingLastPathComponent] lastPathComponent];
if ([[[GBROMManager sharedManager] romFileForROM:potentialROM].stringByStandardizingPath isEqualToString:url.path.stringByStandardizingPath]) { if ([[[GBROMManager sharedManager] romFileForROM:potentialROM].stringByStandardizingPath isEqualToString:url.path.stringByStandardizingPath]) {
[self stop]; [self stop];
[GBROMManager sharedManager].currentROM = potentialROM; [GBROMManager sharedManager].currentROM = potentialROM;
[self loadROM];
[self start]; [self start];
return [GBROMManager sharedManager].currentROM != nil; return [GBROMManager sharedManager].currentROM != nil;
} }
return [self handleOpenURLs:@[url] openInPlace:[options[UIApplicationOpenURLOptionsOpenInPlaceKey] boolValue]]; return [self handleOpenURLs:@[url]
openInPlace:[options[UIApplicationOpenURLOptionsOpenInPlaceKey] boolValue]];
} }
- (void)setRunMode:(GBRunMode)runMode ignoreDynamicSpeed:(bool)ignoreDynamicSpeed - (void)setRunMode:(GBRunMode)runMode ignoreDynamicSpeed:(bool)ignoreDynamicSpeed