git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

iOS/GBCheatsController.m

      1 #import "GBCheatsController.h"
      2 #import "GBROMManager.h"
      3 #import "UIToolbar+disableCompact.h"
      4 #import <CoreServices/CoreServices.h>
      5 #import <objc/runtime.h>
      6 
      7 @interface GBCheatsController()<UIDocumentPickerDelegate>
      8 @end
      9 
     10 @implementation GBCheatsController
     11 {
     12     GB_gameboy_t *_gb;
     13     NSIndexPath *_renamingPath;
     14     __weak UITextField *_editingField;
     15 }
     16 
     17 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
     18 {
     19     return 2;
     20 }
     21 
     22 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
     23 {
     24     if (section == 0) return 1;
     25     size_t count;
     26     GB_get_cheats(_gb, &count);
     27     self.toolbarItems[0].enabled = count;
     28     ((UIButton *)(self.toolbarItems[0].customView.subviews[0])).enabled = count;
     29     return count;
     30 }
     31 
     32 
     33 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
     34 {
     35     
     36     UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
     37 
     38     UISwitch *button = [[UISwitch alloc] init];
     39     cell.accessoryView = button;
     40     const GB_cheat_t *cheat = NULL;
     41     if (indexPath.section == 0) {
     42         button.on = GB_cheats_enabled(_gb);
     43         cell.textLabel.text = @"Enable Cheats";
     44     }
     45     else {
     46         cheat = GB_get_cheats(_gb, NULL)[indexPath.row];
     47         button.on = cheat->enabled;
     48         cell.textLabel.text = @(cheat->description) ?: @"Unnamed Cheat";
     49         button.enabled = GB_cheats_enabled(_gb);
     50     }
     51     
     52     id block = ^(){
     53         if (!cheat) {
     54             GB_set_cheats_enabled(_gb, button.on);
     55             [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone];
     56         }
     57         else {
     58             GB_update_cheat(_gb, cheat, cheat->description,
     59                             cheat->address, cheat->bank,
     60                             cheat->value, cheat->old_value, cheat->use_old_value,
     61                             button.on);
     62         }
     63     };
     64     objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN);
     65     
     66     [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged];
     67     cell.selectionStyle = UITableViewCellSelectionStyleNone;
     68 
     69     return cell;
     70 }
     71 
     72 - (void)addCheat
     73 {
     74     [self setEditing:false animated:true];
     75     UIAlertController *alertController = [UIAlertController alertControllerWithTitle: @"Add Cheat"
     76                                                                               message: @"Add a GameShark or Game Genie cheat code"
     77                                                                        preferredStyle:UIAlertControllerStyleAlert];
     78     [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
     79         textField.placeholder = @"Description";
     80         textField.clearButtonMode = UITextFieldViewModeWhileEditing;
     81     }];
     82     [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
     83         textField.placeholder = @"Cheat Code";
     84         textField.clearButtonMode = UITextFieldViewModeWhileEditing;
     85         textField.keyboardType = UIKeyboardTypeASCIICapable;
     86     }];
     87     [alertController addAction:[UIAlertAction actionWithTitle:@"Add" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
     88         size_t index = [self tableView:self.tableView numberOfRowsInSection:1];
     89         NSString *name = alertController.textFields[0].text;
     90         if (GB_import_cheat(_gb, alertController.textFields[1].text.UTF8String, name.length? name.UTF8String : "Unnamed Cheat", true)) {
     91             [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:1]] withRowAnimation:UITableViewRowAnimationAutomatic];
     92         }
     93         else {
     94             alertController.title = @"Invalid cheat code entered";
     95             [self presentViewController:alertController animated:true completion:nil];
     96         }
     97     }]];
     98     [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
     99     [self presentViewController:alertController animated:true completion:nil];
    100 }
    101 
    102 + (UIBarButtonItem *)buttonWithLabel:(NSString *)label
    103                        imageWithName:(NSString *)imageName
    104                               target:(id)target
    105                               action:(SEL)action
    106 {
    107     if (@available(iOS 13.0, *)) {
    108         UIImage *image = [UIImage systemImageNamed:imageName
    109                                  withConfiguration:[UIImageSymbolConfiguration configurationWithScale:UIImageSymbolScaleLarge]];
    110         UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    111         [button setImage:image forState:UIControlStateNormal];
    112         button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
    113         [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    114         if (label) {
    115             [button setTitle:label forState:UIControlStateNormal];
    116             [button setTitleColor:button.tintColor forState:UIControlStateNormal];
    117             button.titleEdgeInsets = UIEdgeInsetsMake(0, 4, 0, 0);
    118             button.contentEdgeInsets = UIEdgeInsetsMake(0, 12, 0, 0);
    119         }
    120         [button sizeToFit];
    121         CGRect frame = button.frame;
    122         frame.size.width = ceil(frame.size.width + (label? 4 : 0));
    123         if (@available(iOS 19.0, *)) {
    124             if (label) {
    125                 frame.size.width += 12;
    126             }
    127         }
    128         frame.size.height = 28;
    129         button.frame = frame;
    130         UIView *wrapper = [[UIView alloc] initWithFrame:button.bounds];
    131         [wrapper addSubview:button];
    132         UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:wrapper];
    133         return item;
    134     }
    135     return [[UIBarButtonItem alloc] initWithTitle:label style:UIBarButtonItemStylePlain target:target action:action];
    136 }
    137 
    138 - (void)importCheats
    139 {
    140     [self setEditing:false animated:true];
    141     NSString *chtUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"cht", NULL);
    142     
    143         
    144     UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[chtUTI]
    145                                                                                                     inMode:UIDocumentPickerModeImport];
    146     if (@available(iOS 13.0, *)) {
    147         picker.shouldShowFileExtensions = true;
    148     }
    149     picker.delegate = self;
    150     [self presentViewController:picker animated:true completion:nil];
    151 
    152     return;
    153 
    154 }
    155 
    156 - (void)exportCheats
    157 {
    158     [self setEditing:false animated:true];
    159     NSString *cheatsFile = [[GBROMManager sharedManager] cheatsFile];
    160     GB_save_cheats(_gb, cheatsFile.UTF8String);
    161     NSURL *url = [NSURL fileURLWithPath:cheatsFile];
    162     UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url]
    163                                                                              applicationActivities:nil];
    164     
    165     controller.popoverPresentationController.barButtonItem = self.toolbarItems.firstObject;
    166 
    167     [self presentViewController:controller
    168                        animated:true
    169                      completion:nil];
    170 
    171 }
    172 
    173 - (instancetype)initWithGameBoy:(GB_gameboy_t *)gb
    174 {
    175     UITableViewStyle style = UITableViewStyleGrouped;
    176     if (@available(iOS 13.0, *)) {
    177         style = UITableViewStyleInsetGrouped;
    178     }
    179     self = [super initWithStyle:style];
    180     self.tableView.allowsSelectionDuringEditing = true;
    181     self.navigationItem.rightBarButtonItem = self.editButtonItem;
    182     
    183     bool hasSFSymbols = false;
    184     if (@available(iOS 13.0, *)) {
    185         hasSFSymbols = true;
    186     }
    187 
    188     UIBarButtonItem *export = hasSFSymbols?
    189         [self.class buttonWithLabel:nil
    190                       imageWithName:@"square.and.arrow.up"
    191                              target:self
    192                              action:@selector(exportCheats)] :
    193         [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
    194                                                       target:self
    195                                                       action:@selector(exportCheats)];
    196     
    197     UIBarButtonItem *flexItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
    198                                                                               target:nil
    199                                                                               action:NULL];
    200     UIBarButtonItem *import = [self.class buttonWithLabel:@"Import"
    201                                             imageWithName:@"square.and.arrow.down"
    202                                                    target:self
    203                                                    action:@selector(importCheats)];
    204     
    205     UIBarButtonItem *add = [self.class buttonWithLabel:@"Add"
    206                                          imageWithName:@"plus"
    207                                                 target:self
    208                                                 action:@selector(addCheat)];
    209     
    210     if (@available(iOS 19.0, *)) {
    211         self.toolbarItems = @[export,
    212                               flexItem,
    213                               import, [UIBarButtonItem fixedSpaceItemOfWidth:0], add];
    214     }
    215     else {
    216         self.toolbarItems = @[export,
    217                               flexItem,
    218                               import, add];
    219     }
    220     
    221     _gb = gb;
    222     return self;
    223 }
    224 
    225 - (NSString *)title
    226 {
    227     return @"Cheats";
    228 }
    229 
    230 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    231 {
    232     return indexPath.section == 1;
    233 }
    234 
    235 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
    236 {
    237     if (indexPath.section != 1) return;
    238     if (editingStyle != UITableViewCellEditingStyleDelete) return;
    239     
    240     const GB_cheat_t *cheat = GB_get_cheats(_gb, NULL)[indexPath.row];
    241     GB_remove_cheat(_gb, cheat);
    242     [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    243 }
    244 
    245 - (void)viewWillAppear:(BOOL)animated
    246 {
    247     [super viewWillAppear:animated];
    248     [self.navigationController setToolbarHidden:false animated:false];
    249     self.navigationController.toolbar.disableCompactLayout = true;
    250 }
    251 
    252 
    253 - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
    254 {
    255     [url startAccessingSecurityScopedResource];
    256     NSString *tempDir = NSTemporaryDirectory();
    257     NSString *newPath = [tempDir stringByAppendingPathComponent:@"import.cht"];
    258     [[NSFileManager defaultManager] copyItemAtPath:url.path toPath:newPath error:nil];
    259     [url stopAccessingSecurityScopedResource];
    260     unsigned count = [self tableView:self.tableView numberOfRowsInSection:1];
    261     
    262     void (^load)(bool) = ^(bool replace) {
    263         if (GB_load_cheats(_gb, newPath.UTF8String, replace)) {
    264             [[NSFileManager defaultManager] removeItemAtPath:newPath error:nil];
    265             UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Import Failed"
    266                                                                                      message:@"The imported cheats file is invalid."
    267                                                                               preferredStyle:UIAlertControllerStyleAlert];
    268             [alertController addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]];
    269             [self presentViewController:alertController animated:true completion:nil];
    270             return;
    271         }
    272         
    273         [[NSFileManager defaultManager] removeItemAtPath:newPath error:nil];
    274         unsigned newCount = [self tableView:self.tableView numberOfRowsInSection:1];
    275         if (!replace) {
    276             NSMutableArray *paths = [NSMutableArray arrayWithCapacity:newCount - count];
    277             for (unsigned i = count; i < newCount; i++) {
    278                 [paths addObject:[NSIndexPath indexPathForRow:i inSection:1]];
    279             }
    280             if (paths.count) {
    281                 [self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
    282             }
    283         }
    284         else {
    285             NSMutableArray *paths = [NSMutableArray arrayWithCapacity:abs((signed)newCount - (signed)count)];
    286             for (unsigned i = MIN(newCount, count); i < count || i < newCount; i++) {
    287                 [paths addObject:[NSIndexPath indexPathForRow:i inSection:1]];
    288             }
    289             if (newCount > count) {
    290                 [self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
    291             }
    292             else {
    293                 [self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationAutomatic];
    294             }
    295             
    296             paths = [NSMutableArray arrayWithCapacity:MIN(newCount, count)];
    297             for (unsigned i = 0; i < count && i < newCount; i++) {
    298                 [paths addObject:[NSIndexPath indexPathForRow:i inSection:1]];
    299             }
    300             [self.tableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationRight];
    301         }
    302     };
    303     
    304     if (count) {
    305         UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Replace Existing Cheats?"
    306                                                                                  message:@"Append the newly imported cheats or replace the existing ones?"
    307                                                                           preferredStyle:UIAlertControllerStyleAlert];
    308         [alertController addAction:[UIAlertAction actionWithTitle:@"Append" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
    309             load(false);
    310         }]];
    311         [alertController addAction:[UIAlertAction actionWithTitle:@"Replace" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
    312             load(true);
    313         }]];
    314         [self presentViewController:alertController animated:true completion:nil];
    315     }
    316     else {
    317         load(true);
    318     }
    319 }
    320 
    321 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    322 {
    323     if (indexPath.section == 0) return;
    324     if (!self.editing) return;
    325     
    326     UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    327     CGRect frame = cell.textLabel.frame;
    328     frame.size.width = cell.textLabel.superview.frame.size.width - 8 - frame.origin.x;
    329     UITextField *field = [[UITextField alloc] initWithFrame:frame];
    330     field.font = cell.textLabel.font;
    331     field.text = cell.textLabel.text;
    332     cell.textLabel.text = @"";
    333     [[cell.textLabel superview] addSubview:field];
    334     [field becomeFirstResponder];
    335     [field selectAll:nil];
    336     _renamingPath = indexPath;
    337     [field addTarget:self action:@selector(doneRename:) forControlEvents:UIControlEventEditingDidEnd | UIControlEventEditingDidEndOnExit];
    338     _editingField = field;
    339 }
    340 
    341 - (void)doneRename:(UITextField *)sender
    342 {
    343     if (!_renamingPath) return;
    344     const GB_cheat_t *cheat = GB_get_cheats(_gb, NULL)[_renamingPath.row];
    345     GB_update_cheat(_gb, cheat, sender.text.length? sender.text.UTF8String : "Unnamed Cheat",
    346                     cheat->address, cheat->bank,
    347                     cheat->value, cheat->old_value, cheat->use_old_value,
    348                     cheat->enabled);
    349     [self.tableView reloadRowsAtIndexPaths:@[_renamingPath] withRowAnimation:UITableViewRowAnimationNone];
    350     _renamingPath = nil;
    351 }
    352 
    353 - (void)setEditing:(BOOL)editing animated:(BOOL)animated
    354 {
    355     [super setEditing:editing animated:animated];
    356     if (!editing && _editingField) {
    357         [self doneRename:_editingField];
    358     }
    359 }
    360 
    361 - (void)viewWillDisappear:(BOOL)animated
    362 {
    363     [super viewWillDisappear:animated];
    364     NSString *cheatsFile = [[GBROMManager sharedManager] cheatsFile];
    365     [[NSFileManager defaultManager] removeItemAtPath:cheatsFile error:nil];
    366     GB_save_cheats(_gb, cheatsFile.UTF8String);
    367 }
    368 
    369 @end

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.