Update SameBoy to v0.14.7.

This commit is contained in:
Tim Allen 2021-10-31 09:36:19 +11:00
commit 6df1807068
52 changed files with 2273 additions and 666 deletions

View File

@ -1,10 +1,10 @@
set -e set -e
./build/bin/tester/sameboy_tester --jobs 5 \ ./build/bin/tester/sameboy_tester --jobs 5 \
--length 40 .github/actions/cgb_sound.gb \ --length 45 .github/actions/cgb_sound.gb \
--length 10 .github/actions/cgb-acid2.gbc \ --length 10 .github/actions/cgb-acid2.gbc \
--length 10 .github/actions/dmg-acid2.gb \ --length 10 .github/actions/dmg-acid2.gb \
--dmg --length 40 .github/actions/dmg_sound-2.gb \ --dmg --length 45 .github/actions/dmg_sound-2.gb \
--dmg --length 20 .github/actions/oam_bug-2.gb --dmg --length 20 .github/actions/oam_bug-2.gb
mv .github/actions/dmg{,-mode}-acid2.bmp mv .github/actions/dmg{,-mode}-acid2.bmp
@ -15,18 +15,18 @@ mv .github/actions/dmg{,-mode}-acid2.bmp
set +e set +e
FAILED_TESTS=` FAILED_TESTS=`
shasum .github/actions/*.bmp | grep -q -E -v \(\ shasum .github/actions/*.bmp | grep -E -v \(\
44ce0c7d49254df0637849c9155080ac7dc3ef3d\ \ .github/actions/cgb-acid2.bmp\|\ 5283564df0cf5bb78a7a90aff026c1a4692fd39e\ \ .github/actions/cgb-acid2.bmp\|\
dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\ dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\
0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\ 0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\
c50daed36c57a8170ff362042694786676350997\ \ .github/actions/dmg-mode-acid2.bmp\|\ a732077f98f43d9231453b1764d9f797a836924d\ \ .github/actions/dmg-mode-acid2.bmp\|\
c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\ c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\
f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\ f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\
\)` \)`
if [ -n "$FAILED_TESTS" ] ; then if [ -n "$FAILED_TESTS" ] ; then
echo "Failed the following tests:" echo "Failed the following tests:"
echo $FAILED_TESTS | tr " " "\n" | grep -q -o -E "[^/]+\.bmp" | sed s/.bmp// | sort echo $FAILED_TESTS | tr " " "\n" | grep -o -E "[^/]+\.bmp" | sed s/.bmp// | sort
exit 1 exit 1
fi fi

View File

@ -6,7 +6,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, ubuntu-latest, ubuntu-16.04] os: [macos-latest, ubuntu-latest, ubuntu-18.04]
cc: [gcc, clang] cc: [gcc, clang]
include: include:
- os: macos-latest - os: macos-latest

View File

@ -25,6 +25,7 @@ Start:
; Init waveform ; Init waveform
ld c, $10 ld c, $10
ld hl, $FF30
.waveformLoop .waveformLoop
ldi [hl], a ldi [hl], a
cpl cpl
@ -44,7 +45,6 @@ Start:
ldh [$25], a ldh [$25], a
ld a, $77 ld a, $77
ldh [$24], a ldh [$24], a
ld hl, $FF30
; Init BG palette ; Init BG palette
ld a, $fc ld a, $fc
@ -190,10 +190,9 @@ ENDC
IF !DEF(FAST) IF !DEF(FAST)
call DoIntroAnimation call DoIntroAnimation
ld a, 45 ld a, 48 ; frames to wait after playing the chime
ldh [WaitLoopCounter], a ldh [WaitLoopCounter], a
; Wait ~0.75 seconds ld b, 4 ; frames to wait before playing the chime
ld b, a
call WaitBFrames call WaitBFrames
; Play first sound ; Play first sound
@ -1187,7 +1186,7 @@ ChangeAnimationPalette:
call WaitFrame call WaitFrame
call LoadPalettesFromHRAM call LoadPalettesFromHRAM
; Delay the wait loop while the user is selecting a palette ; Delay the wait loop while the user is selecting a palette
ld a, 45 ld a, 48
ldh [WaitLoopCounter], a ldh [WaitLoopCounter], a
pop de pop de
pop bc pop bc

View File

@ -86,7 +86,7 @@ static uint32_t color_to_int(NSColor *color)
} }
if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) { if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) {
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:true];
} }
} }
@ -106,7 +106,7 @@ static uint32_t color_to_int(NSColor *color)
NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame]; NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame];
new.origin.x = old.origin.x; new.origin.x = old.origin.x;
new.origin.y = old.origin.y + (old.size.height - new.size.height); new.origin.y = old.origin.y + (old.size.height - new.size.height);
[_preferencesWindow setFrame:new display:YES animate:_preferencesWindow.visible]; [_preferencesWindow setFrame:new display:true animate:_preferencesWindow.visible];
[_preferencesWindow.contentView addSubview:tab]; [_preferencesWindow.contentView addSubview:tab];
} }
@ -171,7 +171,7 @@ static uint32_t color_to_int(NSColor *color)
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification - (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{ {
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true];
} }
- (void)updateFound - (void)updateFound
@ -242,7 +242,7 @@ static uint32_t color_to_int(NSColor *color)
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.updatesSpinner stopAnimation:nil]; [self.updatesSpinner stopAnimation:nil];
[self.updatesButton setEnabled:YES]; [self.updatesButton setEnabled:true];
}); });
if ([(NSHTTPURLResponse *)response statusCode] == 200) { if ([(NSHTTPURLResponse *)response statusCode] == 200) {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
@ -312,7 +312,7 @@ static uint32_t color_to_int(NSColor *color)
_downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory _downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask inDomain:NSUserDomainMask
appropriateForURL:[[NSBundle mainBundle] bundleURL] appropriateForURL:[[NSBundle mainBundle] bundleURL]
create:YES create:true
error:nil] path]; error:nil] path];
NSTask *unzipTask; NSTask *unzipTask;
if (!_downloadDirectory) { if (!_downloadDirectory) {

View File

@ -321,7 +321,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
{ {
if (_gbsVisualizer) { if (_gbsVisualizer) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[_gbsVisualizer setNeedsDisplay:YES]; [_gbsVisualizer setNeedsDisplay:true];
}); });
} }
[self.view flip]; [self.view flip];
@ -422,7 +422,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
[self.audioClient start]; [self.audioClient start];
} }
hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:true];
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
/* Clear pending alarms, don't play alarms while playing */ /* Clear pending alarms, don't play alarms while playing */
@ -502,7 +502,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
[audioLock unlock]; [audioLock unlock];
[self.audioClient stop]; [self.audioClient stop];
self.audioClient = nil; self.audioClient = nil;
self.view.mouseHidingEnabled = NO; self.view.mouseHidingEnabled = false;
GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]); GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]);
GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]); GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]);
unsigned time_to_alarm = GB_time_to_alarm(&gb); unsigned time_to_alarm = GB_time_to_alarm(&gb);
@ -593,12 +593,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
current_model = (enum model)[sender tag]; current_model = (enum model)[sender tag];
} }
if (!modelsChanging && [sender tag] == MODEL_NONE) { GB_switch_model_and_reset(&gb, [self internalModel]);
GB_reset(&gb);
}
else {
GB_switch_model_and_reset(&gb, [self internalModel]);
}
if (old_width != GB_get_screen_width(&gb)) { if (old_width != GB_get_screen_width(&gb)) {
[self.view screenSizeChanged]; [self.view screenSizeChanged];
@ -689,13 +684,13 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
window_frame.size.width); window_frame.size.width);
window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"], window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"],
window_frame.size.height); window_frame.size.height);
[self.mainWindow setFrame:window_frame display:YES]; [self.mainWindow setFrame:window_frame display:true];
self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised; self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height; NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height;
CGRect vram_window_rect = self.vramWindow.frame; CGRect vram_window_rect = self.vramWindow.frame;
vram_window_rect.size.height = 384 + height_diff + 48; vram_window_rect.size.height = 384 + height_diff + 48;
[self.vramWindow setFrame:vram_window_rect display:YES animate:NO]; [self.vramWindow setFrame:vram_window_rect display:true animate:false];
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [self.fileURL.path lastPathComponent]]; self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [self.fileURL.path lastPathComponent]];
@ -851,7 +846,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
+ (BOOL)autosavesInPlace + (BOOL)autosavesInPlace
{ {
return YES; return true;
} }
- (NSString *)windowNibName - (NSString *)windowNibName
@ -863,7 +858,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type - (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type
{ {
return YES; return true;
} }
- (IBAction)changeGBSTrack:(id)sender - (IBAction)changeGBSTrack:(id)sender
@ -902,7 +897,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
{ {
GB_set_rendering_disabled(&gb, true); GB_set_rendering_disabled(&gb, true);
_view = nil; _view = nil;
for (NSView *view in _mainWindow.contentView.subviews) { for (NSView *view in [_mainWindow.contentView.subviews copy]) {
[view removeFromSuperview]; [view removeFromSuperview];
} }
[[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil]; [[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
@ -1066,7 +1061,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (void) windowWillExitFullScreen:(NSNotification *)notification - (void) windowWillExitFullScreen:(NSNotification *)notification
{ {
fullScreen = false; fullScreen = false;
self.view.mouseHidingEnabled = NO; self.view.mouseHidingEnabled = false;
} }
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame - (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame
@ -1161,14 +1156,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
} }
if (![console_output_timer isValid]) { if (![console_output_timer isValid]) {
console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 target:self selector:@selector(appendPendingOutput) userInfo:nil repeats:NO]; console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 target:self selector:@selector(appendPendingOutput) userInfo:nil repeats:false];
[[NSRunLoop mainRunLoop] addTimer:console_output_timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop mainRunLoop] addTimer:console_output_timer forMode:NSDefaultRunLoopMode];
} }
[console_output_lock unlock]; [console_output_lock unlock];
/* Make sure mouse is not hidden while debugging */ /* Make sure mouse is not hidden while debugging */
self.view.mouseHidingEnabled = NO; self.view.mouseHidingEnabled = false;
} }
- (IBAction)showConsoleWindow:(id)sender - (IBAction)showConsoleWindow:(id)sender
@ -1395,7 +1390,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
bitmapInfo, bitmapInfo,
provider, provider,
NULL, NULL,
YES, true,
renderingIntent); renderingIntent);
CGDataProviderRelease(provider); CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpaceRef); CGColorSpaceRelease(colorSpaceRef);
@ -1603,7 +1598,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
} }
[self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]]; [self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]];
[hex_controller reloadData]; [hex_controller reloadData];
[self.memoryView setNeedsDisplay:YES]; [self.memoryView setNeedsDisplay:true];
} }
- (GB_gameboy_t *) gameboy - (GB_gameboy_t *) gameboy
@ -1613,7 +1608,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
+ (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName + (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName
{ {
return YES; return true;
} }
- (void)cameraRequestUpdate - (void)cameraRequestUpdate
@ -1754,14 +1749,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
window_rect.size.height = 512 + height_diff + 48; window_rect.size.height = 512 + height_diff + 48;
break; break;
case 3: case 3:
window_rect.size.height = 20 * 16 + height_diff + 24; window_rect.size.height = 20 * 16 + height_diff + 34;
break; break;
default: default:
break; break;
} }
window_rect.origin.y -= window_rect.size.height; window_rect.origin.y -= window_rect.size.height;
[self.vramWindow setFrame:window_rect display:YES animate:YES]; [self.vramWindow setFrame:window_rect display:true animate:true];
} }
- (void)mouseDidLeaveImageView:(GBImageView *)view - (void)mouseDidLeaveImageView:(GBImageView *)view
@ -1856,7 +1851,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
case 0: case 0:
return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image
length:64 * 4 * 2 length:64 * 4 * 2
freeWhenDone:NO] freeWhenDone:false]
width:8 width:8
height:oamHeight height:oamHeight
scale:16.0/oamHeight]; scale:16.0/oamHeight];
@ -1899,7 +1894,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{ {
return NO; return false;
} }
- (IBAction)showVRAMViewer:(id)sender - (IBAction)showVRAMViewer:(id)sender
@ -1929,7 +1924,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
frame.size = self.feedImageView.image.size; frame.size = self.feedImageView.image.size;
[self.printerFeedWindow setContentMaxSize:frame.size]; [self.printerFeedWindow setContentMaxSize:frame.size];
frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height; frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height;
[self.printerFeedWindow setFrame:frame display:NO animate: self.printerFeedWindow.isVisible]; [self.printerFeedWindow setFrame:frame display:false animate: self.printerFeedWindow.isVisible];
[self.printerFeedWindow orderFront:NULL]; [self.printerFeedWindow orderFront:NULL];
}); });
@ -1960,8 +1955,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
[imageRep setSize:(NSSize){160, self.feedImageView.image.size.height / 2}]; [imageRep setSize:(NSSize){160, self.feedImageView.image.size.height / 2}];
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
[data writeToURL:savePanel.URL atomically:NO]; [data writeToURL:savePanel.URL atomically:false];
[self.printerFeedWindow setIsVisible:NO]; [self.printerFeedWindow setIsVisible:false];
} }
if (shouldResume) { if (shouldResume) {
[self start]; [self start];
@ -2082,9 +2077,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview; - (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview;
{ {
if ([[splitView arrangedSubviews] lastObject] == subview) { if ([[splitView arrangedSubviews] lastObject] == subview) {
return YES; return true;
} }
return NO; return false;
} }
- (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex - (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex
@ -2100,9 +2095,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view - (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view
{ {
if ([[splitView arrangedSubviews] lastObject] == view) { if ([[splitView arrangedSubviews] lastObject] == view) {
return NO; return false;
} }
return YES; return true;
} }
- (void)splitViewDidResizeSubviews:(NSNotification *)notification - (void)splitViewDidResizeSubviews:(NSNotification *)notification
@ -2206,4 +2201,115 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
{ {
return &gb; return &gb;
} }
- (NSImage *)takeScreenshot
{
NSImage *ret = nil;
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]) {
ret = [_view renderToImage];
}
if (!ret) {
ret = [Document imageFromData:[NSData dataWithBytesNoCopy:_view.currentBuffer
length:GB_get_screen_width(&gb) * GB_get_screen_height(&gb) * 4
freeWhenDone:false]
width:GB_get_screen_width(&gb)
height:GB_get_screen_height(&gb)
scale:1.0];
}
[ret lockFocus];
NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0,
ret.size.width, ret.size.height)];
[ret unlockFocus];
ret = [[NSImage alloc] initWithSize:ret.size];
[ret addRepresentation:bitmapRep];
return ret;
}
- (NSString *)screenshotFilename
{
NSDate *date = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterLongStyle;
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
return [[NSString stringWithFormat:@"%@ %@.png",
self.fileURL.lastPathComponent.stringByDeletingPathExtension,
[dateFormatter stringFromDate:date]] stringByReplacingOccurrencesOfString:@":" withString:@"."]; // Gotta love Mac OS Classic
}
- (IBAction)saveScreenshot:(id)sender
{
NSString *folder = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBScreenshotFolder"];
BOOL isDirectory = false;
if (folder) {
[[NSFileManager defaultManager] fileExistsAtPath:folder isDirectory:&isDirectory];
}
if (!folder) {
bool shouldResume = running;
[self stop];
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.canChooseFiles = false;
openPanel.canChooseDirectories = true;
openPanel.message = @"Choose a folder for screenshots";
[openPanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[[NSUserDefaults standardUserDefaults] setObject:openPanel.URL.path
forKey:@"GBScreenshotFolder"];
[self saveScreenshot:sender];
}
if (shouldResume) {
[self start];
}
}];
return;
}
NSImage *image = [self takeScreenshot];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterLongStyle;
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
NSString *filename = [self screenshotFilename];
filename = [folder stringByAppendingPathComponent:filename];
unsigned i = 2;
while ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
filename = [[filename stringByDeletingPathExtension] stringByAppendingFormat:@" %d.png", i++];
}
NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject;
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
[data writeToFile:filename atomically:false];
[self.osdView displayText:@"Screenshot saved"];
}
- (IBAction)saveScreenshotAs:(id)sender
{
bool shouldResume = running;
[self stop];
NSImage *image = [self takeScreenshot];
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel setNameFieldStringValue:[self screenshotFilename]];
[savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[savePanel orderOut:self];
NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject;
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
[data writeToURL:savePanel.URL atomically:false];
[[NSUserDefaults standardUserDefaults] setObject:savePanel.URL.path.stringByDeletingLastPathComponent
forKey:@"GBScreenshotFolder"];
}
if (shouldResume) {
[self start];
}
}];
[self.osdView displayText:@"Screenshot saved"];
}
- (IBAction)copyScreenshot:(id)sender
{
NSImage *image = [self takeScreenshot];
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] writeObjects:@[image]];
[self.osdView displayText:@"Screenshot copied"];
}
@end @end

