Initial public commit

This commit is contained in:
Lior Halphon 2016-03-30 23:07:55 +03:00
commit f1e9623371
42 changed files with 7947 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

BIN
BootROMs/SameboyLogo.1bpp Normal file

Binary file not shown.

BIN
BootROMs/SameboyLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

1087
BootROMs/cgb_boot.asm Normal file

File diff suppressed because it is too large Load Diff

141
BootROMs/dmg_boot.asm Normal file
View File

@ -0,0 +1,141 @@
; Sameboy CGB bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
SECTION "BootCode", ROM0[$0]
Start:
; Init stack pointer
ld sp, $fffe
; Clear memory VRAM
ld hl, $8000
.clearVRAMLoop
ldi [hl], a
bit 5, h
jr z, .clearVRAMLoop
; Init Audio
ld a, $80
ldh [$26], a
ldh [$11], a
ld a, $f3
ldh [$12], a
ldh [$25], a
ld a, $77
ldh [$24], a
; Init BG palette
ld a, $fc
ldh [$47], a
; Load logo from ROM.
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
; Tiles are ordered left to right, top to bottom.
ld de, $104 ; Logo start
ld hl, $8010 ; This is where we load the tiles in VRAM
.loadLogoLoop
ld a, [de] ; Read 2 rows
ld b, a
call DoubleBitsAndWriteRow
call DoubleBitsAndWriteRow
inc de
ld a, e
xor $34 ; End of logo
jr nz, .loadLogoLoop
; Load trademark symbol
ld de, TrademarkSymbol
ld c,$08
.loadTrademarkSymbolLoop:
ld a,[de]
inc de
ldi [hl],a
inc hl
dec c
jr nz, .loadTrademarkSymbolLoop
; Set up tilemap
ld a,$19 ; Trademark symbol
ld [$9910], a ; ... put in the superscript position
ld hl,$992f ; Bottom right corner of the logo
ld c,$c ; Tiles in a logo row
.tilemapLoop
dec a
jr z, .tilemapDone
ldd [hl], a
dec c
jr nz, .tilemapLoop
ld l,$0f ; Jump to top row
jr .tilemapLoop
.tilemapDone
; Turn on LCD
ld a, $91
ldh [$40], a
; Wait ~0.75 seconds
ld b, 45
call WaitBFrames
; Play first sound
ld a, $83
call PlaySound
ld b, 15
call WaitBFrames
; Play second sound
ld a, $c1
call PlaySound
; Wait ~2.5 seconds
ld b, 150
call WaitBFrames
; Boot the game
jp BootGame
DoubleBitsAndWriteRow:
; Double the most significant 4 bits, b is shifted by 4
ld a, 4
ld c, 0
.doubleCurrentBit
sla b
push af
rl c
pop af
rl c
dec a
jr nz, .doubleCurrentBit
ld a, c
; Write as two rows
ldi [hl], a
inc hl
ldi [hl], a
inc hl
ret
WaitFrame:
ldh a, [$44]
cp $90
jr nz, WaitFrame
ret
WaitBFrames:
call WaitFrame
dec b
jr nz, WaitBFrames
ret
PlaySound:
ldh [$13], a
ld a, $87
ldh [$14], a
ret
TrademarkSymbol:
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
SECTION "BootGame", ROM0[$fc]
BootGame:
ld a, 1
ldh [$50], a

7
Cocoa/AppDelegate.h Normal file
View File

@ -0,0 +1,7 @@
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end

17
Cocoa/AppDelegate.m Normal file
View File

@ -0,0 +1,17 @@
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
@end

BIN
Cocoa/AppIcon.icns Normal file

Binary file not shown.

11
Cocoa/AudioClient.h Normal file
View File

@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
@interface AudioClient : NSObject
@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer);
@property (readonly) UInt32 rate;
@property (readonly, getter=isPlaying) bool playing;
-(void) start;
-(void) stop;
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer)) block
andSampleRate:(UInt32) rate;
@end

115
Cocoa/AudioClient.m Normal file
View File

@ -0,0 +1,115 @@
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import "AudioClient.h"
static OSStatus render(
AudioClient *self,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
// This is a mono tone generator so we only need the first buffer
const int channel = 0;
SInt16 *buffer = (SInt16 *)ioData->mBuffers[channel].mData;
self.renderBlock(self.rate, inNumberFrames, buffer);
return noErr;
}
@implementation AudioClient
{
AudioComponentInstance audioUnit;
}
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer)) block
andSampleRate:(UInt32) rate
{
if(!(self = [super init]))
{
return nil;
}
// Configure the search parameters to find the default playback output unit
// (called the kAudioUnitSubType_RemoteIO on iOS but
// kAudioUnitSubType_DefaultOutput on Mac OS X)
AudioComponentDescription defaultOutputDescription;
defaultOutputDescription.componentType = kAudioUnitType_Output;
defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
defaultOutputDescription.componentFlags = 0;
defaultOutputDescription.componentFlagsMask = 0;
// Get the default playback output unit
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
NSAssert(defaultOutput, @"Can't find default output");
// Create a new unit based on this that we'll use for output
OSErr err = AudioComponentInstanceNew(defaultOutput, &audioUnit);
NSAssert1(audioUnit, @"Error creating unit: %hd", err);
// Set our tone rendering function on the unit
AURenderCallbackStruct input;
input.inputProc = (void*)render;
input.inputProcRefCon = (__bridge void * _Nullable)(self);
err = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&input,
sizeof(input));
NSAssert1(err == noErr, @"Error setting callback: %hd", err);
AudioStreamBasicDescription streamFormat;
streamFormat.mSampleRate = rate;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags =
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
streamFormat.mBytesPerPacket = 2;
streamFormat.mFramesPerPacket = 1;
streamFormat.mBytesPerFrame = 2;
streamFormat.mChannelsPerFrame = 1;
streamFormat.mBitsPerChannel = 2 * 8;
err = AudioUnitSetProperty (audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&streamFormat,
sizeof(AudioStreamBasicDescription));
NSAssert1(err == noErr, @"Error setting stream format: %hd", err);
err = AudioUnitInitialize(audioUnit);
NSAssert1(err == noErr, @"Error initializing unit: %hd", err);
self.renderBlock = block;
_rate = rate;
return self;
}
-(void) start
{
OSErr err = AudioOutputUnitStart(audioUnit);
NSAssert1(err == noErr, @"Error starting unit: %hd", err);
_playing = YES;
}
-(void) stop
{
AudioOutputUnitStop(audioUnit);
_playing = NO;
}
-(void) dealloc {
[self stop];
AudioUnitUninitialize(audioUnit);
AudioComponentInstanceDispose(audioUnit);
}
@end

BIN
Cocoa/Cartridge.icns Normal file

Binary file not shown.

BIN
Cocoa/ColorCartridge.icns Normal file

Binary file not shown.

12
Cocoa/Document.h Normal file
View File

@ -0,0 +1,12 @@
#import <Cocoa/Cocoa.h>
#include "GBView.h"
@interface Document : NSDocument <NSWindowDelegate>
@property (strong) IBOutlet GBView *view;
@property (strong) IBOutlet NSTextView *consoleOutput;
@property (strong) IBOutlet NSPanel *consoleWindow;
@property (strong) IBOutlet NSTextField *consoleInput;
@end

366
Cocoa/Document.m Normal file
View File

@ -0,0 +1,366 @@
#include <CoreAudio/CoreAudio.h>
#include "AudioClient.h"
#import "Document.h"
#include "gb.h"
@interface Document ()
{
/* NSTextViews freeze the entire app if they're modified too often and too quickly.
We use this bool to tune down the write speed. Let me know if there's a more
reasonable alternative to this. */
unsigned long pendingLogLines;
bool tooMuchLogs;
}
@property AudioClient *audioClient;
- (void) vblank;
- (void) log: (const char *) log withAttributes: (gb_log_attributes) attributes;
- (const char *) getDebuggerInput;
@end
static void vblank(GB_gameboy_t *gb)
{
Document *self = (__bridge Document *)(gb->user_data);
[self vblank];
}
static void consoleLog(GB_gameboy_t *gb, const char *string, gb_log_attributes attributes)
{
Document *self = (__bridge Document *)(gb->user_data);
[self log:string withAttributes: attributes];
}
static char *consoleInput(GB_gameboy_t *gb)
{
Document *self = (__bridge Document *)(gb->user_data);
return strdup([self getDebuggerInput]);
}
static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b)
{
return (r << 24) | (g << 16) | (b << 8);
}
@implementation Document
{
GB_gameboy_t gb;
volatile bool running;
volatile bool stopping;
NSConditionLock *has_debugger_input;
NSMutableArray *debugger_input_queue;
bool is_inited;
}
- (instancetype)init {
self = [super init];
if (self) {
has_debugger_input = [[NSConditionLock alloc] initWithCondition:0];
debugger_input_queue = [[NSMutableArray alloc] init];
[self initCGB];
}
return self;
}
- (void) initDMG
{
gb_init(&gb);
gb_load_bios(&gb, [[[NSBundle mainBundle] pathForResource:@"dmg_boot" ofType:@"bin"] UTF8String]);
gb_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
gb_set_log_callback(&gb, (GB_log_callback_t) consoleLog);
gb_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
gb_set_rgb_encode_callback(&gb, rgbEncode);
gb.user_data = (__bridge void *)(self);
}
- (void) initCGB
{
gb_init_cgb(&gb);
gb_load_bios(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]);
gb_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
gb_set_log_callback(&gb, (GB_log_callback_t) consoleLog);
gb_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
gb_set_rgb_encode_callback(&gb, rgbEncode);
gb.user_data = (__bridge void *)(self);
}
- (void) vblank
{
[self.view flip];
gb_set_pixels_output(&gb, self.view.pixels);
}
- (void) run
{
running = true;
gb_set_pixels_output(&gb, self.view.pixels);
self.view.gb = &gb;
gb_set_sample_rate(&gb, 96000);
self.audioClient = [[AudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, SInt16 *buffer) {
//apu_render(&gb, sampleRate, nFrames, buffer);
apu_copy_buffer(&gb, buffer, nFrames);
} andSampleRate:96000];
[self.audioClient start];
while (running) {
gb_run(&gb);
}
[self.audioClient stop];
gb_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
stopping = false;
}
- (void) start
{
if (running) return;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self run];
});
}
- (void) stop
{
if (!running) return;
if (gb.debug_stopped) {
gb.debug_stopped = false;
[self consoleInput:nil];
}
stopping = true;
running = false;
while (stopping);
}
- (IBAction)reset:(id)sender
{
bool was_cgb = gb.is_cgb;
[self stop];
gb_free(&gb);
is_inited = false;
if (([sender tag] == 0 && was_cgb) || [sender tag] == 2) {
[self initCGB];
}
else {
[self initDMG];
}
[self readFromFile:self.fileName ofType:@"gb"];
[self start];
}
- (IBAction)togglePause:(id)sender
{
if (running) {
[self stop];
}
else {
[self start];
}
}
- (void)dealloc
{
gb_free(&gb);
}
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
[super windowControllerDidLoadNib:aController];
self.consoleOutput.textContainerInset = NSMakeSize(4, 4);
[self.view becomeFirstResponder];
[self start];
}
+ (BOOL)autosavesInPlace {
return YES;
}
- (NSString *)windowNibName {
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return @"Document";
}
- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type
{
if (is_inited++) {
return YES;
}
gb_load_rom(&gb, [fileName UTF8String]);
gb_load_battery(&gb, [[[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
return YES;
}
- (void)close
{
[self stop];
[self.consoleWindow close];
[super close];
}
- (IBAction) interrupt:(id)sender
{
[self log:"^C\n"];
gb.debug_stopped = true;
[self.consoleInput becomeFirstResponder];
}
- (IBAction)mute:(id)sender
{
if (self.audioClient.isPlaying) {
[self.audioClient stop];
}
else {
[self.audioClient start];
}
}
- (IBAction)toggleBlend:(id)sender
{
self.view.shouldBlendFrameWithPrevious ^= YES;
}
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
if([anItem action] == @selector(mute:)) {
[(NSMenuItem*)anItem setState:!self.audioClient.isPlaying];
}
if ([anItem action] == @selector(togglePause:)) {
[(NSMenuItem*)anItem setState:!running];
}
if ([anItem action] == @selector(reset:) && anItem.tag != 0) {
[(NSMenuItem*)anItem setState:(anItem.tag == 1 && !gb.is_cgb) || (anItem.tag == 2 && gb.is_cgb)];
}
if([anItem action] == @selector(toggleBlend:)) {
[(NSMenuItem*)anItem setState:self.view.shouldBlendFrameWithPrevious];
}
return [super validateUserInterfaceItem:anItem];
}
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame
{
NSRect rect = window.contentView.frame;
int titlebarSize = window.contentView.superview.frame.size.height - rect.size.height;
int step = 160 / [[window screen] backingScaleFactor];
rect.size.width = floor(rect.size.width / step) * step + step;
rect.size.height = rect.size.width / 10 * 9 + titlebarSize;
if (rect.size.width > newFrame.size.width) {
rect.size.width = 160;
rect.size.height = 144 + titlebarSize;
}
else if (rect.size.height > newFrame.size.height) {
rect.size.width = 160;
rect.size.height = 144 + titlebarSize;
}
rect.origin = window.frame.origin;
rect.origin.y -= rect.size.height - window.frame.size.height;
return rect;
}
- (void) log: (const char *) string withAttributes: (gb_log_attributes) attributes
{
if (pendingLogLines > 128) {
/* The ROM causes so many errors in such a short time, and we can't handle it. */
tooMuchLogs = true;
return;
}
pendingLogLines++;
NSString *nsstring = @(string); // For ref-counting
dispatch_async(dispatch_get_main_queue(), ^{
NSFont *font = [NSFont userFixedPitchFontOfSize:12];
NSUnderlineStyle underline = NSUnderlineStyleNone;
if (attributes & GB_LOG_BOLD) {
font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask];
}
if (attributes & GB_LOG_UNDERLINE_MASK) {
underline = (attributes & GB_LOG_UNDERLINE_MASK) == GB_LOG_DASHED_UNDERLINE? NSUnderlinePatternDot | NSUnderlineStyleSingle : NSUnderlineStyleSingle;
}
NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init];
[paragraph_style setLineSpacing:2];
NSAttributedString *attributed =
[[NSAttributedString alloc] initWithString:nsstring
attributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: [NSColor whiteColor],
NSUnderlineStyleAttributeName: @(underline),
NSParagraphStyleAttributeName: paragraph_style}];
[self.consoleOutput.textStorage appendAttributedString:attributed];
if (pendingLogLines == 1) {
if (tooMuchLogs) {
tooMuchLogs = false;
[self log:"[...]\n"];
}
[self.consoleOutput scrollToEndOfDocument:nil];
[self.consoleWindow orderBack:nil];
}
pendingLogLines--;
});
}
- (IBAction)showConsoleWindow:(id)sender
{
[self.consoleWindow orderBack:nil];
}
- (IBAction)consoleInput:(NSTextField *)sender {
NSString *line = [sender stringValue];
if (!line) {
line = @"";
}
[self log:[line UTF8String]];
[self log:"\n"];
[has_debugger_input lock];
[debugger_input_queue addObject:line];
[has_debugger_input unlockWithCondition:1];
[sender setStringValue:@""];
}
- (const char *) getDebuggerInput
{
[self log:">"];
[has_debugger_input lockWhenCondition:1];
NSString *input = [debugger_input_queue firstObject];
[debugger_input_queue removeObjectAtIndex:0];
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
return [input UTF8String];
}
- (IBAction)saveState:(id)sender
{
bool was_running = running;
if (!gb.debug_stopped) {
[self stop];
}
gb_save_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]);
if (was_running) {
[self start];
}
}
- (IBAction)loadState:(id)sender
{
bool was_running = running;
if (!gb.debug_stopped) {
[self stop];
}
gb_load_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]);
if (was_running) {
[self start];
}
}
- (IBAction)clearConsole:(id)sender
{
[self.consoleOutput setString:@""];
}
- (void)log:(const char *)log
{
[self log:log withAttributes:0];
}
@end

