Merge remote-tracking branch 'origin/master' into wasm

This commit is contained in:
Maximilian Mader 2020-09-21 16:12:49 +02:00
commit 405d85343f
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
50 changed files with 1307 additions and 385 deletions

View File

@ -329,101 +329,103 @@ FirstChecksumWithDuplicate:
ChecksumsEnd:
PalettePerChecksum:
; | $80 means game requires DMG boot tilemap
db 0 ; Default Palette
db 4 ; ALLEY WAY
db 5 ; YAKUMAN
db 35 ; BASEBALL, (Game and Watch 2)
db 34 ; TENNIS
db 3 ; TETRIS
db 31 ; QIX
db 15 ; DR.MARIO
db 10 ; RADARMISSION
db 5 ; F1RACE
db 19 ; YOSSY NO TAMAGO
db 36 ;
db 7 | $80 ; X
db 37 ; MARIOLAND2
db 30 ; YOSSY NO COOKIE
db 44 ; ZELDA
db 21 ;
db 32 ;
db 31 ; TETRIS FLASH
db 20 ; DONKEY KONG
db 5 ; MARIO'S PICROSS
db 33 ;
db 13 ; POKEMON RED, (GAMEBOYCAMERA G)
db 14 ; POKEMON GREEN
db 5 ; PICROSS 2
db 29 ; YOSSY NO PANEPON
db 5 ; KIRAKIRA KIDS
db 18 ; GAMEBOY GALLERY
db 9 ; POCKETCAMERA
db 3 ;
db 2 ; BALLOON KID
db 26 ; KINGOFTHEZOO
db 25 ; DMG FOOTBALL
db 25 ; WORLD CUP
db 41 ; OTHELLO
db 42 ; SUPER RC PRO-AM
db 26 ; DYNABLASTER
db 45 ; BOY AND BLOB GB2
db 42 ; MEGAMAN
db 45 ; STAR WARS-NOA
db 36 ;
db 38 ; WAVERACE
db 26 ;
db 42 ; LOLO2
db 30 ; YOSHI'S COOKIE
db 41 ; MYSTIC QUEST
db 34 ;
db 34 ; TOPRANKINGTENNIS
db 5 ; MANSELL
db 42 ; MEGAMAN3
db 6 ; SPACE INVADERS
db 5 ; GAME&WATCH
db 33 ; DONKEYKONGLAND95
db 25 ; ASTEROIDS/MISCMD
db 42 ; STREET FIGHTER 2
db 42 ; DEFENDER/JOUST
db 40 ; KILLERINSTINCT95
db 2 ; TETRIS BLAST
db 16 ; PINOCCHIO
db 25 ;
db 42 ; BA.TOSHINDEN
db 42 ; NETTOU KOF 95
db 5 ;
db 0 ; TETRIS PLUS
db 39 ; DONKEYKONGLAND 3
db 36 ;
db 22 ; SUPER MARIOLAND
db 25 ; GOLF
db 6 ; SOLARSTRIKER
db 32 ; GBWARS
db 12 ; KAERUNOTAMENI
db 36 ;
db 11 ; POKEMON BLUE
db 39 ; DONKEYKONGLAND
db 18 ; GAMEBOY GALLERY2
db 39 ; DONKEYKONGLAND 2
db 24 ; KID ICARUS
db 31 ; TETRIS2
db 50 ;
db 17 ; MOGURANYA
db 46 ;
db 6 ; GALAGA&GALAXIAN
db 27 ; BT2RAGNAROKWORLD
db 0 ; KEN GRIFFEY JR
db 47 ;
db 41 ; MAGNETIC SOCCER
db 41 ; VEGAS STAKES
db 0 ;
db 0 ; MILLI/CENTI/PEDE
db 19 ; MARIO & YOSHI
db 34 ; SOCCER
db 23 ; POKEBOM
db 18 ; G&W GALLERY
db 29 ; TETRIS ATTACK
palette_index: MACRO ; palette, flags
db ((\1) * 3) | (\2) ; | $80 means game requires DMG boot tilemap
ENDM
palette_index 0, 0 ; Default Palette
palette_index 4, 0 ; ALLEY WAY
palette_index 5, 0 ; YAKUMAN
palette_index 35, 0 ; BASEBALL, (Game and Watch 2)
palette_index 34, 0 ; TENNIS
palette_index 3, 0 ; TETRIS
palette_index 31, 0 ; QIX
palette_index 15, 0 ; DR.MARIO
palette_index 10, 0 ; RADARMISSION
palette_index 5, 0 ; F1RACE
palette_index 19, 0 ; YOSSY NO TAMAGO
palette_index 36, 0 ;
palette_index 7, $80 ; X
palette_index 37, 0 ; MARIOLAND2
palette_index 30, 0 ; YOSSY NO COOKIE
palette_index 44, 0 ; ZELDA
palette_index 21, 0 ;
palette_index 32, 0 ;
palette_index 31, 0 ; TETRIS FLASH
palette_index 20, 0 ; DONKEY KONG
palette_index 5, 0 ; MARIO'S PICROSS
palette_index 33, 0 ;
palette_index 13, 0 ; POKEMON RED, (GAMEBOYCAMERA G)
palette_index 14, 0 ; POKEMON GREEN
palette_index 5, 0 ; PICROSS 2
palette_index 29, 0 ; YOSSY NO PANEPON
palette_index 5, 0 ; KIRAKIRA KIDS
palette_index 18, 0 ; GAMEBOY GALLERY
palette_index 9, 0 ; POCKETCAMERA
palette_index 3, 0 ;
palette_index 2, 0 ; BALLOON KID
palette_index 26, 0 ; KINGOFTHEZOO
palette_index 25, 0 ; DMG FOOTBALL
palette_index 25, 0 ; WORLD CUP
palette_index 41, 0 ; OTHELLO
palette_index 42, 0 ; SUPER RC PRO-AM
palette_index 26, 0 ; DYNABLASTER
palette_index 45, 0 ; BOY AND BLOB GB2
palette_index 42, 0 ; MEGAMAN
palette_index 45, 0 ; STAR WARS-NOA
palette_index 36, 0 ;
palette_index 38, 0 ; WAVERACE
palette_index 26, 0 ;
palette_index 42, 0 ; LOLO2
palette_index 30, 0 ; YOSHI'S COOKIE
palette_index 41, 0 ; MYSTIC QUEST
palette_index 34, 0 ;
palette_index 34, 0 ; TOPRANKINGTENNIS
palette_index 5, 0 ; MANSELL
palette_index 42, 0 ; MEGAMAN3
palette_index 6, 0 ; SPACE INVADERS
palette_index 5, 0 ; GAME&WATCH
palette_index 33, 0 ; DONKEYKONGLAND95
palette_index 25, 0 ; ASTEROIDS/MISCMD
palette_index 42, 0 ; STREET FIGHTER 2
palette_index 42, 0 ; DEFENDER/JOUST
palette_index 40, 0 ; KILLERINSTINCT95
palette_index 2, 0 ; TETRIS BLAST
palette_index 16, 0 ; PINOCCHIO
palette_index 25, 0 ;
palette_index 42, 0 ; BA.TOSHINDEN
palette_index 42, 0 ; NETTOU KOF 95
palette_index 5, 0 ;
palette_index 0, 0 ; TETRIS PLUS
palette_index 39, 0 ; DONKEYKONGLAND 3
palette_index 36, 0 ;
palette_index 22, 0 ; SUPER MARIOLAND
palette_index 25, 0 ; GOLF
palette_index 6, 0 ; SOLARSTRIKER
palette_index 32, 0 ; GBWARS
palette_index 12, 0 ; KAERUNOTAMENI
palette_index 36, 0 ;
palette_index 11, 0 ; POKEMON BLUE
palette_index 39, 0 ; DONKEYKONGLAND
palette_index 18, 0 ; GAMEBOY GALLERY2
palette_index 39, 0 ; DONKEYKONGLAND 2
palette_index 24, 0 ; KID ICARUS
palette_index 31, 0 ; TETRIS2
palette_index 50, 0 ;
palette_index 17, 0 ; MOGURANYA
palette_index 46, 0 ;
palette_index 6, 0 ; GALAGA&GALAXIAN
palette_index 27, 0 ; BT2RAGNAROKWORLD
palette_index 0, 0 ; KEN GRIFFEY JR
palette_index 47, 0 ;
palette_index 41, 0 ; MAGNETIC SOCCER
palette_index 41, 0 ; VEGAS STAKES
palette_index 0, 0 ;
palette_index 0, 0 ; MILLI/CENTI/PEDE
palette_index 19, 0 ; MARIO & YOSHI
palette_index 34, 0 ; SOCCER
palette_index 23, 0 ; POKEBOM
palette_index 18, 0 ; G&W GALLERY
palette_index 29, 0 ; TETRIS ATTACK
Dups4thLetterArray:
db "BEFAARBEKEK R-URAR INAILICE R"

View File

@ -4,6 +4,7 @@
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <err.h>
void opts(uint8_t byte, uint8_t *options)
{
@ -13,6 +14,17 @@ void opts(uint8_t byte, uint8_t *options)
*(options++) = byte & (byte >> 1);
}
void write_all(int fd, const void *buf, size_t count) {
while (count) {
ssize_t written = write(fd, buf, count);
if (written < 0) {
err(1, "write");
}
count -= written;
buf += written;
}
}
int main()
{
static uint8_t source[0x4000];
@ -76,15 +88,15 @@ int main()
if (bits >= 8) {
uint8_t outctl = control >> (bits - 8);
assert(outctl != 1);
write(STDOUT_FILENO, &outctl, 1);
write(STDOUT_FILENO, literals, literals_size);
write_all(STDOUT_FILENO, &outctl, 1);
write_all(STDOUT_FILENO, literals, literals_size);
bits -= 8;
control &= (1 << bits) - 1;
literals_size = 0;
}
}
uint8_t end_byte = 1;
write(STDOUT_FILENO, &end_byte, 1);
write_all(STDOUT_FILENO, &end_byte, 1);
return 0;
}

View File

@ -51,7 +51,9 @@
JOYHatsEmulateButtonsKey: @YES,
}];
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
}
}
- (IBAction)toggleDeveloperMode:(id)sender

30
Cocoa/BigSurToolbar.h Normal file
View File

@ -0,0 +1,30 @@
#import <Cocoa/Cocoa.h>
#ifndef BigSurToolbar_h
#define BigSurToolbar_h
/* Backport the toolbarStyle property to allow compilation with older SDKs*/
#ifndef __MAC_10_16
typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) {
// The default value. The style will be determined by the window's given configuration
NSWindowToolbarStyleAutomatic,
// The toolbar will appear below the window title
NSWindowToolbarStyleExpanded,
// The toolbar will appear below the window title and the items in the toolbar will attempt to have equal widths when possible
NSWindowToolbarStylePreference,
// The window title will appear inline with the toolbar when visible
NSWindowToolbarStyleUnified,
// Same as NSWindowToolbarStyleUnified, but with reduced margins in the toolbar allowing more focus to be on the contents of the window
NSWindowToolbarStyleUnifiedCompact
} API_AVAILABLE(macos(11.0));
@interface NSWindow (toolbarStyle)
@property NSWindowToolbarStyle toolbarStyle API_AVAILABLE(macos(11.0));
@end
@interface NSImage (SFSymbols)
+ (instancetype)imageWithSystemSymbolName:(NSString *)symbolName accessibilityDescription:(NSString *)description API_AVAILABLE(macos(11.0));
@end
#endif
#endif

