From 6178ff2a783f592e02effcecb27c8bbb9b60acfd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Aug 2024 01:11:47 +0300 Subject: [PATCH] Cheat Search API, Cheat Search in Cocoa, and new cheats window layout --- Cocoa/CheatSearch.xib | 254 +++++++++++++++++++++++++ Cocoa/Document.h | 11 +- Cocoa/Document.m | 16 ++ Cocoa/Document.xib | 322 ++++++++++++++++---------------- Cocoa/GBCenteredTextCell.h | 5 + Cocoa/GBCenteredTextCell.m | 29 +++ Cocoa/GBCheatSearchController.h | 8 + Cocoa/GBCheatSearchController.m | 231 +++++++++++++++++++++++ Cocoa/MainMenu.xib | 10 +- Core/cheat_search.c | 142 ++++++++++++++ Core/cheat_search.h | 25 +++ Core/debugger.c | 122 ++++++++---- Core/debugger.h | 3 + Core/gb.c | 10 +- Core/gb.h | 15 ++ 15 files changed, 995 insertions(+), 208 deletions(-) create mode 100644 Cocoa/CheatSearch.xib create mode 100644 Cocoa/GBCenteredTextCell.h create mode 100644 Cocoa/GBCenteredTextCell.m create mode 100644 Cocoa/GBCheatSearchController.h create mode 100644 Cocoa/GBCheatSearchController.m create mode 100644 Core/cheat_search.c create mode 100644 Core/cheat_search.h diff --git a/Cocoa/CheatSearch.xib b/Cocoa/CheatSearch.xib new file mode 100644 index 000000000..f97d247a9 --- /dev/null +++ b/Cocoa/CheatSearch.xib @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 353d09743..b9ec66ed6 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -83,10 +83,11 @@ enum model { + (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale; --(uint8_t) readMemory:(uint16_t) addr; --(void) writeMemory:(uint16_t) addr value:(uint8_t)value; --(void) performAtomicBlock: (void (^)())block; --(void) connectLinkCable:(NSMenuItem *)sender; --(int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound; +- (uint8_t) readMemory:(uint16_t) addr; +- (void) writeMemory:(uint16_t) addr value:(uint8_t)value; +- (void) performAtomicBlock: (void (^)())block; +- (void) connectLinkCable:(NSMenuItem *)sender; +- (int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound; +- (NSString *)captureOutputForBlock: (void (^)())block; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 5486ec53d..4b4d429e1 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -11,6 +11,7 @@ #import "GBTerminalTextFieldCell.h" #import "BigSurToolbar.h" #import "GBPaletteEditorController.h" +#import "GBCheatSearchController.h" #import "GBObjectView.h" #import "GBPaletteView.h" #import "GBHexStatusBarRepresenter.h" @@ -117,6 +118,8 @@ @implementation Document NSDate *_fileModificationTime; __weak NSThread *_emulationThread; + + GBCheatSearchController *_cheatSearchController; } static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) @@ -1775,6 +1778,11 @@ - (void) reloadMemoryView if (self.memoryWindow.isVisible) { [_hexController reloadData]; } + if (_cheatSearchController.window.isVisible) { + if ([_cheatSearchController.tableView editedColumn] != 2) { + [_cheatSearchController.tableView reloadData]; + } + } } - (IBAction) reloadVRAMData: (id) sender @@ -2400,6 +2408,14 @@ - (IBAction)showCheats:(id)sender [self.cheatsWindow makeKeyAndOrderFront:nil]; } +- (IBAction)showCheatSearch:(id)sender +{ + if (!_cheatSearchController) { + _cheatSearchController = [GBCheatSearchController controllerWithDocument:self]; + } + [_cheatSearchController.window makeKeyAndOrderFront:sender]; +} + - (IBAction)toggleCheats:(id)sender { GB_set_cheats_enabled(&_gb, !GB_cheats_enabled(&_gb)); diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index dd5178f98..d7fb2e215 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -720,165 +720,24 @@ - - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - + + + - - + + - - + + @@ -895,7 +754,7 @@ - + @@ -906,24 +765,24 @@ - + - + - + - + @@ -947,13 +806,156 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Cocoa/GBCenteredTextCell.h b/Cocoa/GBCenteredTextCell.h new file mode 100644 index 000000000..829f6bafd --- /dev/null +++ b/Cocoa/GBCenteredTextCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBCenteredTextCell : NSTextFieldCell + +@end diff --git a/Cocoa/GBCenteredTextCell.m b/Cocoa/GBCenteredTextCell.m new file mode 100644 index 000000000..5039988a3 --- /dev/null +++ b/Cocoa/GBCenteredTextCell.m @@ -0,0 +1,29 @@ +#import "GBCenteredTextCell.h" + +@implementation GBCenteredTextCell +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + double height = round([self.attributedStringValue size].height); + cellFrame.origin.y += (cellFrame.size.height - height) / 2; + cellFrame.size.height = height; + [super drawInteriorWithFrame:cellFrame inView:controlView]; +} + + +- (void)selectWithFrame:(NSRect)rect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)delegate start:(NSInteger)selStart length:(NSInteger)selLength +{ + double height = round([self.attributedStringValue size].height); + rect.origin.y += (rect.size.height - height) / 2; + rect.size.height = height; + [super selectWithFrame:rect inView:controlView editor:textObj delegate:delegate start:selStart length:selLength]; +} + +- (void)editWithFrame:(NSRect)rect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)delegate event:(NSEvent *)event +{ + double height = round([self.attributedStringValue size].height); + rect.origin.y += (rect.size.height - height) / 2; + rect.size.height = height; + [super editWithFrame:rect inView:controlView editor:textObj delegate:delegate event:event]; + +} +@end diff --git a/Cocoa/GBCheatSearchController.h b/Cocoa/GBCheatSearchController.h new file mode 100644 index 000000000..fb330a23c --- /dev/null +++ b/Cocoa/GBCheatSearchController.h @@ -0,0 +1,8 @@ +#import +#import "Document.h" + +@interface GBCheatSearchController : NSObject +@property IBOutlet NSWindow *window; +@property IBOutlet NSTableView *tableView; ++ (instancetype)controllerWithDocument:(Document *)document; +@end diff --git a/Cocoa/GBCheatSearchController.m b/Cocoa/GBCheatSearchController.m new file mode 100644 index 000000000..41b96add3 --- /dev/null +++ b/Cocoa/GBCheatSearchController.m @@ -0,0 +1,231 @@ +#import "GBCheatSearchController.h" +#import "GBWarningPopover.h" +#import "GBCheatWindowController.h" + +@interface GBCheatSearchController() +@property IBOutlet NSPopUpButton *dataTypeButton; +@property IBOutlet NSPopUpButton *conditionTypeButton; +@property IBOutlet NSTextField *operandField; +@property IBOutlet NSTextField *conditionField; +@property IBOutlet NSTextField *resultsLabel; +@property (strong) IBOutlet NSButton *addCheatButton; +@end + +@implementation GBCheatSearchController +{ + __weak Document *_document; + size_t _resultCount; + GB_cheat_search_result_t *_results; +} + ++ (instancetype)controllerWithDocument:(Document *)document +{ + GBCheatSearchController *ret = [[self alloc] init]; + ret->_document = document; + NSArray *objects; + [[NSBundle mainBundle] loadNibNamed:@"CheatSearch" owner:ret topLevelObjects:&objects]; + ret->_resultsLabel.stringValue = @""; + ret->_resultsLabel.cell.backgroundStyle = NSBackgroundStyleRaised; + return ret; +} + +- (IBAction)reset:(id)sender +{ + _dataTypeButton.enabled = true; + [_document performAtomicBlock:^{ + GB_cheat_search_reset(_document.gb); + }]; + _resultCount = 0; + if (_results) { + free(_results); + _results = NULL; + } + [_tableView reloadData]; + _resultsLabel.stringValue = @""; +} + +- (IBAction)search:(id)sender +{ + // Dispatch to work around firstResponder oddities + dispatch_async(dispatch_get_main_queue(), ^{ + if ([sender isKindOfClass:[NSTextField class]]) { + // Action sent by losing focus rather than pressing enter + if (![sender currentEditor]) return; + } + _dataTypeButton.enabled = false; + [_document performAtomicBlock:^{ + __block bool success = false; + NSString *error = [_document captureOutputForBlock:^{ + success = GB_cheat_search_filter(_document.gb, _conditionField.stringValue.UTF8String, _dataTypeButton.selectedTag); + }]; + if (!success) { + dispatch_async(dispatch_get_main_queue(), ^{ + [GBWarningPopover popoverWithContents:error onView:_conditionField]; + NSBeep(); + }); + return; + } + _resultCount = GB_cheat_search_result_count(_document.gb); + _results = malloc(sizeof(*_results) * _resultCount); + GB_cheat_search_get_results(_document.gb, _results); + }]; + if (_resultCount == 0) { + _dataTypeButton.enabled = true; + _resultsLabel.stringValue = @"No results."; + } + else { + _resultsLabel.stringValue = [NSString stringWithFormat:@"%@ result%s", + [NSNumberFormatter localizedStringFromNumber:@(_resultCount) + numberStyle:NSNumberFormatterDecimalStyle], + _resultCount > 1? "s" : ""]; + } + [_tableView reloadData]; + }); +} + +- (IBAction)conditionChanged:(id)sender +{ + unsigned index = [_conditionTypeButton indexOfSelectedItem]; + _conditionField.enabled = index == 11; + _operandField.enabled = index >= 1 && index <= 6; + switch ([_conditionTypeButton indexOfSelectedItem]) { + case 0: _conditionField.stringValue = @"1"; break; + case 1: _conditionField.stringValue = [NSString stringWithFormat:@"new == (%@)", _operandField.stringValue]; break; + case 2: _conditionField.stringValue = [NSString stringWithFormat:@"new != (%@)", _operandField.stringValue]; break; + case 3: _conditionField.stringValue = [NSString stringWithFormat:@"new > (%@)", _operandField.stringValue]; break; + case 4: _conditionField.stringValue = [NSString stringWithFormat:@"new >= (%@)", _operandField.stringValue]; break; + case 5: _conditionField.stringValue = [NSString stringWithFormat:@"new < (%@)", _operandField.stringValue]; break; + case 6: _conditionField.stringValue = [NSString stringWithFormat:@"new <= (%@)", _operandField.stringValue]; break; + case 7: _conditionField.stringValue = @"new != old"; break; + case 8: _conditionField.stringValue = @"new == old"; break; + case 9: _conditionField.stringValue = @"new > old"; break; + case 10: _conditionField.stringValue = @"new < old"; break; + } +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return _resultCount; +} + +- (uint8_t *)addressForRow:(unsigned)row +{ + uint8_t *base; + uint32_t offset; + if (_results[row].addr < 0xc000) { + base = GB_get_direct_access(_document.gb, GB_DIRECT_ACCESS_CART_RAM, NULL, NULL); + offset = (_results[row].addr & 0x1FFF) + _results[row].bank * 0x2000; + } + else if (_results[row].addr < 0xe000) { + base = GB_get_direct_access(_document.gb, GB_DIRECT_ACCESS_RAM, NULL, NULL); + offset = (_results[row].addr & 0xFFF) + _results[row].bank * 0x1000; + } + else { + base = GB_get_direct_access(_document.gb, GB_DIRECT_ACCESS_HRAM, NULL, NULL); + offset = (_results[row].addr & 0x7F); + } + return base + offset; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + switch ([[tableView tableColumns] indexOfObject:tableColumn]) { + case 0: + return [NSString stringWithFormat:@"$%02x:$%04x", _results[row].bank, _results[row].addr]; + case 1: + if (_dataTypeButton.selectedTag & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) { + return [NSString stringWithFormat:@"$%04x", _results[row].value]; + } + return [NSString stringWithFormat:@"$%02x", _results[row].value]; + default: { + const uint8_t *data = [self addressForRow:row]; + GB_cheat_search_data_type_t dataType = _dataTypeButton.selectedTag; + uint16_t value = data[0]; + if (!(dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT)) { + return [NSString stringWithFormat:@"$%02x", value]; + } + value |= data[1] << 8; + if ((dataType & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT)) { + value = __builtin_bswap16(value); + } + return [NSString stringWithFormat:@"$%04x", value]; + } + } +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(NSString *)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + [_document performAtomicBlock:^{ + __block bool success = false; + __block uint16_t value; + NSString *error = [_document captureOutputForBlock:^{ + success = !GB_debugger_evaluate(_document.gb, object.UTF8String, &value, NULL); + }]; + if (!success) { + dispatch_async(dispatch_get_main_queue(), ^{ + [GBWarningPopover popoverWithContents:error onView:tableView]; + NSBeep(); + }); + return; + } + uint8_t *dest = [self addressForRow:row]; + GB_cheat_search_data_type_t dataType = _dataTypeButton.selectedTag; + if (dataType & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT) { + value = __builtin_bswap16(value); + } + dest[0] = value; + if (dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) { + dest[1] = value >> 8; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [tableView reloadData]; + }); + }]; +} + +- (void)controlTextDidChange:(NSNotification *)obj +{ + [self conditionChanged:nil]; +} + +- (IBAction)addCheat:(id)sender +{ + GB_cheat_search_result_t *result = _results + _tableView.selectedRow; + uint8_t *data = [self addressForRow:_tableView.selectedRow]; + GB_cheat_search_data_type_t dataType = _dataTypeButton.selectedTag; + size_t rowToSelect = 0; + GB_get_cheats(_document.gb, &rowToSelect); + [_document performAtomicBlock:^{ + GB_add_cheat(_document.gb, + (dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT)? "New Cheat (Part 1)" : "New Cheat", + result->addr, result->bank, + *data, + 0, false, + true); + if (dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) { + GB_add_cheat(_document.gb, + (dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT)? "New Cheat (Part 2)" : "New Cheat", + result->addr + 1, result->bank, + data[1], + 0, false, + true); + } + GB_set_cheats_enabled(_document.gb, true); + }]; + [_document.cheatsWindow makeKeyAndOrderFront:nil]; + [_document.cheatWindowController.cheatsTable reloadData]; + [_document.cheatWindowController.cheatsTable selectRow:rowToSelect byExtendingSelection:false]; + [_document.cheatWindowController.cheatsTable.delegate tableViewSelectionDidChange:nil]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + _addCheatButton.enabled = _tableView.numberOfSelectedRows != 0; +} + +- (void)dealloc +{ + if (_results) free(_results); +} + +@end diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index d1ea5b3d5..e0c28fd9a 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -427,10 +427,16 @@ - + - + + + + + + + diff --git a/Core/cheat_search.c b/Core/cheat_search.c new file mode 100644 index 000000000..3b563093c --- /dev/null +++ b/Core/cheat_search.c @@ -0,0 +1,142 @@ +#include "gb.h" + +void GB_cheat_search_reset(GB_gameboy_t *gb) +{ + if (gb->cheat_search_data) { + free(gb->cheat_search_data); + gb->cheat_search_data = NULL; + } + if (gb->cheat_search_bitmap) { + free(gb->cheat_search_bitmap); + gb->cheat_search_bitmap = NULL; + } + gb->cheat_search_count = 0; +} + +bool GB_cheat_search_filter(GB_gameboy_t *gb, const char *expression, GB_cheat_search_data_type_t data_type) +{ + GB_ASSERT_NOT_RUNNING(gb) + + // Make sure the expression is valid first + if (GB_debugger_evaluate_cheat_filter(gb, expression, NULL, 0, 0)) { + return false; + } + gb->cheat_search_data_type = data_type; + + if (gb->cheat_search_count == 0) { + GB_cheat_search_reset(gb); + gb->cheat_search_count = gb->ram_size + gb->mbc_ram_size + sizeof(gb->hram); + gb->cheat_search_data = malloc(gb->cheat_search_count); + gb->cheat_search_bitmap = malloc((gb->cheat_search_count + 7) / 8); + memset(gb->cheat_search_data, 0, gb->cheat_search_count); + memset(gb->cheat_search_bitmap, 0, (gb->cheat_search_count + 7) / 8); + } + + uint8_t mask = 1; + uint8_t *old_data = gb->cheat_search_data; + uint8_t *bitmap = gb->cheat_search_bitmap; + uint8_t *new_data = gb->ram; + + for (unsigned i = gb->ram_size + gb->mbc_ram_size + sizeof(gb->hram); i--;) { + if (*bitmap & mask) { + goto skip; + } + bool result = false; + if (data_type & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) { + // The last byte of each section always fails on 16-bit searches + if ((new_data != gb->ram + gb->ram_size - 1 && + new_data != gb->mbc_ram + gb->mbc_ram_size - 1 && + new_data != gb->hram + sizeof(gb->hram) - 1)) { + uint16_t old = old_data[0] | (old_data[1] << 8); + uint16_t new = new_data[0] | (new_data[1] << 8); + if (data_type & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT) { + old = __builtin_bswap16(old); + new = __builtin_bswap16(new); + } + GB_debugger_evaluate_cheat_filter(gb, expression, &result, old, new); + } + } + else { + GB_debugger_evaluate_cheat_filter(gb, expression, &result, *old_data, *new_data); + } + if (result) { + // Filter passed, update old value + *old_data = *new_data; + if (data_type & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) { + old_data[1] = new_data[1]; + } + } + else { + // Did not pass filter, remove address + *bitmap |= mask; + gb->cheat_search_count--; + } + skip:; + old_data++; + if (new_data == gb->ram + gb->ram_size - 1) { + new_data = gb->mbc_ram; + } + else if (new_data == gb->mbc_ram + gb->mbc_ram_size - 1) { + new_data = gb->hram; + } + else { + new_data++; + } + mask <<= 1; + if (mask == 0) { + mask = 1; + bitmap++; + } + } + + return true; +} + +size_t GB_cheat_search_result_count(GB_gameboy_t *gb) +{ + return gb->cheat_search_count; +} + +void GB_cheat_search_get_results(GB_gameboy_t *gb, GB_cheat_search_result_t *results) +{ + uint8_t mask = 1; + uint8_t *old_data = gb->cheat_search_data; + uint8_t *bitmap = gb->cheat_search_bitmap; + size_t count = gb->cheat_search_count; + while (count) { + if (!(*bitmap & mask)) { + count--; + if (gb->cheat_search_data_type & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) { + // Do not check for end of section, data_type is required to be the same as the last filter call + uint16_t old = old_data[0] | (old_data[1] << 8); + if (gb->cheat_search_data_type & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT) { + old = __builtin_bswap16(old); + } + results->value = old; + } + else { + results->value = *old_data; + } + size_t offset = old_data - gb->cheat_search_data; + if (offset < gb->ram_size) { + results->bank = offset / 0x1000; + results->addr = (offset & 0xfff) + (results->bank? 0xd000 : 0xc000); + } + else if (offset < gb->ram_size + gb->mbc_ram_size) { + results->addr = (offset & 0x1fff) + 0xa000; + results->bank = (offset - gb->ram_size) / 0x2000; + } + else { + results->addr = (offset & 0x7f) + 0xff80; + results->bank = 0; + } + results++; + } + old_data++; + mask <<= 1; + if (mask == 0) { + mask = 1; + bitmap++; + } + } +} diff --git a/Core/cheat_search.h b/Core/cheat_search.h new file mode 100644 index 000000000..ea7ccd895 --- /dev/null +++ b/Core/cheat_search.h @@ -0,0 +1,25 @@ +#pragma once +#ifndef GB_DISABLE_CHEAT_SEARCH +#include "defs.h" +#include +#include +#include + +typedef struct { + uint16_t addr; + uint16_t bank; + uint16_t value; +} GB_cheat_search_result_t; + +typedef enum { + GB_CHEAT_SEARCH_DATA_TYPE_8BIT = 0, + GB_CHEAT_SEARCH_DATA_TYPE_16BIT = 1, + GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT = 2, // Not used alone + GB_CHEAT_SEARCH_DATA_TYPE_16BIT_BE = GB_CHEAT_SEARCH_DATA_TYPE_16BIT | GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT, +} GB_cheat_search_data_type_t; + +void GB_cheat_search_reset(GB_gameboy_t *gb); +bool GB_cheat_search_filter(GB_gameboy_t *gb, const char *expression, GB_cheat_search_data_type_t data_type); +size_t GB_cheat_search_result_count(GB_gameboy_t *gb); +void GB_cheat_search_get_results(GB_gameboy_t *gb, GB_cheat_search_result_t *results); +#endif diff --git a/Core/debugger.c b/Core/debugger.c index 824a51aae..c5b35cda7 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -369,13 +369,22 @@ static struct { {":", 3, bank}, }; -value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - size_t length, bool *error, - uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); +typedef struct { + union { + uint16_t old_address; + uint16_t old_value; + }; + uint16_t new_value; + bool old_as_value; +} evaluate_conf_t; + +static value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + const evaluate_conf_t *conf); static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, size_t length, bool *error, - uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) + const evaluate_conf_t *conf) { *error = false; // Strip whitespace @@ -403,7 +412,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, } if (string[i] == ')') depth--; } - if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, conf); } else if (string[0] == '[' && string[length - 1] == ']') { // Attempt to strip square parentheses (memory dereference) @@ -418,7 +427,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, if (string[i] == ']') depth--; } if (depth == 0) { - return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, conf)}; } } else if (string[0] == '{' && string[length - 1] == '}') { @@ -434,7 +443,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, if (string[i] == '}') depth--; } if (depth == 0) { - return (lvalue_t){LVALUE_MEMORY16, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + return (lvalue_t){LVALUE_MEMORY16, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, conf)}; } } @@ -473,9 +482,9 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, } #define ERROR ((value_t){0,}) -value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - size_t length, bool *error, - uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) +static value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + const evaluate_conf_t *conf) { /* Disable watchpoints while evaluating expressions */ uint16_t n_watchpoints = gb->n_watchpoints; @@ -510,7 +519,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, if (string[i] == ')') depth--; } if (depth == 0) { - ret = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + ret = debugger_evaluate(gb, string + 1, length - 2, error, conf); goto exit; } } @@ -528,7 +537,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (depth == 0) { - value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, conf); banking_state_t state; if (addr.bank) { save_banking_state(gb, &state); @@ -555,7 +564,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (depth == 0) { - value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, conf); banking_state_t state; if (addr.bank) { save_banking_state(gb, &state); @@ -600,15 +609,15 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } if (operator_index != -1) { unsigned right_start = (unsigned)(operator_pos + strlen(operators[operator_index].string)); - value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); + value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, conf); if (*error) goto exit; if (operators[operator_index].lvalue_operator) { - lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, conf); if (*error) goto exit; ret = operators[operator_index].lvalue_operator(gb, left, right.value); goto exit; } - value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + value_t left = debugger_evaluate(gb, string, operator_pos, error, conf); if (*error) goto exit; ret = operators[operator_index].operator(left, right); goto exit; @@ -640,22 +649,22 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} } } - else if (length == 3) { - if (watchpoint_address && memcmp(string, "old", 3) == 0) { - ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + else if (length == 3 && conf) { + if (memcmp(string, "old", 3) == 0) { + if (conf->old_as_value) { + ret = VALUE_16(conf->old_value); + } + else { + ret = VALUE_16(GB_read_memory(gb, conf->old_address)); + } goto exit; } - if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { - ret = VALUE_16(*watchpoint_new_value); + if (memcmp(string, "new", 3) == 0) { + ret = VALUE_16(conf->new_value); goto exit; } - /* $new is identical to $old in read conditions */ - if (watchpoint_address && memcmp(string, "new", 3) == 0) { - ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); - goto exit; - } } char symbol_name[length + 1]; @@ -1036,7 +1045,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const condition += strlen(" if "); /* Verify condition is sane (Todo: This might have side effects!) */ bool error; - debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL, NULL); + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL); if (error) return true; } @@ -1050,13 +1059,13 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL); if (error) return true; uint16_t length = 0; value_t end = result; if (to) { - end = debugger_evaluate(gb, to, (unsigned)strlen(to), &error, NULL, NULL); + end = debugger_evaluate(gb, to, (unsigned)strlen(to), &error, NULL); if (error) return true; if (end.has_bank && result.has_bank && end.bank != result.bank) { GB_log(gb, "Breakpoint range start and end points have different banks\n"); @@ -1211,9 +1220,12 @@ static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu /* Verify condition is sane (Todo: This might have side effects!) */ bool error; /* To make new and old legal */ - uint16_t dummy = 0; - uint8_t dummy2 = 0; - debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &dummy, &dummy2); + static const evaluate_conf_t conf = { + .old_as_value = true, + .old_value = 0, + .new_value = 0, + }; + debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &conf); if (error) return true; } @@ -1226,13 +1238,13 @@ static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL); uint32_t key = WP_KEY(result); uint16_t length = 0; value_t end = result; if (to) { - end = debugger_evaluate(gb, to, (unsigned)strlen(to), &error, NULL, NULL); + end = debugger_evaluate(gb, to, (unsigned)strlen(to), &error, NULL); if (error) return true; if (end.has_bank && result.has_bank && end.bank != result.bank) { GB_log(gb, "Watchpoint range start and end points have different banks\n"); @@ -1413,7 +1425,7 @@ static unsigned should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) bool error; bool condition = debugger_evaluate(gb, breakpoint->condition, (unsigned)strlen(breakpoint->condition), - &error, NULL, NULL).value; + &error, NULL).value; if (error) { GB_log(gb, "The condition for breakpoint %u is no longer a valid expression\n", breakpoint->id); return breakpoint->id; @@ -1453,7 +1465,7 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu } bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL); if (!error) { switch (modifiers[0]) { case 'a': @@ -1499,7 +1511,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de } bool error; - value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL); uint16_t count = 32; if (modifiers) { @@ -1551,7 +1563,7 @@ static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, cons } bool error; - value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL); + value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL); uint16_t count = 5; if (modifiers) { @@ -2320,9 +2332,19 @@ static void test_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t flags, uint return; } bool error; + evaluate_conf_t conf = { + .old_as_value = flags == WATCHPOINT_READ, + .new_value = value, + }; + if (flags == WATCHPOINT_READ) { + conf.old_value = value; + } + else { + conf.old_address = addr; + } bool condition = debugger_evaluate(gb, watchpoint->condition, (unsigned)strlen(watchpoint->condition), - &error, &addr, flags == WATCHPOINT_WRITE? &value : NULL).value; + &error, &conf).value; if (error) { GB_log(gb, "The condition for watchpoint %u is no longer a valid expression\n", watchpoint->id); GB_debugger_break(gb); @@ -2714,7 +2736,7 @@ bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb) bool error = false; - value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL); + value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL); if (result) { *result = value.value; } @@ -2724,6 +2746,26 @@ bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result return error; } +#ifndef GB_DISABLE_CHEAT_SEARCH +internal bool GB_debugger_evaluate_cheat_filter(GB_gameboy_t *gb, const char *string, bool *result, uint16_t old, uint16_t new) +{ + GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb) + + bool error = false; + evaluate_conf_t conf = { + .old_as_value = true, + .old_value = old, + .new_value = new, + }; + value_t value = debugger_evaluate(gb, string, strlen(string), &error, &conf); + if (result) { + *result = value.value; + } + + return error; +} +#endif + void GB_debugger_break(GB_gameboy_t *gb) { gb->debug_stopped = true; diff --git a/Core/debugger.h b/Core/debugger.h index 8b46b7025..dff7df101 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -31,6 +31,9 @@ internal void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, internal void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); internal const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr, bool prefer_local); internal void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol); +#ifndef GB_DISABLE_CHEAT_SEARCH +internal bool GB_debugger_evaluate_cheat_filter(GB_gameboy_t *gb, const char *string, bool *result, uint16_t old, uint16_t new); +#endif #endif #else // GB_DISABLE_DEBUGGER diff --git a/Core/gb.c b/Core/gb.c index 332c5922b..ef2a8b56c 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -229,6 +229,9 @@ void GB_free(GB_gameboy_t *gb) while (gb->cheats) { GB_remove_cheat(gb, gb->cheats[0]); } +#endif +#ifndef GB_DISABLE_CHEAT_SEARCH + GB_cheat_search_reset(gb); #endif GB_stop_audio_recording(gb); memset(gb, 0, sizeof(*gb)); @@ -1690,7 +1693,7 @@ static void GB_reset_internal(GB_gameboy_t *gb, bool quick) uint8_t extra_oam[sizeof(gb->extra_oam)]; uint8_t dma, obp0, obp1; } *preserved_state = NULL; - + if (quick) { preserved_state = alloca(sizeof(*preserved_state)); memcpy(preserved_state->hram, gb->hram, sizeof(gb->hram)); @@ -1810,6 +1813,11 @@ void GB_quick_reset(GB_gameboy_t *gb) void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) { GB_ASSERT_NOT_RUNNING(gb) + +#ifndef GB_DISABLE_CHEAT_SEARCH + GB_cheat_search_reset(gb); +#endif + gb->model = model; if (GB_is_cgb(gb)) { gb->ram = realloc(gb->ram, gb->ram_size = 0x1000 * 8); diff --git a/Core/gb.h b/Core/gb.h index 06e79522d..53cce9ff6 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -9,6 +9,14 @@ extern "C" { #include #include +#ifdef GB_DISABLE_CHEATS +#define GB_DISABLE_CHEAT_SEARCH +#else +#ifdef GB_DISABLE_DEBUGGER +#define GB_DISABLE_CHEAT_SEARCH +#endif +#endif + #include "model.h" #include "defs.h" #include "save_state.h" @@ -27,6 +35,7 @@ extern "C" { #include "symbol_hash.h" #include "sgb.h" #include "cheats.h" +#include "cheat_search.h" #include "rumble.h" #include "workboy.h" #include "random.h" @@ -819,6 +828,12 @@ struct GB_gameboy_internal_s { GB_cheat_t **cheats; GB_cheat_hash_t *cheat_hash[256]; #endif +#ifndef GB_DISABLE_CHEAT_SEARCH + uint8_t *cheat_search_data; + uint8_t *cheat_search_bitmap; + size_t cheat_search_count; + GB_cheat_search_data_type_t cheat_search_data_type; +#endif /* Misc */ bool turbo;