109
Cocoa/Document.xib Normal file
View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8191" systemVersion="14F27" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8191"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Document">
<connections>
<outlet property="consoleInput" destination="l22-S8-uji" id="Heu-am-YgB"/>
<outlet property="consoleOutput" destination="doS-dM-hnl" id="Gn5-ju-Wb0"/>
<outlet property="consoleWindow" destination="21F-Ah-yHX" id="eQ4-ug-LsT"/>
<outlet property="view" destination="uqf-pe-VAF" id="HMP-rf-Yqk"/>
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="133" y="235" width="160" height="144"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<value key="minSize" type="size" width="160" height="144"/>
<view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<openGLView colorSize="5bit_RGB_8bit_Alpha" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" id="uqf-pe-VAF" customClass="GBView">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</openGLView>
</subviews>
</view>
<connections>
<outlet property="delegate" destination="-2" id="jvM-lC-fKb"/>
</connections>
<point key="canvasLocation" x="293" y="347"/>
</window>
<window title="Debug Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="21F-Ah-yHX" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="320" height="240"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<value key="minSize" type="size" width="320" height="240"/>
<view key="contentView" id="dCP-E5-7Fi">
<rect key="frame" x="0.0" y="0.0" width="320" height="240"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="oTo-zx-o6N">
<rect key="frame" x="0.0" y="25" width="320" height="216"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
<rect key="frame" x="0.0" y="0.0" width="320" height="216"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" baseWritingDirection="leftToRight" findStyle="bar" verticallyResizable="YES" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
<rect key="frame" x="0.0" y="0.0" width="320" height="216"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.14901960784313725" green="0.14901960784313725" blue="0.14901960784313725" alpha="1" colorSpace="calibratedRGB"/>
<size key="minSize" width="320" height="216"/>
<size key="maxSize" width="463" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="320" height="216"/>
<size key="maxSize" width="463" height="10000000"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textView>
</subviews>
<color key="backgroundColor" red="0.16470588235294117" green="0.16470588235294117" blue="0.16470588235294117" alpha="1" colorSpace="calibratedRGB"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="3fZ-tl-Zi7">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="cwi-6E-rbh">
<rect key="frame" x="304" y="0.0" width="16" height="216"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField focusRingType="none" verticalHuggingPriority="750" mirrorLayoutDirectionWhenInternationalizing="never" id="l22-S8-uji">
<rect key="frame" x="0.0" y="0.0" width="320" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" focusRingType="none" id="p3j-nS-44f">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" red="0.16470588235294117" green="0.16470588235294117" blue="0.16470588235294117" alpha="1" colorSpace="calibratedRGB"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
<connections>
<action selector="consoleInput:" target="-2" id="ylQ-vw-ARS"/>
</connections>
</textField>
<box verticalHuggingPriority="750" title="Box" boxType="separator" titlePosition="noTitle" id="960-dL-7ZY">
<rect key="frame" x="0.0" y="23" width="320" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
</subviews>
</view>
<point key="canvasLocation" x="348" y="-29"/>
</window>
</objects>
</document>

9
Cocoa/GBView.h Normal file
View File

@ -0,0 +1,9 @@
#import <Cocoa/Cocoa.h>
#include "gb.h"
@interface GBView : NSOpenGLView
- (void) flip;
- (uint32_t *) pixels;
@property GB_gameboy_t *gb;
@property BOOL shouldBlendFrameWithPrevious;
@end

163
Cocoa/GBView.m Normal file
View File

@ -0,0 +1,163 @@
#import <Carbon/Carbon.h>
#import <OpenGL/gl.h>
#import "GBView.h"
@implementation GBView
{
uint32_t *image_buffers[3];
unsigned char current_buffer;
}
- (void) _init
{
image_buffers[0] = malloc(160 * 144 * 4);
image_buffers[1] = malloc(160 * 144 * 4);
image_buffers[2] = malloc(160 * 144 * 4);
_shouldBlendFrameWithPrevious = 1;
}
- (unsigned char) numberOfBuffers
{
return _shouldBlendFrameWithPrevious? 3 : 2;
}
- (void)dealloc
{
free(image_buffers[0]);
free(image_buffers[1]);
free(image_buffers[2]);
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
if (!(self = [super initWithCoder:coder]))
{
return self;
}
[self _init];
return self;
}
- (instancetype)initWithFrame:(NSRect)frameRect
{
if (!(self = [super initWithFrame:frameRect]))
{
return self;
}
[self _init];
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
double scale = self.window.backingScaleFactor;
glRasterPos2d(-1, 1);
glPixelZoom(self.bounds.size.width / 160 * scale, self.bounds.size.height / -144 * scale);
glDrawPixels(160, 144, GL_ABGR_EXT, GL_UNSIGNED_BYTE, image_buffers[current_buffer]);
if (_shouldBlendFrameWithPrevious) {
glEnable(GL_BLEND);
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
glBlendColor(1, 1, 1, 0.5);
glDrawPixels(160, 144, GL_ABGR_EXT, GL_UNSIGNED_BYTE, image_buffers[(current_buffer + 2) % self.numberOfBuffers]);
glDisable(GL_BLEND);
}
glFlush();
}
- (void) flip
{
current_buffer = (current_buffer + 1) % self.numberOfBuffers;
[self setNeedsDisplay:YES];
}
- (uint32_t *) pixels
{
return image_buffers[(current_buffer + 1) % self.numberOfBuffers];
}
-(void)keyDown:(NSEvent *)theEvent
{
unsigned short key = theEvent.keyCode;
switch (key) {
case kVK_RightArrow:
_gb->keys[0] = true;
break;
case kVK_LeftArrow:
_gb->keys[1] = true;
break;
case kVK_UpArrow:
_gb->keys[2] = true;
break;
case kVK_DownArrow:
_gb->keys[3] = true;
break;
case kVK_ANSI_X:
_gb->keys[4] = true;
break;
case kVK_ANSI_Z:
_gb->keys[5] = true;
break;
case kVK_Delete:
_gb->keys[6] = true;
break;
case kVK_Return:
_gb->keys[7] = true;
break;
case kVK_Space:
_gb->turbo = true;
break;
default:
[super keyDown:theEvent];
break;
}
}
-(void)keyUp:(NSEvent *)theEvent
{
unsigned short key = theEvent.keyCode;
switch (key) {
case kVK_RightArrow:
_gb->keys[0] = false;
break;
case kVK_LeftArrow:
_gb->keys[1] = false;
break;
case kVK_UpArrow:
_gb->keys[2] = false;
break;
case kVK_DownArrow:
_gb->keys[3] = false;
break;
case kVK_ANSI_X:
_gb->keys[4] = false;
break;
case kVK_ANSI_Z:
_gb->keys[5] = false;
break;
case kVK_Delete:
_gb->keys[6] = false;
break;
case kVK_Return:
_gb->keys[7] = false;
break;
case kVK_Space:
_gb->turbo = false;
break;
default:
[super keyUp:theEvent];
break;
}
}
-(void)reshape
{
double scale = self.window.backingScaleFactor;
glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale);
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
@end

71
Cocoa/Info.plist Normal file
View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>14F1509</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gb</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Cartridge</string>
<key>CFBundleTypeName</key>
<string>Gameboy Game</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gbc</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Gameboy Color Game</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>SameBoy</string>
<key>CFBundleIconFile</key>
<string>AppIcon.icns</string>
<key>CFBundleIdentifier</key>
<string>com.github.LIJI32.SameBoy</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>SameBoy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

405
Cocoa/MainMenu.xib Normal file
View File

@ -0,0 +1,405 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8191" systemVersion="14F27" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8191"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="SameBoy" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="SameBoy" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About Gameboy Emulator" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide Gameboy Emulator" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit Gameboy Emulator" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="cGb-fc-V1Y">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="rwF-GI-mkw">
<items>
<menuItem title="Undo" keyEquivalent="z" id="0Ff-de-rjb">
<connections>
<action selector="undo:" target="-1" id="XQH-Wy-wlr"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="Pef-QL-e9D">
<connections>
<action selector="redo:" target="-1" id="5DQ-yl-4ds"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gYa-mS-zMS"/>
<menuItem title="Cut" keyEquivalent="x" id="c0j-SN-BK3">
<connections>
<action selector="cut:" target="-1" id="DCn-sI-ibs"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="kRM-zo-IsI">
<connections>
<action selector="copy:" target="-1" id="lgN-ca-tGx"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="tPP-KM-W2x">
<connections>
<action selector="paste:" target="-1" id="zLc-RU-lUk"/>
</connections>
</menuItem>
<menuItem title="Delete" id="CvF-7s-jyR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="zQk-RN-64A"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="tha-Q5-MNs">
<connections>
<action selector="selectAll:" target="-1" id="IfU-4s-7bE"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Aru-vr-frG"/>
<menuItem title="Find" id="efg-jw-GVP">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="4R6-IU-Jq6">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="tos-1K-NFk">
<connections>
<action selector="performFindPanelAction:" target="-1" id="nTo-u6-2Ne"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="aey-0H-CqY">
<connections>
<action selector="performFindPanelAction:" target="-1" id="DJo-3G-DNV"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="Ex6-6J-WlY">
<connections>
<action selector="performFindPanelAction:" target="-1" id="6Zf-xR-ur5"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="qgQ-0P-lLO">
<connections>
<action selector="performFindPanelAction:" target="-1" id="l2m-8O-aDP"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="Ujj-LE-V19">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="GhX-po-5RK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Emulation" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Emulation" id="HyV-fh-RgO">
<items>
<menuItem title="Reset" keyEquivalent="r" id="p0i-Lt-sTg">
<connections>
<action selector="reset:" target="-1" id="DKW-Bd-h3v"/>
</connections>
</menuItem>
<menuItem title="Pause" keyEquivalent="p" id="4K4-hw-R7Q">
<connections>
<action selector="togglePause:" target="-1" id="osW-wt-QAa"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="QIS-av-Byy"/>
<menuItem title="Save State" id="Hdz-ut-okE">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Save State" id="Mxx-u1-M9D">
<items>
<menuItem title="Slot 1" tag="1" keyEquivalent="1" id="MKg-h9-jfo">
<connections>
<action selector="saveState:" target="-1" id="UZR-bP-ogO"/>
</connections>
</menuItem>
<menuItem title="Slot 2" tag="2" keyEquivalent="2" id="vkn-Zh-eJS">
<connections>
<action selector="saveState:" target="-1" id="Pmj-2O-z6U"/>
</connections>
</menuItem>
<menuItem title="Slot 3" tag="3" keyEquivalent="3" id="9mj-UU-bHY">
<connections>
<action selector="saveState:" target="-1" id="BhO-2h-gyQ"/>
</connections>
</menuItem>
<menuItem title="Slot 4" tag="4" keyEquivalent="4" id="NYY-aj-BHb">
<connections>
<action selector="saveState:" target="-1" id="xlY-3q-JsO"/>
</connections>
</menuItem>
<menuItem title="Slot 5" tag="5" keyEquivalent="5" id="UNN-Yv-1II">
<connections>
<action selector="saveState:" target="-1" id="Kbx-JS-3v5"/>
</connections>
</menuItem>
<menuItem title="Slot 6" tag="6" keyEquivalent="6" id="Io5-NV-GN5">
<connections>
<action selector="saveState:" target="-1" id="SAo-ej-RBG"/>
</connections>
</menuItem>
<menuItem title="Slot 7" tag="7" keyEquivalent="7" id="en2-Uu-Eps">
<connections>
<action selector="saveState:" target="-1" id="MRR-4I-z8l"/>
</connections>
</menuItem>
<menuItem title="Slot 8" tag="8" keyEquivalent="8" id="BHl-sg-rA2">
<connections>
<action selector="saveState:" target="-1" id="WSz-gz-mlZ"/>
</connections>
</menuItem>
<menuItem title="Slot 9" tag="9" keyEquivalent="9" id="vSH-S9-ExZ">
<connections>
<action selector="saveState:" target="-1" id="FOt-UK-jT9"/>
</connections>
</menuItem>
<menuItem title="Slot 10" tag="10" keyEquivalent="0" id="mAB-fq-BJy">
<connections>
<action selector="saveState:" target="-1" id="KQi-wO-F6M"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Load State" id="EXD-SL-cz4">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Load State" id="l9D-Ej-sh2">
<items>
<menuItem title="Slot 1" tag="1" keyEquivalent="1" id="aEJ-6V-7sk">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="rOy-Ve-UUM"/>
</connections>
</menuItem>
<menuItem title="Slot 2" tag="2" keyEquivalent="2" id="EWM-vK-sZm">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="M7f-wx-xt2"/>
</connections>
</menuItem>
<menuItem title="Slot 3" tag="3" keyEquivalent="3" id="YEd-gG-G6p">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="ALD-3X-pJ6"/>
</connections>
</menuItem>
<menuItem title="Slot 4" tag="4" keyEquivalent="4" id="Xgn-pa-LcM">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="I0n-4q-CmW"/>
</connections>
</menuItem>
<menuItem title="Slot 5" tag="5" keyEquivalent="5" id="XIA-qE-emo">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="SAP-0t-CGM"/>
</connections>
</menuItem>
<menuItem title="Slot 6" tag="6" keyEquivalent="6" id="0CQ-w6-dSd">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="CFz-7P-jTJ"/>
</connections>
</menuItem>
<menuItem title="Slot 7" tag="7" keyEquivalent="7" id="sdG-Dc-QNU">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="B49-vL-qN7"/>
</connections>
</menuItem>
<menuItem title="Slot 8" tag="8" keyEquivalent="8" id="pPH-D9-4MJ">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="TZl-ug-0ae"/>
</connections>
</menuItem>
<menuItem title="Slot 9" tag="9" keyEquivalent="9" id="1Uy-yl-ITg">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="Hk5-Pz-VC5"/>
</connections>
</menuItem>
<menuItem title="Slot 10" tag="10" keyEquivalent="0" id="dpk-UF-vN2">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="loadState:" target="-1" id="GEt-4l-90e"/>
</connections>
</menuItem>
</items>
</menu>
<connections>
<action selector="loadState:" target="-1" id="WyW-n5-YHf"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/>
<menuItem title="Gameboy" tag="1" id="vc7-yy-ARW">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="reset:" target="-1" id="E4M-QG-ua9"/>
</connections>
</menuItem>
<menuItem title="Gameboy Color" tag="2" id="hdG-Bl-8nJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="reset:" target="-1" id="xAz-cr-0u2"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="DPb-Sh-5tg"/>
<menuItem title="Mute Sound" keyEquivalent="m" id="1UK-8n-QPP">
<connections>
<action selector="mute:" target="-1" id="YE5-mi-Yzd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="YIZ-pz-N4V"/>
<menuItem title="Blend Frames" id="AWj-r8-L6U">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleBlend:" target="-1" id="TjO-ce-UxL"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Developer" id="IwX-DJ-dBk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Developer" id="UVb-cc-at0">
<items>
<menuItem title="Show Console" id="Wse-UY-Y9l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="showConsoleWindow:" target="-1" id="mFf-4i-jLG"/>
</connections>
</menuItem>
<menuItem title="Clear Console" keyEquivalent="k" id="MyO-VS-MKZ">
<connections>
<action selector="clearConsole:" target="-1" id="1UW-8J-Uwl"/>
</connections>
</menuItem>
<menuItem title="Break Debugger" keyEquivalent="c" id="uBD-GY-Doi">
<modifierMask key="keyEquivalentModifierMask" control="YES"/>
<connections>
<action selector="interrupt:" target="-1" id="ZmB-wG-fTs"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="Gameboy Emulator Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</objects>
</document>

1
Cocoa/PkgInfo Normal file
View File

@ -0,0 +1 @@
APPL????

5
Cocoa/main.m Normal file
View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}

415
Core/apu.c Normal file
View File