View File

@ -30,7 +30,6 @@
@property (strong) IBOutlet NSTableView *spritesTableView;
@property (strong) IBOutlet NSPanel *printerFeedWindow;
@property (strong) IBOutlet NSImageView *feedImageView;
@property (strong) IBOutlet NSButton *feedSaveButton;
@property (strong) IBOutlet NSTextView *debuggerSideViewInput;
@property (strong) IBOutlet NSTextView *debuggerSideView;
@property (strong) IBOutlet GBSplitView *debuggerSplitView;

View File

@ -8,6 +8,8 @@
#include "GBMemoryByteArray.h"
#include "GBWarningPopover.h"
#include "GBCheatWindowController.h"
#include "GBTerminalTextFieldCell.h"
#include "BigSurToolbar.h"
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
/* Todo: Split into category files! This is so messy!!! */
@ -45,7 +47,7 @@ enum model {
bool oamUpdating;
NSMutableData *currentPrinterImageData;
enum {GBAccessoryNone, GBAccessoryPrinter} accessory;
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy} accessory;
bool rom_warning_issued;
@ -136,6 +138,16 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
[self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure];
}
static void setWorkboyTime(GB_gameboy_t *gb, time_t t)
{
[[NSUserDefaults standardUserDefaults] setInteger:time(NULL) - t forKey:@"GBWorkboyTimeOffset"];
}
static time_t getWorkboyTime(GB_gameboy_t *gb)
{
return time(NULL) - [[NSUserDefaults standardUserDefaults] integerForKey:@"GBWorkboyTimeOffset"];
}
static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
@ -403,6 +415,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
unsigned time_to_alarm = GB_time_to_alarm(&gb);
if (time_to_alarm) {
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = (id)[NSApp delegate];
NSUserNotification *notification = [[NSUserNotification alloc] init];
NSString *friendlyName = [[self.fileName lastPathComponent] stringByDeletingPathExtension];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil];
@ -546,6 +559,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
self.debuggerSideViewInput.textColor = [NSColor whiteColor];
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateSideView)
name:NSTextDidChangeNotification
@ -563,16 +577,21 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
[self.feedSaveButton removeFromSuperview];
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [[self.fileURL path] lastPathComponent]];
self.debuggerSplitView.dividerColor = [NSColor clearColor];
/* contentView.superview.subviews.lastObject is the titlebar view */
NSView *titleView = self.printerFeedWindow.contentView.superview.subviews.lastObject;
[titleView addSubview: self.feedSaveButton];
self.feedSaveButton.frame = (NSRect){{268, 2}, {48, 17}};
if (@available(macOS 11.0, *)) {
self.memoryWindow.toolbarStyle = NSWindowToolbarStyleExpanded;
self.printerFeedWindow.toolbarStyle = NSWindowToolbarStyleUnifiedCompact;
[self.printerFeedWindow.toolbar removeItemAtIndex:1];
self.printerFeedWindow.toolbar.items.firstObject.image =
[NSImage imageWithSystemSymbolName:@"square.and.arrow.down"
accessibilityDescription:@"Save"];
self.printerFeedWindow.toolbar.items.lastObject.image =
[NSImage imageWithSystemSymbolName:@"printer"
accessibilityDescription:@"Print"];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateHighpassFilter)
name:@"GBHighpassFilterChanged"
@ -645,7 +664,6 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{
hex_controller = [[HFController alloc] init];
[hex_controller setBytesPerColumn:1];
[hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]];
[hex_controller setEditMode:HFOverwriteMode];
[hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]];
@ -783,6 +801,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
else if ([anItem action] == @selector(connectPrinter:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter];
}
else if ([anItem action] == @selector(connectWorkboy:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy];
}
else if ([anItem action] == @selector(toggleCheats:)) {
[(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)];
}
@ -1008,6 +1029,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[debugger_input_queue removeObjectAtIndex:0];
}
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
if ((id)input == [NSNull null]) {
return NULL;
}
return input? strdup([input UTF8String]): NULL;
}
@ -1632,13 +1656,24 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
scale:2.0];
NSRect frame = self.printerFeedWindow.frame;
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 setMaxSize:frame.size];
[self.printerFeedWindow setFrame:frame display:NO animate: self.printerFeedWindow.isVisible];
[self.printerFeedWindow orderFront:NULL];
});
}
- (void)printDocument:(id)sender
{
if (self.feedImageView.image.size.height == 0) {
NSBeep(); return;
}
NSImageView *view = [[NSImageView alloc] initWithFrame:(NSRect){{0,0}, self.feedImageView.image.size}];
view.image = self.feedImageView.image;
[[NSPrintOperation printOperationWithView:view] runOperationModalForWindow:self.printerFeedWindow delegate:nil didRunSelector:NULL contextInfo:NULL];
}
- (IBAction)savePrinterFeed:(id)sender
{
bool shouldResume = running;
@ -1674,11 +1709,19 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)connectPrinter:(id)sender
{
[self performAtomicBlock:^{
accessory = GBAccessoryPrinter;
accessory = GBAccessoryPrinter;
GB_connect_printer(&gb, printImage);
}];
}
- (IBAction)connectWorkboy:(id)sender
{
[self performAtomicBlock:^{
accessory = GBAccessoryWorkboy;
GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime);
}];
}
- (void) updateHighpassFilter
{
if (GB_is_inited(&gb)) {

View File

@ -19,7 +19,6 @@
<outlet property="debuggerSplitView" destination="pUc-ZN-vl5" id="0sG-0D-cID"/>
<outlet property="debuggerVerticalLine" destination="7bR-gM-1At" id="rfy-7Z-388"/>
<outlet property="feedImageView" destination="Ar0-nN-eop" id="wHa-St-o4G"/>
<outlet property="feedSaveButton" destination="RLc-0I-sYZ" id="Yy9-dG-xXY"/>
<outlet property="gridButton" destination="fL6-2S-Rgd" id="jtV-jh-GHC"/>
<outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/>
<outlet property="memoryBankInput" destination="rdV-q6-hc6" id="KBx-9T-2mX"/>
@ -116,7 +115,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
@ -153,7 +152,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC">
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView ambiguous="YES" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" spellingCorrection="YES" id="w0g-eK-jM4">
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
@ -187,7 +186,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg">
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp">
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
@ -244,9 +243,9 @@
<toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H">
<nil key="toolTip"/>
<size key="minSize" width="100" height="25"/>
<size key="maxSize" width="100" height="25"/>
<size key="maxSize" width="130" height="25"/>
<popUpButton key="view" verticalHuggingPriority="750" id="vfJ-vu-gqJ">
<rect key="frame" x="0.0" y="14" width="100" height="25"/>
<rect key="frame" x="0.0" y="14" width="128" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bpD-j9-omo">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -507,7 +506,7 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView appearanceType="vibrantLight" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" headerView="of1-KC-dXC" id="TOc-XJ-w9w">
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" headerView="of1-KC-dXC" id="TOc-XJ-w9w">
<rect key="frame" x="0.0" y="0.0" width="512" height="391"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
@ -786,9 +785,10 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="320" height="288"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<value key="minSize" type="size" width="320" height="16"/>
<view key="contentView" id="RRS-aa-bPT">
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ar0-nN-eop" customClass="GBImageView">
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
@ -797,20 +797,25 @@
</imageView>
</subviews>
</view>
<toolbar key="toolbar" implicitIdentifier="1FF86A2B-6637-4EE6-A25A-7298D79AE84E" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="gH3-SH-7il">
<allowedToolbarItems>
<toolbarItem implicitItemIdentifier="15EB8D49-8C6E-42F2-9F7F-F7D7A0BBDAAF" label="Save" paletteLabel="Save" tag="-1" image="NSFolder" id="CBz-1N-o0Q">
<connections>
<action selector="savePrinterFeed:" target="-2" id="Dm3-h0-ch4"/>
</connections>
</toolbarItem>
<toolbarItem implicitItemIdentifier="NSToolbarPrintItem" explicitItemIdentifier="Print" id="mtd-zS-DXa"/>
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="AoG-LH-J4b"/>
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="Q0x-n5-Q2Y"/>
</allowedToolbarItems>
<defaultToolbarItems>
<toolbarItem reference="CBz-1N-o0Q"/>
<toolbarItem reference="Q0x-n5-Q2Y"/>
<toolbarItem reference="mtd-zS-DXa"/>
</defaultToolbarItems>
</toolbar>
<point key="canvasLocation" x="-159" y="356"/>
</window>
<button verticalHuggingPriority="750" id="RLc-0I-sYZ">
<rect key="frame" x="0.5" y="0.0" width="48" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" title="Save" bezelStyle="texturedRounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="shw-MJ-B3T">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="miniSystem"/>
</buttonCell>
<connections>
<action selector="savePrinterFeed:" target="-2" id="Y3g-fU-2te"/>
</connections>
<point key="canvasLocation" x="-507" y="397"/>
</button>
<window title="Cheats" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="4Yb-Np-JrF" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="692" height="272"/>
@ -896,7 +901,7 @@
</connections>
</textField>
<button verticalHuggingPriority="750" id="C3V-Ep-bMj">
<rect key="frame" x="202.5" y="12" width="82" height="23"/>
<rect key="frame" x="202.5" y="12" width="83" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" title="Import" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="mMP-KW-YNy">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -970,7 +975,7 @@
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/>
<clipView key="contentView" id="mzf-yu-RID">
<rect key="frame" x="1" y="0.0" width="398" height="274"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" headerView="pvX-uJ-qK5" id="tA3-8T-bxb">
<rect key="frame" x="0.0" y="0.0" width="398" height="249"/>
@ -1069,6 +1074,7 @@
</customObject>
</objects>
<resources>
<image name="NSFolder" width="32" height="32"/>
<image name="NSStopProgressFreestandingTemplate" width="14" height="14"/>
</resources>
</document>

View File

@ -13,8 +13,14 @@ static inline double scale_channel(uint8_t x)
- (void)setObjectValue:(id)objectValue
{
_integerValue = [objectValue integerValue];
super.objectValue = [NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)];
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]
}];
}
- (NSInteger)integerValue

View File

@ -1,6 +1,7 @@
#import "GBPreferencesWindow.h"
#import "NSString+StringForKey.h"
#import "GBButtons.h"
#import "BigSurToolbar.h"
#import <Carbon/Carbon.h>
@implementation GBPreferencesWindow
@ -52,6 +53,11 @@
return filters;
}
- (NSWindowToolbarStyle)toolbarStyle
{
return NSWindowToolbarStylePreference;
}
- (void)close
{
joystick_configuration_state = -1;
@ -164,6 +170,20 @@
return GBGameBoyButtonCount;
}
- (unsigned) usesForKey:(unsigned) key
{
unsigned ret = 0;
for (unsigned player = 4; player--;) {
for (unsigned button = player == 0? GBButtonCount:GBGameBoyButtonCount; button--;) {
NSNumber *other = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(button, player)];
if (other && [other unsignedIntValue] == key) {
ret++;
}
}
}
return ret;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if ([tableColumn.identifier isEqualToString:@"keyName"]) {
@ -176,6 +196,12 @@
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
if (key) {
if ([self usesForKey:[key unsignedIntValue]] > 1) {
return [[NSAttributedString alloc] initWithString:[NSString displayStringForKeyCode: [key unsignedIntegerValue]]
attributes:@{NSForegroundColorAttributeName: [NSColor colorWithRed:0.9375 green:0.25 blue:0.25 alpha:1.0],
NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]
}];
}
return [NSString displayStringForKeyCode: [key unsignedIntegerValue]];
}