View File

@ -1004,7 +1004,7 @@
</buttonCell> </buttonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn> </tableColumn>
<tableColumn width="50" minWidth="40" maxWidth="1000" id="9DZ-oW-Scx"> <tableColumn width="60" minWidth="40" maxWidth="1000" id="9DZ-oW-Scx">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Enabled"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Enabled">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -1029,7 +1029,7 @@
</textFieldCell> </textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn> </tableColumn>
<tableColumn editable="NO" width="144" minWidth="40" maxWidth="1000" id="ACq-gU-K36"> <tableColumn editable="NO" width="134" minWidth="40" maxWidth="1000" id="ACq-gU-K36">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Action"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Action">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>

View File

@ -90,7 +90,7 @@ static OSStatus render(
{ {
OSErr err = AudioOutputUnitStart(audioUnit); OSErr err = AudioOutputUnitStart(audioUnit);
NSAssert1(err == noErr, @"Error starting unit: %hd", err); NSAssert1(err == noErr, @"Error starting unit: %hd", err);
_playing = YES; _playing = true;
} }
@ -98,7 +98,7 @@ static OSStatus render(
-(void) stop -(void) stop
{ {
AudioOutputUnitStop(audioUnit); AudioOutputUnitStop(audioUnit);
_playing = NO; _playing = false;
} }
-(void) dealloc -(void) dealloc

View File

@ -5,12 +5,12 @@
- (void)awakeFromNib - (void)awakeFromNib
{ {
self.wantsLayer = YES; self.wantsLayer = true;
} }
- (BOOL)wantsUpdateLayer - (BOOL)wantsUpdateLayer
{ {
return YES; return true;
} }
- (void)updateLayer - (void)updateLayer

View File

@ -114,7 +114,7 @@
return _fieldEditor; return _fieldEditor;
} }
_fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame]; _fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame];
_fieldEditor.fieldEditor = YES; _fieldEditor.fieldEditor = true;
_fieldEditor.usesAddressFormat = self.usesAddressFormat; _fieldEditor.usesAddressFormat = self.usesAddressFormat;
return _fieldEditor; return _fieldEditor;
} }

View File

@ -52,7 +52,7 @@
if (row >= cheatCount) { if (row >= cheatCount) {
switch (columnIndex) { switch (columnIndex) {
case 0: case 0:
return @(YES); return @YES;
case 1: case 1:
return @NO; return @NO;
@ -67,7 +67,7 @@
switch (columnIndex) { switch (columnIndex) {
case 0: case 0:
return @(NO); return @NO;
case 1: case 1:
return @(cheats[row]->enabled); return @(cheats[row]->enabled);

View File

@ -13,13 +13,13 @@ static inline double scale_channel(uint8_t x)
- (void)setObjectValue:(id)objectValue - (void)setObjectValue:(id)objectValue
{ {
_integerValue = [objectValue integerValue]; _integerValue = [objectValue integerValue];
uint8_t r = _integerValue & 0x1F, uint8_t r = _integerValue & 0x1F,
g = (_integerValue >> 5) & 0x1F, g = (_integerValue >> 5) & 0x1F,
b = (_integerValue >> 10) & 0x1F; b = (_integerValue >> 10) & 0x1F;
super.objectValue = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)] attributes:@{ super.objectValue = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)] attributes:@{
NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor] NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor],
NSFontAttributeName: [NSFont userFixedPitchFontOfSize:12]
}]; }];
} }
@ -36,13 +36,14 @@ static inline double scale_channel(uint8_t x)
- (NSColor *) backgroundColor - (NSColor *) backgroundColor
{ {
/* Todo: color correction */
uint16_t color = self.integerValue; uint16_t color = self.integerValue;
return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0]; return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0];
} }
- (BOOL)drawsBackground - (BOOL)drawsBackground
{ {
return YES; return true;
} }
@end @end

View File

@ -34,6 +34,6 @@
- (void) filterChanged - (void) filterChanged
{ {
self.shader = nil; self.shader = nil;
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
} }
@end @end

View File

@ -28,4 +28,5 @@
@property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox; @property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox;
@property (weak) IBOutlet NSSlider *volumeSlider; @property (weak) IBOutlet NSSlider *volumeSlider;
@property (weak) IBOutlet NSButton *OSDCheckbox; @property (weak) IBOutlet NSButton *OSDCheckbox;
@property (weak) IBOutlet NSButton *screenshotFilterCheckbox;
@end @end

View File

@ -2,6 +2,7 @@
#import "NSString+StringForKey.h" #import "NSString+StringForKey.h"
#import "GBButtons.h" #import "GBButtons.h"
#import "BigSurToolbar.h" #import "BigSurToolbar.h"
#import "GBViewMetal.h"
#import <Carbon/Carbon.h> #import <Carbon/Carbon.h>
@implementation GBPreferencesWindow @implementation GBPreferencesWindow
@ -32,6 +33,7 @@
NSSlider *_volumeSlider; NSSlider *_volumeSlider;
NSButton *_autoUpdatesCheckbox; NSButton *_autoUpdatesCheckbox;
NSButton *_OSDCheckbox; NSButton *_OSDCheckbox;
NSButton *_screenshotFilterCheckbox;
} }
+ (NSArray *)filterList + (NSArray *)filterList
@ -67,8 +69,8 @@
- (void)close - (void)close
{ {
joystick_configuration_state = -1; joystick_configuration_state = -1;
[self.configureJoypadButton setEnabled:YES]; [self.configureJoypadButton setEnabled:true];
[self.skipButton setEnabled:NO]; [self.skipButton setEnabled:false];
[self.configureJoypadButton setTitle:@"Configure Controller"]; [self.configureJoypadButton setTitle:@"Configure Controller"];
[super close]; [super close];
} }
@ -266,12 +268,12 @@
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
is_button_being_modified = true; is_button_being_modified = true;
button_being_modified = row; button_being_modified = row;
tableView.enabled = NO; tableView.enabled = false;
self.playerListButton.enabled = NO; self.playerListButton.enabled = false;
[tableView reloadData]; [tableView reloadData];
[self makeFirstResponder:self]; [self makeFirstResponder:self];
}); });
return NO; return false;
} }
-(void)keyDown:(NSEvent *)theEvent -(void)keyDown:(NSEvent *)theEvent
@ -287,8 +289,8 @@
[[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode [[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode
forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)]; forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)];
self.controlsTableView.enabled = YES; self.controlsTableView.enabled = true;
self.playerListButton.enabled = YES; self.playerListButton.enabled = true;
[self.controlsTableView reloadData]; [self.controlsTableView reloadData];
[self makeFirstResponder:self.controlsTableView]; [self makeFirstResponder:self.controlsTableView];
} }
@ -408,8 +410,8 @@
- (IBAction) configureJoypad:(id)sender - (IBAction) configureJoypad:(id)sender
{ {
[self.configureJoypadButton setEnabled:NO]; [self.configureJoypadButton setEnabled:false];
[self.skipButton setEnabled:YES]; [self.skipButton setEnabled:true];
joystick_being_configured = nil; joystick_being_configured = nil;
[self advanceConfigurationStateMachine]; [self advanceConfigurationStateMachine];
} }
@ -430,8 +432,8 @@
} }
else { else {
joystick_configuration_state = -1; joystick_configuration_state = -1;
[self.configureJoypadButton setEnabled:YES]; [self.configureJoypadButton setEnabled:true];
[self.skipButton setEnabled:NO]; [self.skipButton setEnabled:false];
[self.configureJoypadButton setTitle:@"Configure Joypad"]; [self.configureJoypadButton setTitle:@"Configure Joypad"];
} }
} }
@ -563,8 +565,8 @@
- (IBAction)selectOtherBootROMFolder:(id)sender - (IBAction)selectOtherBootROMFolder:(id)sender
{ {
NSOpenPanel *panel = [[NSOpenPanel alloc] init]; NSOpenPanel *panel = [[NSOpenPanel alloc] init];
[panel setCanChooseDirectories:YES]; [panel setCanChooseDirectories:true];
[panel setCanChooseFiles:NO]; [panel setCanChooseFiles:false];
[panel setPrompt:@"Select"]; [panel setPrompt:@"Select"];
[panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]]; [panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]];
[panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) { [panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) {
@ -588,12 +590,12 @@
[self.bootROMsFolderItem setTitle:[url lastPathComponent]]; [self.bootROMsFolderItem setTitle:[url lastPathComponent]];
NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]]; NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]];
[icon setSize:NSMakeSize(16, 16)]; [icon setSize:NSMakeSize(16, 16)];
[self.bootROMsFolderItem setHidden:NO]; [self.bootROMsFolderItem setHidden:false];
[self.bootROMsFolderItem setImage:icon]; [self.bootROMsFolderItem setImage:icon];
[self.bootROMsButton selectItemAtIndex:1]; [self.bootROMsButton selectItemAtIndex:1];
} }
else { else {
[self.bootROMsFolderItem setHidden:YES]; [self.bootROMsFolderItem setHidden:true];
[self.bootROMsButton selectItemAtIndex:0]; [self.bootROMsButton selectItemAtIndex:0];
} }
} }
@ -766,4 +768,27 @@
forKey:@"GBOSDEnabled"]; forKey:@"GBOSDEnabled"];
} }
- (IBAction)changeFilterScreenshots:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
forKey:@"GBFilterScreenshots"];
}
- (NSButton *)screenshotFilterCheckbox
{
return _screenshotFilterCheckbox;
}
- (void)setScreenshotFilterCheckbox:(NSButton *)screenshotFilterCheckbox
{
_screenshotFilterCheckbox = screenshotFilterCheckbox;
if (![GBViewMetal isSupported]) {
[_screenshotFilterCheckbox setEnabled:false];
}
else {
[_screenshotFilterCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]];
}
}
@end @end

View File

@ -8,7 +8,7 @@
- (void)setDividerColor:(NSColor *)color - (void)setDividerColor:(NSColor *)color
{ {
_dividerColor = color; _dividerColor = color;
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
} }
- (NSColor *)dividerColor - (NSColor *)dividerColor

View File

@ -17,7 +17,7 @@
return field_editor; return field_editor;
} }
field_editor = [[GBTerminalTextView alloc] init]; field_editor = [[GBTerminalTextView alloc] init];
[field_editor setFieldEditor:YES]; [field_editor setFieldEditor:true];
field_editor.gb = self.gb; field_editor.gb = self.gb;
return field_editor; return field_editor;
} }
@ -109,7 +109,7 @@
[self updateReverseSearch]; [self updateReverseSearch];
} }
else { else {
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
reverse_search_mode = true; reverse_search_mode = true;
} }

View File

@ -18,7 +18,7 @@ typedef enum {
@property (nonatomic, weak) IBOutlet Document *document; @property (nonatomic, weak) IBOutlet Document *document;
@property (nonatomic) GB_gameboy_t *gb; @property (nonatomic) GB_gameboy_t *gb;
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; @property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
@property (nonatomic, getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @property (nonatomic, getter=isMouseHidingEnabled) bool mouseHidingEnabled;
@property (nonatomic) bool isRewinding; @property (nonatomic) bool isRewinding;
@property (nonatomic, strong) NSView *internalView; @property (nonatomic, strong) NSView *internalView;
@property (weak) GBOSDView *osdView; @property (weak) GBOSDView *osdView;
@ -27,4 +27,5 @@ typedef enum {
- (uint32_t *)previousBuffer; - (uint32_t *)previousBuffer;
- (void)screenSizeChanged; - (void)screenSizeChanged;
- (void)setRumble: (double)amp; - (void)setRumble: (double)amp;
- (NSImage *)renderToImage;
@end @end

View File

@ -106,9 +106,9 @@ static const uint8_t workboy_vk_to_key[] = {
{ {
uint32_t *image_buffers[3]; uint32_t *image_buffers[3];
unsigned char current_buffer; unsigned char current_buffer;
BOOL mouse_hidden; bool mouse_hidden;
NSTrackingArea *tracking_area; NSTrackingArea *tracking_area;
BOOL _mouseHidingEnabled; bool _mouseHidingEnabled;
bool axisActive[2]; bool axisActive[2];
bool underclockKeyDown; bool underclockKeyDown;
double clockMultiplier; double clockMultiplier;
@ -183,7 +183,7 @@ static const uint8_t workboy_vk_to_key[] = {
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode - (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
{ {
_frameBlendingMode = frameBlendingMode; _frameBlendingMode = frameBlendingMode;
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
} }
@ -585,7 +585,7 @@ static const uint8_t workboy_vk_to_key[] = {
- (BOOL)acceptsFirstResponder - (BOOL)acceptsFirstResponder
{ {
return YES; return true;
} }
- (void)mouseEntered:(NSEvent *)theEvent - (void)mouseEntered:(NSEvent *)theEvent
@ -610,7 +610,7 @@ static const uint8_t workboy_vk_to_key[] = {
[super mouseExited:theEvent]; [super mouseExited:theEvent];
} }
- (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled - (void)setMouseHidingEnabled:(bool)mouseHidingEnabled
{ {
if (mouseHidingEnabled == _mouseHidingEnabled) return; if (mouseHidingEnabled == _mouseHidingEnabled) return;
@ -625,7 +625,7 @@ static const uint8_t workboy_vk_to_key[] = {
} }
} }
- (BOOL)isMouseHidingEnabled - (bool)isMouseHidingEnabled
{ {
return _mouseHidingEnabled; return _mouseHidingEnabled;
} }
@ -658,7 +658,7 @@ static const uint8_t workboy_vk_to_key[] = {
if ( [[pboard types] containsObject:NSURLPboardType] ) { if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
if (GB_is_stave_state(fileURL.fileSystemRepresentation)) { if (GB_is_save_state(fileURL.fileSystemRepresentation)) {
return NSDragOperationGeneric; return NSDragOperationGeneric;
} }
} }
@ -677,4 +677,10 @@ static const uint8_t workboy_vk_to_key[] = {
return false; return false;
} }
- (NSImage *)renderToImage;
{
/* Not going to support this on OpenGL, OpenGL is too much of a terrible API for me
to bother figuring out how the hell something so trivial can be done. */
return nil;
}
@end @end

View File

@ -19,7 +19,7 @@
NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil];
self.internalView = [[GBOpenGLView alloc] initWithFrame:self.frame pixelFormat:pf]; self.internalView = [[GBOpenGLView alloc] initWithFrame:self.frame pixelFormat:pf];
((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = YES; ((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = true;
((GBOpenGLView *)self.internalView).openGLContext = context; ((GBOpenGLView *)self.internalView).openGLContext = context;
} }
@ -27,8 +27,8 @@
{ {
[super flip]; [super flip];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.internalView setNeedsDisplay:YES]; [self.internalView setNeedsDisplay:true];
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
}); });
} }

View File

@ -1,3 +1,4 @@
#import <CoreImage/CoreImage.h>
#import "GBViewMetal.h" #import "GBViewMetal.h"
#pragma clang diagnostic ignored "-Wpartial-availability" #pragma clang diagnostic ignored "-Wpartial-availability"
@ -51,8 +52,9 @@ static const vector_float2 rect[] =
MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())];
view.delegate = self; view.delegate = self;
self.internalView = view; self.internalView = view;
view.paused = YES; view.paused = true;
view.enableSetNeedsDisplay = YES; view.enableSetNeedsDisplay = true;
view.framebufferOnly = false;
vertices = [device newBufferWithBytes:rect vertices = [device newBufferWithBytes:rect
length:sizeof(rect) length:sizeof(rect)
@ -92,7 +94,7 @@ static const vector_float2 rect[] =
withString:scaler_source]; withString:scaler_source];
MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
options.fastMathEnabled = YES; options.fastMathEnabled = true;
id<MTLLibrary> library = [device newLibraryWithSource:shader_source id<MTLLibrary> library = [device newLibraryWithSource:shader_source
options:options options:options
error:&error]; error:&error];
@ -208,8 +210,23 @@ static const vector_float2 rect[] =
{ {
[super flip]; [super flip];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[(MTKView *)self.internalView setNeedsDisplay:YES]; [(MTKView *)self.internalView setNeedsDisplay:true];
}); });
} }
- (NSImage *)renderToImage
{
CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture]
options:@{
kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB()
}];
ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1),
0, ciImage.extent.size.height)];
CIContext *context = [CIContext context];
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size];
CGImageRelease(cgImage);
return ret;
}
@end @end