@ -0,0 +1,415 @@
#include <stdint.h>
#include <math.h>
#include <string.h>
#include "apu.h"
#include "gb.h"
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
#define min(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
static __attribute__((unused)) int16_t generate_sin(double phase, int16_t amplitude)
{
return (int16_t)(sin(phase) * amplitude);
}
static int16_t generate_square(double phase, int16_t amplitude, double duty)
{
if (fmod(phase, 2 * M_PI) > duty * 2 * M_PI) {
return amplitude;
}
return 0;
}
static int16_t generate_wave(double phase, int16_t amplitude, signed char *wave, unsigned char shift)
{
phase = fmod(phase, 2 * M_PI);
return ((wave[(int)(phase / (2 * M_PI) * 32)]) >> shift) * (int)amplitude / 0xF;
}
static int16_t generate_noise(double phase, int16_t amplitude, uint16_t lfsr)
{
if (lfsr & 1) {
return amplitude;
}
return 0;
}
static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit)
{
bool xor = (lfsr & 1) ^ ((lfsr & 2) >> 1);
lfsr >>= 1;
if (xor) {
lfsr |= 0x4000;
}
if (uses_7_bit) {
lfsr &= ~0x40;
if (xor) {
lfsr |= 0x40;
}
}
return lfsr;
}
/* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with
these tests in mind. */
void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, int16_t *samples)
{
for (; n_samples--; samples++) {
*samples = 0;
if (!gb->apu.global_enable) {
continue;
}
gb->io_registers[GB_IO_PCM_12] = 0;
gb->io_registers[GB_IO_PCM_34] = 0;
// Todo: Stereo support
if (gb->apu.left_on[0] || gb->apu.right_on[0]) {
int16_t sample = generate_square(gb->apu.wave_channels[0].phase,
gb->apu.wave_channels[0].amplitude,
gb->apu.wave_channels[0].duty);
*samples += sample;
gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP;
}
if (gb->apu.left_on[1] || gb->apu.right_on[1]) {
int16_t sample = generate_square(gb->apu.wave_channels[1].phase,
gb->apu.wave_channels[1].amplitude,
gb->apu.wave_channels[1].duty);
*samples += sample;
gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
}
if (gb->apu.wave_enable && (gb->apu.left_on[2] || gb->apu.right_on[2])) {
int16_t sample = generate_wave(gb->apu.wave_channels[2].phase,
MAX_CH_AMP,
gb->apu.wave_form,
gb->apu.wave_shift);
*samples += sample;
gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP;
}
if (gb->apu.left_on[3] || gb->apu.right_on[3]) {
int16_t sample = generate_noise(gb->apu.wave_channels[3].phase,
gb->apu.wave_channels[3].amplitude,
gb->apu.lfsr);
*samples += sample;
gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
}
*samples *= gb->apu.left_volume;
for (unsigned char i = 0; i < 4; i++) {
/* Phase */
gb->apu.wave_channels[i].phase += 2 * M_PI * gb->apu.wave_channels[i].frequency / sample_rate;
while (gb->apu.wave_channels[i].phase >= 2 * M_PI) {
if (i == 3) {
gb->apu.lfsr = step_lfsr(gb->apu.lfsr, gb->apu.lfsr_7_bit);
}
gb->apu.wave_channels[i].phase -= 2 * M_PI;
}
/* Stop on Length */
if (gb->apu.wave_channels[i].stop_on_length) {
if (gb->apu.wave_channels[i].sound_length > 0) {
gb->apu.wave_channels[i].sound_length -= 1.0 / sample_rate;
}
if (gb->apu.wave_channels[i].sound_length <= 0) {
gb->apu.wave_channels[i].amplitude = 0;
gb->apu.wave_channels[i].is_playing = false;
gb->apu.wave_channels[i].sound_length = i == 2? 1 : 0.25;
}
}
}
gb->apu.envelope_step_timer += 1.0 / sample_rate;
if (gb->apu.envelope_step_timer >= 1.0 / 64) {
gb->apu.envelope_step_timer -= 1.0 / 64;
for (unsigned char i = 0; i < 4; i++) {
if (gb->apu.wave_channels[i].envelope_steps && !--gb->apu.wave_channels[i].cur_envelope_steps) {
gb->apu.wave_channels[i].amplitude = min(max(gb->apu.wave_channels[i].amplitude + gb->apu.wave_channels[i].envelope_direction * CH_STEP, 0), MAX_CH_AMP);
gb->apu.wave_channels[i].cur_envelope_steps = gb->apu.wave_channels[i].envelope_steps;
}
}
}
gb->apu.sweep_step_timer += 1.0 / sample_rate;
if (gb->apu.sweep_step_timer >= 1.0 / 128) {
gb->apu.sweep_step_timer -= 1.0 / 128;
if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) {
// Convert back to GB format
unsigned short temp = (unsigned short) (2048 - 131072 / gb->apu.wave_channels[0].frequency);
// Apply sweep
temp = temp + gb->apu.wave_channels[0].sweep_direction *
(temp / (1 << gb->apu.wave_channels[0].sweep_shift));
if (temp > 2047) {
temp = 0;
}
// Back to frequency
gb->apu.wave_channels[0].frequency = 131072.0 / (2048 - temp);
gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps;
}
}
}
}
void apu_run(GB_gameboy_t *gb)
{
static bool should_log_overflow = true;
while (gb->audio_copy_in_progress);
double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate;
while (gb->apu_cycles > ticks_per_sample) {
int16_t sample = 0;
apu_render(gb, gb->sample_rate, 1, &sample);
gb->apu_cycles -= ticks_per_sample;
if (gb->audio_position == gb->buffer_size) {
/*
if (should_log_overflow && !gb->turbo) {
gb_log(gb, "Audio overflow\n");
should_log_overflow = false;
}
*/
}
else {
gb->audio_buffer[gb->audio_position++] = sample;
should_log_overflow = true;
}
}
}
void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count)
{
gb->audio_copy_in_progress = true;
if (!gb->audio_stream_started) {
// Intentionally fail the first copy to sync the stream with the Gameboy.
gb->audio_stream_started = true;
gb->audio_position = 0;
}
if (count > gb->audio_position) {
// gb_log(gb, "Audio underflow: %d\n", count - gb->audio_position);
memset(dest + gb->audio_position, 0, (count - gb->audio_position) * 2);
count = gb->audio_position;
}
memcpy(dest, gb->audio_buffer, count * 2);
memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * 2);
gb->audio_position -= count;
gb->audio_copy_in_progress = false;
}
void apu_init(GB_gameboy_t *gb)
{
memset(&gb->apu, 0, sizeof(gb->apu));
gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 0.5;
gb->apu.lfsr = 0x7FFF;
gb->apu.left_volume = 1.0;
gb->apu.right_volume = 1.0;
for (int i = 0; i < 4; i++) {
gb->apu.left_on[i] = gb->apu.right_on[i] = 1;
}
}
unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg)
{
/* Todo: what happens when reading from the wave from while it's playing? */
if (reg == GB_IO_NR52) {
unsigned char value = 0;
for (int i = 0; i < 4; i++) {
value >>= 1;
if (gb->apu.wave_channels[i].is_playing) {
value |= 0x8;
}
}
if (gb->apu.global_enable) {
value |= 0x80;
}
value |= 0x70;
return value;
}
static const char read_mask[GB_IO_WAV_END - GB_IO_NR10 + 1] = {
/* NRX0 NRX1 NRX2 NRX3 NRX4 */
0x80, 0x3F, 0x00, 0xFF, 0xBF, // NR1X
0xFF, 0x3F, 0x00, 0xFF, 0xBF, // NR2X
0x7F, 0xFF, 0x9F, 0xFF, 0xBF, // NR3X
0xFF, 0xFF, 0x00, 0x00, 0xBF, // NR4X
0x00, 0x00, 0x70, 0xFF, 0xFF, // NR5X
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Unused
// Wave RAM
0, /* ... */
};
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.wave_channels[2].is_playing) {
return (unsigned char)((gb->display_cycles * 22695477 * reg) >> 8); // Semi-random but deterministic
}
return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10];
}
void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value)
{
static const double duties[] = {0.125, 0.25, 0.5, 0.75};
static uint16_t NRX3_X4_temp[3] = {0};
unsigned char channel = 0;
if (!gb->apu.global_enable && reg != GB_IO_NR52) {
return;
}
gb->io_registers[reg] = value;
switch (reg) {
case GB_IO_NR10:
case GB_IO_NR11:
case GB_IO_NR12:
case GB_IO_NR13:
case GB_IO_NR14:
channel = 0;
break;
case GB_IO_NR21:
case GB_IO_NR22:
case GB_IO_NR23:
case GB_IO_NR24:
channel = 1;
break;
case GB_IO_NR33:
case GB_IO_NR34:
channel = 2;
break;
case GB_IO_NR41:
case GB_IO_NR42:
channel = 3;
default:
break;
}
switch (reg) {
case GB_IO_NR10:
gb->apu.wave_channels[channel].sweep_direction = value & 8? -1 : 1;
gb->apu.wave_channels[channel].cur_sweep_steps =
gb->apu.wave_channels[channel].sweep_steps = (value & 0x70) >> 4;
gb->apu.wave_channels[channel].sweep_shift = value & 7;
break;
case GB_IO_NR11:
case GB_IO_NR21:
case GB_IO_NR41:
gb->apu.wave_channels[channel].duty = duties[value >> 6];
gb->apu.wave_channels[channel].sound_length = (64 - (value & 0x3F)) / 256.0;
if (gb->apu.wave_channels[channel].sound_length == 0) {
gb->apu.wave_channels[channel].is_playing = false;
}
break;
case GB_IO_NR12:
case GB_IO_NR22:
case GB_IO_NR42:
gb->apu.wave_channels[channel].start_amplitude =
gb->apu.wave_channels[channel].amplitude = CH_STEP * (value >> 4);
if (value >> 4 == 0) {
gb->apu.wave_channels[channel].is_playing = false;
}
gb->apu.wave_channels[channel].envelope_direction = value & 8? 1 : -1;
gb->apu.wave_channels[channel].cur_envelope_steps =
gb->apu.wave_channels[channel].envelope_steps = value & 7;
break;
case GB_IO_NR13:
case GB_IO_NR23:
case GB_IO_NR33:
NRX3_X4_temp[channel] = (NRX3_X4_temp[channel] & 0xFF00) | value;
gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - NRX3_X4_temp[channel]);
if (channel == 2) {
gb->apu.wave_channels[channel].frequency /= 2;
}
break;
case GB_IO_NR14:
case GB_IO_NR24:
case GB_IO_NR34:
gb->apu.wave_channels[channel].stop_on_length = value & 0x40;
if (value & 0x80) {
gb->apu.wave_channels[channel].is_playing = true;
gb->apu.wave_channels[channel].phase = 0;
gb->apu.wave_channels[channel].amplitude = gb->apu.wave_channels[channel].start_amplitude;
gb->apu.wave_channels[channel].cur_envelope_steps = gb->apu.wave_channels[channel].envelope_steps;
}
NRX3_X4_temp[channel] = (NRX3_X4_temp[channel] & 0xFF) | ((value & 0x7) << 8);
gb->apu.wave_channels[channel].frequency = 131072.0 / (2048 - NRX3_X4_temp[channel]);
if (channel == 2) {
gb->apu.wave_channels[channel].frequency /= 2;
}
break;
case GB_IO_NR30:
gb->apu.wave_enable = value & 0x80;
break;
case GB_IO_NR31:
gb->apu.wave_channels[2].sound_length = (256 - value) / 256.0;
if (gb->apu.wave_channels[2].sound_length == 0) {
gb->apu.wave_channels[2].is_playing = false;
}
break;
case GB_IO_NR32:
gb->apu.wave_shift = ((value >> 5) + 3) & 3;
break;
case GB_IO_NR43:
{
double r = value & 0x7;
if (r == 0) r = 0.5;
unsigned char s = value >> 4;
gb->apu.wave_channels[3].frequency = 524288.0 / r / (1 << (s + 1));
gb->apu.lfsr_7_bit = value & 0x8;
break;
}
case GB_IO_NR44:
gb->apu.wave_channels[3].stop_on_length = value & 0x40;
if (value & 0x80) {
gb->apu.wave_channels[3].is_playing = true;
gb->apu.lfsr = 0x7FFF;
gb->apu.wave_channels[3].amplitude = gb->apu.wave_channels[3].start_amplitude;
gb->apu.wave_channels[3].cur_envelope_steps = gb->apu.wave_channels[3].envelope_steps;
}
break;
case GB_IO_NR50:
gb->apu.left_volume = (value & 7) / 7.0;
gb->apu.right_volume = ((value >> 4) & 7) / 7.0;
break;
case GB_IO_NR51:
for (int i = 0; i < 4; i++) {
gb->apu.left_on[i] = value & 1;
gb->apu.right_on[i] = value & 0x10;
value >>= 1;
}
break;
case GB_IO_NR52:
if ((value & 0x80) && !gb->apu.global_enable) {
apu_init(gb);
gb->apu.global_enable = true;
}
else if (!(value & 0x80) && gb->apu.global_enable) {
memset(&gb->apu, 0, sizeof(gb->apu));
memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10);
}
break;
default:
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) {
gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4;
gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF;
}
break;
}
}

59
Core/apu.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef apu_h
#define apu_h
#include <stdbool.h>
#include <stdint.h>
/* Divides nicely and never overflows with 4 channels */
#define MAX_CH_AMP 0x1E00
#define CH_STEP (0x1E00/0xF)
struct GB_gameboy_s;
typedef struct GB_gameboy_s GB_gameboy_t;
/* Not all used on all channels */
typedef struct
{
double phase;
double frequency;
int16_t amplitude;
int16_t start_amplitude;
double duty;
double sound_length; /* In seconds */
bool stop_on_length;
unsigned char envelope_steps;
unsigned char cur_envelope_steps;
signed int envelope_direction;
unsigned char sweep_steps;
unsigned char cur_sweep_steps;
signed int sweep_direction;
unsigned char sweep_shift;
bool is_playing;
} GB_apu_channel_t;
typedef struct
{
GB_apu_channel_t wave_channels[4];
double envelope_step_timer; /* In seconds */
double sweep_step_timer; /* In seconds */
signed char wave_form[32];
unsigned char wave_shift;
bool wave_enable;
uint16_t lfsr;
bool lfsr_7_bit;
double left_volume;
double right_volume;
bool left_on[4];
bool right_on[4];
bool global_enable;
} GB_apu_t;
void apu_render(GB_gameboy_t *gb, unsigned long sample_rate, unsigned long n_samples, int16_t *samples);
void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count);
void apu_write(GB_gameboy_t *gb, unsigned char reg, unsigned char value);
unsigned char apu_read(GB_gameboy_t *gb, unsigned char reg);
void apu_init(GB_gameboy_t *gb);
void apu_copy_buffer(GB_gameboy_t *gb, int16_t *dest, unsigned int count);
void apu_run(GB_gameboy_t *gb);
#endif /* apu_h */

410
Core/debugger.c Normal file
View File