View File

@ -1,5 +1,6 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
@interface GBTerminalTextFieldCell : NSTextFieldCell
@property GB_gameboy_t *gb;
@end

View File

@ -2,6 +2,7 @@
#import "GBTerminalTextFieldCell.h"
@interface GBTerminalTextView : NSTextView
@property GB_gameboy_t *gb;
@end
@implementation GBTerminalTextFieldCell
@ -12,10 +13,12 @@
- (NSTextView *)fieldEditorForView:(NSView *)controlView
{
if (field_editor) {
field_editor.gb = self.gb;
return field_editor;
}
field_editor = [[GBTerminalTextView alloc] init];
[field_editor setFieldEditor:YES];
field_editor.gb = self.gb;
return field_editor;
}
@ -26,6 +29,8 @@
NSMutableOrderedSet *lines;
NSUInteger current_line;
bool reverse_search_mode;
NSRange auto_complete_range;
uintptr_t auto_complete_context;
}
- (instancetype)init
@ -170,6 +175,7 @@
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
{
reverse_search_mode = false;
auto_complete_context = 0;
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
}
@ -188,6 +194,38 @@
[attributes setObject:color forKey:NSForegroundColorAttributeName];
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
}
}
/* Todo: lazy design, make it use a delegate instead of having a gb reference*/
- (void)insertTab:(id)sender
{
if (auto_complete_context == 0) {
NSRange selection = self.selectedRange;
if (selection.length) {
[self delete:nil];
}
auto_complete_range = NSMakeRange(selection.location, 0);
}
char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String);
uintptr_t context = auto_complete_context;
char *completion = GB_debugger_complete_substring(self.gb, substring, &context);
free(substring);
if (completion) {
NSString *ns_completion = @(completion);
free(completion);
if (!ns_completion) {
goto error;
}
self.selectedRange = auto_complete_range;
auto_complete_range.length = ns_completion.length;
[self replaceCharactersInRange:self.selectedRange withString:ns_completion];
auto_complete_context = context;
return;
}
error:
auto_complete_context = context;
NSBeep();
}
@end

View File

@ -1,4 +1,5 @@
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <Carbon/Carbon.h>
#import "GBView.h"
#import "GBViewGL.h"
#import "GBViewMetal.h"
@ -8,6 +9,98 @@
#define JOYSTICK_HIGH 0x4000
#define JOYSTICK_LOW 0x3800
static const uint8_t workboy_ascii_to_key[] = {
['0'] = GB_WORKBOY_0,
['`'] = GB_WORKBOY_UMLAUT,
['1'] = GB_WORKBOY_1,
['2'] = GB_WORKBOY_2,
['3'] = GB_WORKBOY_3,
['4'] = GB_WORKBOY_4,
['5'] = GB_WORKBOY_5,
['6'] = GB_WORKBOY_6,
['7'] = GB_WORKBOY_7,
['8'] = GB_WORKBOY_8,
['9'] = GB_WORKBOY_9,
['\r'] = GB_WORKBOY_ENTER,
[3] = GB_WORKBOY_ENTER,
['!'] = GB_WORKBOY_EXCLAMATION_MARK,
['$'] = GB_WORKBOY_DOLLAR,
['#'] = GB_WORKBOY_HASH,
['~'] = GB_WORKBOY_TILDE,
['*'] = GB_WORKBOY_ASTERISK,
['+'] = GB_WORKBOY_PLUS,
['-'] = GB_WORKBOY_MINUS,
['('] = GB_WORKBOY_LEFT_PARENTHESIS,
[')'] = GB_WORKBOY_RIGHT_PARENTHESIS,
[';'] = GB_WORKBOY_SEMICOLON,
[':'] = GB_WORKBOY_COLON,
['%'] = GB_WORKBOY_PERCENT,
['='] = GB_WORKBOY_EQUAL,
[','] = GB_WORKBOY_COMMA,
['<'] = GB_WORKBOY_LT,
['.'] = GB_WORKBOY_DOT,
['>'] = GB_WORKBOY_GT,
['/'] = GB_WORKBOY_SLASH,
['?'] = GB_WORKBOY_QUESTION_MARK,
[' '] = GB_WORKBOY_SPACE,
['\''] = GB_WORKBOY_QUOTE,
['@'] = GB_WORKBOY_AT,
['q'] = GB_WORKBOY_Q,
['w'] = GB_WORKBOY_W,
['e'] = GB_WORKBOY_E,
['r'] = GB_WORKBOY_R,
['t'] = GB_WORKBOY_T,
['y'] = GB_WORKBOY_Y,
['u'] = GB_WORKBOY_U,
['i'] = GB_WORKBOY_I,
['o'] = GB_WORKBOY_O,
['p'] = GB_WORKBOY_P,
['a'] = GB_WORKBOY_A,
['s'] = GB_WORKBOY_S,
['d'] = GB_WORKBOY_D,
['f'] = GB_WORKBOY_F,
['g'] = GB_WORKBOY_G,
['h'] = GB_WORKBOY_H,
['j'] = GB_WORKBOY_J,
['k'] = GB_WORKBOY_K,
['l'] = GB_WORKBOY_L,
['z'] = GB_WORKBOY_Z,
['x'] = GB_WORKBOY_X,
['c'] = GB_WORKBOY_C,
['v'] = GB_WORKBOY_V,
['b'] = GB_WORKBOY_B,
['n'] = GB_WORKBOY_N,
['m'] = GB_WORKBOY_M,
};
static const uint8_t workboy_vk_to_key[] = {
[kVK_F1] = GB_WORKBOY_CLOCK,
[kVK_F2] = GB_WORKBOY_TEMPERATURE,
[kVK_F3] = GB_WORKBOY_MONEY,
[kVK_F4] = GB_WORKBOY_CALCULATOR,
[kVK_F5] = GB_WORKBOY_DATE,
[kVK_F6] = GB_WORKBOY_CONVERSION,
[kVK_F7] = GB_WORKBOY_RECORD,
[kVK_F8] = GB_WORKBOY_WORLD,
[kVK_F9] = GB_WORKBOY_PHONE,
[kVK_F10] = GB_WORKBOY_UNKNOWN,
[kVK_Delete] = GB_WORKBOY_BACKSPACE,
[kVK_Shift] = GB_WORKBOY_SHIFT_DOWN,
[kVK_RightShift] = GB_WORKBOY_SHIFT_DOWN,
[kVK_UpArrow] = GB_WORKBOY_UP,
[kVK_DownArrow] = GB_WORKBOY_DOWN,
[kVK_LeftArrow] = GB_WORKBOY_LEFT,
[kVK_RightArrow] = GB_WORKBOY_RIGHT,
[kVK_Escape] = GB_WORKBOY_ESCAPE,
[kVK_ANSI_KeypadDecimal] = GB_WORKBOY_DECIMAL_POINT,
[kVK_ANSI_KeypadClear] = GB_WORKBOY_M,
[kVK_ANSI_KeypadMultiply] = GB_WORKBOY_H,
[kVK_ANSI_KeypadDivide] = GB_WORKBOY_J,
};
@implementation GBView
{
uint32_t *image_buffers[3];
@ -188,7 +281,20 @@
-(void)keyDown:(NSEvent *)theEvent
{
if ([theEvent type] != NSEventTypeFlagsChanged && theEvent.isARepeat) return;
unsigned short keyCode = theEvent.keyCode;
if (GB_workboy_is_enabled(_gb)) {
if (theEvent.keyCode < sizeof(workboy_vk_to_key) && workboy_vk_to_key[theEvent.keyCode]) {
GB_workboy_set_key(_gb, workboy_vk_to_key[theEvent.keyCode]);
return;
}
unichar c = [theEvent type] != NSEventTypeFlagsChanged? [theEvent.charactersIgnoringModifiers.lowercaseString characterAtIndex:0] : 0;
if (c < sizeof(workboy_ascii_to_key) && workboy_ascii_to_key[c]) {
GB_workboy_set_key(_gb, workboy_ascii_to_key[c]);
return;
}
}
bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
@ -232,6 +338,15 @@
-(void)keyUp:(NSEvent *)theEvent
{
unsigned short keyCode = theEvent.keyCode;
if (GB_workboy_is_enabled(_gb)) {
if (keyCode == kVK_Shift || keyCode == kVK_RightShift) {
GB_workboy_set_key(_gb, GB_WORKBOY_SHIFT_UP);
}
else {
GB_workboy_set_key(_gb, GB_WORKBOY_NONE);
}
}
bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

BIN
Cocoa/Joypad~dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
Cocoa/Joypad~dark@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -379,6 +379,12 @@
<action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/>
</connections>
</menuItem>
<menuItem title="Workboy" id="lo9-CX-BJj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="connectWorkboy:" target="-1" id="6vS-bq-wAX"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>

View File

@ -0,0 +1,42 @@
#import <AppKit/AppKit.h>
#import <objc/runtime.h>
@interface NSImageRep(PrivateAPI)
@property(setter=_setAppearanceName:) NSString *_appearanceName;
@end
static NSImage * (*imageNamed)(Class self, SEL _cmd, NSString *name);
@implementation NSImage(DarkHooks)
+ (NSImage *)imageNamedWithDark:(NSImageName)name
{
NSImage *light = imageNamed(self, _cmd, name);
if (@available(macOS 10.14, *)) {
NSImage *dark = imageNamed(self, _cmd, [name stringByAppendingString:@"~dark"]);
if (!dark) {
return light;
}
NSImage *ret = [[NSImage alloc] initWithSize:light.size];
for (NSImageRep *rep in light.representations) {
[rep _setAppearanceName:NSAppearanceNameAqua];
[ret addRepresentation:rep];
}
for (NSImageRep *rep in dark.representations) {
[rep _setAppearanceName:NSAppearanceNameDarkAqua];
[ret addRepresentation:rep];
}
return ret;
}
return light;
}
+(void)load
{
if (@available(macOS 10.14, *)) {
imageNamed = (void *)[self methodForSelector:@selector(imageNamed:)];
method_setImplementation(class_getClassMethod(self, @selector(imageNamed:)),
[self methodForSelector:@selector(imageNamedWithDark:)]);
}
}
@end

View File

@ -492,7 +492,7 @@
<rect key="frame" x="1" y="1" width="238" height="209"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" appearanceType="vibrantLight" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
<rect key="frame" x="0.0" y="0.0" width="238" height="209"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
@ -507,8 +507,8 @@
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mqT-jD-eXS">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
@ -520,8 +520,8 @@
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" identifier="keyValue" title="Text Cell" id="tn8-0i-1q1">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>

BIN
Cocoa/Speaker~dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
Cocoa/Speaker~dark@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -132,7 +132,7 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer
}
/* Avoid overflow */
if (symbol && strlen(symbol->name) > 240) {
if (symbol && strlen(symbol->name) >= 240) {
symbol = NULL;
}
@ -172,7 +172,7 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo
}
/* Avoid overflow */
if (symbol && strlen(symbol->name) > 240) {
if (symbol && strlen(symbol->name) >= 240) {
symbol = NULL;
}
@ -689,6 +689,7 @@ exit:
struct debugger_command_s;
typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command);
typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context);
typedef struct debugger_command_s {
const char *command;
@ -697,6 +698,8 @@ typedef struct debugger_command_s {
const char *help_string; // Null if should not appear in help
const char *arguments_format; // For usage message
const char *modifiers_format; // For usage message
debugger_completer_imp_t *argument_completer;
debugger_completer_imp_t *modifiers_completer;
} debugger_command_t;
static const char *lstrip(const char *str)
@ -832,6 +835,19 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
return true;
}
static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"on", "off"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
/* Enable or disable software breakpoints */
static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
@ -873,6 +889,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min;
}
static inline bool is_legal_symbol_char(char c)
{
if (c >= '0' && c <= '9') return true;
if (c >= 'A' && c <= 'Z') return true;
if (c >= 'a' && c <= 'z') return true;
if (c == '_') return true;
if (c == '.') return true;
return false;
}
static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context)
{
const char *symbol_prefix = string;
while (*string) {
if (!is_legal_symbol_char(*string)) {
symbol_prefix = string + 1;
}
string++;
}
if (*symbol_prefix == '$') {
return NULL;
}
struct {
uint16_t bank;
uint32_t symbol;
} *context = (void *)_context;
size_t length = strlen(symbol_prefix);
while (context->bank < 0x200) {
if (gb->bank_symbols[context->bank] == NULL ||
context->symbol >= gb->bank_symbols[context->bank]->n_symbols) {
context->bank++;
context->symbol = 0;
continue;
}
const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name;
if (memcmp(symbol_prefix, candidate, length) == 0) {
return strdup(candidate + length);
}
}
return NULL;
}
static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"j"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
bool is_jump_to = true;
@ -1040,6 +1115,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min;
}
static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"r", "rw", "w"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
@ -1277,6 +1365,19 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to)
return _should_break(gb, full_addr, jump_to);
}
static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"a", "b", "d", "o", "x"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
@ -1432,7 +1533,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
const GB_cartridge_t *cartridge = gb->cartridge_type;
if (cartridge->has_ram) {
GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size);
GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size);
}
else {
GB_log(gb, "No cartridge RAM\n");
@ -1740,6 +1841,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
return true;
}
static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
const char *suggestions[] = {"c", "f", "l"};
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
if (memcmp(string, suggestions[*context], length) == 0) {
return strdup(suggestions[(*context)++] + length);
}
(*context)++;
}
return NULL;
}
static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
@ -1787,7 +1901,7 @@ static const debugger_command_t commands[] = {
{"finish", 1, finish, "Run until the current function returns"},
{"backtrace", 2, backtrace, "Displays the current call stack"},
{"bt", 2, }, /* Alias */
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"},
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"},
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
"used"},
{"registers", 1, registers, "Print values of processor registers and other important registers"},
@ -1796,30 +1910,33 @@ static const debugger_command_t commands[] = {
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"},
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
{"palettes", 3, palettes, "Displays the current CGB palettes"},
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)"},
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer},
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
"jumping to the target.",
"<expression>[ if <condition expression>]", "j"},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"},
"<expression>[ if <condition expression>]", "j",
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
"Default watchpoint type is write-only.",
"<expression>[ if <condition expression>]", "(r|w|rw)"},
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"},
"<expression>[ if <condition expression>]", "(r|w|rw)",
.argument_completer = symbol_completer, .modifiers_completer = rw_completer
},
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]", .argument_completer = symbol_completer},
{"list", 1, list, "List all set breakpoints and watchpoints"},
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
"<expression>", "format"},
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
{"eval", 2, }, /* Alias */
{"examine", 2, examine, "Examine values at address", "<expression>", "count"},
{"examine", 2, examine, "Examine values at address", "<expression>", "count", .argument_completer = symbol_completer},
{"x", 1, }, /* Alias */
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count"},
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count", .argument_completer = symbol_completer},
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
@ -2075,6 +2192,63 @@ 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;
char *arguments = strchr(input, ' ');
if (arguments) {
/* Actually "split" the string. */
arguments[0] = 0;
arguments++;
}
char *modifiers = strchr(command_string, '/');
if (modifiers) {
/* Actually "split" the string. */
modifiers[0] = 0;
modifiers++;
}
const debugger_command_t *command = find_command(command_string);
if (command && command->implementation == help && arguments) {
command_string = arguments;
arguments = NULL;
}
/* No commands and no modifiers, complete the command */
if (!arguments && !modifiers) {
size_t length = strlen(command_string);
if (*context >= sizeof(commands) / sizeof(commands[0])) {
return NULL;
}
for (const debugger_command_t *command = &commands[*context]; command->command; command++) {
(*context)++;
if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */
return strdup(command->command + length);
}
}
return NULL;
}
if (command) {
if (arguments) {
if (command->argument_completer) {
return command->argument_completer(gb, arguments, context);
}
return NULL;
}
if (modifiers) {
if (command->modifiers_completer) {
return command->modifiers_completer(gb, modifiers, context);
}
return NULL;
}
}
return NULL;
}
typedef enum {
JUMP_TO_NONE,
JUMP_TO_BREAK,

View File

@ -34,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G
void
#endif
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path);
const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);