View File

@ -10,7 +10,7 @@ static GBWarningPopover *lastPopover;
lastPopover = [[self alloc] init]; lastPopover = [[self alloc] init];
[lastPopover setBehavior:NSPopoverBehaviorApplicationDefined]; [lastPopover setBehavior:NSPopoverBehaviorApplicationDefined];
[lastPopover setAnimates:YES]; [lastPopover setAnimates:true];
lastPopover.contentViewController = [[NSViewController alloc] initWithNibName:@"PopoverView" bundle:nil]; lastPopover.contentViewController = [[NSViewController alloc] initWithNibName:@"PopoverView" bundle:nil];
NSTextField *field = (NSTextField *)lastPopover.contentViewController.view; NSTextField *field = (NSTextField *)lastPopover.contentViewController.view;
[field setStringValue:contents]; [field setStringValue:contents];
@ -20,7 +20,7 @@ static GBWarningPopover *lastPopover;
[lastPopover setContentSize:textSize]; [lastPopover setContentSize:textSize];
if (!view.window.isVisible) { if (!view.window.isVisible) {
[view.window setIsVisible:YES]; [view.window setIsVisible:true];
} }
[lastPopover showRelativeToRect:view.bounds [lastPopover showRelativeToRect:view.bounds

View File

@ -316,6 +316,23 @@
</menu> </menu>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/> <menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/>
<menuItem title="Save Screenshot" keyEquivalent="s" id="0J3-yf-iXs">
<connections>
<action selector="saveScreenshot:" target="-1" id="gJd-ml-J8p"/>
</connections>
</menuItem>
<menuItem title="Save Screenshot As…" alternate="YES" keyEquivalent="s" id="98X-Fp-Uny">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="saveScreenshotAs:" target="-1" id="Cxc-Gx-ql1"/>
</connections>
</menuItem>
<menuItem title="Copy Screenshot" keyEquivalent="S" id="vbX-pB-QC8">
<connections>
<action selector="copyScreenshot:" target="-1" id="XJC-EB-HNl"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="zk7-gf-LXN"/>
<menuItem title="Game Boy" tag="1" id="g7C-LA-VAr"> <menuItem title="Game Boy" tag="1" id="g7C-LA-VAr">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>

View File

@ -24,10 +24,10 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/> <windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="292" height="224"/> <rect key="contentRect" x="196" y="240" width="320" height="224"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/> <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="EiT-Mj-1SZ"> <view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="292" height="224"/> <rect key="frame" x="0.0" y="0.0" width="320" height="224"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view> </view>
<toolbar key="toolbar" implicitIdentifier="689472FF-3BCD-4B1F-98F8-989CCB01AE27" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="pYZ-Pe-8hq"> <toolbar key="toolbar" implicitIdentifier="689472FF-3BCD-4B1F-98F8-989CCB01AE27" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="pYZ-Pe-8hq">
@ -90,6 +90,7 @@
<outlet property="rewindPopupButton" destination="7fg-Ww-JjR" id="Ka2-TP-B1x"/> <outlet property="rewindPopupButton" destination="7fg-Ww-JjR" id="Ka2-TP-B1x"/>
<outlet property="rtcPopupButton" destination="tFf-H1-XUL" id="zxb-4h-aqg"/> <outlet property="rtcPopupButton" destination="tFf-H1-XUL" id="zxb-4h-aqg"/>
<outlet property="rumbleModePopupButton" destination="Ogs-xG-b4b" id="vuw-VN-MTp"/> <outlet property="rumbleModePopupButton" destination="Ogs-xG-b4b" id="vuw-VN-MTp"/>
<outlet property="screenshotFilterCheckbox" destination="spQ-Md-OFi" id="f9y-Ek-XQV"/>
<outlet property="sgbPopupButton" destination="dza-T7-RkX" id="B0o-Nb-pIH"/> <outlet property="sgbPopupButton" destination="dza-T7-RkX" id="B0o-Nb-pIH"/>
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/> <outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
<outlet property="temperatureSlider" destination="NuA-mL-AJZ" id="w11-n7-Bmj"/> <outlet property="temperatureSlider" destination="NuA-mL-AJZ" id="w11-n7-Bmj"/>
@ -98,12 +99,12 @@
<point key="canvasLocation" x="183" y="354"/> <point key="canvasLocation" x="183" y="354"/>
</window> </window>
<customView id="sRK-wO-K6R"> <customView id="sRK-wO-K6R">
<rect key="frame" x="0.0" y="0.0" width="292" height="395"/> <rect key="frame" x="0.0" y="0.0" width="320" height="421"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
<rect key="frame" x="18" y="358" width="256" height="17"/> <rect key="frame" x="18" y="384" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -111,8 +112,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
<rect key="frame" x="30" y="325" width="234" height="26"/> <rect key="frame" x="30" y="351" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Nearest neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl"> <popUpButtonCell key="cell" type="push" title="Nearest neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -147,9 +148,20 @@
<action selector="graphicFilterChanged:" target="QvC-M9-y7g" id="n87-t4-fbV"/> <action selector="graphicFilterChanged:" target="QvC-M9-y7g" id="n87-t4-fbV"/>
</connections> </connections>
</popUpButton> </popUpButton>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="spQ-Md-OFi">
<rect key="frame" x="32" y="330" width="259" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Apply filter to screenshots" bezelStyle="regularSquare" imagePosition="left" inset="2" id="JbP-bE-w8A">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeFilterScreenshots:" target="QvC-M9-y7g" id="t82-FI-eSe"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
<rect key="frame" x="18" y="303" width="256" height="17"/> <rect key="frame" x="18" y="307" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color correction:" id="5Si-hz-EK3"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color correction:" id="5Si-hz-EK3">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -157,8 +169,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
<rect key="frame" x="30" y="270" width="234" height="26"/> <rect key="frame" x="30" y="274" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm"> <popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -179,9 +191,26 @@
<action selector="colorCorrectionChanged:" target="QvC-M9-y7g" id="Oq4-B5-nO6"/> <action selector="colorCorrectionChanged:" target="QvC-M9-y7g" id="Oq4-B5-nO6"/>
</connections> </connections>
</popUpButton> </popUpButton>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NuA-mL-AJZ">
<rect key="frame" x="32" y="218" width="259" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" minValue="-256" maxValue="256" tickMarkPosition="below" numberOfTickMarks="3" sliderType="linear" id="KX7-G9-k0O"/>
<connections>
<action selector="lightTemperatureChanged:" target="QvC-M9-y7g" id="he8-ib-I3Y"/>
</connections>
</slider>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cCm-Oa-FbN">
<rect key="frame" x="20" y="252" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Ambient light temperature:" id="Lso-GQ-pBl">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MLC-Rx-FgO"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MLC-Rx-FgO">
<rect key="frame" x="18" y="194" width="252" height="17"/> <rect key="frame" x="18" y="194" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Frame blending" id="UCa-EO-tzh"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Frame blending" id="UCa-EO-tzh">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -189,8 +218,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
<rect key="frame" x="30" y="165" width="234" height="22"/> <rect key="frame" x="30" y="165" width="262" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj"> <popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -209,8 +238,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8fG-zm-hpr"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8fG-zm-hpr">
<rect key="frame" x="18" y="143" width="252" height="17"/> <rect key="frame" x="18" y="143" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color palette for monochrome models:" id="LAN-8Y-T7H"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color palette for monochrome models:" id="LAN-8Y-T7H">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -218,8 +247,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Iwr-eI-SD1"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Iwr-eI-SD1">
<rect key="frame" x="30" y="114" width="234" height="22"/> <rect key="frame" x="30" y="114" width="262" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Greyscale" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Ajr-5r-iIk" id="rEU-jh-m3j"> <popUpButtonCell key="cell" type="push" title="Greyscale" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Ajr-5r-iIk" id="rEU-jh-m3j">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -239,8 +268,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Kz-cf-5X6"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Kz-cf-5X6">
<rect key="frame" x="18" y="92" width="248" height="17"/> <rect key="frame" x="18" y="92" width="276" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Display border:" id="HZd-qi-yyk"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Display border:" id="HZd-qi-yyk">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -248,8 +277,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="R9D-FV-bpd"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="R9D-FV-bpd">
<rect key="frame" x="30" y="60" width="234" height="25"/> <rect key="frame" x="30" y="60" width="262" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="heL-AV-0az" id="DY9-2D-h1L"> <popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="heL-AV-0az" id="DY9-2D-h1L">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -267,26 +296,9 @@
<action selector="displayBorderChanged:" target="QvC-M9-y7g" id="GoA-BU-v3h"/> <action selector="displayBorderChanged:" target="QvC-M9-y7g" id="GoA-BU-v3h"/>
</connections> </connections>
</popUpButton> </popUpButton>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NuA-mL-AJZ">
<rect key="frame" x="30" y="218" width="234" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" minValue="-256" maxValue="256" tickMarkPosition="below" numberOfTickMarks="3" sliderType="linear" id="KX7-G9-k0O"/>
<connections>
<action selector="lightTemperatureChanged:" target="QvC-M9-y7g" id="he8-ib-I3Y"/>
</connections>
</slider>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cCm-Oa-FbN">
<rect key="frame" x="20" y="248" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Ambient light temperature:" id="Lso-GQ-pBl">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfj-tg-7OP"> <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfj-tg-7OP">
<rect key="frame" x="18" y="38" width="256" height="18"/> <rect key="frame" x="18" y="38" width="284" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Keep aspect ratio" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lsj-rC-Eo6"> <buttonCell key="cell" type="check" title="Keep aspect ratio" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lsj-rC-Eo6">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -296,8 +308,8 @@
</connections> </connections>
</button> </button>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="quK-gY-Z6h"> <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="quK-gY-Z6h">
<rect key="frame" x="18" y="18" width="252" height="18"/> <rect key="frame" x="18" y="18" width="280" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="On-screen display" bezelStyle="regularSquare" imagePosition="left" inset="2" id="oeT-cD-YRw"> <buttonCell key="cell" type="check" title="On-screen display" bezelStyle="regularSquare" imagePosition="left" inset="2" id="oeT-cD-YRw">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -307,15 +319,15 @@
</connections> </connections>
</button> </button>
</subviews> </subviews>
<point key="canvasLocation" x="-176" y="677.5"/> <point key="canvasLocation" x="-176" y="690.5"/>
</customView> </customView>
<customView id="ymk-46-SX7"> <customView id="ymk-46-SX7">
<rect key="frame" x="0.0" y="0.0" width="292" height="375"/> <rect key="frame" x="0.0" y="0.0" width="320" height="375"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB">
<rect key="frame" x="18" y="283" width="256" height="17"/> <rect key="frame" x="18" y="283" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding duration:" id="JaO-5h-ugl"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding duration:" id="JaO-5h-ugl">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -323,8 +335,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o3Z-34-FJk"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o3Z-34-FJk">
<rect key="frame" x="18" y="228" width="252" height="17"/> <rect key="frame" x="18" y="228" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Real Time Clock emulation:" id="Qoi-ub-YtI"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Real Time Clock emulation:" id="Qoi-ub-YtI">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -332,8 +344,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MI2-ql-f6M"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MI2-ql-f6M">
<rect key="frame" x="18" y="338" width="256" height="17"/> <rect key="frame" x="18" y="338" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Boot ROMs location:" id="nj0-Cb-gEA"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Boot ROMs location:" id="nj0-Cb-gEA">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -341,8 +353,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wg8-hJ-df9"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wg8-hJ-df9">
<rect key="frame" x="18" y="160" width="256" height="17"/> <rect key="frame" x="18" y="160" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy revision:" id="GIA-ep-SBi"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy revision:" id="GIA-ep-SBi">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -350,8 +362,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LFw-Uk-cPR"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LFw-Uk-cPR">
<rect key="frame" x="30" y="127" width="234" height="26"/> <rect key="frame" x="30" y="127" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="DMG-CPU B" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="aXT-sE-m5Z" id="FuX-Hc-uO7"> <popUpButtonCell key="cell" type="push" title="DMG-CPU B" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="aXT-sE-m5Z" id="FuX-Hc-uO7">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -369,8 +381,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MAq-1X-Gpo"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MAq-1X-Gpo">
<rect key="frame" x="16" y="50" width="256" height="17"/> <rect key="frame" x="16" y="50" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Color revision:" id="edD-t7-vwk"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Color revision:" id="edD-t7-vwk">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -378,8 +390,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dlD-sk-SHO"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dlD-sk-SHO">
<rect key="frame" x="28" y="17" width="234" height="26"/> <rect key="frame" x="28" y="17" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="CPU-CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz"> <popUpButtonCell key="cell" type="push" title="CPU-CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -399,8 +411,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tAa-0A-0fP"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tAa-0A-0fP">
<rect key="frame" x="18" y="105" width="256" height="17"/> <rect key="frame" x="18" y="105" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Super Game Boy model:" id="d0g-rk-FK0"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Super Game Boy model:" id="d0g-rk-FK0">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -408,12 +420,12 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="mdm-eW-ia1"> <box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="mdm-eW-ia1">
<rect key="frame" x="12" y="183" width="268" height="5"/> <rect key="frame" x="12" y="183" width="296" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box> </box>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dza-T7-RkX"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dza-T7-RkX">
<rect key="frame" x="28" y="72" width="234" height="26"/> <rect key="frame" x="28" y="72" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Super Game Boy (NTSC)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="x5A-7f-ef9" id="2Mt-ci-bB0"> <popUpButtonCell key="cell" type="push" title="Super Game Boy (NTSC)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="x5A-7f-ef9" id="2Mt-ci-bB0">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -430,8 +442,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tFf-H1-XUL"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tFf-H1-XUL">
<rect key="frame" x="30" y="199" width="234" height="22"/> <rect key="frame" x="30" y="199" width="262" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Sync to system clock" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="arp-Qi-Xix" id="uRs-Ag-Sbw"> <popUpButtonCell key="cell" type="push" title="Sync to system clock" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="arp-Qi-Xix" id="uRs-Ag-Sbw">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -449,8 +461,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wC4-aJ-mhQ"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wC4-aJ-mhQ">
<rect key="frame" x="30" y="305" width="234" height="26"/> <rect key="frame" x="30" y="305" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Use built-in boot ROMs" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tnm-SR-ZEm" id="T3Y-Ln-Onl"> <popUpButtonCell key="cell" type="push" title="Use built-in boot ROMs" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tnm-SR-ZEm" id="T3Y-Ln-Onl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -473,8 +485,8 @@
</popUpButtonCell> </popUpButtonCell>
</popUpButton> </popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR">
<rect key="frame" x="30" y="250" width="234" height="26"/> <rect key="frame" x="30" y="250" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="lxQ-4n-kEv" id="lvb-QF-0Ht"> <popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="lxQ-4n-kEv" id="lvb-QF-0Ht">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -500,12 +512,12 @@
<point key="canvasLocation" x="-501" y="667.5"/> <point key="canvasLocation" x="-501" y="667.5"/>
</customView> </customView>
<customView id="Zn1-Y5-RbR"> <customView id="Zn1-Y5-RbR">
<rect key="frame" x="0.0" y="0.0" width="292" height="183"/> <rect key="frame" x="0.0" y="0.0" width="320" height="201"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Dzd-uB-B6l"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Dzd-uB-B6l">
<rect key="frame" x="18" y="146" width="252" height="17"/> <rect key="frame" x="18" y="164" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Volume:" id="IbP-7k-xsZ"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Volume:" id="IbP-7k-xsZ">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -513,16 +525,16 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LNs-v1-Eki"> <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LNs-v1-Eki">
<rect key="frame" x="40" y="121" width="230" height="19"/> <rect key="frame" x="30" y="130" width="261" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="256" doubleValue="256" tickMarkPosition="below" sliderType="linear" id="Xvk-Vj-NHx"/> <sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="256" doubleValue="256" tickMarkPosition="below" sliderType="linear" id="Xvk-Vj-NHx"/>
<connections> <connections>
<action selector="volumeChanged:" target="QvC-M9-y7g" id="4g3-NK-ay4"/> <action selector="volumeChanged:" target="QvC-M9-y7g" id="4g3-NK-ay4"/>
</connections> </connections>
</slider> </slider>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
<rect key="frame" x="30" y="65" width="234" height="26"/> <rect key="frame" x="30" y="74" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B"> <popUpButtonCell key="cell" type="push" title="Disabled (Keep DC offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -541,8 +553,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
<rect key="frame" x="18" y="98" width="256" height="17"/> <rect key="frame" x="18" y="107" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass filter:" id="YLF-RL-b2D"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass filter:" id="YLF-RL-b2D">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -550,8 +562,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GPt-9I-QBh"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GPt-9I-QBh">
<rect key="frame" x="18" y="43" width="252" height="17"/> <rect key="frame" x="18" y="52" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Interference volume:" id="I2Q-6U-uIx"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Interference volume:" id="I2Q-6U-uIx">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -559,18 +571,18 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FpE-5i-j5L"> <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FpE-5i-j5L">
<rect key="frame" x="30" y="18" width="234" height="19"/> <rect key="frame" x="30" y="18" width="261" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="256" tickMarkPosition="below" sliderType="linear" id="Rbx-DU-xYf"/> <sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="256" tickMarkPosition="below" sliderType="linear" id="Rbx-DU-xYf"/>
<connections> <connections>
<action selector="interferenceVolumeChanged:" target="QvC-M9-y7g" id="HFU-0q-hj1"/> <action selector="interferenceVolumeChanged:" target="QvC-M9-y7g" id="HFU-0q-hj1"/>
</connections> </connections>
</slider> </slider>
</subviews> </subviews>
<point key="canvasLocation" x="-825" y="446.5"/> <point key="canvasLocation" x="-825" y="455.5"/>
</customView> </customView>
<customView id="8TU-6J-NCg"> <customView id="8TU-6J-NCg">
<rect key="frame" x="0.0" y="0.0" width="292" height="467"/> <rect key="frame" x="0.0" y="0.0" width="320" height="467"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
@ -583,8 +595,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DZu-ts-deW"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DZu-ts-deW">
<rect key="frame" x="18" y="87" width="105" height="17"/> <rect key="frame" x="18" y="87" width="133" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Enable rumble:" id="QMX-3p-s1Z"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Enable rumble:" id="QMX-3p-s1Z">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -592,14 +604,14 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa"> <scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
<rect key="frame" x="32" y="208" width="229" height="211"/> <rect key="frame" x="32" y="208" width="257" height="211"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid"> <clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
<rect key="frame" x="1" y="1" width="227" height="209"/> <rect key="frame" x="1" y="1" width="255" height="209"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX"> <tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
<rect key="frame" x="0.0" y="0.0" width="227" height="209"/> <rect key="frame" x="0.0" y="0.0" width="255" height="209"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -618,7 +630,7 @@
</textFieldCell> </textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn> </tableColumn>
<tableColumn width="105" minWidth="40" maxWidth="1000" id="5VG-zV-WM6"> <tableColumn width="133" minWidth="40" maxWidth="1000" id="5VG-zV-WM6">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -650,8 +662,8 @@
</scroller> </scroller>
</scrollView> </scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fcF-wc-KwM"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fcF-wc-KwM">
<rect key="frame" x="30" y="183" width="203" height="17"/> <rect key="frame" x="30" y="183" width="231" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Controller for multiplayer games:" id="AJA-9b-VKI"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Controller for multiplayer games:" id="AJA-9b-VKI">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -659,8 +671,8 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Az-0R-oNw"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Az-0R-oNw">
<rect key="frame" x="42" y="150" width="222" height="26"/> <rect key="frame" x="42" y="150" width="250" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingMiddle" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hy8-cr-RrE" id="uEC-vN-8Jq"> <popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingMiddle" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hy8-cr-RrE" id="uEC-vN-8Jq">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -675,8 +687,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="VEc-Ed-Z6f"> <box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="VEc-Ed-Z6f">
<rect key="frame" x="12" y="139" width="268" height="5"/> <rect key="frame" x="12" y="139" width="296" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box> </box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ReM-uo-H0r"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ReM-uo-H0r">
<rect key="frame" x="227" y="431" width="8" height="17"/> <rect key="frame" x="227" y="431" width="8" height="17"/>
@ -707,8 +719,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ogs-xG-b4b"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ogs-xG-b4b">
<rect key="frame" x="30" y="58" width="234" height="22"/> <rect key="frame" x="30" y="58" width="262" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="jki-7x-bnM" id="o9b-MH-8kd"> <popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="jki-7x-bnM" id="o9b-MH-8kd">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="menu"/>
@ -725,8 +737,8 @@
</connections> </connections>
</popUpButton> </popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RuW-Db-dzW"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RuW-Db-dzW">
<rect key="frame" x="18" y="110" width="264" height="25"/> <rect key="frame" x="18" y="110" width="292" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Analog turbo and slow-motion controls" bezelStyle="regularSquare" imagePosition="left" lineBreakMode="charWrapping" inset="2" id="Mvp-oc-N3t"> <buttonCell key="cell" type="check" title="Analog turbo and slow-motion controls" bezelStyle="regularSquare" imagePosition="left" lineBreakMode="charWrapping" inset="2" id="Mvp-oc-N3t">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -735,20 +747,9 @@
<action selector="changeAnalogControls:" target="QvC-M9-y7g" id="1xR-gY-WKo"/> <action selector="changeAnalogControls:" target="QvC-M9-y7g" id="1xR-gY-WKo"/>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
<rect key="frame" x="198" y="13" width="67" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Skip" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
<rect key="frame" x="26" y="13" width="173" height="32"/> <rect key="frame" x="26" y="13" width="204" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Configure a controller" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim"> <buttonCell key="cell" type="push" title="Configure a controller" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -757,16 +758,27 @@
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/> <action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
<rect key="frame" x="223" y="13" width="72" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Skip" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/>
</connections>
</button>
</subviews> </subviews>
<point key="canvasLocation" x="-159" y="1161.5"/> <point key="canvasLocation" x="-159" y="1161.5"/>
</customView> </customView>
<customView id="ffn-ie-9C3"> <customView id="ffn-ie-9C3">
<rect key="frame" x="0.0" y="0.0" width="292" height="95"/> <rect key="frame" x="0.0" y="0.0" width="320" height="95"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZVh-ob-6wl"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZVh-ob-6wl">
<rect key="frame" x="18" y="59" width="256" height="18"/> <rect key="frame" x="18" y="59" width="284" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Check for updates on launch" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="euw-4z-Urd"> <buttonCell key="cell" type="check" title="Check for updates on launch" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="euw-4z-Urd">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -776,12 +788,12 @@
</connections> </connections>
</button> </button>
<progressIndicator wantsLayer="YES" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fB8-sd-zrh"> <progressIndicator wantsLayer="YES" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fB8-sd-zrh">
<rect key="frame" x="257" y="23" width="16" height="16"/> <rect key="frame" x="285" y="23" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</progressIndicator> </progressIndicator>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KnI-UA-Nlj"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KnI-UA-Nlj">
<rect key="frame" x="14" y="13" width="240" height="32"/> <rect key="frame" x="14" y="13" width="268" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Check for updates now" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="j8a-EZ-Ef5"> <buttonCell key="cell" type="push" title="Check for updates now" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="j8a-EZ-Ef5">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>

View File

@ -289,6 +289,19 @@ static void update_square_sample(GB_gameboy_t *gb, unsigned index)
0); 0);
} }
static inline void update_wave_sample(GB_gameboy_t *gb, unsigned cycles)
{
if (gb->apu.wave_channel.current_sample_index & 1) {
update_sample(gb, GB_WAVE,
(gb->apu.wave_channel.current_sample_byte & 0xF) >> gb->apu.wave_channel.shift,
cycles);
}
else {
update_sample(gb, GB_WAVE,
(gb->apu.wave_channel.current_sample_byte >> 4) >> gb->apu.wave_channel.shift,
cycles);
}
}
/* the effects of NRX2 writes on current volume are not well documented and differ /* the effects of NRX2 writes on current volume are not well documented and differ
between models and variants. The exact behavior can only be verified on CGB as it between models and variants. The exact behavior can only be verified on CGB as it
@ -516,6 +529,16 @@ void GB_apu_div_event(GB_gameboy_t *gb)
if (gb->apu.wave_channel.length_enabled) { if (gb->apu.wave_channel.length_enabled) {
if (gb->apu.wave_channel.pulse_length) { if (gb->apu.wave_channel.pulse_length) {
if (!--gb->apu.wave_channel.pulse_length) { if (!--gb->apu.wave_channel.pulse_length) {
if (gb->apu.is_active[GB_WAVE] && gb->model == GB_MODEL_AGB) {
if (gb->apu.wave_channel.sample_countdown == 0) {
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (((gb->apu.wave_channel.current_sample_index + 1) & 0xF) >> 1)];
}
else if (gb->apu.wave_channel.sample_countdown == 9) {
// TODO: wtf?
gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START];
}
}
gb->apu.is_active[GB_WAVE] = false; gb->apu.is_active[GB_WAVE] = false;
update_sample(gb, GB_WAVE, 0, 0); update_sample(gb, GB_WAVE, 0, 0);
} }
@ -583,6 +606,12 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.apu_cycles = 0; gb->apu.apu_cycles = 0;
if (!cycles) return; if (!cycles) return;
if (unlikely(gb->apu.channel_3_delayed_bugged_read)) {
gb->apu.channel_3_delayed_bugged_read = false;
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)];
}
bool start_ch4 = false; bool start_ch4 = false;
if (likely(!gb->stopped || GB_is_cgb(gb))) { if (likely(!gb->stopped || GB_is_cgb(gb))) {
if (gb->apu.channel_4_dmg_delayed_start) { if (gb->apu.channel_4_dmg_delayed_start) {
@ -665,11 +694,9 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF;
gb->apu.wave_channel.current_sample_index++; gb->apu.wave_channel.current_sample_index++;
gb->apu.wave_channel.current_sample_index &= 0x1F; gb->apu.wave_channel.current_sample_index &= 0x1F;
gb->apu.wave_channel.current_sample = gb->apu.wave_channel.current_sample_byte =
gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; gb->io_registers[GB_IO_WAV_START + (gb->apu.wave_channel.current_sample_index >> 1)];
update_sample(gb, GB_WAVE, update_wave_sample(gb, cycles - cycles_left);
gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift,
cycles - cycles_left);
gb->apu.wave_channel.wave_form_just_read = true; gb->apu.wave_channel.wave_form_just_read = true;
} }
if (cycles_left) { if (cycles_left) {
@ -677,6 +704,23 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.wave_channel.wave_form_just_read = false; gb->apu.wave_channel.wave_form_just_read = false;
} }
} }
else if (gb->apu.wave_channel.enable && gb->apu.channel_3_pulsed && gb->model < GB_MODEL_AGB) {
uint8_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) {
cycles_left -= gb->apu.wave_channel.sample_countdown + 1;
gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF;
if (cycles_left) {
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)];
}
else {
gb->apu.channel_3_delayed_bugged_read = true;
}
}
if (cycles_left) {
gb->apu.wave_channel.sample_countdown -= cycles_left;
}
}
// The noise channel can step even if inactive on the DMG // The noise channel can step even if inactive on the DMG
if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) { if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) {
@ -729,12 +773,8 @@ void GB_apu_run(GB_gameboy_t *gb)
void GB_apu_init(GB_gameboy_t *gb) void GB_apu_init(GB_gameboy_t *gb)
{ {
memset(&gb->apu, 0, sizeof(gb->apu)); memset(&gb->apu, 0, sizeof(gb->apu));
/* Restore the wave form */
for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) {
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4;
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF;
}
gb->apu.lf_div = 1; gb->apu.lf_div = 1;
gb->apu.wave_channel.shift = 4;
/* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on,
the first DIV/APU event is skipped. */ the first DIV/APU event is skipped. */
if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) { if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) {
@ -922,7 +962,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) { if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) {
if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { if ((!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) || gb->model == GB_MODEL_AGB) {
return; return;
} }
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
@ -1059,7 +1099,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1) / 2) & 0x400)) { if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1) / 2) & 0x400)) {
gb->apu.square_channels[index].current_sample_index++; gb->apu.square_channels[index].current_sample_index++;
gb->apu.square_channels[index].current_sample_index &= 0x7; gb->apu.square_channels[index].current_sample_index &= 0x7;
gb->apu.is_active[index] = true;
} }
/* Todo: verify with the schematics what's going on in here */ /* Todo: verify with the schematics what's going on in here */
else if (gb->apu.square_channels[index].sample_length == 0x7FF && else if (gb->apu.square_channels[index].sample_length == 0x7FF &&
@ -1150,6 +1189,16 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR30: case GB_IO_NR30:
gb->apu.wave_channel.enable = value & 0x80; gb->apu.wave_channel.enable = value & 0x80;
if (!gb->apu.wave_channel.enable) { if (!gb->apu.wave_channel.enable) {
gb->apu.channel_3_pulsed = false;
if (gb->apu.is_active[GB_WAVE]) {
// Todo: I assume this happens on pre-CGB models; test this with an audible test
if (gb->apu.wave_channel.sample_countdown == 0 && gb->model < GB_MODEL_AGB) {
gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (gb->pc & 0xF)];
}
else if (gb->apu.wave_channel.wave_form_just_read && gb->model <= GB_MODEL_CGB_C) {
gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (GB_IO_NR30 & 0xF)];
}
}
gb->apu.is_active[GB_WAVE] = false; gb->apu.is_active[GB_WAVE] = false;
update_sample(gb, GB_WAVE, 0, 0); update_sample(gb, GB_WAVE, 0, 0);
} }
@ -1160,7 +1209,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR32: case GB_IO_NR32:
gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3];
if (gb->apu.is_active[GB_WAVE]) { if (gb->apu.is_active[GB_WAVE]) {
update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); update_wave_sample(gb, 0);
} }
break; break;
case GB_IO_NR33: case GB_IO_NR33:
@ -1170,13 +1219,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR34: case GB_IO_NR34:
gb->apu.wave_channel.sample_length &= 0xFF; gb->apu.wave_channel.sample_length &= 0xFF;
gb->apu.wave_channel.sample_length |= (value & 7) << 8; gb->apu.wave_channel.sample_length |= (value & 7) << 8;
if ((value & 0x80)) { if (value & 0x80) {
gb->apu.channel_3_pulsed = true;
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
reads from it. */ reads from it. */
if (!GB_is_cgb(gb) && if (!GB_is_cgb(gb) &&
gb->apu.is_active[GB_WAVE] && gb->apu.is_active[GB_WAVE] &&
gb->apu.wave_channel.sample_countdown == 0 && gb->apu.wave_channel.sample_countdown == 0) {
gb->apu.wave_channel.enable) {
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF; unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
/* This glitch varies between models and even specific instances: /* This glitch varies between models and even specific instances:
@ -1188,26 +1237,24 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
are all deterministic. */ are all deterministic. */
if (offset < 4) { if (offset < 4) {
gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset];
gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2];
gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1];
} }
else { else {
memcpy(gb->io_registers + GB_IO_WAV_START, memcpy(gb->io_registers + GB_IO_WAV_START,
gb->io_registers + GB_IO_WAV_START + (offset & ~3), gb->io_registers + GB_IO_WAV_START + (offset & ~3),
4); 4);
memcpy(gb->apu.wave_channel.wave_form,
gb->apu.wave_channel.wave_form + (offset & ~3) * 2,
8);
} }
} }
if (!gb->apu.is_active[GB_WAVE]) { gb->apu.wave_channel.current_sample_index = 0;
if (gb->apu.is_active[GB_WAVE] && gb->apu.wave_channel.sample_countdown == 0) {
gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START];
}
if (gb->apu.wave_channel.enable) {
gb->apu.is_active[GB_WAVE] = true; gb->apu.is_active[GB_WAVE] = true;
update_sample(gb, GB_WAVE, update_sample(gb, GB_WAVE,
gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, (gb->apu.wave_channel.current_sample_byte >> 4) >> gb->apu.wave_channel.shift,
0); 0);
} }
gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3;
gb->apu.wave_channel.current_sample_index = 0;
if (gb->apu.wave_channel.pulse_length == 0) { if (gb->apu.wave_channel.pulse_length == 0) {
gb->apu.wave_channel.pulse_length = 0x100; gb->apu.wave_channel.pulse_length = 0x100;
gb->apu.wave_channel.length_enabled = false; gb->apu.wave_channel.length_enabled = false;
@ -1232,10 +1279,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
} }
} }
gb->apu.wave_channel.length_enabled = value & 0x40; gb->apu.wave_channel.length_enabled = value & 0x40;
if (gb->apu.is_active[GB_WAVE] && !gb->apu.wave_channel.enable) {
gb->apu.is_active[GB_WAVE] = false;
update_sample(gb, GB_WAVE, 0, 0);
}
break; break;
@ -1401,12 +1444,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.noise_channel.length_enabled = value & 0x40; gb->apu.noise_channel.length_enabled = value & 0x40;
break; break;
} }
default:
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) {
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4;
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF;
}
} }
gb->io_registers[reg] = value; gb->io_registers[reg] = value;
} }