@ -0,0 +1,410 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "debugger.h"
#include "memory.h"
#include "z80_cpu.h"
#include "gb.h"
typedef struct {
enum {
LVALUE_MEMORY,
LVALUE_REG16,
LVALUE_REG_H,
LVALUE_REG_L,
} kind;
union {
unsigned short *register_address;
unsigned short memory_address;
};
} lvalue_t;
static unsigned short read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue)
{
/* Not used until we add support for operators like += */
switch (lvalue.kind) {
case LVALUE_MEMORY:
return read_memory(gb, lvalue.memory_address);
case LVALUE_REG16:
return *lvalue.register_address;
case LVALUE_REG_L:
return *lvalue.register_address & 0x00FF;
case LVALUE_REG_H:
return *lvalue.register_address >> 8;
}
}
static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, unsigned short value)
{
switch (lvalue.kind) {
case LVALUE_MEMORY:
write_memory(gb, lvalue.memory_address, value);
return;
case LVALUE_REG16:
*lvalue.register_address = value;
return;
case LVALUE_REG_L:
*lvalue.register_address &= 0xFF00;
*lvalue.register_address |= value & 0xFF;
return;
case LVALUE_REG_H:
*lvalue.register_address &= 0x00FF;
*lvalue.register_address |= value << 8;
return;
}
}
static unsigned short add(unsigned short a, unsigned short b) {return a + b;};
static unsigned short sub(unsigned short a, unsigned short b) {return a - b;};
static unsigned short mul(unsigned short a, unsigned short b) {return a * b;};
static unsigned short _div(unsigned short a, unsigned short b) {
if (b == 0) {
return 0;
}
return a / b;
};
static unsigned short mod(unsigned short a, unsigned short b) {
if (b == 0) {
return 0;
}
return a % b;
};
static unsigned short and(unsigned short a, unsigned short b) {return a & b;};
static unsigned short or(unsigned short a, unsigned short b) {return a | b;};
static unsigned short xor(unsigned short a, unsigned short b) {return a ^ b;};
static unsigned short shleft(unsigned short a, unsigned short b) {return a << b;};
static unsigned short shright(unsigned short a, unsigned short b) {return a >> b;};
static unsigned short assign(GB_gameboy_t *gb, lvalue_t a, unsigned short b)
{
write_lvalue(gb, a, b);
return read_lvalue(gb, a);
}
static struct {
const char *string;
char priority;
unsigned short (*operator)(unsigned short, unsigned short);
unsigned short (*lvalue_operator)(GB_gameboy_t *, lvalue_t, unsigned short);
} operators[] =
{
// Yes. This is not C-like. But it makes much more sense.
// Deal with it.
{"+", 0, add},
{"-", 0, sub},
{"|", 0, or},
{"*", 1, mul},
{"/", 1, _div},
{"%", 1, mod},
{"&", 1, and},
{"^", 1, xor},
{"<<", 2, shleft},
{">>", 2, shright},
{"=", 2, NULL, assign},
};
unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error);
static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error)
{
*error = false;
// Strip whitespace
while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) {
string++;
length--;
}
while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
length--;
}
if (length == 0)
{
gb_log(gb, "Expected expression.\n");
*error = true;
return (lvalue_t){0,};
}
if (string[0] == '(' && string[length - 1] == ')') {
// Attempt to strip parentheses
signed int depth = 0;
for (int i = 0; i < length; i++) {
if (string[i] == '(') depth++;
if (depth == 0) {
// First and last are not matching
depth = 1;
break;
}
if (string[i] == ')') depth--;
}
if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error);
}
else if (string[0] == '[' && string[length - 1] == ']') {
// Attempt to strip square parentheses (memory dereference)
signed int depth = 0;
for (int i = 0; i < length; i++) {
if (string[i] == '[') depth++;
if (depth == 0) {
// First and last are not matching
depth = 1;
break;
}
if (string[i] == ']') depth--;
}
if (depth == 0) {
return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error)};
}
}
// Registers
if (string[0] == '$') {
if (length == 2) {
switch (string[1]) {
case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]};
case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]};
case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]};
case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]};
case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]};
case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]};
case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]};
case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]};
}
}
else if (length == 3) {
switch (string[1]) {
case 'a': if (string[2] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]};
case 'b': if (string[2] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]};
case 'd': if (string[2] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]};
case 'h': if (string[2] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]};
case 's': if (string[2] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]};
case 'p': if (string[2] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc};
}
}
gb_log(gb, "Unknown register: %.*s\n", length, string);
*error = true;
return (lvalue_t){0,};
}
gb_log(gb, "Expression is not an lvalue: %.*s\n", length, string);
*error = true;
return (lvalue_t){0,};
}
unsigned short debugger_evaluate(GB_gameboy_t *gb, const char *string, unsigned int length, bool *error)
{
*error = false;
// Strip whitespace
while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) {
string++;
length--;
}
while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
length--;
}
if (length == 0)
{
gb_log(gb, "Expected expression.\n");
*error = true;
return -1;
}
if (string[0] == '(' && string[length - 1] == ')') {
// Attempt to strip parentheses
signed int depth = 0;
for (int i = 0; i < length; i++) {
if (string[i] == '(') depth++;
if (depth == 0) {
// First and last are not matching
depth = 1;
break;
}
if (string[i] == ')') depth--;
}
if (depth == 0) return debugger_evaluate(gb, string + 1, length - 2, error);
}
else if (string[0] == '[' && string[length - 1] == ']') {
// Attempt to strip square parentheses (memory dereference)
signed int depth = 0;
for (int i = 0; i < length; i++) {
if (string[i] == '[') depth++;
if (depth == 0) {
// First and last are not matching
depth = 1;
break;
}
if (string[i] == ']') depth--;
}
if (depth == 0) return read_memory(gb, debugger_evaluate(gb, string + 1, length - 2, error));
}
// Search for lowest priority operator
signed int depth = 0;
unsigned int operator_index = -1;
unsigned int operator_pos = 0;
for (int i = 0; i < length; i++) {
if (string[i] == '(') depth++;
else if (string[i] == ')') depth--;
else if (string[i] == '[') depth++;
else if (string[i] == ']') depth--;
else if (depth == 0) {
for (int j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) {
if (strlen(operators[j].string) > length - i) continue; // Operator too big.
// Priority higher than what we already have.
if (operator_index != -1 && operators[operator_index].priority > operators[j].priority) continue;
if (memcmp(string + i, operators[j].string, strlen(operators[j].string)) == 0) {
// Found an operator!
operator_pos = i;
operator_index = j;
}
}
}
}
if (operator_index != -1) {
unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string));
unsigned short right = debugger_evaluate(gb, string + right_start, length - right_start, error);
if (*error) return -1;
if (operators[operator_index].lvalue_operator) {
lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error);
if (*error) return -1;
return operators[operator_index].lvalue_operator(gb, left, right);
}
unsigned short left = debugger_evaluate(gb, string, operator_pos, error);
if (*error) return -1;
return operators[operator_index].operator(left, right);
}
// Not an expression - must be a register or a literal
// Registers
if (string[0] == '$') {
if (length == 2) {
switch (string[1]) {
case 'a': return gb->registers[GB_REGISTER_AF] >> 8;
case 'f': return gb->registers[GB_REGISTER_AF] & 0xFF;
case 'b': return gb->registers[GB_REGISTER_BC] >> 8;
case 'c': return gb->registers[GB_REGISTER_BC] & 0xFF;
case 'd': return gb->registers[GB_REGISTER_DE] >> 8;
case 'e': return gb->registers[GB_REGISTER_DE] & 0xFF;
case 'h': return gb->registers[GB_REGISTER_HL] >> 8;
case 'l': return gb->registers[GB_REGISTER_HL] & 0xFF;
}
}
else if (length == 3) {
switch (string[1]) {
case 'a': if (string[2] == 'f') return gb->registers[GB_REGISTER_AF];
case 'b': if (string[2] == 'c') return gb->registers[GB_REGISTER_BC];
case 'd': if (string[2] == 'e') return gb->registers[GB_REGISTER_DE];
case 'h': if (string[2] == 'l') return gb->registers[GB_REGISTER_HL];
case 's': if (string[2] == 'p') return gb->registers[GB_REGISTER_SP];
case 'p': if (string[2] == 'c') return gb->pc;
}
}
gb_log(gb, "Unknown register: %.*s\n", length, string);
*error = true;
return -1;
}
char *end;
unsigned short literal = (unsigned short) (strtol(string, &end, 16));
if (end != string + length) {
gb_log(gb, "Failed to parse: %.*s\n", length, string);
*error = true;
return -1;
}
return literal;
}
/* The debugger interface is quite primitive. One letter commands with a single parameter maximum.
Only one breakpoint is allowed at a time. More features will be added later. */
void debugger_run(GB_gameboy_t *gb)
{
char *input = NULL;
if (gb->debug_next_command && gb->debug_call_depth == 0) {
gb->debug_stopped = true;
}
if (gb->debug_fin_command && gb->debug_call_depth == -1) {
gb->debug_stopped = true;
}
if (gb->debug_stopped) {
cpu_disassemble(gb, gb->pc, 5);
}
next_command:
if (input) {
free(input);
}
if (gb->pc == gb->breakpoint && !gb->debug_stopped) {
gb->debug_stopped = true;
gb_log(gb, "Breakpoint: PC = %04x\n", gb->pc);
cpu_disassemble(gb, gb->pc, 5);
}
if (gb->debug_stopped) {
gb->debug_next_command = false;
gb->debug_fin_command = false;
input = gb->input_callback(gb);
switch (*input) {
case 'c':
gb->debug_stopped = false;
break;
case 'n':
gb->debug_stopped = false;
gb->debug_next_command = true;
gb->debug_call_depth = 0;
break;
case 'f':
gb->debug_stopped = false;
gb->debug_fin_command = true;
gb->debug_call_depth = 0;
break;
case 's':
break;
case 'r':
gb_log(gb, "AF = %04x\n", gb->registers[GB_REGISTER_AF]);
gb_log(gb, "BC = %04x\n", gb->registers[GB_REGISTER_BC]);
gb_log(gb, "DE = %04x\n", gb->registers[GB_REGISTER_DE]);
gb_log(gb, "HL = %04x\n", gb->registers[GB_REGISTER_HL]);
gb_log(gb, "SP = %04x\n", gb->registers[GB_REGISTER_SP]);
gb_log(gb, "PC = %04x\n", gb->pc);
gb_log(gb, "TIMA = %d/%lu\n", gb->io_registers[GB_IO_TIMA], gb->tima_cycles);
gb_log(gb, "Display Controller: LY = %d/%lu\n", gb->io_registers[GB_IO_LY], gb->display_cycles % 456);
goto next_command;
case 'x':
{
bool error;
unsigned short addr = debugger_evaluate(gb, input + 1, (unsigned int)strlen(input + 1), &error);
if (!error) {
gb_log(gb, "%4x: ", addr);
for (int i = 0; i < 16; i++) {
gb_log(gb, "%02x ", read_memory(gb, addr + i));
}
gb_log(gb, "\n");
}
goto next_command;
}
case 'b':
{
bool error;
unsigned short result = debugger_evaluate(gb, input + 1, (unsigned int)strlen(input + 1), &error);
if (!error) {
gb_log(gb, "Breakpoint moved to %04x\n", gb->breakpoint = result);
}
goto next_command;
}
case 'p':
{
bool error;
unsigned short result = debugger_evaluate(gb, input + 1, (unsigned int)strlen(input + 1), &error);
if (!error) {
gb_log(gb, "=%04x\n", result);
}
goto next_command;
}
default:
goto next_command;
}
free(input);
}
}

7
Core/debugger.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef debugger_h
#define debugger_h
#include "gb.h"
void debugger_run(GB_gameboy_t *gb);
#endif /* debugger_h */

384
Core/display.c Normal file
View File

@ -0,0 +1,384 @@
#include <stdbool.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdlib.h>
#include <assert.h>
#include "gb.h"
#include "display.h"
#pragma pack(push, 1)
typedef struct {
unsigned char y;
unsigned char x;
unsigned char tile;
unsigned char flags;
} GB_sprite_t;
#pragma pack(pop)
static uint32_t get_pixel(GB_gameboy_t *gb, unsigned char x, unsigned char y)
{
/*
Bit 7 - LCD Display Enable (0=Off, 1=On)
Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
Bit 5 - Window Display Enable (0=Off, 1=On)
Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF)
Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16)
Bit 1 - OBJ (Sprite) Display Enable (0=Off, 1=On)
Bit 0 - BG Display (for CGB see below) (0=Off, 1=On)
*/
unsigned short map = 0x1800;
unsigned char tile = 0;
unsigned char attributes = 0;
unsigned char sprite_palette = 0;
unsigned short tile_address = 0;
unsigned char background_pixel = 0, sprite_pixel = 0;
GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam;
unsigned char sprites_in_line = 0;
bool lcd_8_16_mode = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
bool sprites_enabled = (gb->io_registers[GB_IO_LCDC] & 2) != 0;
unsigned char lowest_sprite_x = 0xFF;
bool use_obp1 = false, priority = false;
bool in_window = false;
if (gb->effective_window_enabled && (gb->io_registers[GB_IO_LCDC] & 0x20)) { /* Window Enabled */
if (y >= gb->effective_window_y && x + 7 >= gb->io_registers[GB_IO_WX]) {
in_window = true;
}
}
if (sprites_enabled) {
// Loop all sprites
for (unsigned char i = 40; i--; sprite++) {
int sprite_y = sprite->y - 16;
int sprite_x = sprite->x - 8;
// Is sprite in our line?
if (sprite_y <= y && sprite_y + (lcd_8_16_mode? 16:8) > y) {
unsigned char tile_x, tile_y, current_sprite_pixel;
unsigned short line_address;
// Limit to 10 sprites in one scan line.
if (++sprites_in_line == 11) break;
// Does not overlap our pixel.
if (sprite_x > x || sprite_x + 8 <= x) continue;
tile_x = x - sprite_x;
tile_y = y - sprite_y;
if (sprite->flags & 0x20) tile_x = 7 - tile_x;
if (sprite->flags & 0x40) tile_y = (lcd_8_16_mode? 15:7) - tile_y;
line_address = (lcd_8_16_mode? sprite->tile & 0xFE : sprite->tile) * 0x10 + tile_y * 2;
if (gb->cgb_mode && (sprite->flags & 0x8)) {
line_address += 0x2000;
}
current_sprite_pixel = (((gb->vram[line_address ] >> ((~tile_x)&7)) & 1 ) |
((gb->vram[line_address + 1] >> ((~tile_x)&7)) & 1) << 1 );
/* From Pandocs:
When sprites with different x coordinate values overlap, the one with the smaller x coordinate
(closer to the left) will have priority and appear above any others. This applies in Non CGB Mode
only. When sprites with the same x coordinate values overlap, they have priority according to table
ordering. (i.e. $FE00 - highest, $FE04 - next highest, etc.) In CGB Mode priorities are always
assigned like this.
*/
if (current_sprite_pixel != 0) {
if (!gb->cgb_mode && sprite->x >= lowest_sprite_x) {
break;
}
sprite_pixel = current_sprite_pixel;
lowest_sprite_x = sprite->x;
use_obp1 = (sprite->flags & 0x10) != 0;
sprite_palette = sprite->flags & 7;
priority = (sprite->flags & 0x80) != 0;
if (gb->cgb_mode) {
break;
}
}
}
}
}
if (in_window) {
x -= gb->io_registers[GB_IO_WX] - 7;
y -= gb->effective_window_y;
}
else {
x += gb->io_registers[GB_IO_SCX];
y += gb->io_registers[GB_IO_SCY];
}
if (gb->io_registers[GB_IO_LCDC] & 0x08 && !in_window) {
map = 0x1C00;
}
else if (gb->io_registers[GB_IO_LCDC] & 0x40 && in_window) {
map = 0x1C00;
}
tile = gb->vram[map + x/8 + y/8 * 32];
if (gb->cgb_mode) {
attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000];
}
if (attributes & 0x80) {
priority = true;
}
if (!priority && sprite_pixel) {
if (!gb->cgb_mode) {
sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3;
sprite_palette = use_obp1;
}
return gb->sprite_palletes_rgb[sprite_palette * 4 + sprite_pixel];
}
if (gb->io_registers[GB_IO_LCDC] & 0x10) {
tile_address = tile * 0x10;
}
else {
tile_address = (signed char) tile * 0x10 + 0x1000;
}
if (attributes & 0x8) {
tile_address += 0x2000;
}
if (attributes & 0x20) {
x = ~x;
}
if (attributes & 0x40) {
y = ~y;
}
background_pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) |
((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1 );
if (priority && sprite_pixel && !background_pixel) {
if (!gb->cgb_mode) {
sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3;
sprite_palette = use_obp1;
}
return gb->sprite_palletes_rgb[sprite_palette * 4 + sprite_pixel];
}
if (!gb->cgb_mode) {
background_pixel = ((gb->io_registers[GB_IO_BGP] >> (background_pixel << 1)) & 3);
}
return gb->background_palletes_rgb[(attributes & 7) * 4 + background_pixel];
}
// Todo: FPS capping should not be related to vblank, as the display is not always on, and this causes "jumps"
// when switching the display on and off.
void display_vblank(GB_gameboy_t *gb)
{
_Static_assert(CLOCKS_PER_SEC == 1000000, "CLOCKS_PER_SEC != 1000000");
/* Called every Gameboy vblank. Does FPS-capping and calls user's vblank callback if Turbo Mode allows. */
if (gb->turbo) {
struct timeval now;
gettimeofday(&now, NULL);
signed long nanoseconds = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;
if (nanoseconds <= gb->last_vblank + FRAME_LENGTH) {
return;
}
gb->last_vblank = nanoseconds;
}
/*
static long start = 0;
static long last = 0;
static long frames = 0;
if (last == 0) {
last = time(NULL);
}
if (last != time(NULL)) {
last = time(NULL);
if (start == 0) {
start = last;
frames = 0;
}
printf("Average FPS: %f\n", frames / (double)(last - start));
}
frames++;
*/
gb->vblank_callback(gb);
if (!gb->turbo) {
struct timeval now;
struct timespec sleep = {0,};
gettimeofday(&now, NULL);
signed long nanoseconds = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;
if (labs(nanoseconds - gb->last_vblank) < FRAME_LENGTH ) {
sleep.tv_nsec = (FRAME_LENGTH + gb->last_vblank - nanoseconds);
nanosleep(&sleep, NULL);
gb->last_vblank += FRAME_LENGTH;
}
else {
gb->last_vblank = nanoseconds;
}
}
}
static inline unsigned char scale_channel(unsigned char x)
{
x &= 0x1f;
return (x << 3) | (x >> 2);
}
void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char index)
{
unsigned char *palette_data = background_palette? gb->background_palletes_data : gb->sprite_palletes_data;
unsigned short color = palette_data[index & ~1] | (palette_data[index | 1] << 8);
// No need to &, scale channel does that.
unsigned char r = scale_channel(color);
unsigned char g = scale_channel(color >> 5);
unsigned char b = scale_channel(color >> 10);
assert (gb->rgb_encode_callback);
(background_palette? gb->background_palletes_rgb : gb->sprite_palletes_rgb)[index / 2] = gb->rgb_encode_callback(gb, r, g, b);
}
void display_run(GB_gameboy_t *gb)
{
/*
<del>Display controller bug: For some reason, the OAM STAT interrupt is called, as expected, for LY = 0..143.
However, it is also called from LY = 151! (The last LY is 153! Wonder why is it 151...).</del>
Todo: This discussion in NESDev proves this theory incorrect:
http://forums.nesdev.com/viewtopic.php?f=20&t=13727
Seems like there was a bug in one of my test ROMs.
This behavior needs to be corrected.
*/
unsigned char last_mode = gb->io_registers[GB_IO_STAT] & 3;
if (gb->display_cycles >= LCDC_PERIOD) {
/* VBlank! */
gb->display_cycles -= LCDC_PERIOD;
gb->ly151_bug_oam = false;
gb->ly151_bug_hblank = false;
display_vblank(gb);
}
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* LCD is disabled, do nothing */
gb->io_registers[GB_IO_STAT] &= ~3;
return;
}
gb->io_registers[GB_IO_STAT] &= ~3;
/*
Each line is 456 cycles, approximately:
Mode 2 - 80 cycles
Mode 3 - 172 cycles
Mode 0 - 204 cycles
Todo: Mode lengths are not constants???
*/
gb->io_registers[GB_IO_LY] = gb->display_cycles / 456;
bool previous_coincidence_flag = gb->io_registers[GB_IO_STAT] & 4;
gb->io_registers[GB_IO_STAT] &= ~4;
if (gb->io_registers[GB_IO_LY] == gb->io_registers[GB_IO_LYC]) {
gb->io_registers[GB_IO_STAT] |= 4;
if ((gb->io_registers[GB_IO_STAT] & 0x40) && !previous_coincidence_flag) { /* User requests an interrupt on coincidence*/
gb->io_registers[GB_IO_IF] |= 2;
}
}
/* Todo: This behavior is seen in BGB and it fixes some ROMs with delicate timing, such as Hitman's 8bit.
This should be verified to be correct on a real gameboy. */
if (gb->io_registers[GB_IO_LY] == 153 && gb->display_cycles % 456 > 8) {
gb->io_registers[GB_IO_LY] = 0;
}
if (gb->display_cycles >= 456 * 144) { /* VBlank */
gb->io_registers[GB_IO_STAT] |= 1; /* Set mode to 1 */
gb->effective_window_enabled = false;
gb->effective_window_y = 0xFF;
if (last_mode != 1) {
if (gb->io_registers[GB_IO_STAT] & 16) { /* User requests an interrupt on VBlank*/
gb->io_registers[GB_IO_IF] |= 2;
}
gb->io_registers[GB_IO_IF] |= 1;
}
// LY = 151 interrupt bug
if (gb->io_registers[GB_IO_LY] == 151) {
if (gb->display_cycles % 456 < 80) { // Mode 2
if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->ly151_bug_oam) { /* User requests an interrupt on Mode 2 */
gb->io_registers[GB_IO_IF] |= 2;
}
gb->ly151_bug_oam = true;
}
if (gb->display_cycles % 456 < 80 + 172) { /* Mode 3 */
// Nothing to do
}
else { /* Mode 0 */
if (gb->io_registers[GB_IO_STAT] & 8 && !gb->ly151_bug_hblank) { /* User requests an interrupt on Mode 0 */
gb->io_registers[GB_IO_IF] |= 2;
}
gb->ly151_bug_hblank = true;
}
}
return;
}
// Todo: verify this window behavior. It is assumed from the expected behavior of 007 - The World Is Not Enough.
if ((gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_LY] == gb->io_registers[GB_IO_WY]) {
gb->effective_window_enabled = true;
}
if (gb->display_cycles % 456 < 80) { /* Mode 2 */
gb->io_registers[GB_IO_STAT] |= 2; /* Set mode to 2 */
if (last_mode != 2) {
if (gb->io_registers[GB_IO_STAT] & 0x20) { /* User requests an interrupt on Mode 2 */
gb->io_registers[GB_IO_IF] |= 2;
}
/* User requests an interrupt on LY=LYC*/
if (gb->io_registers[GB_IO_STAT] & 64 && gb->io_registers[GB_IO_STAT] & 4) {
gb->io_registers[GB_IO_IF] |= 2;
}
}
/* See above comment about window behavior. */
if (gb->effective_window_enabled && gb->effective_window_y == 0xFF) {
gb->effective_window_y = gb->io_registers[GB_IO_LY];
}
/* Todo: Figure out how the Gameboy handles in-line changes to SCX */
gb->line_x_bias = - (gb->io_registers[GB_IO_SCX] & 0x7);
gb->previous_lcdc_x = gb->line_x_bias;
return;
}
signed short current_lcdc_x = ((gb->display_cycles % 456 - 80) & ~7) + gb->line_x_bias;
for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) {
if (gb->previous_lcdc_x >= 160) {
continue;
}
if (gb->previous_lcdc_x < 0) {
continue;
}
gb->screen[gb->io_registers[GB_IO_LY] * 160 + gb->previous_lcdc_x] =
get_pixel(gb, gb->previous_lcdc_x, gb->io_registers[GB_IO_LY]);
}
if (gb->display_cycles % 456 < 80 + 172) { /* Mode 3 */
gb->io_registers[GB_IO_STAT] |= 3; /* Set mode to 3 */
return;
}
/* if (gb->display_cycles % 456 < 80 + 172 + 204) */ { /* Mode 0*/
if (last_mode != 0) {
if (gb->io_registers[GB_IO_STAT] & 8) { /* User requests an interrupt on Mode 0 */
gb->io_registers[GB_IO_IF] |= 2;
}
if (gb->hdma_on_hblank) {
gb->hdma_on = true;
gb->hdma_cycles = 0;
}
}
return;
}
}

