diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 9e1e73d..32cfb6b 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -9,6 +9,9 @@ @property (strong) IBOutlet NSWindow *mainWindow; @property (strong) IBOutlet NSView *memoryView; @property (strong) IBOutlet NSPanel *memoryWindow; +@property (readonly) GB_gameboy_t *gameboy; +@property (strong) IBOutlet NSTextField *memoryBankInput; +@property (strong) IBOutlet NSToolbarItem *memoryBankItem; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 2a7a89d..4dad62d 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -8,6 +8,8 @@ #include "HexFiend/HexFiend.h" #include "GBMemoryByteArray.h" +/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ + @interface Document () { /* NSTextViews freeze the entire app if they're modified too often and too quickly. @@ -20,6 +22,7 @@ HFController *hex_controller; NSString *lastConsoleInput; + HFLineCountingRepresenter *lineRep; } @property GBAudioClient *audioClient; @@ -175,6 +178,12 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) } [self readFromFile:self.fileName ofType:@"gb"]; [self start]; + + if (hex_controller) { + /* Verify bank sanity, especially when switching models. */ + [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:0]; + [self hexUpdateBank:self.memoryBankInput]; + } } - (IBAction)togglePause:(id)sender @@ -221,7 +230,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) HFHexTextRepresenter *hexRep = [[HFHexTextRepresenter alloc] init]; HFStringEncodingTextRepresenter *asciiRep = [[HFStringEncodingTextRepresenter alloc] init]; HFVerticalScrollerRepresenter *scrollRep = [[HFVerticalScrollerRepresenter alloc] init]; - HFLineCountingRepresenter *lineRep = [[HFLineCountingRepresenter alloc] init]; + lineRep = [[HFLineCountingRepresenter alloc] init]; HFStatusBarRepresenter *statusRep = [[HFStatusBarRepresenter alloc] init]; lineRep.lineNumberFormat = HFLineNumberFormatHexadecimal; @@ -250,6 +259,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) [layoutView setFrame:layoutViewFrame]; [layoutView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin]; [self.memoryView addSubview:layoutView]; + + self.memoryBankItem.enabled = false; } + (BOOL)autosavesInPlace { @@ -521,7 +532,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) - (void) performAtomicBlock: (void (^)())block { while (!is_inited); - bool was_running = running; + bool was_running = running && !gb.debug_stopped; if (was_running) { [self stop]; } @@ -546,4 +557,96 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) [self.memoryWindow makeKeyAndOrderFront:sender]; } +- (IBAction)hexGoTo:(id)sender +{ + [self performAtomicBlock:^{ + uint16_t addr; + if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, NULL)) { + NSBeep(); + return; + } + addr -= lineRep.valueOffset; + if (addr >= hex_controller.byteArray.length) { + NSBeep(); + return; + } + [hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]]; + [hex_controller _ensureVisibilityOfLocation:addr]; + [self.memoryWindow makeFirstResponder:self.memoryView.subviews[0].subviews[0]]; + }]; +} + +- (IBAction)hexUpdateBank:(NSControl *)sender +{ + [self performAtomicBlock:^{ + uint16_t addr, bank; + if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, &bank)) { + NSBeep(); + return; + } + + if (bank == (uint16_t) -1) { + bank = addr; + } + + uint16_t n_banks = 1; + switch ([(GBMemoryByteArray *)(hex_controller.byteArray) mode]) { + case GBMemoryROM: + n_banks = gb.rom_size / 0x4000; + break; + case GBMemoryVRAM: + n_banks = gb.is_cgb ? 2 : 1; + break; + case GBMemoryExternalRAM: + n_banks = gb.mbc_ram_size / 0x2000; + break; + case GBMemoryRAM: + n_banks = gb.is_cgb ? 8 : 1; + break; + case GBMemoryEntireSpace: + break; + } + + bank %= n_banks; + + [sender setStringValue:[NSString stringWithFormat:@"$%x", bank]]; + [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:bank]; + [hex_controller reloadData]; + }]; +} + +- (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender +{ + self.memoryBankItem.enabled = [sender indexOfSelectedItem] != GBMemoryEntireSpace; + GBMemoryByteArray *byteArray = (GBMemoryByteArray *)(hex_controller.byteArray); + [byteArray setMode:(GB_memory_mode_t)[sender indexOfSelectedItem]]; + switch ((GB_memory_mode_t)[sender indexOfSelectedItem]) { + case GBMemoryEntireSpace: + case GBMemoryROM: + lineRep.valueOffset = 0; + byteArray.selectedBank = gb.mbc_rom_bank; + break; + case GBMemoryVRAM: + lineRep.valueOffset = 0x8000; + byteArray.selectedBank = gb.cgb_vram_bank; + break; + case GBMemoryExternalRAM: + lineRep.valueOffset = 0xA000; + byteArray.selectedBank = gb.mbc_ram_bank; + break; + case GBMemoryRAM: + lineRep.valueOffset = 0xC000; + byteArray.selectedBank = gb.cgb_ram_bank; + break; + } + [self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]]; + [hex_controller reloadData]; + [self.memoryView setNeedsDisplay:YES]; +} + +- (GB_gameboy_t *) gameboy +{ + return &gb; +} + @end \ No newline at end of file diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index e8f62bf..75827e9 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -10,6 +10,8 @@ + + @@ -69,10 +71,10 @@ - + - + NSAllRomanInputSourcesLocaleIdentifier @@ -84,7 +86,7 @@ - + @@ -125,7 +127,79 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/GBMemoryByteArray.h b/Cocoa/GBMemoryByteArray.h index 9599175..e3ed71f 100644 --- a/Cocoa/GBMemoryByteArray.h +++ b/Cocoa/GBMemoryByteArray.h @@ -2,6 +2,16 @@ #import "HexFiend/HexFiend.h" #import "HexFiend/HFByteArray.h" +typedef enum { + GBMemoryEntireSpace, + GBMemoryROM, + GBMemoryVRAM, + GBMemoryExternalRAM, + GBMemoryRAM +} GB_memory_mode_t; + @interface GBMemoryByteArray : HFByteArray - (instancetype) initWithDocument:(Document *)document; +@property uint16_t selectedBank; +@property GB_memory_mode_t mode; @end diff --git a/Cocoa/GBMemoryByteArray.m b/Cocoa/GBMemoryByteArray.m index 8a8c1e8..a81fb11 100644 --- a/Cocoa/GBMemoryByteArray.m +++ b/Cocoa/GBMemoryByteArray.m @@ -17,16 +17,83 @@ - (unsigned long long)length { - return 0x10000; + switch (_mode) { + case GBMemoryEntireSpace: + return 0x10000; + case GBMemoryROM: + return 0x8000; + case GBMemoryVRAM: + return 0x2000; + case GBMemoryExternalRAM: + return 0x2000; + case GBMemoryRAM: + return 0x2000; + } } - (void)copyBytes:(unsigned char *)dst range:(HFRange)range { - uint16_t addr = (uint16_t) range.location; - unsigned long long length = range.length; - while (length) { - *(dst++) = [_document readMemory:addr++]; - length--; + __block uint16_t addr = (uint16_t) range.location; + __block unsigned long long length = range.length; + if (_mode == GBMemoryEntireSpace) { + while (length) { + *(dst++) = [_document readMemory:addr++]; + length--; + } + } + else { + [_document performAtomicBlock:^{ + unsigned char *_dst = dst; + uint16_t bank_backup = 0; + GB_gameboy_t *gb = _document.gameboy; + switch (_mode) { + case GBMemoryROM: + bank_backup = gb->mbc_rom_bank; + gb->mbc_rom_bank = self.selectedBank; + break; + case GBMemoryVRAM: + bank_backup = gb->cgb_vram_bank; + if (gb->is_cgb) { + gb->cgb_vram_bank = self.selectedBank; + } + addr += 0x8000; + break; + case GBMemoryExternalRAM: + bank_backup = gb->mbc_ram_bank; + gb->mbc_ram_bank = self.selectedBank; + addr += 0xA000; + break; + case GBMemoryRAM: + bank_backup = gb->cgb_ram_bank; + if (gb->is_cgb) { + gb->cgb_ram_bank = self.selectedBank; + } + addr += 0xC000; + break; + default: + assert(false); + } + while (length) { + *(_dst++) = [_document readMemory:addr++]; + length--; + } + switch (_mode) { + case GBMemoryROM: + gb->mbc_rom_bank = bank_backup; + break; + case GBMemoryVRAM: + gb->cgb_vram_bank = bank_backup; + break; + case GBMemoryExternalRAM: + gb->mbc_ram_bank = bank_backup; + break; + case GBMemoryRAM: + gb->cgb_ram_bank = bank_backup; + break; + default: + assert(false); + } + }]; } } @@ -49,15 +116,60 @@ { if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */ [_document performAtomicBlock:^{ + uint16_t addr = (uint16_t) lrange.location; + uint16_t bank_backup = 0; + GB_gameboy_t *gb = _document.gameboy; + switch (_mode) { + case GBMemoryROM: + bank_backup = gb->mbc_rom_bank; + gb->mbc_rom_bank = self.selectedBank; + break; + case GBMemoryVRAM: + bank_backup = gb->cgb_vram_bank; + if (gb->is_cgb) { + gb->cgb_vram_bank = self.selectedBank; + } + addr += 0x8000; + break; + case GBMemoryExternalRAM: + bank_backup = gb->mbc_ram_bank; + gb->mbc_ram_bank = self.selectedBank; + addr += 0xA000; + break; + case GBMemoryRAM: + bank_backup = gb->cgb_ram_bank; + if (gb->is_cgb) { + gb->cgb_ram_bank = self.selectedBank; + } + addr += 0xC000; + break; + default: + break; + } uint8_t values[lrange.length]; [slice copyBytes:values range:HFRangeMake(0, lrange.length)]; - uint16_t addr = (uint16_t) lrange.location; uint8_t *src = values; unsigned long long length = lrange.length; while (length) { [_document writeMemory:addr++ value:*(src++)]; length--; } + switch (_mode) { + case GBMemoryROM: + gb->mbc_rom_bank = bank_backup; + break; + case GBMemoryVRAM: + gb->cgb_vram_bank = bank_backup; + break; + case GBMemoryExternalRAM: + gb->mbc_ram_bank = bank_backup; + break; + case GBMemoryRAM: + gb->cgb_ram_bank = bank_backup; + break; + default: + break; + } }]; } diff --git a/Core/debugger.c b/Core/debugger.c index 3d3fc43..51e015d 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -333,11 +333,11 @@ static struct { }; value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - unsigned int length, bool *error, + size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, - unsigned int length, bool *error, + size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { *error = false; @@ -410,19 +410,19 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; } } - GB_log(gb, "Unknown register: %.*s\n", length, string); + GB_log(gb, "Unknown register: %.*s\n", (unsigned int) length, string); *error = true; return (lvalue_t){0,}; } - GB_log(gb, "Expression is not an lvalue: %.*s\n", length, string); + GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned int) length, string); *error = true; return (lvalue_t){0,}; } #define ERROR ((value_t){0,}) value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - unsigned int length, bool *error, + size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { *error = false; @@ -567,7 +567,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, return (value_t){true, symbol->bank, symbol->addr}; } - GB_log(gb, "Unknown register or symbol: %.*s\n", length, string); + GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string); *error = true; return ERROR; } @@ -581,7 +581,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, } uint16_t literal = (uint16_t) (strtol(string, &end, base)); if (end != string + length) { - GB_log(gb, "Failed to parse: %.*s\n", length, string); + GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string); *error = true; return ERROR; } @@ -1558,4 +1558,18 @@ const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr) const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr); if (symbol && symbol->addr == addr) return symbol->name; return NULL; +} + +/* The public version of debugger_evaluate */ +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank) +{ + bool error = false; + value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL); + if (result) { + *result = value.value; + } + if (result_bank) { + *result_bank = value.has_bank? value.value : -1; + } + return error; } \ No newline at end of file diff --git a/Core/debugger.h b/Core/debugger.h index 4e7808f..882f843 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -11,4 +11,5 @@ void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr); +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank); /* result_bank is -1 if unused. */ #endif /* debugger_h */ diff --git a/HexFiend/HFController.h b/HexFiend/HFController.h index 7bdc070..4a59a4d 100644 --- a/HexFiend/HFController.h +++ b/HexFiend/HFController.h @@ -367,7 +367,7 @@ You create an HFController via [[HFController alloc] init]. After that - (unsigned long long)contentsLength; - (void) reloadData; - +- (void)_ensureVisibilityOfLocation:(unsigned long long)location; @end /*! A notification posted whenever any of the HFController's properties change. The object is the HFController. The userInfo contains one key, HFControllerChangedPropertiesKey, which contains an NSNumber with the changed properties as a HFControllerPropertyBits bitmask. This is useful for external objects to be notified of changes. HFRepresenters added to the HFController are notified via the controllerDidChange: message. diff --git a/HexFiend/HFController.m b/HexFiend/HFController.m index aaae78e..73a31b2 100644 --- a/HexFiend/HFController.m +++ b/HexFiend/HFController.m @@ -1796,7 +1796,7 @@ static BOOL rangesAreInAscendingOrder(NSEnumerator *rangeEnumerator) { [cachedData release]; cachedData = nil; [self _updateDisplayedRange]; - [self _addPropertyChangeBits: HFControllerContentValue]; + [self _addPropertyChangeBits: HFControllerContentValue | HFControllerContentLength]; END_TRANSACTION(); } diff --git a/HexFiend/HFLineCountingView.m b/HexFiend/HFLineCountingView.m index 6c9578d..d111bcd 100644 --- a/HexFiend/HFLineCountingView.m +++ b/HexFiend/HFLineCountingView.m @@ -368,7 +368,7 @@ static inline int common_prefix_length(const char *a, const char *b) { [self getLineNumberFormatString:formatString length:sizeof formatString]; while (lineCount--) { - int charCount = sprintf(buffer + bufferIndex, formatString, lineValue); + int charCount = sprintf(buffer + bufferIndex, formatString, lineValue + self.representer.valueOffset); HFASSERT(charCount > 0); bufferIndex += charCount; buffer[bufferIndex++] = '\n';