View File

@ -1131,8 +1131,9 @@ void GB_disconnect_serial(GB_gameboy_t *gb)
gb->serial_transfer_bit_start_callback = NULL;
gb->serial_transfer_bit_end_callback = NULL;
/* Reset any internally-emulated device. Currently, only the printer. */
/* Reset any internally-emulated device. */
memset(&gb->printer, 0, sizeof(gb->printer));
memset(&gb->workboy, 0, sizeof(gb->workboy));
}
bool GB_is_inited(GB_gameboy_t *gb)

View File

@ -23,6 +23,7 @@
#include "sgb.h"
#include "cheats.h"
#include "rumble.h"
#include "workboy.h"
#define GB_STRUCT_VERSION 13
@ -52,6 +53,10 @@
#error Unable to detect endianess
#endif
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; })
#endif
typedef struct {
struct {
uint8_t r, g, b;
@ -368,6 +373,7 @@ struct GB_gameboy_internal_s {
GB_printer_t printer;
uint8_t extra_oam[0xff00 - 0xfea0];
uint32_t ram_size; // Different between CGB and DMG
GB_workboy_t workboy;
);
/* DMA and HDMA */
@ -434,7 +440,7 @@ struct GB_gameboy_internal_s {
bool rumble_state;
bool cart_ir;
// TODO: move to huc3 struct when breaking save compat
// TODO: move to huc3/mbc3 struct when breaking save compat
uint8_t huc3_mode;
uint8_t huc3_access_index;
uint16_t huc3_minutes, huc3_days;
@ -442,6 +448,7 @@ struct GB_gameboy_internal_s {
bool huc3_alarm_enabled;
uint8_t huc3_read;
uint8_t huc3_access_flags;
bool mbc3_rtc_mapped;
);
@ -602,6 +609,9 @@ struct GB_gameboy_internal_s {
GB_icd_vreset_callback_t icd_vreset_callback;
GB_read_memory_callback_t read_memory_callback;
GB_boot_rom_load_callback_t boot_rom_load_callback;
GB_print_image_callback_t printer_callback;
GB_workboy_set_time_callback workboy_set_time_callback;
GB_workboy_get_time_callback workboy_get_time_callback;
/* IR */
uint64_t cycles_since_ir_change; // In 8MHz units

View File

@ -88,7 +88,6 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
gb->mbc_ram_bank = gb->mbc3.ram_bank;
if (!gb->is_mbc30) {
gb->mbc_rom_bank &= 0x7F;
gb->mbc_ram_bank &= 0x3;
}
if (gb->mbc_rom_bank == 0) {
gb->mbc_rom_bank = 1;
@ -135,7 +134,10 @@ void GB_configure_cart(GB_gameboy_t *gb)
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
}
gb->mbc_ram = malloc(gb->mbc_ram_size);
if (gb->mbc_ram_size) {
gb->mbc_ram = malloc(gb->mbc_ram_size);
}
/* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */
memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);

View File

@ -183,7 +183,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
}
}
if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) &&
if ((!gb->mbc_ram_enable) &&
gb->cartridge_type->mbc_subtype != GB_CAMERA &&
gb->cartridge_type->mbc_type != GB_HUC1 &&
gb->cartridge_type->mbc_type != GB_HUC3) {
@ -195,17 +195,17 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
}
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 &&
gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) {
/* RTC read */
gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */
return gb->rtc_latched.data[gb->mbc_ram_bank - 8];
return gb->rtc_latched.data[gb->mbc_ram_bank];
}
if (gb->camera_registers_mapped) {
return GB_camera_read_register(gb, addr);
}
if (!gb->mbc_ram) {
if (!gb->mbc_ram || !gb->mbc_ram_size) {
return 0xFF;
}
@ -213,7 +213,11 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
return GB_camera_read_image(gb, addr - 0xa100);
}
uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)];
uint8_t effective_bank = gb->mbc_ram_bank;
if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) {
effective_bank &= 0x3;
}
uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)];
if (gb->cartridge_type->mbc_type == GB_MBC2) {
ret |= 0xF0;
}
@ -509,7 +513,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break;
case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break;
case 0x4000: case 0x5000:
gb->mbc3.ram_bank = value;
gb->mbc3_rtc_mapped = value & 8;
break;
case 0x6000: case 0x7000:
if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */
memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real));
@ -677,7 +684,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
}
if ((!gb->mbc_ram_enable || !gb->mbc_ram_size)
if ((!gb->mbc_ram_enable)
&& gb->cartridge_type->mbc_type != GB_HUC1) return;
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
@ -693,16 +700,21 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
}
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value;
if (gb->cartridge_type->has_rtc && gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) {
gb->rtc_latched.data[gb->mbc_ram_bank] = gb->rtc_real.data[gb->mbc_ram_bank] = value;
return;
}
if (!gb->mbc_ram) {
if (!gb->mbc_ram || !gb->mbc_ram_size) {
return;
}
uint8_t effective_bank = gb->mbc_ram_bank;
if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) {
effective_bank &= 0x3;
}
gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value;
gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value;
}
static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)

View File

