diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 32cfb6b..1a722e9 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -1,7 +1,8 @@ #import #include "GBView.h" +#include "GBImageView.h" -@interface Document : NSDocument +@interface Document : NSDocument @property (strong) IBOutlet GBView *view; @property (strong) IBOutlet NSTextView *consoleOutput; @property (strong) IBOutlet NSPanel *consoleWindow; @@ -12,6 +13,18 @@ @property (readonly) GB_gameboy_t *gameboy; @property (strong) IBOutlet NSTextField *memoryBankInput; @property (strong) IBOutlet NSToolbarItem *memoryBankItem; +@property (strong) IBOutlet GBImageView *tilesetImageView; +@property (strong) IBOutlet NSPopUpButton *tilesetPaletteButton; +@property (strong) IBOutlet GBImageView *tilemapImageView; +@property (strong) IBOutlet NSPopUpButton *tilemapPaletteButton; +@property (strong) IBOutlet NSPopUpButton *tilemapMapButton; +@property (strong) IBOutlet NSPopUpButton *TilemapSetButton; +@property (strong) IBOutlet NSButton *gridButton; +@property (strong) IBOutlet NSTabView *vramTabView; +@property (strong) IBOutlet NSPanel *vramWindow; +@property (strong) IBOutlet NSTextField *vramStatusLabel; +@property (strong) IBOutlet NSTableView *paletteTableView; +@property (strong) IBOutlet NSTableView *spritesTableView; -(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 1cf2545..9c38859 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -7,10 +7,12 @@ #include "debugger.h" #include "memory.h" #include "camera.h" +#include "display.h" #include "HexFiend/HexFiend.h" #include "GBMemoryByteArray.h" /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ +/* Todo: Split into category files! This is so messy!!! */ @interface Document () { @@ -30,6 +32,11 @@ AVCaptureSession *cameraSession; AVCaptureConnection *cameraConnection; AVCaptureStillImageOutput *cameraOutput; + + GB_oam_info_t oamInfo[40]; + uint16_t oamCount; + uint8_t oamHeight; + bool oamUpdating; } @property GBAudioClient *audioClient; @@ -140,6 +147,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; [self.view flip]; GB_set_pixels_output(&gb, self.view.pixels); + [self reloadVRAMData: nil]; } - (void) run @@ -262,6 +270,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"], window_frame.size.height); [self.mainWindow setFrame:window_frame display:YES]; + self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised; [self start]; } @@ -450,6 +459,7 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) NSString *nsstring = @(string); // For ref-counting dispatch_async(dispatch_get_main_queue(), ^{ [hex_controller reloadData]; + [self reloadVRAMData: nil]; NSFont *font = [NSFont userFixedPitchFontOfSize:12]; NSUnderlineStyle underline = NSUnderlineStyleNone; @@ -595,6 +605,28 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) } } ++ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale +{ + CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data); + CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; + CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; + + CGImageRef iref = CGImageCreate(width, + height, + 8, + 32, + 4 * width, + colorSpaceRef, + bitmapInfo, + provider, + NULL, + YES, + renderingIntent); + + return [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(width * scale, height * scale)]; +} + - (void) reloadMemoryView { if (self.memoryWindow.isVisible) { @@ -602,6 +634,79 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) } } +- (IBAction) reloadVRAMData: (id) sender +{ + if (self.vramWindow.isVisible) { + switch ([self.vramTabView.tabViewItems indexOfObject:self.vramTabView.selectedTabViewItem]) { + case 0: + /* Tileset */ + { + GB_palette_type_t palette_type = GB_PALETTE_NONE; + NSUInteger palette_menu_index = self.tilesetPaletteButton.indexOfSelectedItem; + if (palette_menu_index) { + palette_type = palette_menu_index > 8? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND; + } + size_t bufferLength = 256 * 192 * 4; + NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength]; + data.length = bufferLength; + GB_draw_tileset(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 1) & 7); + + self.tilesetImageView.image = [Document imageFromData:data width:256 height:192 scale:1.0]; + self.tilesetImageView.layer.magnificationFilter = kCAFilterNearest; + } + break; + + case 1: + /* Tilemap */ + { + GB_palette_type_t palette_type = GB_PALETTE_NONE; + NSUInteger palette_menu_index = self.tilemapPaletteButton.indexOfSelectedItem; + if (palette_menu_index > 1) { + palette_type = palette_menu_index > 9? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND; + } + else if (palette_menu_index == 1) { + palette_type = GB_PALETTE_AUTO; + } + + size_t bufferLength = 256 * 256 * 4; + NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength]; + data.length = bufferLength; + GB_draw_tilemap(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 2) & 7, + (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem, + (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem); + + self.tilemapImageView.image = [Document imageFromData:data width:256 height:256 scale:1.0]; + self.tilemapImageView.layer.magnificationFilter = kCAFilterNearest; + } + break; + + case 2: + /* OAM */ + { + oamCount = GB_get_oam_info(&gb, oamInfo); + oamHeight = (gb.io_registers[GB_IO_LCDC] & 4) ? 16:8; + dispatch_async(dispatch_get_main_queue(), ^{ + if (!oamUpdating) { + oamUpdating = true; + [self.spritesTableView reloadData]; + oamUpdating = false; + } + }); + } + break; + + case 3: + /* Palettes */ + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.paletteTableView reloadData]; + }); + } + break; + } + } +} + - (IBAction) showMemory:(id)sender { if (!hex_controller) { @@ -777,4 +882,195 @@ static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) return ret; } + +- (IBAction)toggleTilesetGrid:(NSButton *)sender +{ + if (sender.state) { + self.tilesetImageView.horizontalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:128], + + ]; + self.tilesetImageView.verticalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:64], + ]; + self.tilemapImageView.horizontalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + ]; + self.tilemapImageView.verticalGrids = @[ + [[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8], + ]; + } + else { + self.tilesetImageView.horizontalGrids = nil; + self.tilesetImageView.verticalGrids = nil; + self.tilemapImageView.horizontalGrids = nil; + self.tilemapImageView.verticalGrids = nil; + } +} + +- (IBAction)vramTabChanged:(NSSegmentedControl *)sender +{ + [self.vramTabView selectTabViewItemAtIndex:[sender selectedSegment]]; + [self reloadVRAMData:sender]; + [self.vramTabView.selectedTabViewItem.view addSubview:self.gridButton]; + self.gridButton.hidden = [sender selectedSegment] >= 2; + + NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height; + CGRect window_rect = self.vramWindow.frame; + window_rect.origin.y += window_rect.size.height; + switch ([sender selectedSegment]) { + case 0: + window_rect.size.height = 384 + height_diff + 48; + break; + case 1: + case 2: + window_rect.size.height = 512 + height_diff + 48; + break; + case 3: + window_rect.size.height = 20 * 16 + height_diff + 24; + break; + + default: + break; + } + window_rect.origin.y -= window_rect.size.height; + [self.vramWindow setFrame:window_rect display:YES animate:YES]; +} + +- (void)mouseDidLeaveImageView:(GBImageView *)view +{ + self.vramStatusLabel.stringValue = @""; +} + +- (void)imageView:(GBImageView *)view mouseMovedToX:(NSUInteger)x Y:(NSUInteger)y +{ + if (view == self.tilesetImageView) { + uint8_t bank = x >= 128? 1 : 0; + x &= 127; + uint16_t tile = x / 8 + y / 8 * 16; + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x at %d:$%04x", tile & 0xFF, bank, 0x8000 + tile * 0x10]; + } + else if (view == self.tilemapImageView) { + uint16_t map_offset = x / 8 + y / 8 * 32; + uint16_t map_base = 0x1800; + GB_map_type_t map_type = (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem; + GB_tileset_type_t tileset_type = (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem; + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb.io_registers[GB_IO_LCDC] & 0x08)) { + map_base = 0x1c00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (gb.io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + uint8_t tile = gb.vram[map_base + map_offset]; + uint16_t tile_address = 0; + if (tileset_type == GB_TILESET_8000) { + tile_address = 0x8000 + tile * 0x10; + } + else { + tile_address = 0x9000 + (int8_t)tile * 0x10; + } + + if (gb.is_cgb) { + uint8_t attributes = gb.vram[map_base + map_offset + 0x2000]; + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x (%d:$%04x) at map address $%04x (Attributes: %c%c%c%d%d)", + tile, + attributes & 0x8? 1 : 0, + tile_address, + 0x8000 + map_base + map_offset, + (attributes & 0x80) ? 'P' : '-', + (attributes & 0x40) ? 'V' : '-', + (attributes & 0x20) ? 'H' : '-', + attributes & 0x8? 1 : 0, + attributes & 0x7 + ]; + } + else { + self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x ($%04x) at map address $%04x", + tile, + tile_address, + 0x8000 + map_base + map_offset + ]; + } + + } +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + if (tableView == self.paletteTableView) { + return 16; /* 8 BG palettes, 8 OBJ palettes*/ + } + else if (tableView == self.spritesTableView) { + return oamCount; + } + return 0; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (tableView == self.paletteTableView) { + if (columnIndex == 0) { + return [NSString stringWithFormat:@"%s %d", row >=8? "Object" : "Background", (int)(row & 7)]; + } + + uint16_t index = columnIndex - 1 + (row & 7) * 4; + return @(((row >= 8? gb.sprite_palletes_data : gb.background_palletes_data)[(index << 1) + 1] << 8) | + (row >= 8? gb.sprite_palletes_data : gb.background_palletes_data)[(index << 1)]); + } + else if (tableView == self.spritesTableView) { + switch (columnIndex) { + case 0: + return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image length:64 * 4] width:8 height:oamHeight scale:16.0/oamHeight]; + case 1: + return @((int)oamInfo[row].x - 8); + case 2: + return @((int)oamInfo[row].y - 16); + case 3: + return [NSString stringWithFormat:@"$%02x", oamInfo[row].tile]; + case 4: + return [NSString stringWithFormat:@"$%04x", 0x8000 + oamInfo[row].tile * 0x10]; + case 5: + return [NSString stringWithFormat:@"$%04x", oamInfo[row].oam_addr]; + case 6: + if (gb.cgb_mode) { + return [NSString stringWithFormat:@"%c%c%c%d%d", + oamInfo[row].flags & 0x80? 'P' : '-', + oamInfo[row].flags & 0x40? 'Y' : '-', + oamInfo[row].flags & 0x20? 'X' : '-', + oamInfo[row].flags & 0x08? 1 : 0, + oamInfo[row].flags & 0x07]; + } + return [NSString stringWithFormat:@"%c%c%c%d", + oamInfo[row].flags & 0x80? 'P' : '-', + oamInfo[row].flags & 0x40? 'Y' : '-', + oamInfo[row].flags & 0x20? 'X' : '-', + oamInfo[row].flags & 0x10? 1 : 0]; + case 7: + return oamInfo[row].obscured_by_line_limit? @"Dropped: Too many sprites in line": @""; + + } + } + return nil; +} + +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row +{ + return tableView == self.spritesTableView; +} + +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row +{ + return NO; +} + +- (IBAction)showVRAMViewer:(id)sender +{ + [self.vramWindow makeKeyAndOrderFront:sender]; +} @end \ No newline at end of file diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index 75827e9..c9cf907 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -6,15 +6,27 @@ + + + + + + + + + + + + @@ -50,6 +62,7 @@ + @@ -201,5 +214,470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/GBColorCell.h b/Cocoa/GBColorCell.h new file mode 100644 index 0000000..a622c78 --- /dev/null +++ b/Cocoa/GBColorCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBColorCell : NSTextFieldCell + +@end diff --git a/Cocoa/GBColorCell.m b/Cocoa/GBColorCell.m new file mode 100644 index 0000000..afbbc8d --- /dev/null +++ b/Cocoa/GBColorCell.m @@ -0,0 +1,42 @@ +#import "GBColorCell.h" + +static inline double scale_channel(uint8_t x) +{ + x &= 0x1f; + return x / 31.0; +} + +@implementation GBColorCell +{ + NSInteger _integerValue; +} + +- (void)setObjectValue:(id)objectValue +{ + _integerValue = [objectValue integerValue]; + super.objectValue = [NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)]; +} + +- (NSInteger)integerValue +{ + return _integerValue; +} + +- (int)intValue +{ + return (int)_integerValue; +} + + +- (NSColor *) backgroundColor +{ + uint16_t color = self.integerValue; + return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0]; +} + +- (BOOL)drawsBackground +{ + return YES; +} + +@end diff --git a/Cocoa/GBImageCell.h b/Cocoa/GBImageCell.h new file mode 100644 index 0000000..0323b41 --- /dev/null +++ b/Cocoa/GBImageCell.h @@ -0,0 +1,5 @@ +#import + +@interface GBImageCell : NSImageCell + +@end diff --git a/Cocoa/GBImageCell.m b/Cocoa/GBImageCell.m new file mode 100644 index 0000000..6f54ec8 --- /dev/null +++ b/Cocoa/GBImageCell.m @@ -0,0 +1,10 @@ +#import "GBImageCell.h" + +@implementation GBImageCell +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; + CGContextSetInterpolationQuality(context, kCGInterpolationNone); + [super drawWithFrame:cellFrame inView:controlView]; +} +@end diff --git a/Cocoa/GBImageView.h b/Cocoa/GBImageView.h new file mode 100644 index 0000000..22a8829 --- /dev/null +++ b/Cocoa/GBImageView.h @@ -0,0 +1,21 @@ +#import + +@protocol GBImageViewDelegate; + +@interface GBImageViewGridConfiguration : NSObject +@property NSColor *color; +@property NSUInteger size; +- (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size; +@end + +@interface GBImageView : NSImageView +@property (nonatomic) NSArray *horizontalGrids; +@property (nonatomic) NSArray *verticalGrids; +@property (weak) IBOutlet id delegate; +@end + +@protocol GBImageViewDelegate +@optional +- (void) mouseDidLeaveImageView: (GBImageView *)view; +- (void) imageView: (GBImageView *)view mouseMovedToX:(NSUInteger) x Y:(NSUInteger) y; +@end diff --git a/Cocoa/GBImageView.m b/Cocoa/GBImageView.m new file mode 100644 index 0000000..674d4b2 --- /dev/null +++ b/Cocoa/GBImageView.m @@ -0,0 +1,92 @@ +#import "GBImageView.h" + +@implementation GBImageViewGridConfiguration +- (instancetype)initWithColor:(NSColor *)color size:(NSUInteger)size +{ + self = [super init]; + self.color = color; + self.size = size; + return self; +} +@end + +@implementation GBImageView +{ + NSTrackingArea *trackingArea; +} +- (void)drawRect:(NSRect)dirtyRect +{ + CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; + CGContextSetInterpolationQuality(context, kCGInterpolationNone); + [super drawRect:dirtyRect]; + CGFloat y_ratio = self.frame.size.height / self.image.size.height; + CGFloat x_ratio = self.frame.size.width / self.image.size.width; + for (GBImageViewGridConfiguration *conf in self.verticalGrids) { + [conf.color set]; + for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) { + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(0, y + 0.5)]; + [line lineToPoint:NSMakePoint(self.frame.size.width, y + 0.5)]; + [line setLineWidth:1.0]; + [line stroke]; + } + } + + for (GBImageViewGridConfiguration *conf in self.horizontalGrids) { + [conf.color set]; + for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) { + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(x + 0.5, 0)]; + [line lineToPoint:NSMakePoint(x + 0.5, self.frame.size.height)]; + [line setLineWidth:1.0]; + [line stroke]; + } + } +} + +- (void)setHorizontalGrids:(NSArray *)horizontalGrids +{ + self->_horizontalGrids = horizontalGrids; + [self setNeedsDisplay]; +} + +- (void)setVerticalGrids:(NSArray *)verticalGrids +{ + self->_verticalGrids = verticalGrids; + [self setNeedsDisplay]; +} + +- (void)updateTrackingAreas +{ + if(trackingArea != nil) { + [self removeTrackingArea:trackingArea]; + } + + trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + if ([self.delegate respondsToSelector:@selector(mouseDidLeaveImageView:)]) { + [self.delegate mouseDidLeaveImageView:self]; + } +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + if ([self.delegate respondsToSelector:@selector(imageView:mouseMovedToX:Y:)]) { + NSPoint location = [self convertPoint:theEvent.locationInWindow fromView:nil]; + location.x /= self.bounds.size.width; + location.y /= self.bounds.size.height; + location.y = 1 - location.y; + location.x *= self.image.size.width; + location.y *= self.image.size.height; + [self.delegate imageView:self mouseMovedToX:(NSUInteger)location.x Y:(NSUInteger)location.y]; + } +} + +@end diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index ae3a500..85376a2 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -375,6 +375,12 @@ + + + + + + diff --git a/Core/display.c b/Core/display.c index 8dde5a9..66d7f2b 100755 --- a/Core/display.c +++ b/Core/display.c @@ -11,14 +11,12 @@ #include #endif -#pragma pack(push, 1) -typedef struct { +typedef struct __attribute__((packed)) { uint8_t y; uint8_t x; uint8_t tile; uint8_t flags; } GB_sprite_t; -#pragma pack(pop) static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) { @@ -439,3 +437,178 @@ updateSTAT: gb->io_registers[GB_IO_IF] |= 2; } } + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + + switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + default: + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palletes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palletes_rgb + (4 * (palette_index & 7)); + break; + } + + for (unsigned y = 0; y < 192; y++) { + for (unsigned x = 0; x < 256; x++) { + if (x >= 128 && !gb->is_cgb) { + *(dest++) = gb->background_palletes_rgb[0]; + continue; + } + uint16_t tile = (x % 128) / 8 + y / 8 * 16; + uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0); + uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) | + ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1); + + if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_BACKGROUND) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + else if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_OAM) { + pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3); + } + } + } + + + *(dest++) = palette[pixel]; + } + } +} + +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + uint16_t map = 0x1800; + + switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palletes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palletes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_AUTO: + break; + } + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) { + map = 0x1c00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + for (unsigned y = 0; y < 256; y++) { + for (unsigned x = 0; x < 256; x++) { + uint8_t tile = gb->vram[map + x/8 + y/8 * 32]; + uint16_t tile_address; + uint8_t attributes = 0; + + if (tileset_type == GB_TILESET_8800) { + tile_address = tile * 0x10; + } + else { + tile_address = (int8_t) tile * 0x10 + 0x1000; + } + + if (gb->cgb_mode) { + attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000]; + } + + if (attributes & 0x8) { + tile_address += 0x2000; + } + + uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) | + ((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1); + + if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + + if (palette) { + *(dest++) = palette[pixel]; + } + else { + *(dest++) = gb->background_palletes_rgb[(attributes & 7) * 4 + pixel]; + } + } + } +} + +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest) +{ + uint8_t count = 0; + unsigned sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; + uint8_t oam_to_dest_index[40] = {0,}; + for (unsigned y = 0; y < 144; y++) { + GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; + uint8_t sprites_in_line = 0; + for (uint8_t i = 0; i < 40; i++, sprite++) { + int sprite_y = sprite->y - 16; + bool obscured = false; + // Is sprite not in this line? + if (sprite_y > y || sprite_y + sprite_height <= y) continue; + if (++sprites_in_line == 11) obscured = true; + + GB_oam_info_t *info = NULL; + if (!oam_to_dest_index[i]) { + info = dest + count; + oam_to_dest_index[i] = ++count; + info->x = sprite->x; + info->y = sprite->y; + info->tile = sprite_height == 16? sprite->tile & 0xFE : sprite->tile; + info->flags = sprite->flags; + info->obscured_by_line_limit = false; + info->oam_addr = 0xFE00 + i * sizeof(*sprite); + } + else { + info = dest + oam_to_dest_index[i] - 1; + } + info->obscured_by_line_limit |= obscured; + } + } + + + for (unsigned i = 0; i < count; i++) { + uint16_t vram_address = dest[i].tile * 0x10; + uint8_t flags = dest[i].flags; + uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0); + + for (unsigned y = 0; y < sprite_height; y++) { + for (unsigned x = 0; x < 8; x++) { + uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | + ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); + + if (!gb->cgb_mode) { + color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3; + } + dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?sprite_height - 1 -y:y) * 8] = gb->sprite_palletes_rgb[palette * 4 + color]; + } + vram_address += 2; + } + } + return count; +} \ No newline at end of file diff --git a/Core/display.h b/Core/display.h index c154284..8cc6bf7 100644 --- a/Core/display.h +++ b/Core/display.h @@ -4,4 +4,35 @@ #include "gb.h" void GB_display_run(GB_gameboy_t *gb); void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); + +typedef enum { + GB_PALETTE_NONE, + GB_PALETTE_BACKGROUND, + GB_PALETTE_OAM, + GB_PALETTE_AUTO, +} GB_palette_type_t; + +typedef enum { + GB_MAP_AUTO, + GB_MAP_9800, + GB_MAP_9C00, +} GB_map_type_t; + +typedef enum { + GB_TILESET_AUTO, + GB_TILESET_8800, + GB_TILESET_8000, +} GB_tileset_type_t; + +typedef struct { + uint32_t image[128]; + uint8_t x, y, tile, flags; + uint16_t oam_addr; + bool obscured_by_line_limit; +} GB_oam_info_t; + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); + +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest); #endif /* display_h */ diff --git a/Core/gb.c b/Core/gb.c index 6e82c34..2d5fff4 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -95,7 +95,9 @@ void GB_init(GB_gameboy_t *gb) gb->magic = (uintptr_t)'SAME'; gb->version = GB_STRUCT_VERSION; gb->ram = malloc(gb->ram_size = 0x2000); + memset(gb->ram, 0, gb->ram_size); gb->vram = malloc(gb->vram_size = 0x2000); + memset(gb->vram, 0, gb->vram_size); gb->mbc_rom_bank = 1; gb->last_rtc_second = time(NULL); @@ -121,7 +123,9 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->magic = (uintptr_t)'SAME'; gb->version = GB_STRUCT_VERSION; gb->ram = malloc(gb->ram_size = 0x2000 * 8); + memset(gb->ram, 0, gb->ram_size); gb->vram = malloc(gb->vram_size = 0x2000 * 2); + memset(gb->vram, 0, gb->vram_size); gb->is_cgb = true; gb->cgb_mode = true; diff --git a/HexFiend/HFController.m b/HexFiend/HFController.m index 73a31b2..74e033c 100644 --- a/HexFiend/HFController.m +++ b/HexFiend/HFController.m @@ -19,11 +19,7 @@ /* Used for the anchor range and location */ #define NO_SELECTION ULLONG_MAX -#if ! NDEBUG #define VALIDATE_SELECTION() [self _ensureSelectionIsValid] -#else -#define VALIDATE_SELECTION() do { } while (0) -#endif #define BENCHMARK_BYTEARRAYS 0 @@ -456,7 +452,6 @@ static inline Class preferredByteArrayClass(void) { return [HFRangeWrapper organizeAndMergeRanges:result]; } -#if ! NDEBUG - (void)_ensureSelectionIsValid { HFASSERT(selectedContentsRanges != nil); HFASSERT([selectedContentsRanges count] > 0); @@ -464,11 +459,13 @@ static inline Class preferredByteArrayClass(void) { FOREACH(HFRangeWrapper*, wrapper, selectedContentsRanges) { EXPECT_CLASS(wrapper, HFRangeWrapper); HFRange range = [wrapper HFRange]; - HFASSERT(HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))); + if (!HFRangeIsSubrangeOfRange(range, HFRangeMake(0, [self contentsLength]))){ + [self setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(0, 0)]]]; + return; + } if (onlyOneWrapper == NO) HFASSERT(range.length > 0); /* If we have more than one wrapper, then none of them should be zero length */ } } -#endif - (void)_setSingleSelectedContentsRange:(HFRange)newSelection { HFASSERT(HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength]))); diff --git a/Makefile b/Makefile index 8fe7705..f91fc8d 100755 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ endif ifeq ($(PLATFORM),Darwin) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(shell xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -mmacosx-version-min=10.9 -LDFLAGS += -framework AppKit -framework Carbon +LDFLAGS += -framework AppKit -framework Carbon -framework QuartzCore SDL_LDFLAGS := -framework SDL endif