View File

@ -99,9 +99,9 @@ typedef struct
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint8_t current_sample_index; uint8_t current_sample_index;
uint8_t current_sample; // Current sample before shifting. uint8_t current_sample_byte; // Current sample byte.
int8_t wave_form[32]; GB_PADDING(int8_t, wave_form)[32];
bool wave_form_just_read; bool wave_form_just_read;
} wave_channel; } wave_channel;
@ -122,6 +122,7 @@ typedef struct
} noise_channel; } noise_channel;
/* Todo: merge these into their structs when breaking save state compatibility */
#define GB_SKIP_DIV_EVENT_INACTIVE 0 #define GB_SKIP_DIV_EVENT_INACTIVE 0
#define GB_SKIP_DIV_EVENT_SKIPPED 1 #define GB_SKIP_DIV_EVENT_SKIPPED 1
#define GB_SKIP_DIV_EVENT_SKIP 2 #define GB_SKIP_DIV_EVENT_SKIP 2
@ -136,6 +137,8 @@ typedef struct
GB_envelope_clock_t square_envelope_clock[2]; GB_envelope_clock_t square_envelope_clock[2];
GB_envelope_clock_t noise_envelope_clock; GB_envelope_clock_t noise_envelope_clock;
bool channel_3_pulsed;
bool channel_3_delayed_bugged_read;
} GB_apu_t; } GB_apu_t;
typedef enum { typedef enum {
@ -184,7 +187,6 @@ void GB_apu_div_secondary_event(GB_gameboy_t *gb);
void GB_apu_init(GB_gameboy_t *gb); void GB_apu_init(GB_gameboy_t *gb);
void GB_apu_run(GB_gameboy_t *gb); void GB_apu_run(GB_gameboy_t *gb);
void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb);
void GB_borrow_sgb_border(GB_gameboy_t *gb);
#endif #endif
#endif /* apu_h */ #endif /* apu_h */

View File

@ -428,23 +428,23 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) {
if (length == 1) { if (length == 1) {
switch (string[0]) { switch (string[0]) {
case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]}; case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->af};
case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]}; case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->af};
case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]}; case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->bc};
case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]}; case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->bc};
case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]}; case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->de};
case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]}; case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->de};
case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]}; case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->hl};
case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]}; case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->hl};
} }
} }
else if (length == 2) { else if (length == 2) {
switch (string[0]) { switch (string[0]) {
case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->af};
case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->bc};
case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->de};
case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->hl};
case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->sp};
case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc};
} }
} }
@ -606,23 +606,23 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) {
if (length == 1) { if (length == 1) {
switch (string[0]) { switch (string[0]) {
case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit; case 'a': ret = VALUE_16(gb->af >> 8); goto exit;
case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit; case 'f': ret = VALUE_16(gb->af & 0xFF); goto exit;
case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit; case 'b': ret = VALUE_16(gb->bc >> 8); goto exit;
case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit; case 'c': ret = VALUE_16(gb->bc & 0xFF); goto exit;
case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit; case 'd': ret = VALUE_16(gb->de >> 8); goto exit;
case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit; case 'e': ret = VALUE_16(gb->de & 0xFF); goto exit;
case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit; case 'h': ret = VALUE_16(gb->hl >> 8); goto exit;
case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit; case 'l': ret = VALUE_16(gb->hl & 0xFF); goto exit;
} }
} }
else if (length == 2) { else if (length == 2) {
switch (string[0]) { switch (string[0]) {
case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;} case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->af); goto exit;}
case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;} case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->bc); goto exit;}
case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;} case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->de); goto exit;}
case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;} case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->hl); goto exit;}
case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;} case 's': if (string[1] == 'p') {ret = VALUE_16(gb->sp); goto exit;}
case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;}
} }
} }
@ -811,15 +811,15 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
} }
GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->af, /* AF can't really be an address */
(gb->f & GB_CARRY_FLAG)? 'C' : '-', (gb->f & GB_CARRY_FLAG)? 'C' : '-',
(gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-',
(gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-',
(gb->f & GB_ZERO_FLAG)? 'Z' : '-'); (gb->f & GB_ZERO_FLAG)? 'Z' : '-');
GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); GB_log(gb, "BC = %s\n", value_to_string(gb, gb->bc, false));
GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); GB_log(gb, "DE = %s\n", value_to_string(gb, gb->de, false));
GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); GB_log(gb, "HL = %s\n", value_to_string(gb, gb->hl, false));
GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); GB_log(gb, "SP = %s\n", value_to_string(gb, gb->sp, false));
GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false));
GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled"); GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled");
return true; return true;
@ -1550,7 +1550,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
if (cartridge->has_ram) { if (cartridge->has_ram) {
GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank);
if (gb->cartridge_type->mbc_type != GB_HUC1) { if (gb->cartridge_type->mbc_type != GB_HUC1) {
GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); GB_log(gb, "RAM is currently %s\n", gb->mbc_ram_enable? "enabled" : "disabled");
} }
} }
if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) {
@ -1797,8 +1797,9 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "\nCH3:\n"); GB_log(gb, "\nCH3:\n");
GB_log(gb, " Wave:"); GB_log(gb, " Wave:");
for (uint8_t i = 0; i < 32; i++) { for (uint8_t i = 0; i < 16; i++) {
GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]); GB_log(gb, "%s%X", i % 2? "" : " ", gb->io_registers[GB_IO_WAV_START + i] >> 4);
GB_log(gb, "%X", gb->io_registers[GB_IO_WAV_START + i] & 0xF);
} }
GB_log(gb, "\n"); GB_log(gb, "\n");
GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
@ -1880,11 +1881,14 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) { for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) {
for (uint8_t i = 0; i < 32; i++) { for (uint8_t i = 0; i < 32; i++) {
if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) { uint8_t sample = i & 1?
GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]); (gb->io_registers[GB_IO_WAV_START + i / 2] & 0xF) :
(gb->io_registers[GB_IO_WAV_START + i / 2] >> 4);
if ((sample & mask) == cur_val) {
GB_log(gb, "%X", sample);
} }
else { else {
GB_log(gb, "%c", i%4 == 2 ? '-' : ' '); GB_log(gb, "%c", i % 4 == 2 ? '-' : ' ');
} }
} }
GB_log(gb, "\n"); GB_log(gb, "\n");
@ -2039,7 +2043,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
gb->debug_stopped = true; gb->debug_stopped = true;
} }
else { else {
gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP]; gb->sp_for_call_depth[gb->debug_call_depth] = gb->sp;
gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc; gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc;
} }
} }
@ -2047,7 +2051,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) {
while (gb->backtrace_size) { while (gb->backtrace_size) {
if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) { if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->sp) {
gb->backtrace_size--; gb->backtrace_size--;
} }
else { else {
@ -2055,7 +2059,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
} }
} }
gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP]; gb->backtrace_sps[gb->backtrace_size] = gb->sp;
gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr);
gb->backtrace_returns[gb->backtrace_size].addr = call_addr; gb->backtrace_returns[gb->backtrace_size].addr = call_addr;
gb->backtrace_size++; gb->backtrace_size++;
@ -2076,9 +2080,9 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
gb->debug_stopped = true; gb->debug_stopped = true;
} }
else { else {
if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { if (gb->sp != gb->sp_for_call_depth[gb->debug_call_depth]) {
GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true)); GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true));
GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->sp,
gb->sp_for_call_depth[gb->debug_call_depth]); gb->sp_for_call_depth[gb->debug_call_depth]);
gb->debug_stopped = true; gb->debug_stopped = true;
} }
@ -2086,7 +2090,7 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
} }
while (gb->backtrace_size) { while (gb->backtrace_size) {
if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) { if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->sp) {
gb->backtrace_size--; gb->backtrace_size--;
} }
else { else {
@ -2243,7 +2247,6 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
} }
} }
/* Returns true if debugger waits for more commands */
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context) char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context)
{ {
char *command_string = input; char *command_string = input;
@ -2564,13 +2567,13 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode)
{ {
switch ((opcode >> 3) & 0x3) { switch ((opcode >> 3) & 0x3) {
case 0: case 0:
return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); return !(gb->af & GB_ZERO_FLAG);
case 1: case 1:
return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); return (gb->af & GB_ZERO_FLAG);
case 2: case 2:
return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); return !(gb->af & GB_CARRY_FLAG);
case 3: case 3:
return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); return (gb->af & GB_CARRY_FLAG);
} }
return false; return false;
@ -2587,8 +2590,8 @@ static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode)
static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode) static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode)
{ {
return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | return GB_read_memory(gb, gb->sp) |
(GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); (GB_read_memory(gb, gb->sp + 1) << 8);
} }
@ -2670,7 +2673,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE;
if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) ||
!is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { !is_in_trivial_memory(gb->sp) || !is_in_trivial_memory(gb->sp + 1)) {
return JUMP_TO_NONTRIVIAL; return JUMP_TO_NONTRIVIAL;
} }