7
Core/display.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef display_h
#define display_h
#include "gb.h"
void display_run(GB_gameboy_t *gb);
void palette_changed(GB_gameboy_t *gb, bool background_palette, unsigned char index);
#endif /* display_h */

444
Core/gb.c Normal file
View File

@ -0,0 +1,444 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/time.h>
#include "gb.h"
#include "memory.h"
#include "timing.h"
#include "z80_cpu.h"
#include "joypad.h"
#include "display.h"
#include "debugger.h"
static const GB_cartridge_t cart_defs[256] = {
// From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type
/* MBC RAM BAT. RTC RUMB. */
{ NO_MBC, false, false, false, false}, // 00h ROM ONLY
{ MBC1 , false, false, false, false}, // 01h MBC1
{ MBC1 , true , false, false, false}, // 02h MBC1+RAM
{ MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY
[5] =
{ MBC2 , true , false, false, false}, // 05h MBC2
{ MBC2 , true , true , false, false}, // 06h MBC2+BATTERY
[8] =
{ NO_MBC, true , false, false, false}, // 08h ROM+RAM
{ NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY
[0xB] =
// Todo: What are these?
{ NO_MBC, false, false, false, false}, // 0Bh MMM01
{ NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM
{ NO_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY
[0xF] =
{ MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY
{ MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY
{ MBC3 , false, false, false, false}, // 11h MBC3
{ MBC3 , true , false, false, false}, // 12h MBC3+RAM
{ MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY
[0x15] =
// Todo: Do these exist?
{ MBC4 , false, false, false, false}, // 15h MBC4
{ MBC4 , true , false, false, false}, // 16h MBC4+RAM
{ MBC4 , true , true , false, false}, // 17h MBC4+RAM+BATTERY
[0x19] =
{ MBC5 , false, false, false, false}, // 19h MBC5
{ MBC5 , true , false, false, false}, // 1Ah MBC5+RAM
{ MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY
{ MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE
{ MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM
{ MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
[0xFC] =
// Todo: What are these?
{ NO_MBC, false, false, false, false}, // FCh POCKET CAMERA
{ NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5
{ NO_MBC, false, false, false, false}, // FEh HuC3
{ NO_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY
};
void gb_attributed_logv(GB_gameboy_t *gb, gb_log_attributes attributes, const char *fmt, va_list args)
{
char *string = NULL;
vasprintf(&string, fmt, args);
if (string) {
if (gb->log_callback) {
gb->log_callback(gb, string, attributes);
}
else {
/* Todo: Add ANSI escape sequences for attributed text */
printf("%s", string);
}
}
free(string);
}
void gb_attributed_log(GB_gameboy_t *gb, gb_log_attributes attributes, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
gb_attributed_logv(gb, attributes, fmt, args);
va_end(args);
}
void gb_log(GB_gameboy_t *gb,const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
gb_attributed_logv(gb, 0, fmt, args);
va_end(args);
}
static char *default_input_callback(GB_gameboy_t *gb)
{
char *expression = NULL;
size_t size = 0;
printf(">");
getline(&expression, &size, stdin);
if (!expression) {
return strdup("");
}
return expression;
}
void gb_init(GB_gameboy_t *gb)
{
memset(gb, 0, sizeof(*gb));
gb->magic = (uintptr_t)'SAME';
gb->version = GB_STRUCT_VERSION;
gb->ram = malloc(gb->ram_size = 0x2000);
gb->vram = malloc(gb->vram_size = 0x2000);
struct timeval now;
gettimeofday(&now, NULL);
gb->last_vblank = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;;
gb->mbc_rom_bank = 1;
gb->last_rtc_second = time(NULL);
gb->last_vblank = clock();
gb->breakpoint = 0xFFFF;
gb->cgb_ram_bank = 1;
/* Todo: this bypasses the rgb encoder because it is not set yet. */
gb->sprite_palletes_rgb[4] = gb->sprite_palletes_rgb[0] = gb->background_palletes_rgb[0] = 0xFFFFFFFF;
gb->sprite_palletes_rgb[5] = gb->sprite_palletes_rgb[1] = gb->background_palletes_rgb[1] = 0xAAAAAAAA;
gb->sprite_palletes_rgb[6] = gb->sprite_palletes_rgb[2] = gb->background_palletes_rgb[2] = 0x55555555;
gb->input_callback = default_input_callback;
gb->cartridge_type = &cart_defs[0]; // Default cartridge type
}
void gb_init_cgb(GB_gameboy_t *gb)
{
memset(gb, 0, sizeof(*gb));
gb->magic = (uintptr_t)'SAME';
gb->version = GB_STRUCT_VERSION;
gb->ram = malloc(gb->ram_size = 0x2000 * 8);
gb->vram = malloc(gb->vram_size = 0x2000 * 2);
gb->is_cgb = true;
gb->cgb_mode = true;
struct timeval now;
gettimeofday(&now, NULL);
gb->last_vblank = (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;
gb->mbc_rom_bank = 1;
gb->last_rtc_second = time(NULL);
gb->last_vblank = clock();
gb->breakpoint = 0xFFFF;
gb->cgb_ram_bank = 1;
gb->input_callback = default_input_callback;
gb->cartridge_type = &cart_defs[0]; // Default cartridge type
}
void gb_free(GB_gameboy_t *gb)
{
if (gb->ram) {
free(gb->ram);
}
if (gb->vram) {
free(gb->vram);
}
if (gb->mbc_ram) {
free(gb->mbc_ram);
}
if (gb->rom) {
free(gb->rom);
}
if (gb->audio_buffer) {
free(gb->audio_buffer);
}
}
int gb_load_bios(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "r");
if (!f) return errno;
fread(gb->bios, sizeof(gb->bios), 1, f);
fclose(f);
return 0;
}
int gb_load_rom(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "r");
if (!f) return errno;
fseek(f, 0, SEEK_END);
gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */
fseek(f, 0, SEEK_SET);
gb->rom = malloc(gb->rom_size);
memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */
fread(gb->rom, gb->rom_size, 1, f);
fclose(f);
gb->cartridge_type = &cart_defs[gb->rom[0x147]];
if (gb->cartridge_type->has_ram) {
static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
gb->mbc_ram = malloc(gb->mbc_ram_size);
}
return 0;
}
/* Todo: we need a sane and protable save state format. */
int gb_save_state(GB_gameboy_t *gb, const char *path)
{
GB_gameboy_t save;
memcpy(&save, gb, offsetof(GB_gameboy_t, first_unsaved_data));
save.cartridge_type = NULL; // Kept from load_rom
save.rom = NULL; // Kept from load_rom
save.rom_size = 0; // Kept from load_rom
save.mbc_ram = NULL;
save.ram = NULL;
save.vram = NULL;
save.screen = NULL; // Kept from user
save.audio_buffer = NULL; // Kept from user
save.buffer_size = 0; // Kept from user
save.sample_rate = 0; // Kept from user
save.audio_position = 0; // Kept from previous state
save.vblank_callback = NULL;
save.user_data = NULL;
memset(save.keys, 0, sizeof(save.keys)); // Kept from user
FILE *f = fopen(path, "w");
if (!f) {
return errno;
}
if (fwrite(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) {
fclose(f);
return EIO;
}
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
fclose(f);
return EIO;
}
if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
fclose(f);
return EIO;
}
if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
fclose(f);
return EIO;
}
errno = 0;
fclose(f);
return errno;
}
int gb_load_state(GB_gameboy_t *gb, const char *path)
{
GB_gameboy_t save;
FILE *f = fopen(path, "r");
if (!f) {
return errno;
}
if (fread(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) {
fclose(f);
return EIO;
}
save.cartridge_type = gb->cartridge_type;
save.rom = gb->rom;
save.rom_size = gb->rom_size;
save.mbc_ram = gb->mbc_ram;
save.ram = gb->ram;
save.vram = gb->vram;
save.screen = gb->screen;
save.audio_buffer = gb->audio_buffer;
save.buffer_size = gb->buffer_size;
save.sample_rate = gb->sample_rate;
save.audio_position = gb->audio_position;
save.vblank_callback = gb->vblank_callback;
save.user_data = gb->user_data;
memcpy(save.keys, gb->keys, sizeof(save.keys));
if (gb->magic != save.magic) {
gb_log(gb, "File is not a save state, or is from an incompatible operating system.\n");
fclose(f);
return -1;
}
if (gb->version != save.version) {
gb_log(gb, "Save state is for a different version of SameBoy.\n");
fclose(f);
return -1;
}
if (gb->mbc_ram_size != save.mbc_ram_size) {
gb_log(gb, "Save state has non-matching MBC RAM size.\n");
fclose(f);
return -1;
}
if (gb->ram_size != save.ram_size) {
gb_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n");
fclose(f);
return -1;
}
if (gb->vram_size != save.vram_size) {
gb_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n");
fclose(f);
return -1;
}
if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
fclose(f);
return EIO;
}
if (fread(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
fclose(f);
return EIO;
}
if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
fclose(f);
return EIO;
}
memcpy(gb, &save, offsetof(GB_gameboy_t, first_unsaved_data));
errno = 0;
fclose(f);
return errno;
}
int gb_save_battery(GB_gameboy_t *gb, const char *path)
{
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
FILE *f = fopen(path, "w");
if (!f) {
return errno;
}
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
fclose(f);
return EIO;
}
if (gb->cartridge_type->has_rtc) {
if (fwrite(gb->rtc_data, 1, sizeof(gb->rtc_data), f) != sizeof(gb->rtc_data)) {
fclose(f);
return EIO;
}
if (fwrite(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) {
fclose(f);
return EIO;
}
}
errno = 0;
fclose(f);
return errno;
}
/* Loading will silently stop if the format is incomplete */
void gb_load_battery(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "r");
if (!f) {
return;
}
if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
goto reset_rtc;
}
if (fread(gb->rtc_data, 1, sizeof(gb->rtc_data), f) != sizeof(gb->rtc_data)) {
goto reset_rtc;
}
if (fread(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) {
goto reset_rtc;
}
if (gb->last_rtc_second > time(NULL)) {
/* We must reset RTC here, or it will not advance. */
goto reset_rtc;
}
if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time,
so if the value we read is lower it means it wasn't
really RTC data. */
goto reset_rtc;
}
goto exit;
reset_rtc:
gb->last_rtc_second = time(NULL);
gb->rtc_high |= 0x80; /* This gives the game a hint that the clock should be reset. */
exit:
fclose(f);
return;
}
void gb_run(GB_gameboy_t *gb)
{
update_joyp(gb);
debugger_run(gb);
cpu_run(gb);
display_run(gb);
}
void gb_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
{
gb->screen = output;
}
void gb_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback)
{
gb->vblank_callback = callback;
}
void gb_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback)
{
gb->log_callback = callback;
}
void gb_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
{
gb->input_callback = callback;
}
void gb_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback)
{
gb->rgb_encode_callback = callback;
}
void gb_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate)
{
if (gb->audio_buffer) {
free(gb->audio_buffer);
}
gb->buffer_size = sample_rate / 25; // 40ms delay
gb->audio_buffer = malloc(gb->buffer_size * 2);
gb->sample_rate = sample_rate;
gb->audio_position = 0;
}

304
Core/gb.h Normal file
View File

@ -0,0 +1,304 @@
#ifndef gb_h
#define gb_h
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include "apu.h"
#define GB_STRUCT_VERSION 6
enum {
GB_REGISTER_AF,
GB_REGISTER_BC,
GB_REGISTER_DE,
GB_REGISTER_HL,
GB_REGISTER_SP,
GB_REGISTERS_16_BIT /* Count */
};
/* Todo: Actually use these! */
enum {
GB_CARRY_FLAG = 16,
GB_HALF_CARRY_FLAG = 32,
GB_SUBSTRACT_FLAG = 64,
GB_ZERO_FLAG = 128,
};
enum {
/* Joypad and Serial */
GB_IO_JOYP = 0x00, // Joypad (R/W)
GB_IO_SB = 0x01, // Serial transfer data (R/W)
GB_IO_SC = 0x02, // Serial Transfer Control (R/W)
/* Missing */
/* Timers */
GB_IO_DIV = 0x04, // Divider Register (R/W)
GB_IO_TIMA = 0x05, // Timer counter (R/W)
GB_IO_TMA = 0x06, // Timer Modulo (R/W)
GB_IO_TAC = 0x07, // Timer Control (R/W)
/* Missing */
GB_IO_IF = 0x0f, // Interrupt Flag (R/W)
/* Sound */
GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W)
GB_IO_NR11 = 0x11, // Channel 1 Sound length/Wave pattern duty (R/W)
GB_IO_NR12 = 0x12, // Channel 1 Volume Envelope (R/W)
GB_IO_NR13 = 0x13, // Channel 1 Frequency lo (Write Only)
GB_IO_NR14 = 0x14, // Channel 1 Frequency hi (R/W)
GB_IO_NR21 = 0x16, // Channel 2 Sound Length/Wave Pattern Duty (R/W)
GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W)
GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W)
GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W)
GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W)
GB_IO_NR31 = 0x1b, // Channel 3 Sound Length
GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W)
GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W)
GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W)
/* Missing */
GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W)
GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W)
GB_IO_NR43 = 0x22, // Channel 4 Polynomial Counter (R/W)
GB_IO_NR44 = 0x23, // Channel 4 Counter/consecutive, Inital (R/W)
GB_IO_NR50 = 0x24, // Channel control / ON-OFF / Volume (R/W)
GB_IO_NR51 = 0x25, // Selection of Sound output terminal (R/W)
GB_IO_NR52 = 0x26, // Sound on/off
/* Missing */
GB_IO_WAV_START = 0x30, // Wave pattern start
GB_IO_WAV_END = 0x3f, // Wave pattern end
/* Graphics */
GB_IO_LCDC = 0x40, // LCD Control (R/W)
GB_IO_STAT = 0x41, // LCDC Status (R/W)
GB_IO_SCY = 0x42, // Scroll Y (R/W)
GB_IO_SCX = 0x43, // Scroll X (R/W)
GB_IO_LY = 0x44, // LCDC Y-Coordinate (R)
GB_IO_LYC = 0x45, // LY Compare (R/W)
GB_IO_DMA = 0x46, // DMA Transfer and Start Address (W)
GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only
GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only
GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only
GB_IO_WY = 0x4a, // Window Y Position (R/W)
GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W)
/* Missing */
/* General CGB features */
GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch
/* Missing */
GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank
GB_IO_BIOS = 0x50, // Write to disable the BIOS mapping
/* CGB DMA */
GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High
GB_IO_HDMA2 = 0x52, // CGB Mode Only - New DMA Source, Low
GB_IO_HDMA3 = 0x53, // CGB Mode Only - New DMA Destination, High
GB_IO_HDMA4 = 0x54, // CGB Mode Only - New DMA Destination, Low
GB_IO_HDMA5 = 0x55, // CGB Mode Only - New DMA Length/Mode/Start
/* IR */
GB_IO_RP = 0x56, // CGB Mode Only - Infrared Communications Port
/* Missing */
/* CGB Paletts */
GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index
GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data
GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index
GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data
GB_IO_DMG_EMULATION = 0x6c, // (FEh) Bit 0 (Read/Write) - CGB Mode Only
/* Missing */
GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank
GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write)
GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write)
GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only
GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write)
GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes
GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes
GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only
};
#define LCDC_PERIOD 70224
#define CPU_FREQUENCY 0x400000
#define DIV_CYCLES (0x100)
#define FRAME_LENGTH 16742706 // in nanoseconds
typedef enum {
GB_LOG_BOLD = 1,
GB_LOG_DASHED_UNDERLINE = 2,
GB_LOG_UNDERLINE = 4,
GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE
} gb_log_attributes;
struct GB_gameboy_s;
typedef struct GB_gameboy_s GB_gameboy_t;
typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, gb_log_attributes attributes);
typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b);
typedef struct {
enum {
NO_MBC,
MBC1,
MBC2,
MBC3,
MBC4, // Does this exist???
MBC5,
} mbc_type;
bool has_ram;
bool has_battery;
bool has_rtc;
bool has_rumble;
} GB_cartridge_t;
typedef struct GB_gameboy_s{
uintptr_t magic; // States are currently platform dependent
int version; // and version dependent
/* Registers */
unsigned short pc;
unsigned short registers[GB_REGISTERS_16_BIT];
bool ime;
unsigned char interrupt_enable;
/* CPU and General Hardware Flags*/
bool cgb_mode;
bool is_cgb;
bool cgb_double_speed;
bool halted;
bool stopped;
/* HDMA */
bool hdma_on;
bool hdma_on_hblank;
unsigned char hdma_steps_left;
unsigned short hdma_cycles;
unsigned short hdma_current_src, hdma_current_dest;
/* Memory */
unsigned char *rom;
size_t rom_size;
unsigned short mbc_rom_bank;
const GB_cartridge_t *cartridge_type;
unsigned char *mbc_ram;
unsigned char mbc_ram_bank;
size_t mbc_ram_size;
bool mbc_ram_enable;
bool mbc_ram_banking;
unsigned char *ram;
unsigned long ram_size; // Different between CGB and DMG
unsigned char cgb_ram_bank;
unsigned char hram[0xFFFF - 0xFF80];
unsigned char io_registers[0x80];
/* Video Display */
unsigned char *vram;
unsigned long vram_size; // Different between CGB and DMG
unsigned char cgb_vram_bank;
unsigned char oam[0xA0];
unsigned char background_palletes_data[0x40];
unsigned char sprite_palletes_data[0x40];
uint32_t background_palletes_rgb[0x20];
uint32_t sprite_palletes_rgb[0x20];
bool ly151_bug_oam;
bool ly151_bug_hblank;
signed short previous_lcdc_x;
signed short line_x_bias;
bool effective_window_enabled;
unsigned char effective_window_y;
unsigned char bios[0x900];
bool bios_finished;
/* Timing */
signed long last_vblank;
unsigned long display_cycles;
unsigned long div_cycles;
unsigned long tima_cycles;
unsigned long dma_cycles;
double apu_cycles;
/* APU */
GB_apu_t apu;
int16_t *audio_buffer;
unsigned int buffer_size;
unsigned int sample_rate;
unsigned int audio_position;
volatile bool audio_copy_in_progress;
bool audio_stream_started; // detects first copy request to minimize lag
/* I/O */
uint32_t *screen;
GB_vblank_callback_t vblank_callback;
bool keys[8];
/* RTC */
union {
struct {
unsigned char rtc_seconds;
unsigned char rtc_minutes;
unsigned char rtc_hours;
unsigned char rtc_days;
unsigned char rtc_high;
};
unsigned char rtc_data[5];
};
time_t last_rtc_second;
/* Unsaved User */
struct {} first_unsaved_data;
bool turbo;
bool debug_stopped;
unsigned short breakpoint;
GB_log_callback_t log_callback;
GB_input_callback_t input_callback;
GB_rgb_encode_callback_t rgb_encode_callback;
void *user_data;
int debug_call_depth;
bool debug_fin_command, debug_next_command;
} GB_gameboy_t;
#ifndef __printflike
/* Missing from Linux headers. */
#define __printflike(fmtarg, firstvararg) \
__attribute__((__format__ (__printf__, fmtarg, firstvararg)))
#endif
void gb_init(GB_gameboy_t *gb);
void gb_init_cgb(GB_gameboy_t *gb);
void gb_free(GB_gameboy_t *gb);
int gb_load_bios(GB_gameboy_t *gb, const char *path);
int gb_load_rom(GB_gameboy_t *gb, const char *path);
int gb_save_battery(GB_gameboy_t *gb, const char *path);
void gb_load_battery(GB_gameboy_t *gb, const char *path);
int gb_save_state(GB_gameboy_t *gb, const char *path);
int gb_load_state(GB_gameboy_t *gb, const char *path);
void gb_run(GB_gameboy_t *gb);
void gb_set_pixels_output(GB_gameboy_t *gb, uint32_t *output);
void gb_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
void gb_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
void gb_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3);
void gb_attributed_log(GB_gameboy_t *gb, gb_log_attributes attributes, const char *fmt, ...) __printflike(3, 4);
void gb_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback);
void gb_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate);
void gb_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback);
#endif /* gb_h */