@ -31,8 +31,8 @@ static void handle_command(GB_gameboy_t *gb)
image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
}
if (gb->printer.callback) {
gb->printer.callback(gb, image, gb->printer.image_offset / 160,
if (gb->printer_callback) {
gb->printer_callback(gb, image, gb->printer.image_offset / 160,
gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7,
gb->printer.command_data[3] & 0x7F);
}
@ -189,13 +189,13 @@ static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received)
static void serial_start(GB_gameboy_t *gb, bool bit_received)
{
gb->printer.byte_being_recieved <<= 1;
gb->printer.byte_being_recieved |= bit_received;
gb->printer.bits_recieved++;
if (gb->printer.bits_recieved == 8) {
byte_reieve_completed(gb, gb->printer.byte_being_recieved);
gb->printer.bits_recieved = 0;
gb->printer.byte_being_recieved = 0;
gb->printer.byte_being_received <<= 1;
gb->printer.byte_being_received |= bit_received;
gb->printer.bits_received++;
if (gb->printer.bits_received == 8) {
byte_reieve_completed(gb, gb->printer.byte_being_received);
gb->printer.bits_received = 0;
gb->printer.byte_being_received = 0;
}
}
@ -212,5 +212,5 @@ void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback)
memset(&gb->printer, 0, sizeof(gb->printer));
GB_set_serial_transfer_bit_start_callback(gb, serial_start);
GB_set_serial_transfer_bit_end_callback(gb, serial_end);
gb->printer.callback = callback;
gb->printer_callback = callback;
}

View File

@ -48,13 +48,14 @@ typedef struct
uint8_t image[160 * 200];
uint16_t image_offset;
GB_print_image_callback_t callback;
/* TODO: Delete me. */
uint64_t padding;
uint8_t compression_run_lenth;
bool compression_run_is_compressed;
uint8_t bits_recieved;
uint8_t byte_being_recieved;
uint8_t bits_received;
uint8_t byte_being_received;
bool bit_to_send;
} GB_printer_t;

View File

@ -40,7 +40,6 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
}
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
goto error;
}
@ -116,13 +115,21 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
}
/* Best-effort read function for maximum future compatibility. */
static bool read_section(FILE *f, void *dest, uint32_t size)
static bool read_section(FILE *f, void *dest, uint32_t size, bool fix_broken_windows_saves)
{
uint32_t saved_size = 0;
if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
return false;
}
if (fix_broken_windows_saves) {
if (saved_size < 4) {
return false;
}
saved_size -= 4;
fseek(f, 4, SEEK_CUR);
}
if (saved_size <= size) {
if (fread(dest, 1, saved_size, f) != saved_size) {
return false;
@ -139,11 +146,21 @@ static bool read_section(FILE *f, void *dest, uint32_t size)
}
#undef DUMP_SECTION
static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
{
if (gb->magic != save->magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) {
/* This is a save state with a bad printer struct from a 32-bit OS */
memcpy(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam);
}
if (save->ram_size == 0) {
/* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
incorrect RAM amount if it's a CGB instance */
if (GB_is_cgb(save)) {
save->ram_size = 0x2000 * 8; // Incorrect RAM size
}
else {
save->ram_size = gb->ram_size;
}
}
if (gb->version != save->version) {
@ -202,7 +219,7 @@ static void sanitize_state(GB_gameboy_t *gb)
}
}
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
int GB_load_state(GB_gameboy_t *gb, const char *path)
{
@ -219,7 +236,18 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
return errno;
}
bool fix_broken_windows_saves = false;
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
if (save.magic == 0) {
/* Potentially legacy, broken Windows save state */
fseek(f, 4, SEEK_SET);
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
fix_broken_windows_saves = true;
}
if (gb->magic != save.magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
}
if (!READ_SECTION(&save, f, core_state)) goto error;
if (!READ_SECTION(&save, f, dma )) goto error;
if (!READ_SECTION(&save, f, mbc )) goto error;
@ -229,24 +257,13 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
if (!READ_SECTION(&save, f, rtc )) goto error;
if (!READ_SECTION(&save, f, video )) goto error;
if (save.ram_size == 0) {
/* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
incorrect RAM amount if it's a CGB instance */
if (GB_is_cgb(&save)) {
save.ram_size = 0x2000 * 8; // Incorrect RAM size
}
else {
save.ram_size = gb->ram_size;
}
}
if (!verify_state_compatibility(gb, &save)) {
if (!verify_and_update_state_compatibility(gb, &save)) {
errno = -1;
goto error;
}
if (GB_is_hle_sgb(gb)) {
if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
if (!read_section(f, gb->sgb, sizeof(*gb->sgb), false)) goto error;
}
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
@ -297,7 +314,7 @@ static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, siz
return length;
}
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size)
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size, bool fix_broken_windows_saves)
{
uint32_t saved_size = 0;
if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
@ -306,6 +323,14 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
if (saved_size > *buffer_length) return false;
if (fix_broken_windows_saves) {
if (saved_size < 4) {
return false;
}
saved_size -= 4;
*buffer += 4;
}
if (saved_size <= size) {
if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
return false;
@ -322,15 +347,27 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
return true;
}
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
{
GB_gameboy_t save;
/* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save));
bool fix_broken_windows_saves = false;
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
if (save.magic == 0) {
/* Potentially legacy, broken Windows save state*/
buffer -= GB_SECTION_SIZE(header) - 4;
length += GB_SECTION_SIZE(header) - 4;
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
fix_broken_windows_saves = true;
}
if (gb->magic != save.magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
}
if (!READ_SECTION(&save, buffer, length, core_state)) return -1;
if (!READ_SECTION(&save, buffer, length, dma )) return -1;
if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
@ -339,13 +376,14 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
if (!READ_SECTION(&save, buffer, length, apu )) return -1;
if (!READ_SECTION(&save, buffer, length, rtc )) return -1;
if (!READ_SECTION(&save, buffer, length, video )) return -1;
if (!verify_state_compatibility(gb, &save)) {
if (!verify_and_update_state_compatibility(gb, &save)) {
return -1;
}
if (GB_is_hle_sgb(gb)) {
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1;
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb), false)) return -1;
}
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);

View File

@ -5,7 +5,7 @@
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end
#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_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))

View File

@ -291,8 +291,20 @@ void GB_rtc_run(GB_gameboy_t *gb)
}
return;
}
if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */
time_t current_time = time(NULL);
while (gb->last_rtc_second + 60 * 60 * 24 < current_time) {
gb->last_rtc_second += 60 * 60 * 24;
if (++gb->rtc_real.days == 0) {
if (gb->rtc_real.high & 1) { /* Bit 8 of days*/
gb->rtc_real.high |= 0x80; /* Overflow bit */
}
gb->rtc_real.high ^= 1;
}
}
while (gb->last_rtc_second < current_time) {
gb->last_rtc_second++;
if (++gb->rtc_real.seconds == 60) {

169
Core/workboy.c Normal file
View File

@ -0,0 +1,169 @@
#include "gb.h"
#include <time.h>
static inline uint8_t int_to_bcd(uint8_t i)
{
return (i % 10) + ((i / 10) << 4);
}
static inline uint8_t bcd_to_int(uint8_t i)
{
return (i & 0xF) + (i >> 4) * 10;
}
/*
Note: This peripheral was never released. This is a hacky software reimplementation of it that allows
reaccessing all of the features present in Workboy's ROM. Some of the implementation details are
obviously wrong, but without access to the actual hardware, this is the best I can do.
*/
static void serial_start(GB_gameboy_t *gb, bool bit_received)
{
gb->workboy.byte_being_received <<= 1;
gb->workboy.byte_being_received |= bit_received;
gb->workboy.bits_received++;
if (gb->workboy.bits_received == 8) {
gb->workboy.byte_to_send = 0;
if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'R') {
gb->workboy.byte_to_send = 'D';
gb->workboy.key = GB_WORKBOY_NONE;
gb->workboy.mode = gb->workboy.byte_being_received;
gb->workboy.buffer_index = 1;
time_t time = gb->workboy_get_time_callback(gb);
struct tm tm;
tm = *localtime(&time);
memset(gb->workboy.buffer, 0, sizeof(gb->workboy.buffer));
gb->workboy.buffer[0] = 4; // Unknown, unused, but appears to be expected to be 4
gb->workboy.buffer[2] = int_to_bcd(tm.tm_sec); // Seconds, BCD
gb->workboy.buffer[3] = int_to_bcd(tm.tm_min); // Minutes, BCD
gb->workboy.buffer[4] = int_to_bcd(tm.tm_hour); // Hours, BCD
gb->workboy.buffer[5] = int_to_bcd(tm.tm_mday); // Days, BCD. Upper most 2 bits are added to Year for some reason
gb->workboy.buffer[6] = int_to_bcd(tm.tm_mon + 1); // Months, BCD
gb->workboy.buffer[0xF] = tm.tm_year; // Years, plain number, since 1900
}
else if (gb->workboy.mode != 'W' && gb->workboy.byte_being_received == 'W') {
gb->workboy.byte_to_send = 'D'; // It is actually unknown what this value should be
gb->workboy.key = GB_WORKBOY_NONE;
gb->workboy.mode = gb->workboy.byte_being_received;
gb->workboy.buffer_index = 0;
}
else if (gb->workboy.mode != 'W' && (gb->workboy.byte_being_received == 'O' || gb->workboy.mode == 'O')) {
gb->workboy.mode = 'O';
gb->workboy.byte_to_send = gb->workboy.key;
if (gb->workboy.key != GB_WORKBOY_NONE) {
if (gb->workboy.key & GB_WORKBOY_REQUIRE_SHIFT) {
gb->workboy.key &= ~GB_WORKBOY_REQUIRE_SHIFT;
if (gb->workboy.shift_down) {
gb->workboy.byte_to_send = gb->workboy.key;
gb->workboy.key = GB_WORKBOY_NONE;
}
else {
gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_DOWN;
gb->workboy.shift_down = true;
}
}
else if (gb->workboy.key & GB_WORKBOY_FORBID_SHIFT) {
gb->workboy.key &= ~GB_WORKBOY_FORBID_SHIFT;
if (!gb->workboy.shift_down) {
gb->workboy.byte_to_send = gb->workboy.key;
gb->workboy.key = GB_WORKBOY_NONE;
}
else {
gb->workboy.byte_to_send = GB_WORKBOY_SHIFT_UP;
gb->workboy.shift_down = false;
}
}
else {
if (gb->workboy.key == GB_WORKBOY_SHIFT_DOWN) {
gb->workboy.shift_down = true;
gb->workboy.user_shift_down = true;
}
else if (gb->workboy.key == GB_WORKBOY_SHIFT_UP) {
gb->workboy.shift_down = false;
gb->workboy.user_shift_down = false;
}
gb->workboy.byte_to_send = gb->workboy.key;
gb->workboy.key = GB_WORKBOY_NONE;
}
}
}
else if (gb->workboy.mode == 'R') {
if (gb->workboy.buffer_index / 2 >= sizeof(gb->workboy.buffer)) {
gb->workboy.byte_to_send = 0;
}
else {
if (gb->workboy.buffer_index & 1) {
gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] & 0xF];
}
else {
gb->workboy.byte_to_send = "0123456789ABCDEF"[gb->workboy.buffer[gb->workboy.buffer_index / 2] >> 4];
}
gb->workboy.buffer_index++;
}
}
else if (gb->workboy.mode == 'W') {
gb->workboy.byte_to_send = 'D';
if (gb->workboy.buffer_index < 2) {
gb->workboy.buffer_index++;
}
else if ((gb->workboy.buffer_index - 2) < sizeof(gb->workboy.buffer)) {
gb->workboy.buffer[gb->workboy.buffer_index - 2] = gb->workboy.byte_being_received;
gb->workboy.buffer_index++;
if (gb->workboy.buffer_index - 2 == sizeof(gb->workboy.buffer)) {
struct tm tm = {0,};
tm.tm_sec = bcd_to_int(gb->workboy.buffer[7]);
tm.tm_min = bcd_to_int(gb->workboy.buffer[8]);
tm.tm_hour = bcd_to_int(gb->workboy.buffer[9]);
tm.tm_mday = bcd_to_int(gb->workboy.buffer[0xA]);
tm.tm_mon = bcd_to_int(gb->workboy.buffer[0xB] & 0x3F) - 1;
tm.tm_year = (uint8_t)(gb->workboy.buffer[0x14] + (gb->workboy.buffer[0xA] >> 6)); // What were they thinking?
gb->workboy_set_time_callback(gb, mktime(&tm));
gb->workboy.mode = 'O';
}
}
}
gb->workboy.bits_received = 0;
gb->workboy.byte_being_received = 0;
}
}
static bool serial_end(GB_gameboy_t *gb)
{
bool ret = gb->workboy.bit_to_send;
gb->workboy.bit_to_send = gb->workboy.byte_to_send & 0x80;
gb->workboy.byte_to_send <<= 1;
return ret;
}
void GB_connect_workboy(GB_gameboy_t *gb,
GB_workboy_set_time_callback set_time_callback,
GB_workboy_get_time_callback get_time_callback)
{
memset(&gb->workboy, 0, sizeof(gb->workboy));
GB_set_serial_transfer_bit_start_callback(gb, serial_start);
GB_set_serial_transfer_bit_end_callback(gb, serial_end);
gb->workboy_set_time_callback = set_time_callback;
gb->workboy_get_time_callback = get_time_callback;
}
bool GB_workboy_is_enabled(GB_gameboy_t *gb)
{
return gb->workboy.mode;
}
void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key)
{
if (gb->workboy.user_shift_down != gb->workboy.shift_down &&
(key & (GB_WORKBOY_REQUIRE_SHIFT | GB_WORKBOY_FORBID_SHIFT)) == 0) {
if (gb->workboy.user_shift_down) {
key |= GB_WORKBOY_REQUIRE_SHIFT;
}
else {
key |= GB_WORKBOY_FORBID_SHIFT;
}
}
gb->workboy.key = key;
}