View File

@ -632,7 +632,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
GB_FETCHER_SLEEP, GB_FETCHER_SLEEP,
} fetcher_step_t; } fetcher_step_t;
fetcher_step_t fetcher_state_machine [8] = { static const fetcher_step_t fetcher_state_machine [8] = {
GB_FETCHER_SLEEP, GB_FETCHER_SLEEP,
GB_FETCHER_GET_TILE, GB_FETCHER_GET_TILE,
GB_FETCHER_SLEEP, GB_FETCHER_SLEEP,
@ -666,7 +666,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
x = gb->window_tile_x; x = gb->window_tile_x;
} }
else { else {
x = ((gb->io_registers[GB_IO_SCX] / 8) + gb->fetcher_x) & 0x1F; /* TODO: There is some CGB timing error around here.
Adjusting SCX by 7 or less shouldn't have an effect on a CGB,
but SameBoy is affected by a change of both 7 and 6 (but not less). */
x = ((gb->io_registers[GB_IO_SCX] + gb->position_in_line + 8) / 8) & 0x1F;
} }
if (gb->model > GB_MODEL_CGB_C) { if (gb->model > GB_MODEL_CGB_C) {
/* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */
@ -777,12 +780,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
// fallthrough // fallthrough
case GB_FETCHER_PUSH: { case GB_FETCHER_PUSH: {
if (gb->fetcher_state == 6) {
/* The background map index increase at this specific point. If this state is not reached,
it will simply not increase. */
gb->fetcher_x++;
gb->fetcher_x &= 0x1f;
}
if (gb->fetcher_state < 7) { if (gb->fetcher_state < 7) {
gb->fetcher_state++; gb->fetcher_state++;
} }
@ -1049,7 +1046,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
gb->lcd_x = 0; gb->lcd_x = 0;
gb->fetcher_x = 0;
gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7);
@ -1530,7 +1526,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h
uint8_t count = 0; uint8_t count = 0;
*sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8;
uint8_t oam_to_dest_index[40] = {0,}; uint8_t oam_to_dest_index[40] = {0,};
for (unsigned y = 0; y < LINES; y++) { for (signed y = 0; y < LINES; y++) {
GB_object_t *sprite = (GB_object_t *) &gb->oam; GB_object_t *sprite = (GB_object_t *) &gb->oam;
uint8_t sprites_in_line = 0; uint8_t sprites_in_line = 0;
for (uint8_t i = 0; i < 40; i++, sprite++) { for (uint8_t i = 0; i < 40; i++, sprite++) {

View File

@ -284,7 +284,7 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path)
gb->rom_size |= gb->rom_size >> 1; gb->rom_size |= gb->rom_size >> 1;
gb->rom_size++; gb->rom_size++;
} }
if (gb->rom_size == 0) { if (gb->rom_size < 0x8000) {
gb->rom_size = 0x8000; gb->rom_size = 0x8000;
} }
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
@ -396,7 +396,7 @@ int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size
gb->rom_size++; gb->rom_size++;
} }
if (gb->rom_size == 0) { if (gb->rom_size < 0x8000) {
gb->rom_size = 0x8000; gb->rom_size = 0x8000;
} }
@ -412,6 +412,7 @@ int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size
if (gb->mbc_ram) { if (gb->mbc_ram) {
free(gb->mbc_ram); free(gb->mbc_ram);
gb->mbc_ram = NULL; gb->mbc_ram = NULL;
gb->mbc_ram_size = 0;
} }
if (gb->cartridge_type->has_ram) { if (gb->cartridge_type->has_ram) {
@ -1198,7 +1199,7 @@ uint8_t GB_run(GB_gameboy_t *gb)
{ {
gb->vblank_just_occured = false; gb->vblank_just_occured = false;
if (gb->sgb && gb->sgb->intro_animation < 140) { if (gb->sgb && gb->sgb->intro_animation < 96) {
/* On the SGB, the GB is halted after finishing the boot ROM. /* On the SGB, the GB is halted after finishing the boot ROM.
Then, after the boot animation is almost done, it's reset. Then, after the boot animation is almost done, it's reset.
Since the SGB HLE does not perform any header validity checks, Since the SGB HLE does not perform any header validity checks,
@ -1535,18 +1536,13 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */ case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
case GB_MODEL_SGB2: case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC: { case GB_MODEL_SGB2_NO_SFC: {
uint8_t temp;
for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) {
if (i & 1) { if (i & 1) {
temp = GB_random() & GB_random() & GB_random(); gb->io_registers[GB_IO_WAV_START + i] = GB_random() & GB_random() & GB_random();
} }
else { else {
temp = GB_random() | GB_random() | GB_random(); gb->io_registers[GB_IO_WAV_START + i] = GB_random() | GB_random() | GB_random();
} }
gb->apu.wave_channel.wave_form[i * 2] = temp >> 4;
gb->apu.wave_channel.wave_form[i * 2 + 1] = temp & 0xF;
gb->io_registers[GB_IO_WAV_START + i] = temp;
} }
break; break;
} }
@ -1602,7 +1598,10 @@ void GB_reset(GB_gameboy_t *gb)
{ {
uint32_t mbc_ram_size = gb->mbc_ram_size; uint32_t mbc_ram_size = gb->mbc_ram_size;
GB_model_t model = gb->model; GB_model_t model = gb->model;
uint8_t rtc_section[GB_SECTION_SIZE(rtc)];
memcpy(rtc_section, GB_GET_SECTION(gb, rtc), sizeof(rtc_section));
memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved)); memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved));
memcpy(GB_GET_SECTION(gb, rtc), rtc_section, sizeof(rtc_section));
gb->model = model; gb->model = model;
gb->version = GB_STRUCT_VERSION; gb->version = GB_STRUCT_VERSION;

View File

@ -24,6 +24,7 @@
#include "cheats.h" #include "cheats.h"
#include "rumble.h" #include "rumble.h"
#include "workboy.h" #include "workboy.h"
#include "random.h"
#define GB_STRUCT_VERSION 13 #define GB_STRUCT_VERSION 13
@ -434,6 +435,7 @@ struct GB_gameboy_internal_s {
int32_t ir_sensor; int32_t ir_sensor;
bool effective_ir_input; bool effective_ir_input;
uint16_t address_bus;
); );
/* DMA and HDMA */ /* DMA and HDMA */
@ -553,7 +555,7 @@ struct GB_gameboy_internal_s {
/* Video Display */ /* Video Display */
GB_SECTION(video, GB_SECTION(video,
uint32_t vram_size; // Different between CGB and DMG uint32_t vram_size; // Different between CGB and DMG
uint8_t cgb_vram_bank; bool cgb_vram_bank;
uint8_t oam[0xA0]; uint8_t oam[0xA0];
uint8_t background_palettes_data[0x40]; uint8_t background_palettes_data[0x40];
uint8_t sprite_palettes_data[0x40]; uint8_t sprite_palettes_data[0x40];
@ -581,7 +583,7 @@ struct GB_gameboy_internal_s {
uint8_t current_line; uint8_t current_line;
uint16_t ly_for_comparison; uint16_t ly_for_comparison;
GB_fifo_t bg_fifo, oam_fifo; GB_fifo_t bg_fifo, oam_fifo;
uint8_t fetcher_x; GB_PADDING(uint8_t, fetcher_x);
uint8_t fetcher_y; uint8_t fetcher_y;
uint16_t cycles_for_line; uint16_t cycles_for_line;
uint8_t current_tile; uint8_t current_tile;
@ -893,4 +895,8 @@ unsigned GB_get_player_count(GB_gameboy_t *gb);
void GB_get_rom_title(GB_gameboy_t *gb, char *title); void GB_get_rom_title(GB_gameboy_t *gb, char *title);
uint32_t GB_get_rom_crc32(GB_gameboy_t *gb); uint32_t GB_get_rom_crc32(GB_gameboy_t *gb);
#ifdef GB_INTERNAL
void GB_borrow_sgb_border(GB_gameboy_t *gb);
#endif
#endif /* GB_h */ #endif /* GB_h */

View File

@ -12,7 +12,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
gb->io_registers[GB_IO_JOYP] &= 0xF0; gb->io_registers[GB_IO_JOYP] &= 0xF0;
uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0; uint8_t current_player = gb->sgb? gb->sgb->current_player : 0;
switch (key_selection) { switch (key_selection) {
case 3: case 3:
if (gb->sgb && gb->sgb->player_count > 1) { if (gb->sgb && gb->sgb->player_count > 1) {
@ -52,6 +52,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
break; break;
default: default:
__builtin_unreachable();
break; break;
} }

View File

@ -141,6 +141,7 @@ void GB_configure_cart(GB_gameboy_t *gb)
if (gb->mbc_ram) { if (gb->mbc_ram) {
free(gb->mbc_ram); free(gb->mbc_ram);
gb->mbc_ram = NULL; gb->mbc_ram = NULL;
gb->mbc_ram_size = 0;
} }
if (gb->cartridge_type->has_ram) { if (gb->cartridge_type->has_ram) {

View File

@ -291,7 +291,7 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr)
addr = gb->last_tile_data_address; addr = gb->last_tile_data_address;
} }
} }
return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)];
} }
static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
@ -464,7 +464,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
break; break;
default: default:
break; __builtin_unreachable();
} }
for (unsigned i = 0; i < 8; i++) { for (unsigned i = 0; i < 8; i++) {
@ -635,8 +635,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
} }
return 0xFF; return 0xFF;
} }
/* Hardware registers */ __builtin_unreachable();
return 0;
} }
if (addr == 0xFFFF) { if (addr == 0xFFFF) {
@ -815,7 +814,7 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
addr = gb->last_tile_data_address; addr = gb->last_tile_data_address;
} }
} }
gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)] = value;
} }
static bool huc3_write(GB_gameboy_t *gb, uint8_t value) static bool huc3_write(GB_gameboy_t *gb, uint8_t value)
@ -1234,12 +1233,11 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->io_registers[GB_IO_DMA] = value; gb->io_registers[GB_IO_DMA] = value;
return; return;
case GB_IO_SVBK: case GB_IO_SVBK:
if (!gb->cgb_mode) { if (gb->cgb_mode || (GB_is_cgb(gb) && !gb->boot_rom_finished)) {
return; gb->cgb_ram_bank = value & 0x7;
} if (!gb->cgb_ram_bank) {
gb->cgb_ram_bank = value & 0x7; gb->cgb_ram_bank++;
if (!gb->cgb_ram_bank) { }
gb->cgb_ram_bank++;
} }
return; return;
case GB_IO_VBK: case GB_IO_VBK:
@ -1396,7 +1394,7 @@ static GB_write_function_t * const write_map[] =
write_vram, write_vram, /* 8XXX, 9XXX */ write_vram, write_vram, /* 8XXX, 9XXX */
write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */ write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */
write_ram, write_banked_ram, /* CXXX, DXXX */ write_ram, write_banked_ram, /* CXXX, DXXX */
write_ram, write_high_memory, /* EXXX FXXX */ write_ram, write_high_memory, /* EXXX FXXX */
}; };
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)