50
Core/joypad.c Normal file
View File

@ -0,0 +1,50 @@
#include <stdio.h>
#include "gb.h"
#include "joypad.h"
void update_joyp(GB_gameboy_t *gb)
{
unsigned char key_selection = 0;
unsigned char previous_state = 0;
/* Todo: add delay to key selection */
previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
gb->io_registers[GB_IO_JOYP] &= 0xF0;
switch (key_selection) {
case 3:
/* Nothing is wired, all up */
gb->io_registers[GB_IO_JOYP] |= 0x0F;
break;
case 2:
/* Direction keys */
for (unsigned char i = 0; i < 4; i++) {
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i;
}
break;
case 1:
/* Other keys */
for (unsigned char i = 0; i < 4; i++) {
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i;
}
break;
case 0:
/* Todo: verifiy this is correct */
for (unsigned char i = 0; i < 4; i++) {
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i;
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i;
}
break;
default:
break;
}
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
/* Todo: disable when emulating CGB */
gb->io_registers[GB_IO_IF] |= 0x10;
}
gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support
}

8
Core/joypad.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef joypad_h
#define joypad_h
#include "gb.h"
void update_joyp(GB_gameboy_t *gb);
void update_keys_status(GB_gameboy_t *gb);
#endif /* joypad_h */

531
Core/memory.c Normal file
View File

@ -0,0 +1,531 @@
#include <stdio.h>
#include <stdbool.h>
#include "gb.h"
#include "joypad.h"
#include "display.h"
#include "memory.h"
typedef unsigned char GB_read_function_t(GB_gameboy_t *gb, unsigned short addr);
typedef void GB_write_function_t(GB_gameboy_t *gb, unsigned short addr, unsigned char value);
static unsigned char read_rom(GB_gameboy_t *gb, unsigned short addr)
{
if (addr < 0x100 && !gb->bios_finished) {
return gb->bios[addr];
}
if (addr >= 0x200 && addr < 0x900 && gb->is_cgb && !gb->bios_finished) {
return gb->bios[addr];
}
if (!gb->rom_size) {
return 0xFF;
}
return gb->rom[addr];
}
static unsigned char read_mbc_rom(GB_gameboy_t *gb, unsigned short addr)
{
if (gb->mbc_rom_bank >= gb->rom_size / 0x4000) {
return 0xFF;
}
return gb->rom[(addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000];
}
static unsigned char read_vram(GB_gameboy_t *gb, unsigned short addr)
{
if ((gb->io_registers[GB_IO_STAT] & 0x3) == 3) {
return 0xFF;
}
return gb->vram[(addr & 0x1FFF) + (unsigned short) gb->cgb_vram_bank * 0x2000];
}
static unsigned char read_mbc_ram(GB_gameboy_t *gb, unsigned short addr)
{
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
/* RTC read */
gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */
return gb->rtc_data[gb->mbc_ram_bank - 8];
}
unsigned int ram_index = (addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000;
if (!gb->mbc_ram_enable)
{
gb_log(gb, "Read from %02x:%04x (%06x) (Disabled MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index);
return 0xFF;
}
if (ram_index >= gb->mbc_ram_size) {
gb_log(gb, "Read from %02x:%04x (%06x) (Unmapped MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index);
return 0xFF;
}
return gb->mbc_ram[(addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000];
}
static unsigned char read_ram(GB_gameboy_t *gb, unsigned short addr)
{
return gb->ram[addr & 0x0FFF];
}
static unsigned char read_banked_ram(GB_gameboy_t *gb, unsigned short addr)
{
return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000];
}
static unsigned char read_high_memory(GB_gameboy_t *gb, unsigned short addr)
{
if (addr < 0xFE00) {
return gb->ram[addr & 0x0FFF];
}
if (addr < 0xFEA0) {
if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) {
return 0xFF;
}
return gb->oam[addr & 0xFF];
}
if (addr < 0xFF00) {
/* Unusable, simulate Gameboy Color */
if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */
return 0xFF;
}
return (addr & 0xF0) | ((addr >> 4) & 0xF);
}
if (addr < 0xFF80) {
switch (addr & 0xFF) {
case GB_IO_JOYP:
case GB_IO_IF:
case GB_IO_DIV:
case GB_IO_TIMA:
case GB_IO_TMA:
case GB_IO_TAC:
case GB_IO_LCDC:
case GB_IO_STAT:
case GB_IO_SCY:
case GB_IO_SCX:
case GB_IO_LY:
case GB_IO_LYC:
case GB_IO_BGP:
case GB_IO_OBP0:
case GB_IO_OBP1:
case GB_IO_WY:
case GB_IO_WX:
case GB_IO_HDMA1:
case GB_IO_HDMA2:
case GB_IO_HDMA3:
case GB_IO_HDMA4:
case GB_IO_PCM_12:
case GB_IO_PCM_34:
case GB_IO_SB:
return gb->io_registers[addr & 0xFF];
case GB_IO_HDMA5:
return gb->io_registers[GB_IO_HDMA5] | 0x7F;
case GB_IO_SVBK:
if (!gb->cgb_mode) {
return 0xFF;
}
return gb->cgb_ram_bank | ~0x7;
case GB_IO_VBK:
if (!gb->cgb_mode) {
return 0xFF;
}
return gb->cgb_vram_bank | ~0x1;
case GB_IO_BGPI:
case GB_IO_OBPI:
if (!gb->is_cgb) {
return 0xFF;
}
return gb->io_registers[addr & 0xFF] | 0x40;
case GB_IO_BGPD:
case GB_IO_OBPD:
{
if (!gb->is_cgb) {
return 0xFF;
}
unsigned char index_reg = (addr & 0xFF) - 1;
return ((addr & 0xFF) == GB_IO_BGPD?
gb->background_palletes_data :
gb->sprite_palletes_data)[gb->io_registers[index_reg] & 0x3F];
}
case GB_IO_KEY1:
if (!gb->is_cgb) {
return 0xFF;
}
return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E);
default:
if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) {
return apu_read(gb, addr & 0xFF);
}
return 0xFF;
}
/* Hardware registers */
return 0;
}
if (addr == 0xFFFF) {
/* Interrupt Mask */
return gb->interrupt_enable;
}
/* HRAM */
return gb->hram[addr - 0xFF80];
}
static GB_read_function_t * const read_map[] =
{
read_rom, read_rom, read_rom, read_rom, /* 0XXX, 1XXX, 2XXX, 3XXX */
read_mbc_rom, read_mbc_rom, read_mbc_rom, read_mbc_rom, /* 4XXX, 5XXX, 6XXX, 7XXX */
read_vram, read_vram, /* 8XXX, 9XXX */
read_mbc_ram, read_mbc_ram, /* AXXX, BXXX */
read_ram, read_banked_ram, /* CXXX, DXXX */
read_high_memory, read_high_memory, /* EXXX FXXX */
};
unsigned char read_memory(GB_gameboy_t *gb, unsigned short addr)
{
if (addr < 0xFF00 && gb->dma_cycles) {
/* Todo: can we access IO registers during DMA? */
return 0xFF;
}
return read_map[addr >> 12](gb, addr);
}
static void write_mbc(GB_gameboy_t *gb, unsigned short addr, unsigned char value)
{
if (gb->cartridge_type->mbc_type == NO_MBC) return;
switch (addr >> 12) {
case 0:
case 1:
gb->mbc_ram_enable = value == 0x0a;
break;
case 2:
bank_low:
/* Bank number, lower bits */
if (gb->cartridge_type->mbc_type == MBC1) {
value &= 0x1F;
}
if (gb->cartridge_type->mbc_type != MBC5 && !value) {
value++;
}
gb->mbc_rom_bank = (gb->mbc_rom_bank & 0x100) | value;
break;
case 3:
if (gb->cartridge_type->mbc_type != MBC5) goto bank_low;
if (value > 1) {
gb_log(gb, "Bank overflow: [%x] <- %d\n", addr, value);
}
gb->mbc_rom_bank = (gb->mbc_rom_bank & 0xFF) | value << 8;
break;
case 4:
case 5:
if (gb->cartridge_type->mbc_type == MBC1) {
if (gb->mbc_ram_banking) {
gb->mbc_ram_bank = value & 0x3;
}
else {
gb->mbc_rom_bank = (gb->mbc_rom_bank & 0x1F) | ((value & 0x3) << 5);
}
}
else {
gb->mbc_ram_bank = value;
}
break;
case 6:
case 7:
if (gb->cartridge_type->mbc_type == MBC1) {
value &= 1;
if (value & !gb->mbc_ram_banking) {
gb->mbc_ram_bank = gb->mbc_rom_bank >> 5;
gb->mbc_rom_bank &= 0x1F;
}
else if (value & !gb->mbc_ram_banking) {
gb->mbc_rom_bank = gb->mbc_rom_bank | (gb->mbc_ram_bank << 5);
gb->mbc_ram_bank = 0;
}
gb->mbc_ram_banking = value;
}
break;
}
if (gb->cartridge_type->mbc_type != MBC5 && !gb->mbc_rom_bank) {
gb->mbc_rom_bank = 1;
}
}
static void write_vram(GB_gameboy_t *gb, unsigned short addr, unsigned char value)
{
if ((gb->io_registers[GB_IO_STAT] & 0x3) == 3) {
//gb_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr);
return;
}
gb->vram[(addr & 0x1FFF) + (unsigned short) gb->cgb_vram_bank * 0x2000] = value;
}
static void write_mbc_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value)
{
if (gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
/* RTC write*/
gb->rtc_data[gb->mbc_ram_bank - 8] = value;
gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */
return;
}
unsigned int ram_index = (addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000;
if (!gb->mbc_ram_enable)
{
gb_log(gb, "Write to %02x:%04x (%06x) (Disabled MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index);
return;
}
if (ram_index >= gb->mbc_ram_size) {
gb_log(gb, "Write to %02x:%04x (%06x) (Unmapped MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index);
return;
}
gb->mbc_ram[(addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000] = value;
}
static void write_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value)
{
gb->ram[addr & 0x0FFF] = value;
}
static void write_banked_ram(GB_gameboy_t *gb, unsigned short addr, unsigned char value)
{
gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value;
}
static void write_high_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value)
{
if (addr < 0xFE00) {
gb_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr);
gb->ram[addr & 0x0FFF] = value;
return;
}
if (addr < 0xFEA0) {
if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) {
return;
}
gb->oam[addr & 0xFF] = value;
return;
}
if (addr < 0xFF00) {
gb_log(gb, "Wrote %02x to %04x (Unused)\n", value, addr);
return;
}
if (addr < 0xFF80) {
/* Hardware registers */
switch (addr & 0xFF) {
case GB_IO_TAC:
case GB_IO_SCX:
case GB_IO_IF:
case GB_IO_TIMA:
case GB_IO_TMA:
case GB_IO_SCY:
case GB_IO_LYC:
case GB_IO_BGP:
case GB_IO_OBP0:
case GB_IO_OBP1:
case GB_IO_WY:
case GB_IO_WX:
case GB_IO_HDMA1:
case GB_IO_HDMA2:
case GB_IO_HDMA3:
case GB_IO_HDMA4:
case GB_IO_SB:
gb->io_registers[addr & 0xFF] = value;
return;
case GB_IO_LCDC:
if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
gb->display_cycles = 0;
}
gb->io_registers[GB_IO_LCDC] = value;
return;
case GB_IO_STAT:
/* Delete previous R/W bits */
gb->io_registers[GB_IO_STAT] &= 7;
/* Set them by value */
gb->io_registers[GB_IO_STAT] |= value & ~7;
/* Set unused bit to 1 */
gb->io_registers[GB_IO_STAT] |= 0x80;
return;
case GB_IO_DIV:
gb->io_registers[GB_IO_DIV] = 0;
return;
case GB_IO_JOYP:
gb->io_registers[GB_IO_JOYP] &= 0x0F;
gb->io_registers[GB_IO_JOYP] |= value & 0xF0;
return;
case GB_IO_BIOS:
gb->bios_finished = true;
return;
case GB_IO_DMG_EMULATION:
// Todo: Can it be disabled? What about values other than 1?
gb->cgb_mode = false;
return;
case GB_IO_DMA:
if (value <= 0xD0) {
for (unsigned char i = 0xA0; i--;) {
gb->oam[i] = read_memory(gb, (value << 8) + i);
}
}
/* Todo: measure this value */
gb->dma_cycles = 640;
return;
case GB_IO_SVBK:
if (!gb->cgb_mode) {
return;
}
gb->cgb_ram_bank = value & 0x7;
if (!gb->cgb_ram_bank) {
gb->cgb_ram_bank++;
}
return;
case GB_IO_VBK:
if (!gb->cgb_mode) {
return;
}
gb->cgb_vram_bank = value & 0x1;
return;
case GB_IO_BGPI:
case GB_IO_OBPI:
if (!gb->is_cgb) {
return;
}
gb->io_registers[addr & 0xFF] = value;
return;
case GB_IO_BGPD:
case GB_IO_OBPD:
if (!gb->is_cgb) {
return;
}
unsigned char index_reg = (addr & 0xFF) - 1;
((addr & 0xFF) == GB_IO_BGPD?
gb->background_palletes_data :
gb->sprite_palletes_data)[gb->io_registers[index_reg] & 0x3F] = value;
palette_changed(gb, (addr & 0xFF) == GB_IO_BGPD, gb->io_registers[index_reg] & 0x3F);
if (gb->io_registers[index_reg] & 0x80) {
gb->io_registers[index_reg]++;
gb->io_registers[index_reg] |= 0x80;
}
return;
case GB_IO_KEY1:
if (!gb->is_cgb) {
return;
}
gb->io_registers[GB_IO_KEY1] = value;
return;
case GB_IO_HDMA5:
if ((value & 0x80) == 0 && gb->hdma_on_hblank) {
gb->hdma_on_hblank = false;
return;
}
gb->hdma_on = (value & 0x80) == 0;
gb->hdma_on_hblank = (value & 0x80) != 0;
gb->io_registers[GB_IO_HDMA5] = value;
gb->hdma_current_src = (gb->io_registers[GB_IO_HDMA1] << 8) | (gb->io_registers[GB_IO_HDMA2] & 0xF0);
gb->hdma_current_dest = (gb->io_registers[GB_IO_HDMA3] << 8) | (gb->io_registers[GB_IO_HDMA4] & 0xF0);
gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1;
gb->hdma_cycles = 0;
return;
case GB_IO_SC:
if ((value & 0x80) && (value & 0x1) ) {
gb->io_registers[GB_IO_SB] = 0xFF;
gb->io_registers[GB_IO_IF] |= 0x8;
}
return;
default:
if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) {
apu_write(gb, addr & 0xFF, value);
return;
}
if (gb->io_registers[addr & 0xFF] != 0x37) {
gb_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr);
}
gb->io_registers[addr & 0xFF] = 0x37;
return;
}
}
if (addr == 0xFFFF) {
/* Interrupt mask */
gb->interrupt_enable = value;
return;
}
/* HRAM */
gb->hram[addr - 0xFF80] = value;
}
static GB_write_function_t * const write_map[] =
{
write_mbc, write_mbc, write_mbc, write_mbc, /* 0XXX, 1XXX, 2XXX, 3XXX */
write_mbc, write_mbc, write_mbc, write_mbc, /* 4XXX, 5XXX, 6XXX, 7XXX */
write_vram, write_vram, /* 8XXX, 9XXX */
write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */
write_ram, write_banked_ram, /* CXXX, DXXX */
write_high_memory, write_high_memory, /* EXXX FXXX */
};
void write_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value)
{
if (addr < 0xFF00 && gb->dma_cycles) {
/* Todo: can we access IO registers during DMA? */
return;
}
write_map[addr >> 12](gb, addr, value);
}
void hdma_run(GB_gameboy_t *gb)
{
if (!gb->hdma_on) return;
while (gb->hdma_cycles >= 8) {
gb->hdma_cycles -= 8;
// The CGB bios uses the dest in "absolute" space, while some games use it relative to VRAM.
// This "normalizes" the dest to the CGB address space.
gb->hdma_current_dest &= 0x1fff;
gb->hdma_current_dest |= 0x8000;
if ((gb->hdma_current_src < 0x8000 || (gb->hdma_current_src >= 0xa000 && gb->hdma_current_src < 0xe000))) {
for (unsigned char i = 0; i < 0x10; i++) {
write_memory(gb, gb->hdma_current_dest + i, read_memory(gb, gb->hdma_current_src + i));
}
}
else {
gb->halted = false;
}
gb->hdma_current_src += 0x10;
gb->hdma_current_dest += 0x10;
if(--gb->hdma_steps_left == 0){
gb->hdma_on = false;
gb->hdma_on_hblank = false;
gb->io_registers[GB_IO_HDMA5] &= 0x7F;
break;
}
if (gb->hdma_on_hblank) {
gb->hdma_on = false;
break;
}
}
}

