Initial public commit
This commit is contained in:
commit
f1e9623371
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build
|
BIN
BootROMs/SameboyLogo.1bpp
Normal file
BIN
BootROMs/SameboyLogo.1bpp
Normal file
Binary file not shown.
BIN
BootROMs/SameboyLogo.png
Normal file
BIN
BootROMs/SameboyLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
1087
BootROMs/cgb_boot.asm
Normal file
1087
BootROMs/cgb_boot.asm
Normal file
File diff suppressed because it is too large
Load Diff
141
BootROMs/dmg_boot.asm
Normal file
141
BootROMs/dmg_boot.asm
Normal 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
7
Cocoa/AppDelegate.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
17
Cocoa/AppDelegate.m
Normal file
17
Cocoa/AppDelegate.m
Normal 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
BIN
Cocoa/AppIcon.icns
Normal file
Binary file not shown.
11
Cocoa/AudioClient.h
Normal file
11
Cocoa/AudioClient.h
Normal 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
115
Cocoa/AudioClient.m
Normal 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
BIN
Cocoa/Cartridge.icns
Normal file
Binary file not shown.
BIN
Cocoa/ColorCartridge.icns
Normal file
BIN
Cocoa/ColorCartridge.icns
Normal file
Binary file not shown.
12
Cocoa/Document.h
Normal file
12
Cocoa/Document.h
Normal 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
366
Cocoa/Document.m
Normal 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
109
Cocoa/Document.xib
Normal 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
9
Cocoa/GBView.h
Normal 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
163
Cocoa/GBView.m
Normal 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
71
Cocoa/Info.plist
Normal 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
405
Cocoa/MainMenu.xib
Normal 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
1
Cocoa/PkgInfo
Normal file
@ -0,0 +1 @@
|
|||||||
|
APPL????
|
5
Cocoa/main.m
Normal file
5
Cocoa/main.m
Normal 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
415
Core/apu.c
Normal 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
59
Core/apu.h
Normal 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
410
Core/debugger.c
Normal 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
7
Core/debugger.h
Normal 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
384
Core/display.c
Normal 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
7
Core/display.h
Normal 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
444
Core/gb.c
Normal 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
304
Core/gb.h
Normal 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
50
Core/joypad.c
Normal 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
8
Core/joypad.h
Normal 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
531
Core/memory.c
Normal 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
9
Core/memory.h
Normal 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
81
Core/timing.c
Normal 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
7
Core/timing.h
Normal 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
1342
Core/z80_cpu.c
Normal file
File diff suppressed because it is too large
Load Diff
7
Core/z80_cpu.h
Normal file
7
Core/z80_cpu.h
Normal 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
680
Core/z80_disassembler.c
Normal 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
94
Makefile
Normal 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
16
SDL/SDLMain.h
Normal 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
382
SDL/SDLMain.m
Normal 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
190
SDL/main.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user