diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 4b4d429..1067ada 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1166,7 +1166,7 @@ static bool is_path_writeable(const char *path) } } GB_load_battery(&_gb, self.savPath.UTF8String); - GB_load_cheats(&_gb, self.chtPath.UTF8String); + GB_load_cheats(&_gb, self.chtPath.UTF8String, true); dispatch_async(dispatch_get_main_queue(), ^{ [self.cheatWindowController cheatsUpdated]; }); diff --git a/Cocoa/NSToolbarItem+NoOverflow.m b/Cocoa/NSToolbarItem+NoOverflow.m index d5864bd..5b9c53c 100644 --- a/Cocoa/NSToolbarItem+NoOverflow.m +++ b/Cocoa/NSToolbarItem+NoOverflow.m @@ -6,7 +6,7 @@ static id nop(id self, SEL _cmd) return nil; } -static double blah(id self, SEL _cmd) +static double minSize(id self, SEL _cmd) { return 80.0; } @@ -18,7 +18,7 @@ static double blah(id self, SEL _cmd) // Prevent collapsing toolbar items into menu items, they don't work in that form method_setImplementation(class_getInstanceMethod(self, @selector(menuFormRepresentation)), (IMP)nop); // Prevent over-agressive collapsing of the Printer Feed menu - method_setImplementation(class_getInstanceMethod(NSClassFromString(@"NSToolbarTitleView"), @selector(minSize)), (IMP)blah); + method_setImplementation(class_getInstanceMethod(NSClassFromString(@"NSToolbarTitleView"), @selector(minSize)), (IMP)minSize); } @end diff --git a/Core/cheats.c b/Core/cheats.c index 563ccef..b3bc2e7 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -141,6 +141,13 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat) free((void *)cheat); } +void GB_remove_all_cheats(GB_gameboy_t *gb) +{ + while (gb->cheats) { + GB_remove_cheat(gb, gb->cheats[0]); + } +} + const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled) { GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb) @@ -261,13 +268,13 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des #define CHEAT_MAGIC 'SBCh' -void GB_load_cheats(GB_gameboy_t *gb, const char *path) +int GB_load_cheats(GB_gameboy_t *gb, const char *path, bool replace_existing) { GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb) FILE *f = fopen(path, "rb"); if (!f) { - return; + return errno; } uint32_t magic = 0; @@ -276,17 +283,17 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path) fread(&struct_size, sizeof(struct_size), 1, f); if (magic != LE32(CHEAT_MAGIC) && magic != BE32(CHEAT_MAGIC)) { GB_log(gb, "The file is not a SameBoy cheat database"); - return; + return -1; } if (struct_size != sizeof(GB_cheat_t)) { GB_log(gb, "This cheat database is not compatible with this version of SameBoy"); - return; + return -1; } // Remove all cheats first - while (gb->cheats) { - GB_remove_cheat(gb, gb->cheats[0]); + if (replace_existing) { + GB_remove_all_cheats(gb); } GB_cheat_t cheat; @@ -299,7 +306,7 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path) GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled); } - return; + return 0; } int GB_save_cheats(GB_gameboy_t *gb, const char *path) diff --git a/Core/cheats.h b/Core/cheats.h index 321d858..976f4c8 100644 --- a/Core/cheats.h +++ b/Core/cheats.h @@ -11,9 +11,10 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat, const char *desc const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled); const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size); void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat); +void GB_remove_all_cheats(GB_gameboy_t *gb); bool GB_cheats_enabled(GB_gameboy_t *gb); void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled); -void GB_load_cheats(GB_gameboy_t *gb, const char *path); +int GB_load_cheats(GB_gameboy_t *gb, const char *path, bool replace_existing); int GB_save_cheats(GB_gameboy_t *gb, const char *path); #ifdef GB_INTERNAL diff --git a/Core/gb.c b/Core/gb.c index ef2a8b5..050739b 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -226,9 +226,7 @@ void GB_free(GB_gameboy_t *gb) #endif GB_rewind_reset(gb); #ifndef GB_DISABLE_CHEATS - while (gb->cheats) { - GB_remove_cheat(gb, gb->cheats[0]); - } + GB_remove_all_cheats(gb); #endif #ifndef GB_DISABLE_CHEAT_SEARCH GB_cheat_search_reset(gb); diff --git a/SDL/main.c b/SDL/main.c index af318b3..5ec2d33 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -923,7 +923,9 @@ restart:; char cheat_path[path_length + 5]; replace_extension(filename, path_length, cheat_path, ".cht"); - GB_load_cheats(&gb, cheat_path); + // Remove all cheats before loading, so they're cleared even if loading fails. + GB_remove_all_cheats(&gb); + GB_load_cheats(&gb, cheat_path, false); end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning", previous); diff --git a/iOS/CheatsTemplate@2x.png b/iOS/CheatsTemplate@2x.png new file mode 100644 index 0000000..b5d773f Binary files /dev/null and b/iOS/CheatsTemplate@2x.png differ diff --git a/iOS/CheatsTemplate@3x.png b/iOS/CheatsTemplate@3x.png new file mode 100644 index 0000000..b7fb3be Binary files /dev/null and b/iOS/CheatsTemplate@3x.png differ diff --git a/iOS/GBCheatsController.h b/iOS/GBCheatsController.h new file mode 100644 index 0000000..8117966 --- /dev/null +++ b/iOS/GBCheatsController.h @@ -0,0 +1,7 @@ +#import +#import + +@interface GBCheatsController : UITableViewController +- (instancetype)initWithGameBoy:(GB_gameboy_t *)gb; +@end + diff --git a/iOS/GBCheatsController.m b/iOS/GBCheatsController.m new file mode 100644 index 0000000..98c7c6f --- /dev/null +++ b/iOS/GBCheatsController.m @@ -0,0 +1,350 @@ +#import "GBCheatsController.h" +#import "GBROMManager.h" +#import "UIToolbar+disableCompact.h" +#import +#import + +@interface GBCheatsController() +@end + +@implementation GBCheatsController +{ + GB_gameboy_t *_gb; + NSIndexPath *_renamingPath; + __weak UITextField *_editingField; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 2; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + if (section == 0) return 1; + size_t count; + GB_get_cheats(_gb, &count); + self.toolbarItems.firstObject.enabled = count; + return count; +} + + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil]; + + UISwitch *button = [[UISwitch alloc] init]; + cell.accessoryView = button; + const GB_cheat_t *cheat = NULL; + if (indexPath.section == 0) { + button.on = GB_cheats_enabled(_gb); + cell.textLabel.text = @"Enable Cheats"; + } + else { + cheat = GB_get_cheats(_gb, NULL)[indexPath.row]; + button.on = cheat->enabled; + cell.textLabel.text = @(cheat->description) ?: @"Unnamed Cheat"; + button.enabled = GB_cheats_enabled(_gb); + } + + id block = ^(){ + if (!cheat) { + GB_set_cheats_enabled(_gb, button.on); + [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone]; + } + else { + GB_update_cheat(_gb, cheat, cheat->description, + cheat->address, cheat->bank, + cheat->value, cheat->old_value, cheat->use_old_value, + button.on); + } + }; + objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN); + + [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + return cell; +} + +- (void)addCheat +{ + [self setEditing:false animated:true]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle: @"Add Cheat" + message: @"Add a GameShark or Game Genie cheat code" + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = @"Description"; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + }]; + [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = @"Cheat Code"; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + textField.keyboardType = UIKeyboardTypeASCIICapable; + }]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Add" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + size_t index = [self tableView:self.tableView numberOfRowsInSection:1]; + NSString *name = alertController.textFields[0].text; + if (GB_import_cheat(_gb, alertController.textFields[1].text.UTF8String, name.length? name.UTF8String : "Unnamed Cheat", true)) { + [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:1]] withRowAnimation:UITableViewRowAnimationAutomatic]; + } + else { + alertController.title = @"Invalid cheat code entered"; + [self presentViewController:alertController animated:YES completion:nil]; + } + }]]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [self presentViewController:alertController animated:YES completion:nil]; +} + ++ (UIBarButtonItem *)buttonWithLabel:(NSString *)label + imageWithName:(NSString *)imageName + target:(id)target + action:(SEL)action +{ + if (@available(iOS 13.0, *)) { + UIImage *image = [UIImage systemImageNamed:imageName + withConfiguration:[UIImageSymbolConfiguration configurationWithScale:UIImageSymbolScaleLarge]]; + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + [button setImage:image forState:UIControlStateNormal]; + button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside]; + if (label) { + [button setTitle:label forState:UIControlStateNormal]; + [button setTitleColor:button.tintColor forState:UIControlStateNormal]; + button.titleEdgeInsets = UIEdgeInsetsMake(0, 4, 0, 0); + button.contentEdgeInsets = UIEdgeInsetsMake(0, 12, 0, 0); + } + [button sizeToFit]; + CGRect frame = button.frame; + frame.size.width = ceil(frame.size.width + (label? 4 : 0)); + frame.size.height = 28; + button.frame = frame; + return [[UIBarButtonItem alloc] initWithCustomView:button]; + } + return [[UIBarButtonItem alloc] initWithTitle:label style:UIBarButtonItemStylePlain target:target action:action]; +} + +- (void)importCheats +{ + [self setEditing:false animated:true]; + NSString *chtUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"cht", NULL); + + + UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[chtUTI] + inMode:UIDocumentPickerModeImport]; + if (@available(iOS 13.0, *)) { + picker.shouldShowFileExtensions = true; + } + picker.delegate = self; + [self presentViewController:picker animated:true completion:nil]; + + return; + +} + +- (void)exportCheats +{ + [self setEditing:false animated:true]; + NSString *cheatsFile = [[GBROMManager sharedManager] cheatsFile]; + GB_save_cheats(_gb, cheatsFile.UTF8String); + NSURL *url = [NSURL fileURLWithPath:cheatsFile]; + UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url] + applicationActivities:nil]; + + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { + controller.popoverPresentationController.barButtonItem = self.toolbarItems.firstObject; + } + + [self presentViewController:controller + animated:true + completion:nil]; + +} + +- (instancetype)initWithGameBoy:(GB_gameboy_t *)gb +{ + UITableViewStyle style = UITableViewStyleGrouped; + if (@available(iOS 13.0, *)) { + style = UITableViewStyleInsetGrouped; + } + self = [super initWithStyle:style]; + self.tableView.allowsSelectionDuringEditing = true; + self.navigationItem.rightBarButtonItem = self.editButtonItem; + + bool hasSFSymbols = false; + if (@available(iOS 13.0, *)) { + hasSFSymbols = true; + } + + self.toolbarItems = @[ + hasSFSymbols? + [self.class buttonWithLabel:nil + imageWithName:@"square.and.arrow.up" + target:self + action:@selector(exportCheats)] : + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction + target:self + action:@selector(exportCheats)], + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace + target:nil + action:NULL], + [self.class buttonWithLabel:@"Import" + imageWithName:@"square.and.arrow.down" + target:self + action:@selector(importCheats)], + [self.class buttonWithLabel:@"Add" + imageWithName:@"plus" + target:self + action:@selector(addCheat)], + + ]; + + _gb = gb; + return self; +} + +- (NSString *)title +{ + return @"Cheats"; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +{ + return indexPath.section == 1; +} + +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section != 1) return; + if (editingStyle != UITableViewCellEditingStyleDelete) return; + + const GB_cheat_t *cheat = GB_get_cheats(_gb, NULL)[indexPath.row]; + GB_remove_cheat(_gb, cheat); + [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [self.navigationController setToolbarHidden:false animated:false]; + self.navigationController.toolbar.disableCompactLayout = true; +} + +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url +{ + [url startAccessingSecurityScopedResource]; + NSString *tempDir = NSTemporaryDirectory(); + NSString *newPath = [tempDir stringByAppendingPathComponent:@"import.cht"]; + [[NSFileManager defaultManager] copyItemAtPath:url.path toPath:newPath error:nil]; + [url stopAccessingSecurityScopedResource]; + unsigned count = [self tableView:self.tableView numberOfRowsInSection:1]; + + void (^load)(bool) = ^(bool replace) { + if (GB_load_cheats(_gb, newPath.UTF8String, replace)) { + [[NSFileManager defaultManager] removeItemAtPath:newPath error:nil]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Import Failed" + message:@"The imported cheats file is invalid." + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]]; + [self presentViewController:alertController animated:true completion:nil]; + return; + } + + [[NSFileManager defaultManager] removeItemAtPath:newPath error:nil]; + unsigned newCount = [self tableView:self.tableView numberOfRowsInSection:1]; + if (!replace) { + NSMutableArray *paths = [NSMutableArray arrayWithCapacity:newCount - count]; + for (unsigned i = count; i < newCount; i++) { + [paths addObject:[NSIndexPath indexPathForRow:i inSection:1]]; + } + if (paths.count) { + [self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic]; + } + } + else { + NSMutableArray *paths = [NSMutableArray arrayWithCapacity:abs((signed)newCount - (signed)count)]; + for (unsigned i = MIN(newCount, count); i < count || i < newCount; i++) { + [paths addObject:[NSIndexPath indexPathForRow:i inSection:1]]; + } + if (newCount > count) { + [self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic]; + } + else { + [self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic]; + } + + paths = [NSMutableArray arrayWithCapacity:MIN(newCount, count)]; + for (unsigned i = 0; i < count && i < newCount; i++) { + [paths addObject:[NSIndexPath indexPathForRow:i inSection:1]]; + } + [self.tableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationRight]; + } + }; + + if (count) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Replace Existing Cheats?" + message:@"Append the newly imported cheats or replace the existing ones?" + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Append" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + load(false); + }]]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Replace" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) { + load(true); + }]]; + [self presentViewController:alertController animated:true completion:nil]; + } + else { + load(true); + } +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == 0) return; + if (!self.editing) return; + + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + CGRect frame = cell.textLabel.frame; + frame.size.width = cell.textLabel.superview.frame.size.width - 8 - frame.origin.x; + UITextField *field = [[UITextField alloc] initWithFrame:frame]; + field.font = cell.textLabel.font; + field.text = cell.textLabel.text; + cell.textLabel.text = @""; + [[cell.textLabel superview] addSubview:field]; + [field becomeFirstResponder]; + [field selectAll:nil]; + _renamingPath = indexPath; + [field addTarget:self action:@selector(doneRename:) forControlEvents:UIControlEventEditingDidEnd | UIControlEventEditingDidEndOnExit]; + _editingField = field; +} + +- (void)doneRename:(UITextField *)sender +{ + if (!_renamingPath) return; + const GB_cheat_t *cheat = GB_get_cheats(_gb, NULL)[_renamingPath.row]; + GB_update_cheat(_gb, cheat, sender.text.length? sender.text.UTF8String : "Unnamed Cheat", + cheat->address, cheat->bank, + cheat->value, cheat->old_value, cheat->use_old_value, + cheat->enabled); + [self.tableView reloadRowsAtIndexPaths:@[_renamingPath] withRowAnimation:UITableViewRowAnimationNone]; + _renamingPath = nil; +} + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated +{ + [super setEditing:editing animated:animated]; + if (!editing && _editingField) { + [self doneRename:_editingField]; + } +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + NSString *cheatsFile = [[GBROMManager sharedManager] cheatsFile]; + [[NSFileManager defaultManager] removeItemAtPath:cheatsFile error:nil]; + GB_save_cheats(_gb, cheatsFile.UTF8String); +} + +@end diff --git a/iOS/GBMenuViewController.m b/iOS/GBMenuViewController.m index 02e3fa1..5ab2099 100644 --- a/iOS/GBMenuViewController.m +++ b/iOS/GBMenuViewController.m @@ -61,7 +61,7 @@ static NSString *const tips[] = { {@"Connect", @"LinkCableTemplate", SelectorString(openConnectMenu), true}, {@"Model", @"ModelTemplate", SelectorString(changeModel)}, {@"States", @"square.stack", SelectorString(openStates), true}, - {@"Cheats", @"wand.and.stars", nil}, // TODO + {@"Cheats", @"CheatsTemplate", SelectorString(openCheats), true}, // TODO {@"Settings", @"gear", SelectorString(openSettings)}, {@"About", @"info.circle", SelectorString(showAbout)}, }; @@ -85,10 +85,6 @@ static NSString *const tips[] = { button.enabled = false; continue; } - if (buttons[i].selector == nil) { - button.enabled = false; - continue; - } SEL selector = NSSelectorFromString(buttons[i].selector); if (buttons[i].requireRunning && ![GBROMManager sharedManager].currentROM) { button.enabled = false; diff --git a/iOS/GBROMManager.h b/iOS/GBROMManager.h index 4c71538..3e95436 100644 --- a/iOS/GBROMManager.h +++ b/iOS/GBROMManager.h @@ -9,6 +9,7 @@ @property (readonly) NSString *romFile; @property (readonly) NSString *batterySaveFile; @property (readonly) NSString *autosaveStateFile; +@property (readonly) NSString *cheatsFile; - (NSString *)stateFile:(unsigned)index; - (NSString *)romFileForROM:(NSString *)rom; diff --git a/iOS/GBROMManager.m b/iOS/GBROMManager.m index 6697d09..4963956 100644 --- a/iOS/GBROMManager.m +++ b/iOS/GBROMManager.m @@ -103,6 +103,17 @@ return [self stateFile:index forROM:_currentROM]; } + +- (NSString *)cheatsFileForROM:(NSString *)rom +{ + return [self auxilaryFileForROM:rom withExtension:@"cht"]; +} + +- (NSString *)cheatsFile +{ + return [self cheatsFileForROM:_currentROM]; +} + - (NSArray *)allROMs { NSMutableArray *ret = [NSMutableArray array]; diff --git a/iOS/GBViewController.h b/iOS/GBViewController.h index bd168cd..487bab4 100644 --- a/iOS/GBViewController.h +++ b/iOS/GBViewController.h @@ -23,6 +23,7 @@ typedef enum { - (void)openSettings; - (void)showAbout; - (void)openConnectMenu; +- (void)openCheats; - (void)emptyPrinterFeed; - (void)saveStateToFile:(NSString *)file; - (bool)loadStateFromFile:(NSString *)file; diff --git a/iOS/GBViewController.m b/iOS/GBViewController.m index e6dd3a4..66a7045 100644 --- a/iOS/GBViewController.m +++ b/iOS/GBViewController.m @@ -14,6 +14,7 @@ #import "GBStatesViewController.h" #import "GBCheckableAlertController.h" #import "GBPrinterFeedController.h" +#import "GBCheatsController.h" #import "GCExtendedGamepad+AllElements.h" #import "GBZipReader.h" #import @@ -674,6 +675,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) if (_romLoaded) { GB_reset(&_gb); GB_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation); + GB_remove_all_cheats(&_gb); + GB_load_cheats(&_gb, [GBROMManager sharedManager].cheatsFile.UTF8String, false); if (![self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]) { // Newly played ROM, pick the best model uint8_t *rom = GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, NULL, NULL); @@ -820,6 +823,19 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [self presentViewController:[[GBAboutController alloc] init] animated:true completion:nil]; } +- (void)openCheats +{ + UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBCheatsController alloc] initWithGameBoy:&_gb]]; + UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close" + style:UIBarButtonItemStylePlain + target:self + action:@selector(dismissViewController)]; + [controller.visibleViewController.navigationItem setLeftBarButtonItem:close]; + [self presentViewController:controller + animated:true + completion:nil]; +} + - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion { [super dismissViewControllerAnimated:flag completion:^() { diff --git a/iOS/Info.plist b/iOS/Info.plist index aaf0a0c..223406f 100644 --- a/iOS/Info.plist +++ b/iOS/Info.plist @@ -176,6 +176,23 @@ + + UTTypeConformsTo + + public.data + + UTTypeDescription + SameBoy Cheats Database + UTTypeIdentifier + com.github.liji32.sameboy.cheats + UTTypeTagSpecification + + public.filename-extension + + cht + + + UIDeviceFamily diff --git a/iOS/LinkCableTemplate@2x.png b/iOS/LinkCableTemplate@2x.png index 2e9552f..266474d 100644 Binary files a/iOS/LinkCableTemplate@2x.png and b/iOS/LinkCableTemplate@2x.png differ diff --git a/iOS/LinkCableTemplate@3x.png b/iOS/LinkCableTemplate@3x.png index edcef79..64b6c63 100644 Binary files a/iOS/LinkCableTemplate@3x.png and b/iOS/LinkCableTemplate@3x.png differ diff --git a/iOS/UIToolbar+disableCompact.h b/iOS/UIToolbar+disableCompact.h new file mode 100644 index 0000000..2691c5c --- /dev/null +++ b/iOS/UIToolbar+disableCompact.h @@ -0,0 +1,5 @@ +#import + +@interface UIToolbar (disableCompact) +@property bool disableCompactLayout; +@end diff --git a/iOS/UIToolbar+disableCompact.m b/iOS/UIToolbar+disableCompact.m new file mode 100644 index 0000000..bd81003 --- /dev/null +++ b/iOS/UIToolbar+disableCompact.m @@ -0,0 +1,33 @@ +#import "UIToolbar+disableCompact.h" +#import + +@implementation UIToolbar (disableCompact) + +- (void)setDisableCompactLayout:(bool)disableCompactLayout +{ + objc_setAssociatedObject(self, @selector(disableCompactLayout), @(disableCompactLayout), OBJC_ASSOCIATION_RETAIN); +} + +- (bool)disableCompactLayout +{ + return [objc_getAssociatedObject(self, _cmd) boolValue]; +} + +- (CGSize)sizeThatFitsHook:(CGSize)size +{ + CGSize ret = [self sizeThatFitsHook:size]; + if (!self.disableCompactLayout) return ret; + if (ret.height < 44) { + ret.height = 44; + } + return ret; +} + ++ (void)load +{ + method_exchangeImplementations(class_getInstanceMethod(self, @selector(sizeThatFitsHook:)), + class_getInstanceMethod(self, @selector(sizeThatFits:))); + +} + +@end