View File

@ -9,6 +9,15 @@
#define BESS_NAME "SameBoy v" GB_VERSION #define BESS_NAME "SameBoy v" GB_VERSION
#endif #endif
_Static_assert((GB_SECTION_OFFSET(core_state) & 7) == 0, "Section core_state is not aligned");
_Static_assert((GB_SECTION_OFFSET(dma) & 7) == 0, "Section dma is not aligned");
_Static_assert((GB_SECTION_OFFSET(mbc) & 7) == 0, "Section mbc is not aligned");
_Static_assert((GB_SECTION_OFFSET(hram) & 7) == 0, "Section hram is not aligned");
_Static_assert((GB_SECTION_OFFSET(timing) & 7) == 0, "Section timing is not aligned");
_Static_assert((GB_SECTION_OFFSET(apu) & 7) == 0, "Section apu is not aligned");
_Static_assert((GB_SECTION_OFFSET(rtc) & 7) == 0, "Section rtc is not aligned");
_Static_assert((GB_SECTION_OFFSET(video) & 7) == 0, "Section video is not aligned");
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) {
uint32_t magic; uint32_t magic;
uint32_t size; uint32_t size;
@ -298,6 +307,11 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t
return false; return false;
} }
if (GB_is_cgb(gb) != GB_is_cgb(save) || GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) {
GB_log(gb, "The save state is for a different Game Boy model. Try changing the emulated model.\n");
return false;
}
if (gb->mbc_ram_size < save->mbc_ram_size) { if (gb->mbc_ram_size < save->mbc_ram_size) {
GB_log(gb, "The save state has non-matching MBC RAM size.\n"); GB_log(gb, "The save state has non-matching MBC RAM size.\n");
return false; return false;
@ -324,7 +338,24 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t
} }
} }
return true; switch (save->model) {
case GB_MODEL_DMG_B: return true;
case GB_MODEL_SGB_NTSC: return true;
case GB_MODEL_SGB_PAL: return true;
case GB_MODEL_SGB_NTSC_NO_SFC: return true;
case GB_MODEL_SGB_PAL_NO_SFC: return true;
case GB_MODEL_SGB2: return true;
case GB_MODEL_SGB2_NO_SFC: return true;
case GB_MODEL_CGB_C: return true;
case GB_MODEL_CGB_E: return true;
case GB_MODEL_AGB: return true;
}
if ((gb->model & GB_MODEL_FAMILY_MASK) == (save->model & GB_MODEL_FAMILY_MASK)) {
save->model = gb->model;
return true;
}
GB_log(gb, "This save state is for an unknown Game Boy model\n");
return false;
} }
static void sanitize_state(GB_gameboy_t *gb) static void sanitize_state(GB_gameboy_t *gb)
@ -338,8 +369,36 @@ static void sanitize_state(GB_gameboy_t *gb)
gb->bg_fifo.write_end &= 0xF; gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF; gb->oam_fifo.write_end &= 0xF;
gb->last_tile_index_address &= 0x1FFF;
gb->window_tile_x &= 0x1F;
/* These are kind of DOS-ish if too large */
if (abs(gb->display_cycles) > 0x8000) {
gb->display_cycles = 0;
}
if (abs(gb->div_cycles) > 0x8000) {
gb->div_cycles = 0;
}
if (!GB_is_cgb(gb)) {
gb->cgb_mode = false;
}
if (gb->ram_size == 0x8000) {
gb->cgb_ram_bank &= 0x7;
}
else {
gb->cgb_ram_bank = 1;
}
if (gb->vram_size != 0x4000) {
gb->cgb_vram_bank = 0;
}
if (!GB_is_cgb(gb)) {
gb->current_tile_attributes = 0;
}
gb->object_low_line_address &= gb->vram_size & ~1; gb->object_low_line_address &= gb->vram_size & ~1;
gb->fetcher_x &= 0x1f;
if (gb->lcd_x > gb->position_in_line) { if (gb->lcd_x > gb->position_in_line) {
gb->lcd_x = gb->position_in_line; gb->lcd_x = gb->position_in_line;
} }
@ -347,6 +406,12 @@ static void sanitize_state(GB_gameboy_t *gb)
if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) {
gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X;
} }
if (gb->sgb) {
if (gb->sgb->player_count != 1 && gb->sgb->player_count != 2 && gb->sgb->player_count != 4) {
gb->sgb->player_count = 1;
}
gb->sgb->current_player &= gb->sgb->player_count - 1;
}
if (gb->sgb && !gb->sgb->v14_3) { if (gb->sgb && !gb->sgb->v14_3) {
#ifdef GB_BIG_ENDIAN #ifdef GB_BIG_ENDIAN
for (unsigned i = 0; i < sizeof(gb->sgb->border.raw_data) / 2; i++) { for (unsigned i = 0; i < sizeof(gb->sgb->border.raw_data) / 2; i++) {
@ -711,7 +776,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
bess_sgb.attribute_files = (BESS_buffer_t){LE32(sizeof(gb->sgb->attribute_files)), bess_sgb.attribute_files = (BESS_buffer_t){LE32(sizeof(gb->sgb->attribute_files)),
LE32(sgb_offset + offsetof(GB_sgb_t, attribute_files))}; LE32(sgb_offset + offsetof(GB_sgb_t, attribute_files))};
bess_sgb.multiplayer_state = (gb->sgb->player_count << 4) | (gb->sgb->current_player & (gb->sgb->player_count - 1)); bess_sgb.multiplayer_state = (gb->sgb->player_count << 4) | gb->sgb->current_player;
if (file->write(file, &bess_sgb, sizeof(bess_sgb)) != sizeof(bess_sgb)) { if (file->write(file, &bess_sgb, sizeof(bess_sgb)) != sizeof(bess_sgb)) {
goto error; goto error;
} }
@ -905,6 +970,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
save.halted = core.execution_mode == 1; save.halted = core.execution_mode == 1;
save.stopped = core.execution_mode == 2; save.stopped = core.execution_mode == 2;
// Done early for compatibility with 0.14.x
GB_write_memory(&save, 0xFF00 + GB_IO_SVBK, core.io_registers[GB_IO_SVBK]);
// CPU related // CPU related
// Determines DMG mode // Determines DMG mode
@ -965,7 +1032,6 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
GB_write_memory(&save, 0xFF00 + GB_IO_BGPI, core.io_registers[GB_IO_BGPI]); GB_write_memory(&save, 0xFF00 + GB_IO_BGPI, core.io_registers[GB_IO_BGPI]);
GB_write_memory(&save, 0xFF00 + GB_IO_OBPI, core.io_registers[GB_IO_OBPI]); GB_write_memory(&save, 0xFF00 + GB_IO_OBPI, core.io_registers[GB_IO_OBPI]);
GB_write_memory(&save, 0xFF00 + GB_IO_OPRI, core.io_registers[GB_IO_OPRI]); GB_write_memory(&save, 0xFF00 + GB_IO_OPRI, core.io_registers[GB_IO_OPRI]);
GB_write_memory(&save, 0xFF00 + GB_IO_SVBK, core.io_registers[GB_IO_SVBK]);
// Interrupts // Interrupts
GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]); GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]);
@ -1004,6 +1070,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
case BE32('MBC '): case BE32('MBC '):
if (!found_core) goto parse_error; if (!found_core) goto parse_error;
if (LE32(block.size) % 3 != 0) goto parse_error; if (LE32(block.size) % 3 != 0) goto parse_error;
if (LE32(block.size) > 0x1000) goto parse_error;
for (unsigned i = LE32(block.size); i > 0; i -= 3) { for (unsigned i = LE32(block.size); i > 0; i -= 3) {
BESS_MBC_pair_t pair; BESS_MBC_pair_t pair;
file->read(file, &pair, sizeof(pair)); file->read(file, &pair, sizeof(pair));
@ -1151,6 +1218,7 @@ error:
GB_log(gb, "Attempted to import a save state from a different emulator or incompatible version, but the save state is invalid.\n"); GB_log(gb, "Attempted to import a save state from a different emulator or incompatible version, but the save state is invalid.\n");
} }
GB_free(&save); GB_free(&save);
sanitize_state(gb);
return errno; return errno;
} }
@ -1258,7 +1326,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
} }
bool GB_is_stave_state(const char *path) bool GB_is_save_state(const char *path)
{ {
bool ret = false; bool ret = false;
FILE *f = fopen(path, "rb"); FILE *f = fopen(path, "rb");

View File

@ -10,7 +10,7 @@
as anonymous enums inside unions */ as anonymous enums inside unions */
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__ #define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__
#else #else
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) union {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0] #define GB_SECTION(name, ...) union __attribute__ ((aligned (8))) {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]
#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) #define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start))
#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start)) #define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) #define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))
@ -27,7 +27,7 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer);
int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state(GB_gameboy_t *gb, const char *path);
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length);
bool GB_is_stave_state(const char *path); bool GB_is_save_state(const char *path);
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
static inline uint32_t state_magic(void) static inline uint32_t state_magic(void)
{ {

View File

@ -375,39 +375,36 @@ static void command_ready(GB_gameboy_t *gb)
} }
break; break;
case PAL_TRN: case PAL_TRN:
gb->sgb->vram_transfer_countdown = 2; gb->sgb->vram_transfer_countdown = 3;
gb->sgb->transfer_dest = TRANSFER_PALETTES; gb->sgb->transfer_dest = TRANSFER_PALETTES;
break; break;
case DATA_SND: case DATA_SND:
// Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this
break; break;
case MLT_REQ: case MLT_REQ:
if (gb->sgb->player_count == 1) {
gb->sgb->current_player = 0;
}
gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility, gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility,
fix this to be 0 based. */ fix this to be 0 based. */
if (gb->sgb->player_count == 3) { if (gb->sgb->player_count == 3) {
gb->sgb->current_player++; gb->sgb->player_count++;
} }
gb->sgb->mlt_lock = true; gb->sgb->current_player &= (gb->sgb->player_count - 1);
break; break;
case CHR_TRN: case CHR_TRN:
gb->sgb->vram_transfer_countdown = 2; gb->sgb->vram_transfer_countdown = 3;
gb->sgb->transfer_dest = (gb->sgb->command[1] & 1)? TRANSFER_HIGH_TILES : TRANSFER_LOW_TILES; gb->sgb->transfer_dest = (gb->sgb->command[1] & 1)? TRANSFER_HIGH_TILES : TRANSFER_LOW_TILES;
break; break;
case PCT_TRN: case PCT_TRN:
gb->sgb->vram_transfer_countdown = 2; gb->sgb->vram_transfer_countdown = 3;
gb->sgb->transfer_dest = TRANSFER_BORDER_DATA; gb->sgb->transfer_dest = TRANSFER_BORDER_DATA;
break; break;
case ATTR_TRN: case ATTR_TRN:
gb->sgb->vram_transfer_countdown = 2; gb->sgb->vram_transfer_countdown = 3;
gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES; gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES;
break; break;
case ATTR_SET: case ATTR_SET:
load_attribute_file(gb, gb->sgb->command[0] & 0x3F); load_attribute_file(gb, gb->sgb->command[1] & 0x3F);
if (gb->sgb->command[0] & 0x40) { if (gb->sgb->command[1] & 0x40) {
gb->sgb->mask_mode = MASK_DISABLED; gb->sgb->mask_mode = MASK_DISABLED;
} }
break; break;
@ -437,27 +434,22 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
return; return;
} }
if (gb->sgb->disable_commands) return; if (gb->sgb->disable_commands) return;
if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) {
return;
}
uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8;
if ((gb->sgb->command[0] & 0xF1) == 0xF1) { if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
command_size = SGB_PACKET_SIZE * 8; command_size = SGB_PACKET_SIZE * 8;
} }
if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) { if ((value & 0x20) != 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) == 0) {
gb->sgb->mlt_lock ^= true; if ((gb->sgb->player_count & 1) == 0) {
gb->sgb->current_player++;
gb->sgb->current_player &= (gb->sgb->player_count - 1);
}
} }
switch ((value >> 4) & 3) { switch ((value >> 4) & 3) {
case 3: case 3:
gb->sgb->ready_for_pulse = true; gb->sgb->ready_for_pulse = true;
if ((gb->sgb->player_count & 1) == 0 && !gb->sgb->mlt_lock) {
gb->sgb->current_player++;
gb->sgb->current_player &= 3;
gb->sgb->mlt_lock = true;
}
break; break;
case 2: // Zero case 2: // Zero
@ -473,10 +465,12 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
gb->sgb->ready_for_stop = false; gb->sgb->ready_for_stop = false;
} }
else { else {
gb->sgb->command_write_index++; if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) {
gb->sgb->ready_for_pulse = false; gb->sgb->command_write_index++;
if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { gb->sgb->ready_for_pulse = false;
gb->sgb->ready_for_stop = true; if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb->ready_for_stop = true;
}
} }
} }
break; break;
@ -490,11 +484,13 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); memset(gb->sgb->command, 0, sizeof(gb->sgb->command));
} }
else { else {
gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7); if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) {
gb->sgb->command_write_index++; gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7);
gb->sgb->ready_for_pulse = false; gb->sgb->command_write_index++;
if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { gb->sgb->ready_for_pulse = false;
gb->sgb->ready_for_stop = true; if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb->ready_for_stop = true;
}
} }
} }
break; break;
@ -537,7 +533,6 @@ static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_
return GB_convert_rgb15(gb, color, false); return GB_convert_rgb15(gb, color, false);
} }
#include <stdio.h>
static void render_boot_animation (GB_gameboy_t *gb) static void render_boot_animation (GB_gameboy_t *gb)
{ {
#include "graphics/sgb_animation_logo.inc" #include "graphics/sgb_animation_logo.inc"
@ -653,7 +648,7 @@ void GB_sgb_render(GB_gameboy_t *gb)
} }
} }
if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) { if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) {
gb->sgb->border_animation = 64; gb->sgb->border_animation = 105; // Measured on an SGB2, but might be off by ±2
} }
} }
} }
@ -734,7 +729,10 @@ void GB_sgb_render(GB_gameboy_t *gb)
} }
uint32_t border_colors[16 * 4]; uint32_t border_colors[16 * 4];
if (gb->sgb->border_animation == 0 || gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) { if (gb->sgb->border_animation == 0 || gb->sgb->border_animation > 64 || gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) {
if (gb->sgb->border_animation != 0) {
gb->sgb->border_animation--;
}
for (unsigned i = 0; i < 16 * 4; i++) { for (unsigned i = 0; i < 16 * 4; i++) {
border_colors[i] = convert_rgb15(gb, LE16(gb->sgb->border.palette[i])); border_colors[i] = convert_rgb15(gb, LE16(gb->sgb->border.palette[i]));
} }
@ -767,6 +765,7 @@ void GB_sgb_render(GB_gameboy_t *gb)
continue; continue;
} }
uint16_t tile = LE16(gb->sgb->border.map[tile_x + tile_y * 32]); uint16_t tile = LE16(gb->sgb->border.map[tile_x + tile_y * 32]);
if (tile & 0x300) continue; // Unused tile
uint8_t flip_x = (tile & 0x4000)? 0:7; uint8_t flip_x = (tile & 0x4000)? 0:7;
uint8_t flip_y = (tile & 0x8000)? 7:0; uint8_t flip_y = (tile & 0x8000)? 7:0;
uint8_t palette = (tile >> 10) & 3; uint8_t palette = (tile >> 10) & 3;