9
Core/memory.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef memory_h
#define memory_h
#include "gb.h"
unsigned char read_memory(GB_gameboy_t *gb, unsigned short addr);
void write_memory(GB_gameboy_t *gb, unsigned short addr, unsigned char value);
void hdma_run(GB_gameboy_t *gb);
#endif /* memory_h */

81
Core/timing.c Normal file
View File

@ -0,0 +1,81 @@
#include "gb.h"
#include "timing.h"
#include "memory.h"
void advance_cycles(GB_gameboy_t *gb, unsigned char cycles)
{
// Affected by speed boost
if (gb->dma_cycles > cycles){
gb->dma_cycles -= cycles;
}
else {
gb->dma_cycles = 0;
}
if (gb->cgb_double_speed) {
cycles >>=1;
}
// Not affected by speed boost
gb->hdma_cycles += cycles;
gb->display_cycles += cycles;
gb->div_cycles += cycles;
gb->tima_cycles += cycles;
gb->apu_cycles += cycles;
hdma_run(gb);
timers_run(gb);
apu_run(gb);
}
void timers_run(GB_gameboy_t *gb)
{
/* Standard Timers */
static const unsigned long GB_TAC_RATIOS[] = {1024, 16, 64, 256};
if (gb->div_cycles >= DIV_CYCLES) {
gb->div_cycles -= DIV_CYCLES;
gb->io_registers[GB_IO_DIV]++;
}
while (gb->tima_cycles >= GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3]) {
gb->tima_cycles -= GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3];
if (gb->io_registers[GB_IO_TAC] & 4) {
gb->io_registers[GB_IO_TIMA]++;
if (gb->io_registers[GB_IO_TIMA] == 0) {
gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA];
gb->io_registers[GB_IO_IF] |= 4;
}
}
}
/* RTC */
if (gb->display_cycles >= LCDC_PERIOD) { /* Time is a syscall and therefore is slow, so we update the RTC
only during vblanks. */
if ((gb->rtc_high & 0x40) == 0) { /* is timer running? */
time_t current_time = time(NULL);
while (gb->last_rtc_second < current_time) {
gb->last_rtc_second++;
if (++gb->rtc_seconds == 60)
{
gb->rtc_seconds = 0;
if (++gb->rtc_minutes == 60)
{
gb->rtc_minutes = 0;
if (++gb->rtc_hours == 24)
{
gb->rtc_hours = 0;
if (++gb->rtc_days == 0)
{
if (gb->rtc_high & 1) /* Bit 8 of days*/
{
gb->rtc_high |= 0x80; /* Overflow bit */
}
gb->rtc_high ^= 1;
}
}
}
}
}
}
}
}

7
Core/timing.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef timing_h
#define timing_h
#include "gb.h"
void advance_cycles(GB_gameboy_t *gb, unsigned char cycles);
void timers_run(GB_gameboy_t *gb);
#endif /* timing_h */

1342
Core/z80_cpu.c Normal file

File diff suppressed because it is too large Load Diff

7
Core/z80_cpu.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef z80_cpu_h
#define z80_cpu_h
#include "gb.h"
void cpu_disassemble(GB_gameboy_t *gb, unsigned short pc, unsigned short count);
void cpu_run(GB_gameboy_t *gb);
#endif /* z80_cpu_h */

680
Core/z80_disassembler.c Normal file
View File

@ -0,0 +1,680 @@
#include <stdio.h>
#include <stdbool.h>
#include "z80_cpu.h"
#include "memory.h"
#include "gb.h"
typedef void GB_opcode_t(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc);
static void ill(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, ".BYTE %02x\n", opcode);
(*pc)++;
}
static void nop(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "NOP\n");
(*pc)++;
}
static void stop(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "STOP\n");
(*pc)++;
}
static char *register_names[] = {"af", "bc", "de", "hl", "sp"};
static void ld_rr_d16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
unsigned short value;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
value = read_memory(gb, (*pc)++);
value |= read_memory(gb, (*pc)++) << 8;
gb_log(gb, "LD %s, %04x\n", register_names[register_id], value);
}
static void ld_drr_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
gb_log(gb, "LD [%s], a\n", register_names[register_id]);
}
static void inc_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
gb_log(gb, "INC %s\n", register_names[register_id]);
}
static void inc_hr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
(*pc)++;
register_id = ((opcode >> 4) + 1) & 0x03;
gb_log(gb, "INC %c\n", register_names[register_id][0]);
}
static void dec_hr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
(*pc)++;
register_id = ((opcode >> 4) + 1) & 0x03;
gb_log(gb, "DEC %c\n", register_names[register_id][0]);
}
static void ld_hr_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
(*pc)++;
register_id = ((opcode >> 4) + 1) & 0x03;
gb_log(gb, "LD %c, %02x\n", register_names[register_id][0], read_memory(gb, (*pc)++));
}
static void rlca(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RLCA\n");
}
static void rla(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RLA\n");
}
static void ld_da16_sp(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc){
unsigned short addr;
(*pc)++;
addr = read_memory(gb, (*pc)++);
addr |= read_memory(gb, (*pc)++) << 8;
gb_log(gb, "LD [%04x], sp\n", addr);
}
static void add_hl_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
(*pc)++;
register_id = (opcode >> 4) + 1;
gb_log(gb, "ADD hl, %s\n", register_names[register_id]);
}
static void ld_a_drr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
gb_log(gb, "LD a, [%s]\n", register_names[register_id]);
}
static void dec_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
gb_log(gb, "DEC %s\n", register_names[register_id]);
}
static void inc_lr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
gb_log(gb, "INC %c\n", register_names[register_id][1]);
}
static void dec_lr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
gb_log(gb, "DEC %c\n", register_names[register_id][1]);
}
static void ld_lr_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = (read_memory(gb, (*pc)++) >> 4) + 1;
gb_log(gb, "LD %c, %02x\n", register_names[register_id][1], read_memory(gb, (*pc)++));
}
static void rrca(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "RRCA\n");
(*pc)++;
}
static void rra(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "RRA\n");
(*pc)++;
}
static void jr_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_attributed_log(gb, GB_LOG_UNDERLINE, "JR %04x\n", *pc + (signed char) read_memory(gb, (*pc)) + 1);
(*pc)++;
}
static const char *condition_code(unsigned char opcode)
{
switch ((opcode >> 3) & 0x3) {
case 0:
return "nz";
case 1:
return "z";
case 2:
return "nc";
case 3:
return "c";
}
return NULL;
}
static void jr_cc_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %04x\n", condition_code(opcode), *pc + (signed char)read_memory(gb, (*pc)) + 1);
(*pc)++;
}
static void daa(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "DAA\n");
(*pc)++;
}
static void cpl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "CPL\n");
(*pc)++;
}
static void scf(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "SCF\n");
(*pc)++;
}
static void ccf(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "CCF\n");
(*pc)++;
}
static void ld_dhli_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "LD [hli], a\n");
(*pc)++;
}
static void ld_dhld_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "LD [hld], a\n");
(*pc)++;
}
static void ld_a_dhli(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "LD a, [hli]\n");
(*pc)++;
}
static void ld_a_dhld(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "LD a, [hld]\n");
(*pc)++;
}
static void inc_dhl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "INC [hl]\n");
(*pc)++;
}
static void dec_dhl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
gb_log(gb, "DEC [hl]\n");
(*pc)++;
}
static void ld_dhl_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LD [hl], %02x\n", read_memory(gb, (*pc)++));
}
static const char *get_src_name(unsigned char opcode)
{
unsigned char src_register_id;
unsigned char src_low;
src_register_id = ((opcode >> 1) + 1) & 3;
src_low = !(opcode & 1);
if (src_register_id == GB_REGISTER_AF && src_low) {
return "[hl]";
}
if (src_low) {
return register_names[src_register_id] + 1;
}
static const char *high_register_names[] = {"a", "b", "d", "h"};
return high_register_names[src_register_id];
}
static const char *get_dst_name(unsigned char opcode)
{
unsigned char dst_register_id;
unsigned char dst_low;
dst_register_id = ((opcode >> 4) + 1) & 3;
dst_low = opcode & 8;
if (dst_register_id == GB_REGISTER_AF && dst_low) {
return "[hl]";
}
if (dst_low) {
return register_names[dst_register_id] + 1;
}
static const char *high_register_names[] = {"a", "b", "d", "h"};
return high_register_names[dst_register_id];
}
static void ld_r_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LD %s, %s\n", get_dst_name(opcode), get_src_name(opcode));
}
static void add_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "ADD %s\n", get_src_name(opcode));
}
static void adc_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "ADC %s\n", get_src_name(opcode));
}
static void sub_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "SUB %s\n", get_src_name(opcode));
}
static void sbc_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "SBC %s\n", get_src_name(opcode));
}
static void and_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "AND %s\n", get_src_name(opcode));
}
static void xor_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "XOR %s\n", get_src_name(opcode));
}
static void or_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "OR %s\n", get_src_name(opcode));
}
static void cp_a_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "CP %s\n", get_src_name(opcode));
}
static void halt(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "HALT\n");
}
static void ret_cc(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "RET %s\n", condition_code(opcode));
}
static void pop_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = ((read_memory(gb, (*pc)++) >> 4) + 1) & 3;
gb_log(gb, "POP %s\n", register_names[register_id]);
}
static void jp_cc_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %04x\n", condition_code(opcode), read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8));
(*pc) += 2;
}
static void jp_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "JP %04x\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8));
(*pc) += 2;
}
static void call_cc_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "CALL %s, %04x\n", condition_code(opcode), read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8));
(*pc) += 2;
}
static void push_rr(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char register_id;
register_id = ((read_memory(gb, (*pc)++) >> 4) + 1) & 3;
gb_log(gb, "PUSH %s\n", register_names[register_id]);
}
static void add_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "ADD %02x\n", read_memory(gb, (*pc)++));
}
static void adc_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "ADC %02x\n", read_memory(gb, (*pc)++));
}
static void sub_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "SUB %02x\n", read_memory(gb, (*pc)++));
}
static void sbc_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LBC %02x\n", read_memory(gb, (*pc)++));
}
static void and_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "AND %02x\n", read_memory(gb, (*pc)++));
}
static void xor_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "XOR %02x\n", read_memory(gb, (*pc)++));
}
static void or_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "OR %02x\n", read_memory(gb, (*pc)++));
}
static void cp_a_d8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "CP %02x\n", read_memory(gb, (*pc)++));
}
static void rst(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RST %02x\n", opcode ^ 0xC7);
}
static void ret(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n");
}
static void reti(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n");
}
static void call_a16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "CALL %04x\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8));
(*pc) += 2;
}
static void ld_da8_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
unsigned char temp = read_memory(gb, (*pc)++);
gb_log(gb, "LDH [%02x], a\n", temp);
}
static void ld_a_da8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
unsigned char temp = read_memory(gb, (*pc)++);
gb_log(gb, "LDH a, [%02x]\n", temp);
}
static void ld_dc_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LDH [c], a\n");
}
static void ld_a_dc(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LDH a, [c]\n");
}
static void add_sp_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
signed char temp = read_memory(gb, (*pc)++);
gb_log(gb, "ADD SP, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp);
}
static void jp_hl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "JP hl\n");
}
static void ld_da16_a(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LD [%04x], a\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8));
(*pc) += 2;
}
static void ld_a_da16(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LD a, [%04x]\n", read_memory(gb, *pc) | (read_memory(gb, *pc + 1) << 8));
(*pc) += 2;
}
static void di(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "DI\n");
}
static void ei(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "EI\n");
}
static void ld_hl_sp_r8(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
signed char temp = read_memory(gb, (*pc)++);
gb_log(gb, "LD hl, sp, %s%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp);
}
static void ld_sp_hl(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "LD sp, hl\n");
}
static void rlc_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RLC %s\n", get_src_name(opcode));
}
static void rrc_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RRC %s\n", get_src_name(opcode));
}
static void rl_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RL %s\n", get_src_name(opcode));
}
static void rr_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RR %s\n", get_src_name(opcode));
}
static void sla_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "SLA %s\n", get_src_name(opcode));
}
static void sra_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "SRA %s\n", get_src_name(opcode));
}
static void srl_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "SRL %s\n", get_src_name(opcode));
}
static void swap_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
(*pc)++;
gb_log(gb, "RLC %s\n", get_src_name(opcode));
}
static void bit_r(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
unsigned char bit;
(*pc)++;
bit = ((opcode >> 3) & 7);
if ((opcode & 0xC0) == 0x40) { /* Bit */
gb_log(gb, "BIT %s, %d\n", get_src_name(opcode), bit);
}
else if ((opcode & 0xC0) == 0x80) { /* res */
gb_log(gb, "RES %s, %d\n", get_src_name(opcode), bit);
}
else if ((opcode & 0xC0) == 0xC0) { /* set */
gb_log(gb, "SET %s, %d\n", get_src_name(opcode), bit);
}
}
static void cb_prefix(GB_gameboy_t *gb, unsigned char opcode, unsigned short *pc)
{
opcode = read_memory(gb, ++*pc);
switch (opcode >> 3) {
case 0:
rlc_r(gb, opcode, pc);
break;
case 1:
rrc_r(gb, opcode, pc);
break;
case 2:
rl_r(gb, opcode, pc);
break;
case 3:
rr_r(gb, opcode, pc);
break;
case 4:
sla_r(gb, opcode, pc);
break;
case 5:
sra_r(gb, opcode, pc);
break;
case 6:
swap_r(gb, opcode, pc);
break;
case 7:
srl_r(gb, opcode, pc);
break;
default:
bit_r(gb, opcode, pc);
break;
}
}
static GB_opcode_t *opcodes[256] = {
/* X0 X1 X2 X3 X4 X5 X6 X7 */
/* X8 X9 Xa Xb Xc Xd Xe Xf */
nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */
ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca,
stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */
jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra,
jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */
jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl,
jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */
jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 4X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 5X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 6X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, halt, ld_r_r, /* 7X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */
adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r,
sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */
sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r,
and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */
xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r,
or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */
cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r,
ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */
ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst,
ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */
ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst,
ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */
add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst,
ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */
ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst,
};
void cpu_disassemble(GB_gameboy_t *gb, unsigned short pc, unsigned short count)
{
while (count--) {
gb_log(gb, "%s%04x: ", pc == gb->pc? "-> ": " ", pc);
unsigned char opcode = read_memory(gb, pc);
opcodes[opcode](gb, opcode, &pc);
}
}