118
Core/workboy.h Normal file
View File

@ -0,0 +1,118 @@
#ifndef workboy_h
#define workboy_h
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include "gb_struct_def.h"
typedef struct {
uint8_t byte_to_send;
bool bit_to_send;
uint8_t byte_being_received;
uint8_t bits_received;
uint8_t mode;
uint8_t key;
bool shift_down;
bool user_shift_down;
uint8_t buffer[0x15];
uint8_t buffer_index; // In nibbles during read, in bytes during write
} GB_workboy_t;
typedef void (*GB_workboy_set_time_callback)(GB_gameboy_t *gb, time_t time);
typedef time_t (*GB_workboy_get_time_callback)(GB_gameboy_t *gb);
enum {
GB_WORKBOY_NONE = 0xFF,
GB_WORKBOY_REQUIRE_SHIFT = 0x40,
GB_WORKBOY_FORBID_SHIFT = 0x80,
GB_WORKBOY_CLOCK = 1,
GB_WORKBOY_TEMPERATURE = 2,
GB_WORKBOY_MONEY = 3,
GB_WORKBOY_CALCULATOR = 4,
GB_WORKBOY_DATE = 5,
GB_WORKBOY_CONVERSION = 6,
GB_WORKBOY_RECORD = 7,
GB_WORKBOY_WORLD = 8,
GB_WORKBOY_PHONE = 9,
GB_WORKBOY_ESCAPE = 10,
GB_WORKBOY_BACKSPACE = 11,
GB_WORKBOY_UNKNOWN = 12,
GB_WORKBOY_LEFT = 13,
GB_WORKBOY_Q = 17,
GB_WORKBOY_1 = 17 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_W = 18,
GB_WORKBOY_2 = 18 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_E = 19,
GB_WORKBOY_3 = 19 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_R = 20,
GB_WORKBOY_T = 21,
GB_WORKBOY_Y = 22 ,
GB_WORKBOY_U = 23 ,
GB_WORKBOY_I = 24,
GB_WORKBOY_EXCLAMATION_MARK = 24 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_O = 25,
GB_WORKBOY_TILDE = 25 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_P = 26,
GB_WORKBOY_ASTERISK = 26 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_DOLLAR = 27 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_HASH = 27 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_A = 28,
GB_WORKBOY_4 = 28 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_S = 29,
GB_WORKBOY_5 = 29 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_D = 30,
GB_WORKBOY_6 = 30 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_F = 31,
GB_WORKBOY_PLUS = 31 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_G = 32,
GB_WORKBOY_MINUS = 32 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_H = 33,
GB_WORKBOY_J = 34,
GB_WORKBOY_K = 35,
GB_WORKBOY_LEFT_PARENTHESIS = 35 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_L = 36,
GB_WORKBOY_RIGHT_PARENTHESIS = 36 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_SEMICOLON = 37 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_COLON = 37,
GB_WORKBOY_ENTER = 38,
GB_WORKBOY_SHIFT_DOWN = 39,
GB_WORKBOY_Z = 40,
GB_WORKBOY_7 = 40 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_X = 41,
GB_WORKBOY_8 = 41 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_C = 42,
GB_WORKBOY_9 = 42 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_V = 43,
GB_WORKBOY_DECIMAL_POINT = 43 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_B = 44,
GB_WORKBOY_PERCENT = 44 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_N = 45,
GB_WORKBOY_EQUAL = 45 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_M = 46,
GB_WORKBOY_COMMA = 47 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_LT = 47 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_DOT = 48 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_GT = 48 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_SLASH = 49 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_QUESTION_MARK = 49 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_SHIFT_UP = 50,
GB_WORKBOY_0 = 51 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_UMLAUT = 51,
GB_WORKBOY_SPACE = 52,
GB_WORKBOY_QUOTE = 53 | GB_WORKBOY_FORBID_SHIFT,
GB_WORKBOY_AT = 53 | GB_WORKBOY_REQUIRE_SHIFT,
GB_WORKBOY_UP = 54,
GB_WORKBOY_DOWN = 55,
GB_WORKBOY_RIGHT = 56,
};
void GB_connect_workboy(GB_gameboy_t *gb,
GB_workboy_set_time_callback set_time_callback,
GB_workboy_get_time_callback get_time_callback);
bool GB_workboy_is_enabled(GB_gameboy_t *gb);
void GB_workboy_set_key(GB_gameboy_t *gb, uint8_t key);
#endif

View File

@ -4,7 +4,7 @@
#import <libkern/OSAtomic.h>
#define HFDEFAULT_FONT (@"Monaco")
#define HFDEFAULT_FONTSIZE ((CGFloat)10.)
#define HFDEFAULT_FONTSIZE ((CGFloat)11.)
#define HFZeroRange (HFRange){0, 0}

View File