View File

@ -61,7 +61,7 @@ struct GB_sgb_s {
uint8_t received_header[0x54]; uint8_t received_header[0x54];
/* Multiplayer (cont) */ /* Multiplayer (cont) */
bool mlt_lock; GB_PADDING(bool, mlt_lock);
bool v14_3; // True on save states created on 0.14.3 or newer; Remove when breaking save state compatibility! bool v14_3; // True on save states created on 0.14.3 or newer; Remove when breaking save state compatibility!
}; };

File diff suppressed because it is too large Load Diff

View File

@ -434,7 +434,7 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac)
/* The bit used for overflow testing must have been 1 */ /* The bit used for overflow testing must have been 1 */
if (gb->div_counter & old_clocks) { if (gb->div_counter & old_clocks) {
/* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */
if (!(new_tac & 4) || gb->div_counter & new_clocks) { if (!(new_tac & 4) || !(gb->div_counter & new_clocks)) {
increase_tima(gb); increase_tima(gb);
} }
} }

View File

@ -98,7 +98,7 @@ static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportT
uint32_t reportID, uint8_t *report, CFIndex reportLength) uint32_t reportID, uint8_t *report, CFIndex reportLength)
{ {
if (reportLength) { if (reportLength) {
[(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:false]];
} }
} }

View File

@ -128,10 +128,10 @@ endif
ifeq (,$(PKG_CONFIG)) ifeq (,$(PKG_CONFIG))
SDL_CFLAGS := $(shell sdl2-config --cflags) SDL_CFLAGS := $(shell sdl2-config --cflags)
SDL_LDFLAGS := $(shell sdl2-config --libs) SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread
else else
SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2) SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2)
SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) -lpthread
endif endif
ifeq (,$(PKG_CONFIG)) ifeq (,$(PKG_CONFIG))
GL_LDFLAGS := -lGL GL_LDFLAGS := -lGL

View File