94
Makefile Normal file
View File

@ -0,0 +1,94 @@
ifeq ($(shell uname -s),Darwin)
default: cocoa
else
default: sdl
endif
BIN := build/bin
OBJ := build/obj
CC := clang
CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE
SDL_LDFLAGS := -lSDL
LDFLAGS += -lc -lm
ifeq ($(shell uname -s),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
SDL_LDFLAGS := -framework SDL
endif
cocoa: $(BIN)/Sameboy.app
sdl: $(BIN)/sdl/sameboy $(BIN)/sdl/dmg_boot.bin $(BIN)/sdl/cgb_boot.bin
bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin
CORE_SOURCES := $(shell echo Core/*.c)
SDL_SOURCES := $(shell echo SDL/*.c)
ifeq ($(shell uname -s),Darwin)
COCOA_SOURCES := $(shell echo Cocoa/*.m)
SDL_SOURCES += $(shell echo SDL/*.m)
endif
CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES))
COCOA_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(COCOA_SOURCES))
SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES))
ALL_OBJECTS := $(CORE_OBJECTS) $(COCOA_OBJECTS) $(SDL_OBJECTS)
# Automatic dependency generation
-include $(ALL_OBJECTS:.o=.dep)
$(OBJ)/%.dep: %
-@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@
$(OBJ)/%.c.o: %.c
-@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJ)/%.m.o: %.m
-@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@
# Cocoa Port
$(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \
$(shell echo Cocoa/*.icns) \
$(shell echo Cocoa/info.plist) \
$(BIN)/BootROMs/dmg_boot.bin \
$(BIN)/BootROMs/cgb_boot.bin \
$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Document.nib \
$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/MainMenu.nib
mkdir -p $(BIN)/Sameboy.app/Contents/Resources
cp Cocoa/*.icns $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/Sameboy.app/Contents/Resources/
cp Cocoa/info.plist $(BIN)/Sameboy.app/Contents/
$(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
-@mkdir -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit
$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib
ibtool --compile $@ $^
$(BIN)/sdl/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS)
-@mkdir -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS)
$(BIN)/BootROMs/%.bin: BootROMs/%.asm
-@mkdir -p $(dir $@)
cd BootROMs && rgbasm -o ../$@.tmp ../$<
rgblink -o $@.tmp2 $@.tmp
head -c $(if $(filter dmg,$(CC)), 256, 2309) $@.tmp2 > $@
@rm $@.tmp $@.tmp2
$(BIN)/sdl/%.bin: $(BIN)/BootROMs/%.bin
-@mkdir -p $(dir $@)
cp -f $^ $@
clean:
rm -rf build

16
SDL/SDLMain.h Normal file
View File

@ -0,0 +1,16 @@
/* SDLMain.m - main entry point for our Cocoa-ized SDL app
Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
Non-NIB-Code & other changes: Max Horn <max@quendi.de>
Feel free to customize this file to suit your needs
*/
#ifndef _SDLMain_h_
#define _SDLMain_h_
#import <Cocoa/Cocoa.h>
@interface SDLMain : NSObject<NSApplicationDelegate>
@end
#endif /* _SDLMain_h_ */

382
SDL/SDLMain.m Normal file
View File

@ -0,0 +1,382 @@
/* SDLMain.m - main entry point for our Cocoa-ized SDL app
Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
Non-NIB-Code & other changes: Max Horn <max@quendi.de>
Feel free to customize this file to suit your needs
*/
#include <SDL/SDL.h>
#include "SDLMain.h"
#include <sys/param.h> /* for MAXPATHLEN */
#include <unistd.h>
#import <objc/runtime.h>
/* For some reaon, Apple removed setAppleMenu from the headers in 10.4,
but the method still is there and works. To avoid warnings, we declare
it ourselves here. */
@interface NSApplication(SDL_Missing_Methods)
- (void)setAppleMenu:(NSMenu *)menu;
@end
/* Use this flag to determine whether we use SDLMain.nib or not */
#define SDL_USE_NIB_FILE 0
/* Use this flag to determine whether we use CPS (docking) or not */
#define SDL_USE_CPS 1
#ifdef SDL_USE_CPS
/* Portions of CPS.h */
typedef struct CPSProcessSerNum
{
UInt32 lo;
UInt32 hi;
} CPSProcessSerNum;
extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn);
extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn);
#endif /* SDL_USE_CPS */
static int gArgc;
static char **gArgv;
static BOOL gFinderLaunch;
static BOOL gCalledAppMainline = FALSE;
static NSString *getApplicationName(void)
{
const NSDictionary *dict;
NSString *appName = 0;
/* Determine the application name */
dict = (__bridge const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle());
if (dict)
appName = [dict objectForKey: @"CFBundleName"];
if (![appName length])
appName = [[NSProcessInfo processInfo] processName];
return appName;
}
#if SDL_USE_NIB_FILE
/* A helper category for NSString */
@interface NSString (ReplaceSubString)
- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString;
@end
#endif
@interface NSApplication (SDLApplication)
@end
@implementation NSApplication (SDLApplication)
/* Invoked from the Quit menu item */
- (void)_terminate:(id)sender
{
/* Post a SDL_QUIT event */
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
/* Call "super" */
[self _terminate:sender];
}
/* Use swizzling to avoid warning and undocumented Obj C runtime behavior. Didn't feel like rewriting SDLMain for this. */
+ (void) load
{
method_exchangeImplementations(class_getInstanceMethod(self, @selector(terminate:)), class_getInstanceMethod(self, @selector(_terminate:)));
}
@end
/* The main class of the application, the application's delegate */
@implementation SDLMain
/* Set the working directory to the .app's parent directory */
- (void) setupWorkingDirectory:(BOOL)shouldChdir
{
if (shouldChdir)
{
char parentdir[MAXPATHLEN];
CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url);
if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) {
chdir(parentdir); /* chdir to the binary app's parent */
}
CFRelease(url);
CFRelease(url2);
}
}
#if SDL_USE_NIB_FILE
/* Fix menu to contain the real app name instead of "SDL App" */
- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName
{
NSRange aRange;
NSEnumerator *enumerator;
NSMenuItem *menuItem;
aRange = [[aMenu title] rangeOfString:@"SDL App"];
if (aRange.length != 0)
[aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]];
enumerator = [[aMenu itemArray] objectEnumerator];
while ((menuItem = [enumerator nextObject]))
{
aRange = [[menuItem title] rangeOfString:@"SDL App"];
if (aRange.length != 0)
[menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]];
if ([menuItem hasSubmenu])
[self fixMenu:[menuItem submenu] withAppName:appName];
}
}
#else
static void setApplicationMenu(void)
{
/* warning: this code is very odd */
NSMenu *appleMenu;
NSMenuItem *menuItem;
NSString *title;
NSString *appName;
appName = getApplicationName();
appleMenu = [[NSMenu alloc] initWithTitle:@""];
/* Add menu items */
title = [@"About " stringByAppendingString:appName];
[appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
[appleMenu addItem:[NSMenuItem separatorItem]];
title = [@"Hide " stringByAppendingString:appName];
[appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
[menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
[appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
[appleMenu addItem:[NSMenuItem separatorItem]];
title = [@"Quit " stringByAppendingString:appName];
[appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
/* Put menu into the menubar */
menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
[menuItem setSubmenu:appleMenu];
[[NSApp mainMenu] addItem:menuItem];
/* Tell the application object that this is now the application menu */
[NSApp setAppleMenu:appleMenu];
}
/* Create a window menu */
static void setupWindowMenu(void)
{
NSMenu *windowMenu;
NSMenuItem *windowMenuItem;
NSMenuItem *menuItem;
windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
/* "Minimize" item */
menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
[windowMenu addItem:menuItem];
/* Put menu into the menubar */
windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
[windowMenuItem setSubmenu:windowMenu];
[[NSApp mainMenu] addItem:windowMenuItem];
/* Tell the application object that this is now the window menu */
[NSApp setWindowsMenu:windowMenu];
/* Finally give up our references to the objects */
}
/* Replacement for NSApplicationMain */
static void CustomApplicationMain (int argc, char **argv)
{
SDLMain *sdlMain;
/* Ensure the application object is initialised */
[NSApplication sharedApplication];
#ifdef SDL_USE_CPS
{
CPSProcessSerNum PSN;
/* Tell the dock about us */
if (!CPSGetCurrentProcess(&PSN))
if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103))
if (!CPSSetFrontProcess(&PSN))
[NSApplication sharedApplication];
}
#endif /* SDL_USE_CPS */
/* Set up the menubar */
[NSApp setMainMenu:[[NSMenu alloc] init]];
setApplicationMenu();
setupWindowMenu();
/* Create SDLMain and make it the app delegate */
sdlMain = [[SDLMain alloc] init];
[NSApp setDelegate:sdlMain];
/* Start the main event loop */
[NSApp run];
}
#endif
/*
* Catch document open requests...this lets us notice files when the app
* was launched by double-clicking a document, or when a document was
* dragged/dropped on the app's icon. You need to have a
* CFBundleDocumentsType section in your Info.plist to get this message,
* apparently.
*
* Files are added to gArgv, so to the app, they'll look like command line
* arguments. Previously, apps launched from the finder had nothing but
* an argv[0].
*
* This message may be received multiple times to open several docs on launch.
*
* This message is ignored once the app's mainline has been called.
*/
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
const char *temparg;
size_t arglen;
char *arg;
char **newargv;
if (!gFinderLaunch) /* MacOS is passing command line args. */
return FALSE;
if (gCalledAppMainline) /* app has started, ignore this document. */
return FALSE;
temparg = [filename UTF8String];
arglen = SDL_strlen(temparg) + 1;
arg = (char *) SDL_malloc(arglen);
if (arg == NULL)
return FALSE;
newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2));
if (newargv == NULL)
{
SDL_free(arg);
return FALSE;
}
gArgv = newargv;
SDL_strlcpy(arg, temparg, arglen);
gArgv[gArgc++] = arg;
gArgv[gArgc] = NULL;
return TRUE;
}
/* Called when the internal event loop has just started running */
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
int status;
/* Set the working directory to the .app's parent directory */
[self setupWorkingDirectory:gFinderLaunch];
#if SDL_USE_NIB_FILE
/* Set the main menu to contain the real app name instead of "SDL App" */
[self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()];
#endif
/* Hand off to main application code */
gCalledAppMainline = TRUE;
status = SDL_main (gArgc, gArgv);
/* We're done, thank you for playing */
exit(status);
}
@end
@implementation NSString (ReplaceSubString)
- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString
{
unsigned int bufferSize;
unsigned int selfLen = [self length];
unsigned int aStringLen = [aString length];
unichar *buffer;
NSRange localRange;
NSString *result;
bufferSize = selfLen + aStringLen - aRange.length;
buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar));
/* Get first part into buffer */
localRange.location = 0;
localRange.length = aRange.location;
[self getCharacters:buffer range:localRange];
/* Get middle part into buffer */
localRange.location = 0;
localRange.length = aStringLen;
[aString getCharacters:(buffer+aRange.location) range:localRange];
/* Get last part into buffer */
localRange.location = aRange.location + aRange.length;
localRange.length = selfLen - localRange.location;
[self getCharacters:(buffer+aRange.location+aStringLen) range:localRange];
/* Build output string */
result = [NSString stringWithCharacters:buffer length:bufferSize];
NSDeallocateMemoryPages(buffer, bufferSize);
return result;
}
@end
void cocoa_disable_filtering(void) {
CGContextSetInterpolationQuality([[NSGraphicsContext currentContext] CGContext], kCGInterpolationNone);
}
#ifdef main
# undef main
#endif
/* Main entry point to executable - should *not* be SDL_main! */
int main (int argc, char **argv)
{
/* Copy the arguments into a global variable */
/* This is passed if we are launched by double-clicking */
if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) {
gArgv = (char **) SDL_malloc(sizeof (char *) * 2);
gArgv[0] = argv[0];
gArgv[1] = NULL;
gArgc = 1;
gFinderLaunch = YES;
} else {
int i;
gArgc = argc;
gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1));
for (i = 0; i <= argc; i++)
gArgv[i] = argv[i];
gFinderLaunch = NO;
}
#if SDL_USE_NIB_FILE
NSApplicationMain (argc, argv);
#else
CustomApplicationMain (argc, argv);
#endif
return 0;
}

190
SDL/main.c Normal file
View File

@ -0,0 +1,190 @@
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include <SDL/SDL.h>
#include "gb.h"
void update_keys_status(GB_gameboy_t *gb)
{
static bool ctrl = false;
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch( event.type ){
case SDL_QUIT:
exit(0);
case SDL_KEYDOWN:
case SDL_KEYUP:
gb->stopped = false;
switch (event.key.keysym.sym) {
case SDLK_RIGHT:
gb->keys[0] = event.type == SDL_KEYDOWN;
break;
case SDLK_LEFT:
gb->keys[1] = event.type == SDL_KEYDOWN;
break;
case SDLK_UP:
gb->keys[2] = event.type == SDL_KEYDOWN;
break;
case SDLK_DOWN:
gb->keys[3] = event.type == SDL_KEYDOWN;
break;
case SDLK_x:
gb->keys[4] = event.type == SDL_KEYDOWN;
break;
case SDLK_z:
gb->keys[5] = event.type == SDL_KEYDOWN;
break;
case SDLK_BACKSPACE:
gb->keys[6] = event.type == SDL_KEYDOWN;
break;
case SDLK_RETURN:
gb->keys[7] = event.type == SDL_KEYDOWN;
break;
case SDLK_SPACE:
gb->turbo = event.type == SDL_KEYDOWN;
break;
case SDLK_LCTRL:
ctrl = event.type == SDL_KEYDOWN;
break;
case SDLK_c:
if (ctrl && event.type == SDL_KEYDOWN) {
ctrl = false;
gb->debug_stopped = true;
}
break;
default:
break;
}
break;
default:
break;
}
}
}
void vblank(GB_gameboy_t *gb)
{
SDL_Surface *screen = gb->user_data;
SDL_Flip(screen);
update_keys_status(gb);
gb_set_pixels_output(gb, screen->pixels);
}
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
static const char *executable_folder(void)
{
static char path[1024] = {0,};
if (path[0]) {
return path;
}
/* Ugly unportable code! :( */
#ifdef __APPLE__
unsigned int length = sizeof(path) - 1;
_NSGetExecutablePath(&path[0], &length);
#else
#ifdef __linux__
ssize_t length = readlink("/proc/self/exe", &path[0], sizeof(path) - 1);
assert (length != -1);
#else
#warning Unsupported OS: sameboy will only run from CWD
/* No OS-specific way, assume running from CWD */
getcwd(&path[0], sizeof(path) - 1);
return path;
#endif
#endif
size_t pos = strlen(path);
while (pos) {
pos--;
if (path[pos] == '/') {
path[pos] = 0;
break;
}
}
return path;
}
static char *executable_relative_path(const char *filename)
{
static char path[1024];
snprintf(path, sizeof(path), "%s/%s", executable_folder(), filename);
return path;
}
static SDL_Surface *screen = NULL;
static uint32_t rgb_encode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b)
{
return SDL_MapRGB(screen->format, r, g, b);
}
#ifdef __APPLE__
extern void cocoa_disable_filtering(void);
#endif
int main(int argc, char **argv)
{
GB_gameboy_t gb;
bool dmg = false;
if (argc == 1 || argc > 3) {
usage:
fprintf(stderr, "Usage: %s [--dmg] rom\n", argv[0]);
exit(1);
}
if (argc == 3) {
if (strcmp(argv[1], "--dmg") == 0) {
dmg = true;
}
else {
goto usage;
}
}
if (dmg) {
gb_init(&gb);
if (gb_load_bios(&gb, executable_relative_path("dmg_boot.bin"))) {
perror("Failed to load boot ROM");
exit(1);
}
}
else {
gb_init_cgb(&gb);
if (gb_load_bios(&gb, executable_relative_path("cgb_boot.bin"))) {
perror("Failed to load boot ROM");
exit(1);
}
}
if (gb_load_rom(&gb, argv[argc - 1])) {
perror("Failed to load ROM");
exit(1);
}
SDL_Init( SDL_INIT_EVERYTHING );
screen = SDL_SetVideoMode(160, 144, 32, SDL_SWSURFACE );
#ifdef __APPLE__
cocoa_disable_filtering();
#endif
SDL_LockSurface(screen);
gb_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
gb.user_data = screen;
gb_set_pixels_output(&gb, screen->pixels);
gb_set_rgb_encode_callback(&gb, rgb_encode);
while (true) {
gb_run(&gb);
}
return 0;
}