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
./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/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
mv .github/actions/dmg{,-mode}-acid2.bmp
@ -15,18 +15,18 @@ mv .github/actions/dmg{,-mode}-acid2.bmp
set +e
FAILED_TESTS=`
shasum .github/actions/*.bmp | grep -q -E -v \(\
44ce0c7d49254df0637849c9155080ac7dc3ef3d\ \ .github/actions/cgb-acid2.bmp\|\
shasum .github/actions/*.bmp | grep -E -v \(\
5283564df0cf5bb78a7a90aff026c1a4692fd39e\ \ .github/actions/cgb-acid2.bmp\|\
dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.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\|\
f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\
\)`
if [ -n "$FAILED_TESTS" ] ; then
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
fi

View File

@ -6,7 +6,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, ubuntu-16.04]
os: [macos-latest, ubuntu-latest, ubuntu-18.04]
cc: [gcc, clang]
include:
- os: macos-latest
@ -33,4 +33,4 @@ jobs:
uses: actions/upload-artifact@v1
with:
name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }}
path: build/bin
path: build/bin

View File

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

View File

@ -86,7 +86,7 @@ static uint32_t color_to_int(NSColor *color)
}
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];
new.origin.x = old.origin.x;
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];
}
@ -171,7 +171,7 @@ static uint32_t color_to_int(NSColor *color)
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES];
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true];
}
- (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) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.updatesSpinner stopAnimation:nil];
[self.updatesButton setEnabled:YES];
[self.updatesButton setEnabled:true];
});
if ([(NSHTTPURLResponse *)response statusCode] == 200) {
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
inDomain:NSUserDomainMask
appropriateForURL:[[NSBundle mainBundle] bundleURL]
create:YES
create:true
error:nil] path];
NSTask *unzipTask;
if (!_downloadDirectory) {

View File

@ -321,7 +321,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
{
if (_gbsVisualizer) {
dispatch_async(dispatch_get_main_queue(), ^{
[_gbsVisualizer setNeedsDisplay:YES];
[_gbsVisualizer setNeedsDisplay:true];
});
}
[self.view flip];
@ -422,7 +422,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
[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];
/* Clear pending alarms, don't play alarms while playing */
@ -502,7 +502,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
[audioLock unlock];
[self.audioClient stop];
self.audioClient = nil;
self.view.mouseHidingEnabled = NO;
self.view.mouseHidingEnabled = false;
GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]);
GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]);
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];
}
if (!modelsChanging && [sender tag] == MODEL_NONE) {
GB_reset(&gb);
}
else {
GB_switch_model_and_reset(&gb, [self internalModel]);
}
GB_switch_model_and_reset(&gb, [self internalModel]);
if (old_width != GB_get_screen_width(&gb)) {
[self.view screenSizeChanged];
@ -689,13 +684,13 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
window_frame.size.width);
window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"],
window_frame.size.height);
[self.mainWindow setFrame:window_frame display:YES];
[self.mainWindow setFrame:window_frame display:true];
self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height;
CGRect vram_window_rect = self.vramWindow.frame;
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]];
@ -851,7 +846,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
+ (BOOL)autosavesInPlace
{
return YES;
return true;
}
- (NSString *)windowNibName
@ -863,7 +858,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type
{
return YES;
return true;
}
- (IBAction)changeGBSTrack:(id)sender
@ -902,7 +897,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
{
GB_set_rendering_disabled(&gb, true);
_view = nil;
for (NSView *view in _mainWindow.contentView.subviews) {
for (NSView *view in [_mainWindow.contentView.subviews copy]) {
[view removeFromSuperview];
}
[[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
@ -1066,7 +1061,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (void) windowWillExitFullScreen:(NSNotification *)notification
{
fullScreen = false;
self.view.mouseHidingEnabled = NO;
self.view.mouseHidingEnabled = false;
}
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame
@ -1161,14 +1156,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
}
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];
}
[console_output_lock unlock];
/* Make sure mouse is not hidden while debugging */
self.view.mouseHidingEnabled = NO;
self.view.mouseHidingEnabled = false;
}
- (IBAction)showConsoleWindow:(id)sender
@ -1395,7 +1390,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
bitmapInfo,
provider,
NULL,
YES,
true,
renderingIntent);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpaceRef);
@ -1603,7 +1598,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
}
[self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]];
[hex_controller reloadData];
[self.memoryView setNeedsDisplay:YES];
[self.memoryView setNeedsDisplay:true];
}
- (GB_gameboy_t *) gameboy
@ -1613,7 +1608,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
+ (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName
{
return YES;
return true;
}
- (void)cameraRequestUpdate
@ -1754,14 +1749,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
window_rect.size.height = 512 + height_diff + 48;
break;
case 3:
window_rect.size.height = 20 * 16 + height_diff + 24;
window_rect.size.height = 20 * 16 + height_diff + 34;
break;
default:
break;
}
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
@ -1856,7 +1851,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
case 0:
return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image
length:64 * 4 * 2
freeWhenDone:NO]
freeWhenDone:false]
width:8
height: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
{
return NO;
return false;
}
- (IBAction)showVRAMViewer:(id)sender
@ -1929,7 +1924,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
frame.size = self.feedImageView.image.size;
[self.printerFeedWindow setContentMaxSize:frame.size];
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];
});
@ -1960,8 +1955,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
[imageRep setSize:(NSSize){160, self.feedImageView.image.size.height / 2}];
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
[data writeToURL:savePanel.URL atomically:NO];
[self.printerFeedWindow setIsVisible:NO];
[data writeToURL:savePanel.URL atomically:false];
[self.printerFeedWindow setIsVisible:false];
}
if (shouldResume) {
[self start];
@ -2082,9 +2077,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview;
{
if ([[splitView arrangedSubviews] lastObject] == subview) {
return YES;
return true;
}
return NO;
return false;
}
- (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
{
if ([[splitView arrangedSubviews] lastObject] == view) {
return NO;
return false;
}
return YES;
return true;
}
- (void)splitViewDidResizeSubviews:(NSNotification *)notification
@ -2206,4 +2201,115 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
{
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

View File

@ -1004,7 +1004,7 @@
</buttonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</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">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -1029,7 +1029,7 @@
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</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">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>

View File

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

View File

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

View File

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

View File

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

View File

@ -13,13 +13,13 @@ static inline double scale_channel(uint8_t x)
- (void)setObjectValue:(id)objectValue
{
_integerValue = [objectValue integerValue];
uint8_t r = _integerValue & 0x1F,
g = (_integerValue >> 5) & 0x1F,
b = (_integerValue >> 10) & 0x1F;
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
{
/* Todo: color correction */
uint16_t color = self.integerValue;
return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0];
}
- (BOOL)drawsBackground
{
return YES;
return true;
}
@end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ typedef enum {
@property (nonatomic, weak) IBOutlet Document *document;
@property (nonatomic) GB_gameboy_t *gb;
@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, strong) NSView *internalView;
@property (weak) GBOSDView *osdView;
@ -27,4 +27,5 @@ typedef enum {
- (uint32_t *)previousBuffer;
- (void)screenSizeChanged;
- (void)setRumble: (double)amp;
- (NSImage *)renderToImage;
@end

View File

@ -106,9 +106,9 @@ static const uint8_t workboy_vk_to_key[] = {
{
uint32_t *image_buffers[3];
unsigned char current_buffer;
BOOL mouse_hidden;
bool mouse_hidden;
NSTrackingArea *tracking_area;
BOOL _mouseHidingEnabled;
bool _mouseHidingEnabled;
bool axisActive[2];
bool underclockKeyDown;
double clockMultiplier;
@ -183,7 +183,7 @@ static const uint8_t workboy_vk_to_key[] = {
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
{
_frameBlendingMode = frameBlendingMode;
[self setNeedsDisplay:YES];
[self setNeedsDisplay:true];
}
@ -585,7 +585,7 @@ static const uint8_t workboy_vk_to_key[] = {
- (BOOL)acceptsFirstResponder
{
return YES;
return true;
}
- (void)mouseEntered:(NSEvent *)theEvent
@ -610,7 +610,7 @@ static const uint8_t workboy_vk_to_key[] = {
[super mouseExited:theEvent];
}
- (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled
- (void)setMouseHidingEnabled:(bool)mouseHidingEnabled
{
if (mouseHidingEnabled == _mouseHidingEnabled) return;
@ -625,7 +625,7 @@ static const uint8_t workboy_vk_to_key[] = {
}
}
- (BOOL)isMouseHidingEnabled
- (bool)isMouseHidingEnabled
{
return _mouseHidingEnabled;
}
@ -658,7 +658,7 @@ static const uint8_t workboy_vk_to_key[] = {
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
if (GB_is_stave_state(fileURL.fileSystemRepresentation)) {
if (GB_is_save_state(fileURL.fileSystemRepresentation)) {
return NSDragOperationGeneric;
}
}
@ -677,4 +677,10 @@ static const uint8_t workboy_vk_to_key[] = {
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

View File

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

View File

@ -1,3 +1,4 @@
#import <CoreImage/CoreImage.h>
#import "GBViewMetal.h"
#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())];
view.delegate = self;
self.internalView = view;
view.paused = YES;
view.enableSetNeedsDisplay = YES;
view.paused = true;
view.enableSetNeedsDisplay = true;
view.framebufferOnly = false;
vertices = [device newBufferWithBytes:rect
length:sizeof(rect)
@ -92,7 +94,7 @@ static const vector_float2 rect[] =
withString:scaler_source];
MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
options.fastMathEnabled = YES;
options.fastMathEnabled = true;
id<MTLLibrary> library = [device newLibraryWithSource:shader_source
options:options
error:&error];
@ -208,8 +210,23 @@ static const vector_float2 rect[] =
{
[super flip];
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

View File

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

View File

@ -316,6 +316,23 @@
</menu>
</menuItem>
<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">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>

View File

@ -24,10 +24,10 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="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"/>
<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"/>
</view>
<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="rtcPopupButton" destination="tFf-H1-XUL" id="zxb-4h-aqg"/>
<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="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
<outlet property="temperatureSlider" destination="NuA-mL-AJZ" id="w11-n7-Bmj"/>
@ -98,12 +99,12 @@
<point key="canvasLocation" x="183" y="354"/>
</window>
<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"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
<rect key="frame" x="18" y="358" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="384" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -111,8 +112,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
<rect key="frame" x="30" y="325" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="351" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -147,9 +148,20 @@
<action selector="graphicFilterChanged:" target="QvC-M9-y7g" id="n87-t4-fbV"/>
</connections>
</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">
<rect key="frame" x="18" y="303" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="307" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color correction:" id="5Si-hz-EK3">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -157,8 +169,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
<rect key="frame" x="30" y="270" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="274" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -179,9 +191,26 @@
<action selector="colorCorrectionChanged:" target="QvC-M9-y7g" id="Oq4-B5-nO6"/>
</connections>
</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">
<rect key="frame" x="18" y="194" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="194" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Frame blending" id="UCa-EO-tzh">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -189,8 +218,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
<rect key="frame" x="30" y="165" width="234" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="165" width="262" height="22"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -209,8 +238,8 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8fG-zm-hpr">
<rect key="frame" x="18" y="143" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="143" width="280" height="17"/>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -218,8 +247,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Iwr-eI-SD1">
<rect key="frame" x="30" y="114" width="234" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="114" width="262" height="22"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -239,8 +268,8 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Kz-cf-5X6">
<rect key="frame" x="18" y="92" width="248" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="92" width="276" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Display border:" id="HZd-qi-yyk">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -248,8 +277,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="R9D-FV-bpd">
<rect key="frame" x="30" y="60" width="234" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="60" width="262" height="25"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -267,26 +296,9 @@
<action selector="displayBorderChanged:" target="QvC-M9-y7g" id="GoA-BU-v3h"/>
</connections>
</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">
<rect key="frame" x="18" y="38" width="256" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="38" width="284" height="18"/>
<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">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -296,8 +308,8 @@
</connections>
</button>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="quK-gY-Z6h">
<rect key="frame" x="18" y="18" width="252" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="18" width="280" height="18"/>
<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">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -307,15 +319,15 @@
</connections>
</button>
</subviews>
<point key="canvasLocation" x="-176" y="677.5"/>
<point key="canvasLocation" x="-176" y="690.5"/>
</customView>
<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"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB">
<rect key="frame" x="18" y="283" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="283" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding duration:" id="JaO-5h-ugl">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -323,8 +335,8 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o3Z-34-FJk">
<rect key="frame" x="18" y="228" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="228" width="280" height="17"/>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -332,8 +344,8 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MI2-ql-f6M">
<rect key="frame" x="18" y="338" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="338" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Boot ROMs location:" id="nj0-Cb-gEA">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -341,8 +353,8 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wg8-hJ-df9">
<rect key="frame" x="18" y="160" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="160" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy revision:" id="GIA-ep-SBi">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -350,8 +362,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LFw-Uk-cPR">
<rect key="frame" x="30" y="127" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="127" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -369,8 +381,8 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MAq-1X-Gpo">
<rect key="frame" x="16" y="50" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="16" y="50" width="284" height="17"/>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -378,8 +390,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dlD-sk-SHO">
<rect key="frame" x="28" y="17" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="28" y="17" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -399,8 +411,8 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tAa-0A-0fP">
<rect key="frame" x="18" y="105" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="105" width="284" height="17"/>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -408,12 +420,12 @@
</textFieldCell>
</textField>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="mdm-eW-ia1">
<rect key="frame" x="12" y="183" width="268" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="12" y="183" width="296" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dza-T7-RkX">
<rect key="frame" x="28" y="72" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="28" y="72" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -430,8 +442,8 @@
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tFf-H1-XUL">
<rect key="frame" x="30" y="199" width="234" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="199" width="262" height="22"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -449,8 +461,8 @@
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wC4-aJ-mhQ">
<rect key="frame" x="30" y="305" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="305" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -473,8 +485,8 @@
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR">
<rect key="frame" x="30" y="250" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="250" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -500,12 +512,12 @@
<point key="canvasLocation" x="-501" y="667.5"/>
</customView>
<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"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Dzd-uB-B6l">
<rect key="frame" x="18" y="146" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="164" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Volume:" id="IbP-7k-xsZ">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -513,16 +525,16 @@
</textFieldCell>
</textField>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LNs-v1-Eki">
<rect key="frame" x="40" y="121" width="230" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="130" width="261" height="28"/>
<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"/>
<connections>
<action selector="volumeChanged:" target="QvC-M9-y7g" id="4g3-NK-ay4"/>
</connections>
</slider>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
<rect key="frame" x="30" y="65" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="74" width="262" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -541,8 +553,8 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
<rect key="frame" x="18" y="98" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="107" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass filter:" id="YLF-RL-b2D">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -550,8 +562,8 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GPt-9I-QBh">
<rect key="frame" x="18" y="43" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="52" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Interference volume:" id="I2Q-6U-uIx">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -559,18 +571,18 @@
</textFieldCell>
</textField>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FpE-5i-j5L">
<rect key="frame" x="30" y="18" width="234" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="18" width="261" height="28"/>
<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"/>
<connections>
<action selector="interferenceVolumeChanged:" target="QvC-M9-y7g" id="HFU-0q-hj1"/>
</connections>
</slider>
</subviews>
<point key="canvasLocation" x="-825" y="446.5"/>
<point key="canvasLocation" x="-825" y="455.5"/>
</customView>
<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"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
@ -583,8 +595,8 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DZu-ts-deW">
<rect key="frame" x="18" y="87" width="105" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="87" width="133" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Enable rumble:" id="QMX-3p-s1Z">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -592,14 +604,14 @@
</textFieldCell>
</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">
<rect key="frame" x="32" y="208" width="229" height="211"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="32" y="208" width="257" height="211"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<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"/>
<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">
<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"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -618,7 +630,7 @@
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</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">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -650,8 +662,8 @@
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fcF-wc-KwM">
<rect key="frame" x="30" y="183" width="203" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="183" width="231" height="17"/>
<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">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -659,8 +671,8 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Az-0R-oNw">
<rect key="frame" x="42" y="150" width="222" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="42" y="150" width="250" height="26"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -675,8 +687,8 @@
</connections>
</popUpButton>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="VEc-Ed-Z6f">
<rect key="frame" x="12" y="139" width="268" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="12" y="139" width="296" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ReM-uo-H0r">
<rect key="frame" x="227" y="431" width="8" height="17"/>
@ -707,8 +719,8 @@
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ogs-xG-b4b">
<rect key="frame" x="30" y="58" width="234" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="30" y="58" width="262" height="22"/>
<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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -725,8 +737,8 @@
</connections>
</popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RuW-Db-dzW">
<rect key="frame" x="18" y="110" width="264" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="110" width="292" height="25"/>
<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">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -735,20 +747,9 @@
<action selector="changeAnalogControls:" target="QvC-M9-y7g" id="1xR-gY-WKo"/>
</connections>
</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">
<rect key="frame" x="26" y="13" width="173" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="26" y="13" width="204" height="32"/>
<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">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@ -757,16 +758,27 @@
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
</connections>
</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>
<point key="canvasLocation" x="-159" y="1161.5"/>
</customView>
<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"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZVh-ob-6wl">
<rect key="frame" x="18" y="59" width="256" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="18" y="59" width="284" height="18"/>
<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">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -776,12 +788,12 @@
</connections>
</button>
<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"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="285" y="23" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</progressIndicator>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KnI-UA-Nlj">
<rect key="frame" x="14" y="13" width="240" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="14" y="13" width="268" height="32"/>
<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">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>

View File

@ -289,6 +289,19 @@ static void update_square_sample(GB_gameboy_t *gb, unsigned index)
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
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.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;
update_sample(gb, GB_WAVE, 0, 0);
}
@ -583,6 +606,12 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.apu_cycles = 0;
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;
if (likely(!gb->stopped || GB_is_cgb(gb))) {
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.current_sample_index++;
gb->apu.wave_channel.current_sample_index &= 0x1F;
gb->apu.wave_channel.current_sample =
gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index];
update_sample(gb, GB_WAVE,
gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift,
cycles - cycles_left);
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (gb->apu.wave_channel.current_sample_index >> 1)];
update_wave_sample(gb, cycles - cycles_left);
gb->apu.wave_channel.wave_form_just_read = true;
}
if (cycles_left) {
@ -677,6 +704,23 @@ void GB_apu_run(GB_gameboy_t *gb)
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
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)
{
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.wave_channel.shift = 4;
/* 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. */
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 (!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;
}
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)) {
gb->apu.square_channels[index].current_sample_index++;
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 */
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:
gb->apu.wave_channel.enable = value & 0x80;
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;
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:
gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3];
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;
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:
gb->apu.wave_channel.sample_length &= 0xFF;
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
reads from it. */
if (!GB_is_cgb(gb) &&
gb->apu.is_active[GB_WAVE] &&
gb->apu.wave_channel.sample_countdown == 0 &&
gb->apu.wave_channel.enable) {
gb->apu.wave_channel.sample_countdown == 0) {
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
/* 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. */
if (offset < 4) {
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 {
memcpy(gb->io_registers + GB_IO_WAV_START,
gb->io_registers + GB_IO_WAV_START + (offset & ~3),
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;
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);
}
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) {
gb->apu.wave_channel.pulse_length = 0x100;
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;
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;
@ -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;
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;
}

View File

@ -99,9 +99,9 @@ typedef struct
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
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;
} wave_channel;
@ -122,6 +122,7 @@ typedef struct
} 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_SKIPPED 1
#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 noise_envelope_clock;
bool channel_3_pulsed;
bool channel_3_delayed_bugged_read;
} GB_apu_t;
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_run(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 /* 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 (length == 1) {
switch (string[0]) {
case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]};
case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]};
case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]};
case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]};
case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]};
case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]};
case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]};
case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]};
case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->af};
case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->af};
case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->bc};
case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->bc};
case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->de};
case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->de};
case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->hl};
case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->hl};
}
}
else if (length == 2) {
switch (string[0]) {
case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]};
case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]};
case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]};
case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]};
case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]};
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->bc};
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->hl};
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};
}
}
@ -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 (length == 1) {
switch (string[0]) {
case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit;
case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit;
case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit;
case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit;
case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit;
case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit;
case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit;
case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit;
case 'a': ret = VALUE_16(gb->af >> 8); goto exit;
case 'f': ret = VALUE_16(gb->af & 0xFF); goto exit;
case 'b': ret = VALUE_16(gb->bc >> 8); goto exit;
case 'c': ret = VALUE_16(gb->bc & 0xFF); goto exit;
case 'd': ret = VALUE_16(gb->de >> 8); goto exit;
case 'e': ret = VALUE_16(gb->de & 0xFF); goto exit;
case 'h': ret = VALUE_16(gb->hl >> 8); goto exit;
case 'l': ret = VALUE_16(gb->hl & 0xFF); goto exit;
}
}
else if (length == 2) {
switch (string[0]) {
case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;}
case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;}
case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;}
case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;}
case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); 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->bc); 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->hl); 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;}
}
}
@ -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_HALF_CARRY_FLAG)? 'H' : '-',
(gb->f & GB_SUBTRACT_FLAG)? 'N' : '-',
(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, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false));
GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false));
GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], 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->de, 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->sp, false));
GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false));
GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled");
return true;
@ -1550,7 +1550,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
if (cartridge->has_ram) {
GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank);
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) {
@ -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, " Wave:");
for (uint8_t i = 0; i < 32; i++) {
GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]);
for (uint8_t i = 0; i < 16; 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, " 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 (uint8_t i = 0; i < 32; i++) {
if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) {
GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]);
uint8_t sample = i & 1?
(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 {
GB_log(gb, "%c", i%4 == 2 ? '-' : ' ');
GB_log(gb, "%c", i % 4 == 2 ? '-' : ' ');
}
}
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;
}
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;
}
}
@ -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])) {
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--;
}
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].addr = call_addr;
gb->backtrace_size++;
@ -2076,9 +2080,9 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
gb->debug_stopped = true;
}
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, "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->debug_stopped = true;
}
@ -2086,7 +2090,7 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
}
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--;
}
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 *command_string = input;
@ -2564,13 +2567,13 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode)
{
switch ((opcode >> 3) & 0x3) {
case 0:
return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG);
return !(gb->af & GB_ZERO_FLAG);
case 1:
return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG);
return (gb->af & GB_ZERO_FLAG);
case 2:
return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
return !(gb->af & GB_CARRY_FLAG);
case 3:
return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
return (gb->af & GB_CARRY_FLAG);
}
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)
{
return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) |
(GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8);
return GB_read_memory(gb, gb->sp) |
(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 (!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;
}

View File

@ -632,7 +632,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
GB_FETCHER_SLEEP,
} fetcher_step_t;
fetcher_step_t fetcher_state_machine [8] = {
static const fetcher_step_t fetcher_state_machine [8] = {
GB_FETCHER_SLEEP,
GB_FETCHER_GET_TILE,
GB_FETCHER_SLEEP,
@ -666,7 +666,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
x = gb->window_tile_x;
}
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) {
/* 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
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) {
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->lcd_x = 0;
gb->fetcher_x = 0;
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;
*sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8;
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;
uint8_t sprites_in_line = 0;
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++;
}
if (gb->rom_size == 0) {
if (gb->rom_size < 0x8000) {
gb->rom_size = 0x8000;
}
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++;
}
if (gb->rom_size == 0) {
if (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) {
free(gb->mbc_ram);
gb->mbc_ram = NULL;
gb->mbc_ram_size = 0;
}
if (gb->cartridge_type->has_ram) {
@ -1198,7 +1199,7 @@ uint8_t GB_run(GB_gameboy_t *gb)
{
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.
Then, after the boot animation is almost done, it's reset.
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_SGB2:
case GB_MODEL_SGB2_NO_SFC: {
uint8_t temp;
for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) {
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 {
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;
}
@ -1602,7 +1598,10 @@ void GB_reset(GB_gameboy_t *gb)
{
uint32_t mbc_ram_size = gb->mbc_ram_size;
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));
memcpy(GB_GET_SECTION(gb, rtc), rtc_section, sizeof(rtc_section));
gb->model = model;
gb->version = GB_STRUCT_VERSION;

View File

@ -24,6 +24,7 @@
#include "cheats.h"
#include "rumble.h"
#include "workboy.h"
#include "random.h"
#define GB_STRUCT_VERSION 13
@ -434,6 +435,7 @@ struct GB_gameboy_internal_s {
int32_t ir_sensor;
bool effective_ir_input;
uint16_t address_bus;
);
/* DMA and HDMA */
@ -553,7 +555,7 @@ struct GB_gameboy_internal_s {
/* Video Display */
GB_SECTION(video,
uint32_t vram_size; // Different between CGB and DMG
uint8_t cgb_vram_bank;
bool cgb_vram_bank;
uint8_t oam[0xA0];
uint8_t background_palettes_data[0x40];
uint8_t sprite_palettes_data[0x40];
@ -581,7 +583,7 @@ struct GB_gameboy_internal_s {
uint8_t current_line;
uint16_t ly_for_comparison;
GB_fifo_t bg_fifo, oam_fifo;
uint8_t fetcher_x;
GB_PADDING(uint8_t, fetcher_x);
uint8_t fetcher_y;
uint16_t cycles_for_line;
uint8_t current_tile;
@ -892,5 +894,9 @@ unsigned GB_get_player_count(GB_gameboy_t *gb);
// `title` must be at least 17 bytes in size
void GB_get_rom_title(GB_gameboy_t *gb, char *title);
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 */

View File

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

View File

@ -141,6 +141,7 @@ void GB_configure_cart(GB_gameboy_t *gb)
if (gb->mbc_ram) {
free(gb->mbc_ram);
gb->mbc_ram = NULL;
gb->mbc_ram_size = 0;
}
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;
}
}
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)
@ -464,7 +464,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
break;
default:
break;
__builtin_unreachable();
}
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;
}
/* Hardware registers */
return 0;
__builtin_unreachable();
}
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;
}
}
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)
@ -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;
return;
case GB_IO_SVBK:
if (!gb->cgb_mode) {
return;
}
gb->cgb_ram_bank = value & 0x7;
if (!gb->cgb_ram_bank) {
gb->cgb_ram_bank++;
if (gb->cgb_mode || (GB_is_cgb(gb) && !gb->boot_rom_finished)) {
gb->cgb_ram_bank = value & 0x7;
if (!gb->cgb_ram_bank) {
gb->cgb_ram_bank++;
}
}
return;
case GB_IO_VBK:
@ -1396,7 +1394,7 @@ static GB_write_function_t * const write_map[] =
write_vram, write_vram, /* 8XXX, 9XXX */
write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */
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)

View File

@ -9,6 +9,15 @@
#define BESS_NAME "SameBoy v" GB_VERSION
#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)) {
uint32_t magic;
uint32_t size;
@ -298,6 +307,11 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t
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) {
GB_log(gb, "The save state has non-matching MBC RAM size.\n");
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)
@ -338,8 +369,36 @@ static void sanitize_state(GB_gameboy_t *gb)
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_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->fetcher_x &= 0x1f;
if (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) {
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) {
#ifdef GB_BIG_ENDIAN
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)),
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)) {
goto error;
}
@ -904,7 +969,9 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
save.halted = core.execution_mode == 1;
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
// 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_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_SVBK, core.io_registers[GB_IO_SVBK]);
// Interrupts
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 '):
if (!found_core) 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) {
BESS_MBC_pair_t 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_free(&save);
sanitize_state(gb);
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;
FILE *f = fopen(path, "rb");

View File

@ -10,7 +10,7 @@
as anonymous enums inside unions */
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__
#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_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))
@ -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_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
static inline uint32_t state_magic(void)
{

View File

@ -375,39 +375,36 @@ static void command_ready(GB_gameboy_t *gb)
}
break;
case PAL_TRN:
gb->sgb->vram_transfer_countdown = 2;
gb->sgb->vram_transfer_countdown = 3;
gb->sgb->transfer_dest = TRANSFER_PALETTES;
break;
case DATA_SND:
// Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this
break;
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,
fix this to be 0 based. */
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;
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;
break;
case PCT_TRN:
gb->sgb->vram_transfer_countdown = 2;
gb->sgb->vram_transfer_countdown = 3;
gb->sgb->transfer_dest = TRANSFER_BORDER_DATA;
break;
case ATTR_TRN:
gb->sgb->vram_transfer_countdown = 2;
gb->sgb->vram_transfer_countdown = 3;
gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES;
break;
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;
}
break;
@ -437,27 +434,22 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
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;
if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
command_size = SGB_PACKET_SIZE * 8;
}
if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) {
gb->sgb->mlt_lock ^= true;
if ((value & 0x20) != 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) == 0) {
if ((gb->sgb->player_count & 1) == 0) {
gb->sgb->current_player++;
gb->sgb->current_player &= (gb->sgb->player_count - 1);
}
}
switch ((value >> 4) & 3) {
case 3:
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;
case 2: // Zero
@ -473,10 +465,12 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
gb->sgb->ready_for_stop = false;
}
else {
gb->sgb->command_write_index++;
gb->sgb->ready_for_pulse = false;
if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb->ready_for_stop = true;
if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) {
gb->sgb->command_write_index++;
gb->sgb->ready_for_pulse = false;
if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb->ready_for_stop = true;
}
}
}
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));
}
else {
gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7);
gb->sgb->command_write_index++;
gb->sgb->ready_for_pulse = false;
if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb->ready_for_stop = true;
if (gb->sgb->command_write_index < sizeof(gb->sgb->command) * 8) {
gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7);
gb->sgb->command_write_index++;
gb->sgb->ready_for_pulse = false;
if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb->ready_for_stop = true;
}
}
}
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);
}
#include <stdio.h>
static void render_boot_animation (GB_gameboy_t *gb)
{
#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) {
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];
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++) {
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;
}
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_y = (tile & 0x8000)? 7:0;
uint8_t palette = (tile >> 10) & 3;