@ -5,6 +5,8 @@
char *do_open_rom_dialog(void) char *do_open_rom_dialog(void)
{ {
@autoreleasepool { @autoreleasepool {
int stderr_fd = dup(STDERR_FILENO);
close(STDERR_FILENO);
NSWindow *key = [NSApp keyWindow]; NSWindow *key = [NSApp keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel]; NSOpenPanel *dialog = [NSOpenPanel openPanel];
dialog.title = @"Open ROM"; dialog.title = @"Open ROM";
@ -12,6 +14,7 @@ char *do_open_rom_dialog(void)
[dialog runModal]; [dialog runModal];
[key makeKeyAndOrderFront:nil]; [key makeKeyAndOrderFront:nil];
NSString *ret = [[[dialog URLs] firstObject] path]; NSString *ret = [[[dialog URLs] firstObject] path];
dup2(stderr_fd, STDERR_FILENO);
if (ret) { if (ret) {
return strdup(ret.UTF8String); return strdup(ret.UTF8String);
} }
@ -22,6 +25,8 @@ char *do_open_rom_dialog(void)
char *do_open_folder_dialog(void) char *do_open_folder_dialog(void)
{ {
@autoreleasepool { @autoreleasepool {
int stderr_fd = dup(STDERR_FILENO);
close(STDERR_FILENO);
NSWindow *key = [NSApp keyWindow]; NSWindow *key = [NSApp keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel]; NSOpenPanel *dialog = [NSOpenPanel openPanel];
dialog.title = @"Select Boot ROMs Folder"; dialog.title = @"Select Boot ROMs Folder";
@ -30,6 +35,7 @@ char *do_open_folder_dialog(void)
[dialog runModal]; [dialog runModal];
[key makeKeyAndOrderFront:nil]; [key makeKeyAndOrderFront:nil];
NSString *ret = [[[dialog URLs] firstObject] path]; NSString *ret = [[[dialog URLs] firstObject] path];
dup2(stderr_fd, STDERR_FILENO);
if (ret) { if (ret) {
return strdup(ret.UTF8String); return strdup(ret.UTF8String);
} }

View File

@ -43,10 +43,10 @@ static OSStatus render(CGContextRef cgContext, CFURLRef url, bool showBorder)
bitmapInfo, bitmapInfo,
provider, provider,
NULL, NULL,
YES, true,
renderingIntent); renderingIntent);
CGContextSetInterpolationQuality(cgContext, kCGInterpolationNone); CGContextSetInterpolationQuality(cgContext, kCGInterpolationNone);
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:NO]; NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:false];
[NSGraphicsContext setCurrentContext:context]; [NSGraphicsContext setCurrentContext:context];

1020
bsnes/gb/SDL/console.c Normal file

File diff suppressed because it is too large Load Diff

49
bsnes/gb/SDL/console.h Normal file
View File

@ -0,0 +1,49 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdarg.h>
#define CON_EOF "\x04"
bool CON_start(char *(*completer)(const char *substring, uintptr_t *context));
char *CON_readline(const char *prompt);
char *CON_readline_async(void);
typedef struct {
enum {
CON_COLOR_NONE = 0,
CON_COLOR_BLACK,
CON_COLOR_RED,
CON_COLOR_GREEN,
CON_COLOR_YELLOW,
CON_COLOR_BLUE,
CON_COLOR_MAGENTA,
CON_COLOR_CYAN,
CON_COLOR_LIGHT_GREY,
CON_COLOR_DARK_GREY = CON_COLOR_BLACK + 0x10,
CON_COLOR_BRIGHT_RED = CON_COLOR_RED + 0x10,
CON_COLOR_BRIGHT_GREEN = CON_COLOR_GREEN + 0x10,
CON_COLOR_BRIGHT_YELLOW = CON_COLOR_YELLOW + 0x10,
CON_COLOR_BRIGHT_BLUE = CON_COLOR_BLUE + 0x10,
CON_COLOR_BRIGHT_MAGENTA = CON_COLOR_MAGENTA + 0x10,
CON_COLOR_BRIGHT_CYAN = CON_COLOR_CYAN + 0x10,
CON_COLOR_WHITE = CON_COLOR_LIGHT_GREY + 0x10,
} color:8, background:8;
bool bold;
bool italic;
bool underline;
} CON_attributes_t;
#ifndef __printflike
/* Missing from Linux headers. */
#define __printflike(fmtarg, firstvararg) \
__attribute__((__format__ (__printf__, fmtarg, firstvararg)))
#endif
void CON_print(const char *string);
void CON_attributed_print(const char *string, CON_attributes_t *attributes);
void CON_vprintf(const char *fmt, va_list args);
void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args);
void CON_printf(const char *fmt, ...) __printflike(1, 2);
void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...) __printflike(1, 3);
void CON_set_async_prompt(const char *string);
void CON_set_repeat_empty(bool repeat);

View File

@ -1160,6 +1160,12 @@ void run_gui(bool is_running)
static SDL_Surface *converted_background = NULL; static SDL_Surface *converted_background = NULL;
if (!converted_background) { if (!converted_background) {
SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp")); SDL_Surface *background = SDL_LoadBMP(resource_path("background.bmp"));
/* Create a blank background if background.bmp could not be loaded */
if (!background) {
background = SDL_CreateRGBSurface(0, 160, 144, 8, 0, 0, 0, 0);
}
SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4); SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4);
converted_background = SDL_ConvertSurface(background, pixel_format, 0); converted_background = SDL_ConvertSurface(background, pixel_format, 0);
SDL_LockSurface(converted_background); SDL_LockSurface(converted_background);
@ -1338,7 +1344,7 @@ void run_gui(bool is_running)
break; break;
} }
case SDL_DROPFILE: { case SDL_DROPFILE: {
if (GB_is_stave_state(event.drop.file)) { if (GB_is_save_state(event.drop.file)) {
if (GB_is_inited(&gb)) { if (GB_is_inited(&gb)) {
dropped_state_file = event.drop.file; dropped_state_file = event.drop.file;
pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND;

View File

@ -49,8 +49,8 @@ extern unsigned command_parameter;
extern char *dropped_state_file; extern char *dropped_state_file;
typedef enum { typedef enum {
JOYPAD_BUTTON_LEFT,
JOYPAD_BUTTON_RIGHT, JOYPAD_BUTTON_RIGHT,
JOYPAD_BUTTON_LEFT,
JOYPAD_BUTTON_UP, JOYPAD_BUTTON_UP,
JOYPAD_BUTTON_DOWN, JOYPAD_BUTTON_DOWN,
JOYPAD_BUTTON_A, JOYPAD_BUTTON_A,

View File

@ -10,6 +10,7 @@
#include "gui.h" #include "gui.h"
#include "shader.h" #include "shader.h"
#include "audio/audio.h" #include "audio/audio.h"
#include "console.h"
#ifndef _WIN32 #ifndef _WIN32
#include <unistd.h> #include <unistd.h>
@ -29,6 +30,7 @@ static char *filename = NULL;
static typeof(free) *free_function = NULL; static typeof(free) *free_function = NULL;
static char *battery_save_path_ptr = NULL; static char *battery_save_path_ptr = NULL;
static SDL_GLContext gl_context = NULL; static SDL_GLContext gl_context = NULL;
static bool console_supported = false;
bool uses_gl(void) bool uses_gl(void)
{ {
@ -44,6 +46,80 @@ void set_filename(const char *new_filename, typeof(free) *new_free_function)
free_function = new_free_function; free_function = new_free_function;
} }
static char *completer(const char *substring, uintptr_t *context)
{
if (!GB_is_inited(&gb)) return NULL;
char *temp = strdup(substring);
char *ret = GB_debugger_complete_substring(&gb, temp, context);
free(temp);
return ret;
}
static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes)
{
CON_attributes_t con_attributes = {0,};
con_attributes.bold = attributes & GB_LOG_BOLD;
con_attributes.underline = attributes & GB_LOG_UNDERLINE;
if (attributes & GB_LOG_DASHED_UNDERLINE) {
while (*string) {
con_attributes.underline ^= true;
CON_attributed_printf("%c", &con_attributes, *string);
string++;
}
}
else {
CON_attributed_print(string, &con_attributes);
}
}
static void handle_eof(void)
{
CON_set_async_prompt("");
char *line = CON_readline("Quit? [y]/n > ");
if (line[0] == 'n' || line[0] == 'N') {
free(line);
CON_set_async_prompt("> ");
}
else {
exit(0);
}
}
static char *input_callback(GB_gameboy_t *gb)
{
retry: {
char *ret = CON_readline("Stopped> ");
if (strcmp(ret, CON_EOF) == 0) {
handle_eof();
free(ret);
goto retry;
}
else {
CON_attributes_t attributes = {.bold = true};
CON_attributed_printf("> %s\n", &attributes, ret);
}
return ret;
}
}
static char *asyc_input_callback(GB_gameboy_t *gb)
{
retry: {
char *ret = CON_readline_async();
if (ret && strcmp(ret, CON_EOF) == 0) {
handle_eof();
free(ret);
goto retry;
}
else if (ret) {
CON_attributes_t attributes = {.bold = true};
CON_attributed_printf("> %s\n", &attributes, ret);
}
return ret;
}
}
static char *captured_log = NULL; static char *captured_log = NULL;
static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes)
@ -67,7 +143,7 @@ static void start_capturing_logs(void)
static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title) static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title)
{ {
GB_set_log_callback(&gb, NULL); GB_set_log_callback(&gb, console_supported? log_callback : NULL);
if (captured_log[0] == 0) { if (captured_log[0] == 0) {
free(captured_log); free(captured_log);
captured_log = NULL; captured_log = NULL;
@ -149,7 +225,7 @@ static void handle_events(GB_gameboy_t *gb)
break; break;
case SDL_DROPFILE: { case SDL_DROPFILE: {
if (GB_is_stave_state(event.drop.file)) { if (GB_is_save_state(event.drop.file)) {
dropped_state_file = event.drop.file; dropped_state_file = event.drop.file;
pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND;
} }
@ -256,6 +332,7 @@ static void handle_events(GB_gameboy_t *gb)
} }
case SDL_SCANCODE_C: case SDL_SCANCODE_C:
if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) { if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) {
CON_print("^C\a\n");
GB_debugger_break(gb); GB_debugger_break(gb);
} }
break; break;
@ -408,12 +485,15 @@ static void rumble(GB_gameboy_t *gb, double amp)
static void debugger_interrupt(int ignore) static void debugger_interrupt(int ignore)
{ {
if (!GB_is_inited(&gb)) return; if (!GB_is_inited(&gb)) exit(0);
/* ^C twice to exit */ /* ^C twice to exit */
if (GB_debugger_is_stopped(&gb)) { if (GB_debugger_is_stopped(&gb)) {
GB_save_battery(&gb, battery_save_path_ptr); GB_save_battery(&gb, battery_save_path_ptr);
exit(0); exit(0);
} }
if (console_supported) {
CON_print("^C\n");
}
GB_debugger_break(&gb); GB_debugger_break(&gb);
} }
@ -443,7 +523,6 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
} }
static bool handle_pending_command(void) static bool handle_pending_command(void)
{ {
switch (pending_command) { switch (pending_command) {
@ -577,6 +656,13 @@ restart:
GB_set_rtc_mode(&gb, configuration.rtc_mode); GB_set_rtc_mode(&gb, configuration.rtc_mode);
GB_set_update_input_hint_callback(&gb, handle_events); GB_set_update_input_hint_callback(&gb, handle_events);
GB_apu_set_sample_callback(&gb, gb_audio_callback); GB_apu_set_sample_callback(&gb, gb_audio_callback);
if (console_supported) {
CON_set_async_prompt("> ");
GB_set_log_callback(&gb, log_callback);
GB_set_input_callback(&gb, input_callback);
GB_set_async_input_callback(&gb, asyc_input_callback);
}
} }
if (stop_on_start) { if (stop_on_start) {
stop_on_start = false; stop_on_start = false;
@ -682,18 +768,29 @@ static bool get_arg_flag(const char *flag, int *argc, char **argv)
return false; return false;
} }
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
static void enable_smooth_scrolling(void)
{
CFPreferencesSetAppValue(CFSTR("AppleMomentumScrollSupported"), kCFBooleanTrue, kCFPreferencesCurrentApplication);
}
#endif
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
#ifdef _WIN32 #ifdef _WIN32
SetProcessDPIAware(); SetProcessDPIAware();
#endif #endif
fprintf(stderr, "SameBoy v" GB_VERSION "\n"); #ifdef __APPLE__
enable_smooth_scrolling();
#endif
bool fullscreen = get_arg_flag("--fullscreen", &argc, argv) || get_arg_flag("-f", &argc, argv); bool fullscreen = get_arg_flag("--fullscreen", &argc, argv) || get_arg_flag("-f", &argc, argv);
bool nogl = get_arg_flag("--nogl", &argc, argv); bool nogl = get_arg_flag("--nogl", &argc, argv);
stop_on_start = get_arg_flag("--stop-debugger", &argc, argv) || get_arg_flag("-s", &argc, argv); stop_on_start = get_arg_flag("--stop-debugger", &argc, argv) || get_arg_flag("-s", &argc, argv);
if (argc > 2 || (argc == 2 && argv[1][0] == '-')) { if (argc > 2 || (argc == 2 && argv[1][0] == '-')) {
fprintf(stderr, "SameBoy v" GB_VERSION "\n");
fprintf(stderr, "Usage: %s [--fullscreen|-f] [--nogl] [--stop-debugger|-s] [rom]\n", argv[0]); fprintf(stderr, "Usage: %s [--fullscreen|-f] [--nogl] [--stop-debugger|-s] [rom]\n", argv[0]);
exit(1); exit(1);
} }
@ -705,6 +802,13 @@ int main(int argc, char **argv)
signal(SIGINT, debugger_interrupt); signal(SIGINT, debugger_interrupt);
SDL_Init(SDL_INIT_EVERYTHING); SDL_Init(SDL_INIT_EVERYTHING);
if ((console_supported = CON_start(completer))) {
CON_set_repeat_empty(true);
CON_printf("SameBoy v" GB_VERSION "\n");
}
else {
fprintf(stderr, "SameBoy v" GB_VERSION "\n");
}
strcpy(prefs_path, resource_path("prefs.bin")); strcpy(prefs_path, resource_path("prefs.bin"));
if (access(prefs_path, R_OK | W_OK) != 0) { if (access(prefs_path, R_OK | W_OK) != 0) {

View File

@ -33,7 +33,7 @@ GB_gameboy_t gb;
static unsigned int frames = 0; static unsigned int frames = 0;
static bool use_tga = false; static bool use_tga = false;
static const uint8_t bmp_header[] = { static uint8_t bmp_header[] = {
0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00, 0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00,
0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF,
@ -45,13 +45,13 @@ static const uint8_t bmp_header[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}; };
static const uint8_t tga_header[] = { static uint8_t tga_header[] = {
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x90, 0x00,
0x20, 0x28, 0x20, 0x28,
}; };
uint32_t bitmap[160*144]; uint32_t bitmap[256*224];
static char *async_input_callback(GB_gameboy_t *gb) static char *async_input_callback(GB_gameboy_t *gb)
{ {
@ -140,10 +140,22 @@ static void vblank(GB_gameboy_t *gb)
if (frames >= test_length && !gb->disable_rendering) { if (frames >= test_length && !gb->disable_rendering) {
bool is_screen_blank = true; bool is_screen_blank = true;
for (unsigned i = 160*144; i--;) { if (!gb->sgb) {
if (bitmap[i] != bitmap[0]) { for (unsigned i = 160 * 144; i--;) {
is_screen_blank = false; if (bitmap[i] != bitmap[0]) {
break; is_screen_blank = false;
break;
}
}
}
else {
if (gb->sgb->mask_mode == 0) {
for (unsigned i = 160 * 144; i--;) {
if (gb->sgb->screen_buffer[i] != gb->sgb->screen_buffer[0]) {
is_screen_blank = false;
break;
}
}
} }
} }
@ -151,12 +163,20 @@ static void vblank(GB_gameboy_t *gb)
if (!is_screen_blank || frames >= test_length + 60 * 4) { if (!is_screen_blank || frames >= test_length + 60 * 4) {
FILE *f = fopen(bmp_filename, "wb"); FILE *f = fopen(bmp_filename, "wb");
if (use_tga) { if (use_tga) {
tga_header[0xC] = GB_get_screen_width(gb);
tga_header[0xD] = GB_get_screen_width(gb) >> 8;
tga_header[0xE] = GB_get_screen_height(gb);
tga_header[0xF] = GB_get_screen_height(gb) >> 8;
fwrite(&tga_header, 1, sizeof(tga_header), f); fwrite(&tga_header, 1, sizeof(tga_header), f);
} }
else { else {
(*(uint32_t *)&bmp_header[0x2]) = sizeof(bmp_header) + sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb) + 2;
(*(uint32_t *)&bmp_header[0x12]) = GB_get_screen_width(gb);
(*(int32_t *)&bmp_header[0x16]) = -GB_get_screen_height(gb);
(*(uint32_t *)&bmp_header[0x22]) = sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb) + 2;
fwrite(&bmp_header, 1, sizeof(bmp_header), f); fwrite(&bmp_header, 1, sizeof(bmp_header), f);
} }
fwrite(&bitmap, 1, sizeof(bitmap), f); fwrite(&bitmap, 1, sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb), f);
fclose(f); fclose(f);
if (!gb->boot_rom_finished) { if (!gb->boot_rom_finished) {
GB_log(gb, "Boot ROM did not finish.\n"); GB_log(gb, "Boot ROM did not finish.\n");
@ -271,7 +291,7 @@ int main(int argc, char **argv)
fprintf(stderr, "SameBoy Tester v" GB_VERSION "\n"); fprintf(stderr, "SameBoy Tester v" GB_VERSION "\n");
if (argc == 1) { if (argc == 1) {
fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds] [--sav] [--boot path to boot ROM]" fprintf(stderr, "Usage: %s [--dmg] [--sgb] [--cgb] [--start] [--length seconds] [--sav] [--boot path to boot ROM]"
#ifndef _WIN32 #ifndef _WIN32
" [--jobs number of tests to run simultaneously]" " [--jobs number of tests to run simultaneously]"
#endif #endif
@ -285,6 +305,7 @@ int main(int argc, char **argv)
#endif #endif
bool dmg = false; bool dmg = false;
bool sgb = false;
bool sav = false; bool sav = false;
const char *boot_rom_path = NULL; const char *boot_rom_path = NULL;
@ -294,6 +315,21 @@ int main(int argc, char **argv)
if (strcmp(argv[i], "--dmg") == 0) { if (strcmp(argv[i], "--dmg") == 0) {
fprintf(stderr, "Using DMG mode\n"); fprintf(stderr, "Using DMG mode\n");
dmg = true; dmg = true;
sgb = false;
continue;
}
if (strcmp(argv[i], "--sgb") == 0) {
fprintf(stderr, "Using SGB mode\n");
sgb = true;
dmg = false;
continue;
}
if (strcmp(argv[i], "--cgb") == 0) {
fprintf(stderr, "Using CGB mode\n");
dmg = false;
sgb = false;
continue; continue;
} }
@ -374,6 +410,13 @@ int main(int argc, char **argv)
exit(1); exit(1);
} }
} }
else if (sgb) {
GB_init(&gb, GB_MODEL_SGB2);
if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("sgb2_boot.bin"))) {
fprintf(stderr, "Failed to load boot ROM from '%s'\n", boot_rom_path ?: executable_relative_path("sgb2_boot.bin"));
exit(1);
}
}
else { else {
GB_init(&gb, GB_MODEL_CGB_E); GB_init(&gb, GB_MODEL_CGB_E);
if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("cgb_boot.bin"))) { if (GB_load_boot_rom(&gb, boot_rom_path ?: executable_relative_path("cgb_boot.bin"))) {

View File

@ -0,0 +1,86 @@
/* Very minimal pthread implementation for Windows */
#include <Windows.h>
#include <assert.h>
typedef HANDLE pthread_t;
typedef struct pthread_attr_s pthread_attr_t;
static inline int pthread_create(pthread_t *pthread,
const pthread_attr_t *attrs,
LPTHREAD_START_ROUTINE function,
void *context)
{
assert(!attrs);
*pthread = CreateThread(NULL, 0, function,
context, 0, NULL);
return *pthread? 0 : GetLastError();
}
typedef struct {
unsigned status;
CRITICAL_SECTION cs;
} pthread_mutex_t;
#define PTHREAD_MUTEX_INITIALIZER {0,}
static inline CRITICAL_SECTION *pthread_mutex_to_cs(pthread_mutex_t *mutex)
{
retry:
if (mutex->status == 2) return &mutex->cs;
unsigned expected = 0;
unsigned desired = 1;
if (__atomic_compare_exchange(&mutex->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
InitializeCriticalSection(&mutex->cs);
mutex->status = 2;
return &mutex->cs;
}
goto retry;
}
static inline int pthread_mutex_lock(pthread_mutex_t *mutex)
{
EnterCriticalSection(pthread_mutex_to_cs(mutex));
return 0;
}
static inline int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
LeaveCriticalSection(pthread_mutex_to_cs(mutex));
return 0;
}
typedef struct {
unsigned status;
CONDITION_VARIABLE cond;
} pthread_cond_t;
#define PTHREAD_COND_INITIALIZER {0,}
static inline CONDITION_VARIABLE *pthread_cond_to_win(pthread_cond_t *cond)
{
retry:
if (cond->status == 2) return &cond->cond;
unsigned expected = 0;
unsigned desired = 1;
if (__atomic_compare_exchange(&cond->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
InitializeConditionVariable(&cond->cond);
cond->status = 2;
return &cond->cond;
}
goto retry;
}
static inline int pthread_cond_signal(pthread_cond_t *cond)
{
WakeConditionVariable(pthread_cond_to_win(cond));
return 0;
}
static inline int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex)
{
// Mutex is locked therefore already initialized
return SleepConditionVariableCS(pthread_cond_to_win(cond), &mutex->cs, INFINITE);
}

View File

@ -5,3 +5,4 @@
#define read(...) _read(__VA_ARGS__) #define read(...) _read(__VA_ARGS__)
#define write(...) _write(__VA_ARGS__) #define write(...) _write(__VA_ARGS__)
#define isatty(...) _isatty(__VA_ARGS__)

View File

@ -1 +1 @@
VERSION := 0.14.4 VERSION := 0.14.7

View File

@ -183,7 +183,7 @@ auto Presentation::create() -> void {
.setName("SameBoy") .setName("SameBoy")
.setLogo(Resource::SameBoy) .setLogo(Resource::SameBoy)
.setDescription("Super Game Boy emulator") .setDescription("Super Game Boy emulator")
.setVersion("0.14.4") .setVersion("0.14.7")
.setCopyright("Lior Halphon") .setCopyright("Lior Halphon")
.setLicense("MIT") .setLicense("MIT")
.setWebsite("https://sameboy.github.io") .setWebsite("https://sameboy.github.io")