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';