@ -57,8 +57,8 @@ static CGFloat maximumDigitAdvanceForFont(NSFont *font) {
interiorShadowEdge = NSMaxXEdge;
_borderedEdges = (1 << NSMaxXEdge);
_borderColor = [[NSColor darkGrayColor] retain];
_backgroundColor = [[NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
_borderColor = [[NSColor controlShadowColor] retain];
_backgroundColor = [[NSColor windowBackgroundColor] retain];
}
return self;
}
@ -82,9 +82,9 @@ static CGFloat maximumDigitAdvanceForFont(NSFont *font) {
lineNumberFormat = (HFLineNumberFormat)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
_borderedEdges = [coder decodeObjectForKey:@"HFBorderedEdges"] ? (NSInteger)[coder decodeInt64ForKey:@"HFBorderedEdges"] : 0;
_borderColor = [[coder decodeObjectForKey:@"HFBorderColor"] ?: [NSColor darkGrayColor] retain];
_backgroundColor = [[coder decodeObjectForKey:@"HFBackgroundColor"] ?: [NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
_borderColor = [[NSColor controlShadowColor] retain];
_backgroundColor = [[NSColor windowBackgroundColor] retain];
return self;
}

View File

@ -119,20 +119,6 @@
[super viewWillMoveToWindow:newWindow];
}
- (void)drawGradientWithClip:(NSRect)clip {
[_representer.backgroundColor set];
NSRectFill(clip);
NSInteger shadowEdge = _representer.interiorShadowEdge;
if (shadowEdge >= 0) {
const CGFloat shadowWidth = 6;
NSWindow *window = self.window;
BOOL drawActive = (window == nil || [window isKeyWindow] || [window isMainWindow]);
HFDrawShadow([[NSGraphicsContext currentContext] graphicsPort], self.bounds, shadowWidth, shadowEdge, drawActive, clip);
}
}
- (void)drawDividerWithClip:(NSRect)clipRect {
USE(clipRect);
@ -267,7 +253,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
NSUInteger glyphCount;
[textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
if (previousTextStorageCharacterCount == 0) {
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, nil];
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, nil];
[textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
[atts release];
}
@ -305,7 +291,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
[mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
@ -456,12 +442,12 @@ static inline int common_prefix_length(const char *a, const char *b) {
}
}
if (! textAttributes) {
if (!textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
[textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])];
}
@ -489,27 +475,28 @@ static inline int common_prefix_length(const char *a, const char *b) {
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1.5;
NSRect textRect = self.bounds;
textRect.size.width -= 5;
textRect.origin.y -= verticalOffset;
textRect.size.height += verticalOffset;
if (! textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
[mutableStyle setMinimumLineHeight:_lineHeight];
[mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
textRect.size.height += verticalOffset + _lineHeight;
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
[mutableStyle setMinimumLineHeight:_lineHeight];
[mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
NSColor *color = [[NSColor textColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
color = [NSColor colorWithRed:color.redComponent green:color.greenComponent blue:color.blueComponent alpha:0.75];
NSDictionary *_textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:_font.fontName size:9], NSFontAttributeName, color, NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
[string drawInRect:textRect withAttributes:textAttributes];
[string drawInRect:textRect withAttributes:_textAttributes];
[string release];
[_textAttributes release];
}
- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
@ -533,7 +520,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
[mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
@ -568,7 +555,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
#if TIME_LINE_NUMBERS
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
#endif
NSInteger drawingMode = (useStringDrawingPath ? 1 : 3);
NSInteger drawingMode = 3; // (useStringDrawingPath ? 1 : 3);
switch (drawingMode) {
// Drawing can't be done right if fonts are wider than expected, but all
// of these have rather nasty behavior in that case. I've commented what
@ -606,7 +593,6 @@ static inline int common_prefix_length(const char *a, const char *b) {
}
- (void)drawRect:(NSRect)clipRect {
[self drawGradientWithClip:clipRect];
[self drawDividerWithClip:clipRect];
[self drawLineNumbersWithClip:clipRect];
}

View File

@ -441,7 +441,7 @@ enum LineCoverage_t {
- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect {
NSRect caretRect = NSIntersectionRect(caretRectToDraw, clipRect);
if (! NSIsEmptyRect(caretRect)) {
[[NSColor blackColor] set];
[[NSColor controlTextColor] set];
NSRectFill(caretRect);
lastDrawnCaretRect = caretRect;
}
@ -456,12 +456,18 @@ enum LineCoverage_t {
/* This is the color when we are not in the key window */
- (NSColor *)inactiveTextSelectionColor {
if (@available(macOS 10.14, *)) {
return [NSColor unemphasizedSelectedTextBackgroundColor];
}
return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
}
/* This is the color when we are not the first responder, but we are in the key window */
- (NSColor *)secondaryTextSelectionColor {
return [[self primaryTextSelectionColor] blendedColorWithFraction:.66 ofColor:[NSColor colorWithCalibratedWhite:.8f alpha:1]];
if (@available(macOS 10.14, *)) {
return [NSColor unemphasizedSelectedTextBackgroundColor];
}
return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
}
- (NSColor *)textSelectionColor {
@ -826,7 +832,7 @@ enum LineCoverage_t {
- (HFTextVisualStyleRun *)styleRunForByteAtIndex:(NSUInteger)byteIndex {
HFTextVisualStyleRun *run = [[HFTextVisualStyleRun alloc] init];
[run setRange:NSMakeRange(0, NSUIntegerMax)];
[run setForegroundColor:[NSColor blackColor]];
[run setForegroundColor:[NSColor textColor]];
return [run autorelease];
}
@ -902,8 +908,8 @@ static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count)
guideIndex++;
}
if (rectIndex > 0) {
[[NSColor colorWithCalibratedWhite:(CGFloat).8 alpha:1] set];
NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositePlusDarker);
[[NSColor gridColor] set];
NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositeSourceOver);
}
FREE_ARRAY(lineRects);
}
@ -1344,7 +1350,7 @@ static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count)
[self _drawDefaultLineBackgrounds:clip withLineHeight:[self lineHeight] maxLines:ll2l(HFRoundUpToNextMultipleSaturate(byteCount, bytesPerLine) / bytesPerLine)];
[self drawSelectionIfNecessaryWithClip:clip];
NSColor *textColor = [NSColor blackColor];
NSColor *textColor = [NSColor textColor];
[textColor set];
if (! antialias) {

View File

@ -29,7 +29,7 @@
- (void)_sharedInitStatusBarView {
NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
[style setAlignment:NSCenterTextAlignment];
cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor colorWithCalibratedWhite:(CGFloat).15 alpha:1], NSForegroundColorAttributeName, [NSFont labelFontOfSize:10], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil];
cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor windowFrameTextColor], NSForegroundColorAttributeName, [NSFont labelFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil];
cell = [[NSCell alloc] initTextCell:@""];
[cell setAlignment:NSCenterTextAlignment];
[cell setBackgroundStyle:NSBackgroundStyleRaised];
@ -62,51 +62,24 @@
[self setNeedsDisplay:YES];
}
- (void)drawDividerWithClip:(NSRect)clipRect {
[[NSColor lightGrayColor] set];
NSRect bounds = [self bounds];
NSRect lineRect = bounds;
lineRect.size.height = 1;
NSRectFill(NSIntersectionRect(lineRect, clipRect));
}
- (NSGradient *)getGradient:(BOOL)active {
static NSGradient *sActiveGradient;
static NSGradient *sInactiveGradient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sActiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithCalibratedWhite:.89 alpha:1.], 0.00,
[NSColor colorWithCalibratedWhite:.77 alpha:1.], 0.9,
[NSColor colorWithCalibratedWhite:.82 alpha:1.], 1.0,
nil];
sInactiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithCalibratedWhite:.93 alpha:1.], 0.00,
[NSColor colorWithCalibratedWhite:.87 alpha:1.], 0.9,
[NSColor colorWithCalibratedWhite:.90 alpha:1.], 1.0,
nil];
});
return active ? sActiveGradient : sInactiveGradient;
}
- (void)drawRect:(NSRect)clip {
USE(clip);
NSRect bounds = [self bounds];
// [[NSColor colorWithCalibratedWhite:(CGFloat).91 alpha:1] set];
// NSRectFill(clip);
NSWindow *window = [self window];
BOOL drawActive = (window == nil || [window isMainWindow] || [window isKeyWindow]);
[[self getGradient:drawActive] drawInRect:bounds angle:90.];
[self drawDividerWithClip:clip];
NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - cellSize.height / 2), NSWidth(bounds), cellSize.height);
[cell drawWithFrame:cellRect inView:self];
}
- (void)setFrame:(NSRect)frame
{
[super setFrame:frame];
[self.window setContentBorderThickness:frame.origin.y + frame.size.height forEdge:NSMinYEdge];
}
- (void)mouseDown:(NSEvent *)event {
USE(event);
HFStatusBarMode newMode = ([representer statusMode] + 1) % HFSTATUSMODECOUNT;
@ -122,6 +95,7 @@
- (void)viewDidMoveToWindow {
HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
registeredForAppNotifications = YES;
[self.window setContentBorderThickness:self.frame.origin.y + self.frame.size.height forEdge:NSMinYEdge];
[super viewDidMoveToWindow];
}

View File

@ -20,9 +20,13 @@
- (instancetype)init {
self = [super init];
NSColor *color1 = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
NSColor *color2 = [NSColor colorWithCalibratedRed:.87 green:.89 blue:1. alpha:1.];
_rowBackgroundColors = [@[color1, color2] retain];
if (@available(macOS 10.14, *)) {
_rowBackgroundColors = [[NSColor alternatingContentBackgroundColors] retain];
} else {
NSColor *color1 = [NSColor windowBackgroundColor];
NSColor *color2 = [NSColor colorWithDeviceWhite:0.96 alpha:1];
_rowBackgroundColors = [@[color1, color2] retain];
}
return self;
}
@ -237,7 +241,7 @@
FOREACH(HFRangeWrapper *, wrapper, selectedRanges) {
HFRange selectedRange = [wrapper HFRange];
BOOL clippedRangeIsVisible;
NSRange clippedSelectedRange;
NSRange clippedSelectedRange = {0,};
/* Necessary because zero length ranges do not intersect anything */
if (selectedRange.length == 0) {
/* Remember that {6, 0} is considered a subrange of {3, 3} */

View File

@ -16,8 +16,10 @@ endif
ifeq ($(PLATFORM),windows32)
_ := $(shell chcp 65001)
EXESUFFIX:=.exe
NATIVE_CC = clang -IWindows -Wno-deprecated-declarations
else
EXESUFFIX:=
NATIVE_CC := cc
endif
PB12_COMPRESS := build/pb12$(EXESUFFIX)
@ -34,7 +36,7 @@ ifeq ($(MAKECMDGOALS),)
MAKECMDGOALS := $(DEFAULT)
endif
VERSION := 0.13
VERSION := 0.13.6
export VERSION
CONF ?= debug
SDL_AUDIO_DRIVER ?= sdl
@ -74,6 +76,13 @@ LDFLAGS += -march=native -mtune=native
CFLAGS += -march=native -mtune=native
endif
ifeq ($(CONF),fat_release)
override CONF := release
FAT_FLAGS += -arch x86_64 -arch arm64
endif
# Set compilation and linkage flags based on target, platform and configuration
OPEN_DIALOG = OpenDialog/gtk.c
@ -97,6 +106,11 @@ ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2>
WARNINGS += -Wpartial-availability
endif
# GCC's implementation of this warning has false positives, so we skip it
ifneq ($(shell $(CC) --version 2>&1 | grep "gcc"), )
WARNINGS += -Wno-maybe-uninitialized
endif
CFLAGS += $(WARNINGS)
CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES
@ -132,9 +146,9 @@ ifeq ($(SYSROOT),/Library/Developer/CommandLineTools/SDKs/)
$(error Could not find a macOS SDK)
endif
CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9
CFLAGS += -F/Library/Frameworks -mmacosx-version-min=10.9 -isysroot $(SYSROOT)
OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT)
LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9
LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -weak_framework Metal -weak_framework MetalKit -mmacosx-version-min=10.9 -isysroot $(SYSROOT)
GL_LDFLAGS := -framework OpenGL
endif
CFLAGS += -Wno-deprecated-declarations
@ -227,24 +241,24 @@ $(OBJ)/%.dep: %
$(OBJ)/Core/%.c.o: Core/%.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -DGB_INTERNAL -c $< -o $@
$(CC) $(CFLAGS) $(FAT_FLAGS) -DGB_INTERNAL -c $< -o $@
$(OBJ)/SDL/%.c.o: SDL/%.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@
$(CC) $(CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@
$(OBJ)/%.c.o: %.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(CC) $(CFLAGS) $(FAT_FLAGS) -c $< -o $@
# HexFiend requires more flags
$(OBJ)/HexFiend/%.m.o: HexFiend/%.m
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch
$(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch
$(OBJ)/%.m.o: %.m
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@
$(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@
# Cocoa Port
@ -272,13 +286,13 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \
$(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
-@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit
$(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit
ifeq ($(CONF), release)
$(STRIP) $@
endif
$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib
ibtool --compile $@ $^
ibtool --compile $@ $^ 2>&1 | cat -
# Quick Look generator
@ -294,7 +308,7 @@ $(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL
# once in the QL Generator. It should probably become a dylib instead.
$(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK_OBJECTS)
-@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook
$(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook
# cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided
# boot ROM directory.
@ -307,7 +321,7 @@ $(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs
# Unix versions build only one binary
$(BIN)/SDL/sameboy: $(CORE_OBJECTS) $(SDL_OBJECTS)
-@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS)
$(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS)
ifeq ($(CONF), release)
$(STRIP) $@
endif
@ -390,7 +404,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE
$(realpath $(PB12_COMPRESS)) < $< > $@
$(PB12_COMPRESS): BootROMs/pb12.c
$(CC) $(LDFLAGS) $(CFLAGS) -Wall -Werror $< -o $@
$(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@
$(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm
$(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm

View File

@ -61,9 +61,9 @@ static OSStatus render(CGContextRef cgContext, CFURLRef url, bool showBorder)
}
if (showBorder) {
/* Use the CGB flag to determine the cartrdige "look":
/* Use the CGB flag to determine the cartridge "look":
- DMG cartridges are grey
- CGB cartrdiges are transparent
- CGB cartridges are transparent
- CGB cartridges that support DMG systems are black
*/
NSImage *effectiveTemplate = nil;
@ -105,8 +105,9 @@ OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview,
OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize)
{
extern NSString *kQLThumbnailPropertyIconFlavorKey;
@autoreleasepool {
CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, ((NSSize){1024, 1024}), true, (__bridge CFDictionaryRef)(@{@"IconFlavor" : @(0)}));
CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, ((NSSize){1024, 1024}), true, (__bridge CFDictionaryRef)(@{kQLThumbnailPropertyIconFlavorKey : @(0)}));
if (render(cgContext, url, true) == noErr) {
QLThumbnailRequestFlushContext(thumbnail, cgContext);
CGContextRelease(cgContext);
@ -115,4 +116,4 @@ OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thum
CGContextRelease(cgContext);
return -1;
}
}
}

View File

@ -46,8 +46,8 @@ On Windows, SameBoy also requires:
* [GnuWin](http://gnuwin32.sourceforge.net/)
* Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively.
To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release` or `CONF=native_release` to control optimization and symbols. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. You may set `BOOTROMS_DIR=...` to a directory containing precompiled `dmg_boot.bin` and `cgb_boot.bin` files, otherwise the build system will compile and use SameBoy's own boot ROMs.
To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release`, `CONF=native_release` or `CONF=fat_release` to control optimization, symbols and multi-architectures. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. `fat_release` is exclusive to macOS and builds x86-64 and ARM64 fat binaries; this requires using a recent enough `clang` and macOS SDK using `xcode-select`, or setting them explicitly with `CC=` and `SYSROOT=`, respectively. All other configurations will build to your host architecture. You may set `BOOTROMS_DIR=...` to a directory containing precompiled boot ROM files, otherwise the build system will compile and use SameBoy's own boot ROMs.
By default, the SDL port will look for resource files with a path relative to executable. If you are packaging SameBoy, you may wish to override this by setting the `DATA_DIR` variable during compilation to the target path of the directory containing all files (apart from the executable, that's not necessary) from the `build/bin/SDL` directory in the source tree. Make sure the variable ends with a `/` character.
SameBoy was compiled and tested on macOS, Ubuntu and 32-bit Windows 7.
SameBoy was compiled and tested on macOS, Ubuntu and 64-bit Windows 7.

View File

@ -109,6 +109,7 @@ configuration_t configuration =
.model = MODEL_CGB,
.volume = 100,
.rumble_mode = GB_RUMBLE_ALL_GAMES,
.default_scale = 2,
};
@ -170,6 +171,11 @@ void update_viewport(void)
}
}
static void rescale_window(void)
{
SDL_SetWindowSize(window, GB_get_screen_width(&gb) * configuration.default_scale, GB_get_screen_height(&gb) * configuration.default_scale);
}
/* Does NOT check for bounds! */
static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color)
{
@ -435,6 +441,12 @@ const char *current_scaling_mode(unsigned index)
[configuration.scaling_mode];
}
const char *current_default_scale(unsigned index)
{
return (const char *[]){"1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x"}
[configuration.default_scale - 1];
}
const char *current_color_correction_mode(unsigned index)
{
return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness", "Reduce Contrast"}
@ -475,6 +487,32 @@ void cycle_scaling_backwards(unsigned index)
render_texture(NULL, NULL);
}
void cycle_default_scale(unsigned index)
{
if (configuration.default_scale == GB_SDL_DEFAULT_SCALE_MAX) {
configuration.default_scale = 1;
}
else {
configuration.default_scale++;
}
rescale_window();
update_viewport();
}
void cycle_default_scale_backwards(unsigned index)
{
if (configuration.default_scale == 1) {
configuration.default_scale = GB_SDL_DEFAULT_SCALE_MAX;
}
else {
configuration.default_scale--;
}
rescale_window();
update_viewport();
}
static void cycle_color_correction(unsigned index)
{
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) {
@ -643,6 +681,7 @@ const char *blending_mode_string(unsigned index)
static const struct menu_item graphics_menu[] = {
{"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards},
{"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards},
{"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards},
{"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards},
{"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards},

View File

@ -41,6 +41,7 @@ enum pending_command {
GB_SDL_QUIT_COMMAND,
};
#define GB_SDL_DEFAULT_SCALE_MAX 8
extern enum pending_command pending_command;
extern unsigned command_parameter;
@ -107,6 +108,8 @@ typedef struct {
GB_border_mode_t border_mode;
uint8_t volume;
GB_rumble_mode_t rumble_mode;
uint8_t default_scale;
} configuration_t;
extern configuration_t configuration;

View File

@ -624,12 +624,47 @@ int main(int argc, char **argv)
SDL_Init(SDL_INIT_EVERYTHING);
strcpy(prefs_path, resource_path("prefs.bin"));
if (access(prefs_path, R_OK | W_OK) != 0) {
char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
SDL_free(prefs_dir);
}
FILE *prefs_file = fopen(prefs_path, "rb");
if (prefs_file) {
fread(&configuration, 1, sizeof(configuration), prefs_file);
fclose(prefs_file);
/* Sanitize for stability */
configuration.color_correction_mode %= GB_COLOR_CORRECTION_REDUCE_CONTRAST +1;
configuration.scaling_mode %= GB_SDL_SCALING_MAX;
configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1;
configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1;
configuration.highpass_mode %= GB_HIGHPASS_MAX;
configuration.model %= MODEL_MAX;
configuration.sgb_revision %= SGB_MAX;
configuration.dmg_palette %= 3;
configuration.border_mode %= GB_BORDER_ALWAYS + 1;
configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1;
}
if (configuration.model >= MODEL_MAX) {
configuration.model = MODEL_CGB;
}
if (configuration.default_scale == 0) {
configuration.default_scale = 2;
}
atexit(save_configuration);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
160 * configuration.default_scale, 144 * configuration.default_scale, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_SetWindowMinimumSize(window, 160, 144);
if (fullscreen) {
@ -660,24 +695,6 @@ int main(int argc, char **argv)
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
strcpy(prefs_path, resource_path("prefs.bin"));
if (access(prefs_path, R_OK | W_OK) != 0) {
char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
SDL_free(prefs_dir);
}
FILE *prefs_file = fopen(prefs_path, "rb");
if (prefs_file) {
fread(&configuration, 1, sizeof(configuration), prefs_file);
fclose(prefs_file);
}
if (configuration.model >= MODEL_MAX) {
configuration.model = MODEL_CGB;
}
atexit(save_configuration);
if (!init_shader_with_name(&shader, configuration.filter)) {
init_shader_with_name(&shader, "NearestNeighbor");
}

View File

@ -158,8 +158,5 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
ret *= output_resolution.y - pixel_position.y;
}
// Gamma correction
ret = pow(ret, vec4(0.72));
return ret;
}

View File

@ -12,7 +12,7 @@ STATIC vec3 rgb_to_hq_colorspace(vec4 rgb)
STATIC bool is_different(vec4 a, vec4 b)
{
vec3 diff = abs(rgb_to_hq_colorspace(a) - rgb_to_hq_colorspace(b));
return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031;
return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005;
}
#define P(m, r) ((pattern & (m)) == (r))
@ -31,7 +31,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
{
// o = offset, the width of a pixel
vec2 o = 1.0 / input_resolution;
/* We always calculate the top left pixel. If we need a different pixel, we flip the image */
// p = the position within a pixel [0...1]
@ -84,7 +84,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0);
}
if (P(0x2f,0x2f)) {
return interp_3px(w4, 1.04, w3, 1.0, w1, 1.0);
return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0);
}
if (P(0xbf,0x37) || P(0xdb,0x13)) {
return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0);
@ -110,6 +110,6 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
P(0x3b,0x1b)) {
return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0);
}
return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0);
}

View File

@ -9,14 +9,22 @@ uniform vec2 origin;
#define equal(x, y) ((x) == (y))
#define inequal(x, y) ((x) != (y))
#define STATIC
#define GAMMA (2.2)
out vec4 frag_color;
vec4 _texture(sampler2D t, vec2 pos)
{
return pow(texture(t, pos), vec4(GAMMA));
}
#define texture _texture
#line 1
{filter}
#define BLEND_BIAS (1.0/3.0)
#define BLEND_BIAS (2.0/5.0)
#define DISABLED 0
#define SIMPLE 1
@ -35,7 +43,7 @@ void main()
switch (frame_blending_mode) {
default:
case DISABLED:
frag_color = scale(image, position, input_resolution, output_resolution);
frag_color = pow(scale(image, position, input_resolution, output_resolution), vec4(1.0 / GAMMA));
return;
case SIMPLE:
ratio = 0.5;
@ -58,7 +66,7 @@ void main()
break;
}
frag_color = mix(scale(image, position, input_resolution, output_resolution),
scale(previous_image, position, input_resolution, output_resolution), ratio);
frag_color = pow(mix(scale(image, position, input_resolution, output_resolution),
scale(previous_image, position, input_resolution, output_resolution), ratio), vec4(1.0 / GAMMA));
}

View File

@ -12,6 +12,7 @@ typedef texture2d<half> sampler2D;
#define equal(x, y) all((x) == (y))
#define inequal(x, y) any((x) != (y))
#define STATIC static
#define GAMMA (2.2)
typedef struct {
float4 position [[position]];
@ -36,7 +37,7 @@ vertex rasterizer_data vertex_shader(uint index [[ vertex_id ]],
static inline float4 texture(texture2d<half> texture, float2 pos)
{
constexpr sampler texture_sampler;
return float4(texture.sample(texture_sampler, pos));
return pow(float4(texture.sample(texture_sampler, pos)), GAMMA);
}
#line 1
@ -87,7 +88,7 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]],
break;
}
return mix(scale(image, in.texcoords, input_resolution, *output_resolution),
scale(previous_image, in.texcoords, input_resolution, *output_resolution), ratio);
return pow(mix(scale(image, in.texcoords, input_resolution, *output_resolution),
scale(previous_image, in.texcoords, input_resolution, *output_resolution), ratio), 1 / GAMMA);
}

View File

@ -19,7 +19,7 @@ STATIC vec3 rgb_to_hq_colospace(vec4 rgb)
STATIC bool is_different(vec4 a, vec4 b)
{
vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b));
return diff.x > 0.125 || diff.y > 0.027 || diff.z > 0.031;
return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005;
}
#define P(m, r) ((pattern & (m)) == (r))

View File

@ -389,47 +389,12 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
}
}
static void init_for_current_model(unsigned id)
static void retro_set_memory_maps(void)
{
unsigned i = id;
enum model effective_model;
effective_model = model[i];
if (effective_model == MODEL_AUTO) {
effective_model = auto_model;
}
if (GB_is_inited(&gameboy[i])) {
GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]);
}
else {
GB_init(&gameboy[i], libretro_to_internal_model[effective_model]);
}
GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load);
/* When running multiple devices they are assumed to use the same resolution */
GB_set_pixels_output(&gameboy[i],
(uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i));
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
GB_set_rumble_callback(&gameboy[i], rumble_callback);
/* todo: attempt to make these more generic */
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);
if (emulated_devices == 2) {
GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2);
if (link_cable_emulation) {
set_link_cable_state(true);
}
}
struct retro_memory_descriptor descs[11];
size_t size;
uint16_t bank;
unsigned i;
/* todo: add netplay awareness for this so achievements can be granted on the respective client */
@ -489,6 +454,45 @@ static void init_for_current_model(unsigned id)
mmaps.descriptors = descs;
mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]);
environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
}
static void init_for_current_model(unsigned id)
{
unsigned i = id;
enum model effective_model;
effective_model = model[i];
if (effective_model == MODEL_AUTO) {
effective_model = auto_model;
}
if (GB_is_inited(&gameboy[i])) {
GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]);
}
else {
GB_init(&gameboy[i], libretro_to_internal_model[effective_model]);
}
GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load);
/* When running multiple devices they are assumed to use the same resolution */
GB_set_pixels_output(&gameboy[i],
(uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i));
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
GB_set_rumble_callback(&gameboy[i], rumble_callback);
/* todo: attempt to make these more generic */
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);
if (emulated_devices == 2) {
GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2);
if (link_cable_emulation) {
set_link_cable_state(true);
}
}
/* Let's be extremely nitpicky about how devices and descriptors are set */
if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) {
@ -1070,6 +1074,9 @@ bool retro_load_game(const struct retro_game_info *info)
}
check_variables();
retro_set_memory_maps();
return true;
}