View File

@ -61,7 +61,7 @@ struct GB_sgb_s {
uint8_t received_header[0x54];
/* 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!
};

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 */
if (gb->div_counter & old_clocks) {
/* 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);
}
}

View File

@ -98,7 +98,7 @@ static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportT
uint32_t reportID, uint8_t *report, CFIndex 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))
SDL_CFLAGS := $(shell sdl2-config --cflags)
SDL_LDFLAGS := $(shell sdl2-config --libs)
SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread
else
SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2)
SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2)
SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) -lpthread
endif
ifeq (,$(PKG_CONFIG))
GL_LDFLAGS := -lGL

View File

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

View File

@ -43,10 +43,10 @@ static OSStatus render(CGContextRef cgContext, CFURLRef url, bool showBorder)
bitmapInfo,
provider,
NULL,
YES,
true,
renderingIntent);
CGContextSetInterpolationQuality(cgContext, kCGInterpolationNone);
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:NO];
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:false];
[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;
if (!converted_background) {
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);
converted_background = SDL_ConvertSurface(background, pixel_format, 0);
SDL_LockSurface(converted_background);
@ -1338,7 +1344,7 @@ void run_gui(bool is_running)
break;
}
case SDL_DROPFILE: {
if (GB_is_stave_state(event.drop.file)) {
if (GB_is_save_state(event.drop.file)) {
if (GB_is_inited(&gb)) {
dropped_state_file = event.drop.file;
pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND;

View File

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

View File

@ -10,6 +10,7 @@
#include "gui.h"
#include "shader.h"
#include "audio/audio.h"
#include "console.h"
#ifndef _WIN32
#include <unistd.h>
@ -29,6 +30,7 @@ static char *filename = NULL;
static typeof(free) *free_function = NULL;
static char *battery_save_path_ptr = NULL;
static SDL_GLContext gl_context = NULL;
static bool console_supported = false;
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;
}
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 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)
{
GB_set_log_callback(&gb, NULL);
GB_set_log_callback(&gb, console_supported? log_callback : NULL);
if (captured_log[0] == 0) {
free(captured_log);
captured_log = NULL;
@ -149,7 +225,7 @@ static void handle_events(GB_gameboy_t *gb)
break;
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;
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:
if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) {
CON_print("^C\a\n");
GB_debugger_break(gb);
}
break;
@ -408,12 +485,15 @@ static void rumble(GB_gameboy_t *gb, double amp)
static void debugger_interrupt(int ignore)
{
if (!GB_is_inited(&gb)) return;
if (!GB_is_inited(&gb)) exit(0);
/* ^C twice to exit */
if (GB_debugger_is_stopped(&gb)) {
GB_save_battery(&gb, battery_save_path_ptr);
exit(0);
}
if (console_supported) {
CON_print("^C\n");
}
GB_debugger_break(&gb);
}
@ -442,7 +522,6 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
GB_audio_queue_sample(sample);
}
static bool handle_pending_command(void)
{
@ -577,6 +656,13 @@ restart:
GB_set_rtc_mode(&gb, configuration.rtc_mode);
GB_set_update_input_hint_callback(&gb, handle_events);
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) {
stop_on_start = false;
@ -682,18 +768,29 @@ static bool get_arg_flag(const char *flag, int *argc, char **argv)
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)
{
#ifdef _WIN32
SetProcessDPIAware();
#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 nogl = get_arg_flag("--nogl", &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] == '-')) {
fprintf(stderr, "SameBoy v" GB_VERSION "\n");
fprintf(stderr, "Usage: %s [--fullscreen|-f] [--nogl] [--stop-debugger|-s] [rom]\n", argv[0]);
exit(1);
}
@ -705,6 +802,13 @@ int main(int argc, char **argv)
signal(SIGINT, debugger_interrupt);
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"));
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 bool use_tga = false;
static const uint8_t bmp_header[] = {
static uint8_t bmp_header[] = {
0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00,
0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF,
@ -45,13 +45,13 @@ static const uint8_t bmp_header[] = {
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, 0x00, 0x00, 0xA0, 0x00, 0x90, 0x00,
0x20, 0x28,
};
uint32_t bitmap[160*144];
uint32_t bitmap[256*224];
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) {
bool is_screen_blank = true;
for (unsigned i = 160*144; i--;) {
if (bitmap[i] != bitmap[0]) {
is_screen_blank = false;
break;
if (!gb->sgb) {
for (unsigned i = 160 * 144; i--;) {
if (bitmap[i] != bitmap[0]) {
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) {
FILE *f = fopen(bmp_filename, "wb");
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);
}
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(&bitmap, 1, sizeof(bitmap), f);
fwrite(&bitmap, 1, sizeof(bitmap[0]) * GB_get_screen_width(gb) * GB_get_screen_height(gb), f);
fclose(f);
if (!gb->boot_rom_finished) {
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");
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
" [--jobs number of tests to run simultaneously]"
#endif
@ -285,6 +305,7 @@ int main(int argc, char **argv)
#endif
bool dmg = false;
bool sgb = false;
bool sav = false;
const char *boot_rom_path = NULL;
@ -294,6 +315,21 @@ int main(int argc, char **argv)
if (strcmp(argv[i], "--dmg") == 0) {
fprintf(stderr, "Using DMG mode\n");
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;
}
@ -374,6 +410,13 @@ int main(int argc, char **argv)
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 {
GB_init(&gb, GB_MODEL_CGB_E);
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 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")
.setLogo(Resource::SameBoy)
.setDescription("Super Game Boy emulator")
.setVersion("0.14.4")
.setVersion("0.14.7")
.setCopyright("Lior Halphon")
.setLicense("MIT")
.setWebsite("https://sameboy.github.io")