diff --git a/Cocoa/Document.h b/Cocoa/Document.h
index b651646..e0a3aa3 100644
--- a/Cocoa/Document.h
+++ b/Cocoa/Document.h
@@ -39,6 +39,14 @@
@property (nonatomic, strong) IBOutlet GBCheatWindowController *cheatWindowController;
@property (nonatomic, readonly) Document *partner;
@property (nonatomic, readonly) bool isSlave;
+@property (strong) IBOutlet NSView *gbsPlayerView;
+@property (strong) IBOutlet NSTextField *gbsTitle;
+@property (strong) IBOutlet NSTextField *gbsAuthor;
+@property (strong) IBOutlet NSTextField *gbsCopyright;
+@property (strong) IBOutlet NSPopUpButton *gbsTracks;
+@property (strong) IBOutlet NSButton *gbsPlayPauseButton;
+@property (strong) IBOutlet NSButton *gbsRewindButton;
+@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton;
-(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
diff --git a/Cocoa/Document.m b/Cocoa/Document.m
index e7812d4..a5d291e 100644
--- a/Cocoa/Document.m
+++ b/Cocoa/Document.m
@@ -513,6 +513,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (void) start
{
+ self.gbsPlayPauseButton.state = true;
self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0;
if (master) {
[master start];
@@ -524,6 +525,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (void) stop
{
+ self.gbsPlayPauseButton.state = false;
if (master) {
if (!master->running) return;
GB_debugger_set_disabled(&gb, true);
@@ -834,14 +836,86 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
return YES;
}
+- (IBAction)changeGBSTrack:(id)sender
+{
+ [self performAtomicBlock:^{
+ GB_gbs_switch_track(&gb, self.gbsTracks.indexOfSelectedItem);
+ }];
+}
+- (IBAction)gbsNextPrevPushed:(id)sender
+{
+ if (self.gbsNextPrevButton.selectedSegment == 0) {
+ // Previous
+ if (self.gbsTracks.indexOfSelectedItem == 0) {
+ [self.gbsTracks selectItemAtIndex:self.gbsTracks.numberOfItems - 1];
+ }
+ else {
+ [self.gbsTracks selectItemAtIndex:self.gbsTracks.indexOfSelectedItem - 1];
+ }
+ }
+ else {
+ // Next
+ if (self.gbsTracks.indexOfSelectedItem == self.gbsTracks.numberOfItems - 1) {
+ [self.gbsTracks selectItemAtIndex: 0];
+ }
+ else {
+ [self.gbsTracks selectItemAtIndex:self.gbsTracks.indexOfSelectedItem + 1];
+ }
+ }
+ [self changeGBSTrack:sender];
+}
+
+- (void)prepareGBSInterface: (GB_gbs_info_t *)info
+{
+ GB_set_rendering_disabled(&gb, true);
+ _view = nil;
+ for (NSView *view in _mainWindow.contentView.subviews) {
+ [view removeFromSuperview];
+ }
+ [[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
+ [_mainWindow setContentSize:self.gbsPlayerView.bounds.size];
+ _mainWindow.styleMask &= ~NSWindowStyleMaskResizable;
+ [_mainWindow.contentView addSubview:self.gbsPlayerView];
+
+ self.gbsTitle.stringValue = [NSString stringWithCString:info->title encoding:NSISOLatin1StringEncoding] ?: @"GBS Player";
+ self.gbsAuthor.stringValue = [NSString stringWithCString:info->author encoding:NSISOLatin1StringEncoding] ?: @"Unknown Composer";
+ NSString *copyright = [NSString stringWithCString:info->copyright encoding:NSISOLatin1StringEncoding];
+ if (copyright) {
+ copyright = [@"©" stringByAppendingString:copyright];
+ }
+ self.gbsCopyright.stringValue = copyright ?: @"Missing copyright information";
+ for (unsigned i = 0; i < info->track_count; i++) {
+ [self.gbsTracks addItemWithTitle:[NSString stringWithFormat:@"Track %u", i + 1]];
+ }
+ [self.gbsTracks selectItemAtIndex:info->first_track];
+ self.gbsPlayPauseButton.image.template = true;
+ self.gbsPlayPauseButton.alternateImage.template = true;
+ self.gbsRewindButton.image.template = true;
+ for (unsigned i = 0; i < 2; i++) {
+ [self.gbsNextPrevButton imageForSegment:i].template = true;
+ }
+
+ if (!self.audioClient.isPlaying) {
+ [self.audioClient start];
+ }
+
+ if (@available(macOS 10.10, *)) {
+ _mainWindow.titlebarAppearsTransparent = true;
+ }
+}
+
- (void) loadROM
{
NSString *rom_warnings = [self captureOutputForBlock:^{
GB_debugger_clear_symbols(&gb);
- if ([[self.fileType pathExtension] isEqualToString:@"isx"]) {
+ if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"isx"]) {
GB_load_isx(&gb, self.fileURL.path.UTF8String);
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String);
-
+ }
+ else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) {
+ GB_gbs_info_t info;
+ GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info);
+ [self prepareGBSInterface:&info];
}
else {
GB_load_rom(&gb, [self.fileURL.path UTF8String]);
@@ -861,8 +935,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (void)close
{
[self disconnectLinkCable];
- [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"];
- [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"];
+ if (!self.gbsPlayerView) {
+ [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"];
+ [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"];
+ }
[self stop];
[self.consoleWindow close];
[self.memoryWindow close];
diff --git a/Cocoa/GBS.xib b/Cocoa/GBS.xib
new file mode 100644
index 0000000..6d5ba01
--- /dev/null
+++ b/Cocoa/GBS.xib
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist
index 4d42b46..5e409c9 100644
--- a/Cocoa/Info.plist
+++ b/Cocoa/Info.plist
@@ -51,7 +51,7 @@
CFBundleTypeExtensions
- gbc
+ isx
CFBundleTypeIconFile
ColorCartridge
@@ -68,6 +68,26 @@
NSDocumentClass
Document
+
+ CFBundleTypeExtensions
+
+ gbs
+
+ CFBundleTypeIconFile
+ ColorCartridge
+ CFBundleTypeName
+ Game Boy Sound File
+ CFBundleTypeRole
+ Viewer
+ LSItemContentTypes
+
+ com.github.liji32.sameboy.gbs
+
+ LSTypeIsPackage
+ 0
+ NSDocumentClass
+ Document
+
CFBundleExecutable
SameBoy
@@ -156,6 +176,25 @@
+
+ UTTypeConformsTo
+
+ public.data
+
+ UTTypeDescription
+ Game Boy Sound File
+ UTTypeIconFile
+ ColorCartridge
+ UTTypeIdentifier
+ com.github.liji32.sameboy.gbs
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ gbs
+
+
+
NSCameraUsageDescription
SameBoy needs to access your camera to emulate the Game Boy Camera
diff --git a/Cocoa/Next.png b/Cocoa/Next.png
new file mode 100644
index 0000000..cd9a4c3
Binary files /dev/null and b/Cocoa/Next.png differ
diff --git a/Cocoa/Next@2x.png b/Cocoa/Next@2x.png
new file mode 100644
index 0000000..1debb1d
Binary files /dev/null and b/Cocoa/Next@2x.png differ
diff --git a/Cocoa/Pause.png b/Cocoa/Pause.png
new file mode 100644
index 0000000..2bb380b
Binary files /dev/null and b/Cocoa/Pause.png differ
diff --git a/Cocoa/Pause@2x.png b/Cocoa/Pause@2x.png
new file mode 100644
index 0000000..36b6da0
Binary files /dev/null and b/Cocoa/Pause@2x.png differ
diff --git a/Cocoa/Play.png b/Cocoa/Play.png
new file mode 100644
index 0000000..3f87092
Binary files /dev/null and b/Cocoa/Play.png differ
diff --git a/Cocoa/Play@2x.png b/Cocoa/Play@2x.png
new file mode 100644
index 0000000..0de0553
Binary files /dev/null and b/Cocoa/Play@2x.png differ
diff --git a/Cocoa/Previous.png b/Cocoa/Previous.png
new file mode 100644
index 0000000..cc91221
Binary files /dev/null and b/Cocoa/Previous.png differ
diff --git a/Cocoa/Previous@2x.png b/Cocoa/Previous@2x.png
new file mode 100644
index 0000000..77b0157
Binary files /dev/null and b/Cocoa/Previous@2x.png differ
diff --git a/Cocoa/Rewind.png b/Cocoa/Rewind.png
new file mode 100644
index 0000000..999f358
Binary files /dev/null and b/Cocoa/Rewind.png differ
diff --git a/Cocoa/Rewind@2x.png b/Cocoa/Rewind@2x.png
new file mode 100644
index 0000000..d845b54
Binary files /dev/null and b/Cocoa/Rewind@2x.png differ
diff --git a/Core/display.c b/Core/display.c
index 2545e3b..03d600d 100644
--- a/Core/display.c
+++ b/Core/display.c
@@ -126,7 +126,7 @@ static void display_vblank(GB_gameboy_t *gb)
bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80;
- if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) {
+ if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) {
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
if (!GB_is_sgb(gb)) {
uint32_t color = 0;
@@ -153,7 +153,7 @@ static void display_vblank(GB_gameboy_t *gb)
}
}
- if (gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) {
+ if (!gb->disable_rendering && gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) {
GB_borrow_sgb_border(gb);
uint32_t border_colors[16 * 4];