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

This commit is contained in:
Maximilian Mader 2021-01-05 20:45:39 +01:00
commit 15fbf0994a
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
63 changed files with 3579 additions and 1028 deletions

4
.gitattributes vendored
View File

@ -1,3 +1,7 @@
# Always use LF line endings for shaders
*.fsh text eol=lf
*.metal text eol=lf
HexFiend/* linguist-vendored
*.inc linguist-language=C
Core/*.h linguist-language=C

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

@ -13,6 +13,18 @@ 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) {
fprintf(stderr, "write");
exit(1);
}
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;
}

79
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,79 @@
# SameBoy Coding and Contribution Guidelines
## Issues
GitHub Issues are the most effective way to report a bug or request a feature in SameBoy. When reporting a bug, make sure you use the latest stable release, and make sure you mention the SameBoy frontend (Cocoa, SDL, Libretro) and operating system you're using. If you're using Linux/BSD/etc, or you build your own copy of SameBoy for another reason, give as much details as possible on your environment.
If your bug involves a crash, please attach a crash log or a core dump. If you're using Linux/BSD/etc, or if you're using the Libretro core, please attach the `sameboy` binary (or `libretro_sameboy` library) in that case.
If your bug is a regression, it'd be extremely helpful if you can report the the first affected version. You get extra credits if you use `git bisect` to point the exact breaking commit.
If your bug is an emulation bug (Such as a failing test ROM), and you have access to a Game Boy you can test on, please confirm SameBoy is indeed behaving differently from hardware, and report both the emulated model and revision in SameBoy, and the hardware revision you're testing on.
If your issue is a feature request, demonstrating use cases can help me better prioritize it.
## Pull Requests
To allow quicker integration into SameBoy's master branch, contributors are asked to follow SameBoy's style and coding guidelines. Keep in mind that despite the seemingly strict guidelines, all pull requests are welcome not following the guidelines does not mean your pull request will not be accepted, but it will require manual tweaks from my side for integrating.
### Languages and Compilers
SameBoy's core, SDL frontend, Libretro frontend, and automatic tester (Folders `Core`, `SDL` & `OpenDialog`, `libretro`, and `Tester`; respectively) are all written in C11. The Cocoa frontend, SameBoy's fork of Hex Fiend, JoyKit and the Quick Look previewer (Folders `Cocoa`, `HexFiend`, `JoyKit` and `QuickLook`; respectively) are all written in ARC-enabled Objective-C. The SameBoot ROMs (Under `BootROMs`) are written in rgbds-flavor SM83 assembly, with build tools in C11. The shaders (inside `Shaders`) are written in a polyglot GLSL and Metal style, with a few GLSL- and Metal-specific sources. The build system uses standalone Make, in the GNU flavor. Avoid adding new languages (C++, Swift, Python, CMake...) to any of the existing sub-projects.
SameBoy's main target compiler is Clang, but GCC is also supported when targeting Linux and Libretro. Other compilers (e.g. MSVC) are not supported, and unless there's a good reason, there's no need to go out of your way to add specific support for them. Extensions that are supported by both compilers (Such as `typeof`) may be used if it makes sense. It's OK if you can't test one of these compilers yourself; once you push a commit, the CI bot will let you know if you broke something.
### Third Party Libraries and Tools
Avoid adding new required dependencies; run-time and compile-time dependencies alike. Most importantly, avoid linking against GPL licensed libraries (LGPL libraries are fine), so SameBoy can retain its MIT license.
### Spacing, Indentation and Formatting
In all files and languages (Other than Makefiles when required), 4 spaces are used for indentation. Unix line endings (`\n`) are used exclusively, even in Windows-specific source files. (`\r` and `\t` shouldn't appear in any source file). Opening braces belong on the same line as their control flow directive, and on their own line when following a function prototype. The `else` keyword always starts on its own line. The `case` keyword is indented relative to its `switch` block, and the code inside a `case` is indented relative to its label. A control flow keyword should have a space between it and the following `(`, commas should follow a space, and operator (except `.` and `->`) should be surrounded by spaces.
Control flow statements must use `{}`, with the exception of `if` statements that only contain a single `break`, `continue`, or trivial `return` statements. If `{}`s are omitted, the statement must be on the same line as the `if` condition. Functions that do not have any argument must be specified as `(void)`, as mandated by the C standard. The `sizeof` and `typeof` operators should be used as if they're functions (With `()`). `*`, when used to declare pointer types (including functions that return pointers), and when used to dereference a pointer, is attached to the right side (The variable name) not to the left, and not with spaces on both sides.
No strict limitations on a line's maximum width, but use your best judgement if you think a statement would benefit from an additional line break.
Well formatted code example:
```
static void my_function(void)
{
GB_something_t *thing = GB_function(&gb, GB_FLAG_ONE | GB_FLAG_TWO, sizeof(thing));
if (GB_is_thing(thing)) return;
switch (*thing) {
case GB_QUACK:
// Something
case GB_DUCK:
// Something else
}
}
```
Badly formatted code example:
```
static void my_function(){
GB_something_t* thing=GB_function(&gb , GB_FLAG_ONE|GB_FLAG_TWO , sizeof thing);
if( GB_is_thing ( thing ) )
return;
switch(* thing)
{
case GB_QUACK:
// Something
case GB_DUCK:
// Something else
}
}
```
### Other Coding Conventions
The primitive types to be used in SameBoy are `unsigned` and `signed` (Without the `int` keyword), the `(u)int*_t` types, `char *` for UTF-8 strings, `double` for non-integer numbers, and `bool` for booleans (Including in Objective-C code, avoid `BOOL`). As long as it's not mandated by a 3rd-party API (e.g. `int` when using file descriptors), avoid using other primitive types. Use `const` whenever possible.
Most C names should be `lower_case_snake_case`. Constants and macros use `UPPER_CASE_SNAKE_CASE`. Type definitions use a `_t` suffix. Type definitions, as well as non-static (exported) core symbols, should be prefixed with `GB_` (SameBoy's core is intended to be used as a library, so it shouldn't contaminate the global namespace without prefixes). Exported symbols that are only meant to be used by other parts of the core should still get the `GB_` prefix, but their header definition should be inside `#ifdef GB_INTERNAL`.
For Objective-C naming conventions, use Apple's conventions (Some old Objective-C code mixes these with the C naming convention; new code should use Apple's convention exclusively). The name prefix for SameBoy classes and constants is `GB`. JoyKit's prefix is `JOY`, and Hex Fiend's prefix is `HF`.
In all languages, prefer long, unambiguous names over short ambiguous ones.

View File

@ -1,6 +1,6 @@
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate>
@property IBOutlet NSWindow *preferencesWindow;
@property (strong) IBOutlet NSView *graphicsTab;
@ -10,6 +10,7 @@
- (IBAction)showPreferences: (id) sender;
- (IBAction)toggleDeveloperMode:(id)sender;
- (IBAction)switchPreferencesTab:(id)sender;
@property (weak) IBOutlet NSMenuItem *linkCableMenuItem;
@end

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
@ -79,10 +81,29 @@
if ([anItem action] == @selector(toggleDeveloperMode:)) {
[(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]];
}
if (anItem == self.linkCableMenuItem) {
return [[NSDocumentController sharedDocumentController] documents].count > 1;
}
return true;
}
- (void)menuNeedsUpdate:(NSMenu *)menu
{
NSMutableArray *items = [NSMutableArray array];
NSDocument *currentDocument = [[NSDocumentController sharedDocumentController] currentDocument];
for (NSDocument *document in [[NSDocumentController sharedDocumentController] documents]) {
if (document == currentDocument) continue;
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:document.displayName action:@selector(connectLinkCable:) keyEquivalent:@""];
item.representedObject = document;
item.image = [[NSWorkspace sharedWorkspace] iconForFile:document.fileURL.path];
[item.image setSize:NSMakeSize(16, 16)];
[items addObject:item];
}
menu.itemArray = items;
}
- (IBAction) showPreferences: (id) sender
{
NSArray *objects;
@ -107,4 +128,8 @@
{
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES];
}
- (IBAction)nop:(id)sender
{
}
@end

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

@ -6,6 +6,7 @@
@class GBCheatWindowController;
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
@property (readonly) GB_gameboy_t *gb;
@property (strong) IBOutlet GBView *view;
@property (strong) IBOutlet NSTextView *consoleOutput;
@property (strong) IBOutlet NSPanel *consoleWindow;
@ -36,10 +37,12 @@
@property (strong) IBOutlet NSBox *debuggerVerticalLine;
@property (strong) IBOutlet NSPanel *cheatsWindow;
@property (strong) IBOutlet GBCheatWindowController *cheatWindowController;
@property (readonly) Document *partner;
@property (readonly) bool isSlave;
-(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
-(void) performAtomicBlock: (void (^)())block;
-(void) connectLinkCable:(NSMenuItem *)sender;
@end

View File

@ -9,6 +9,7 @@
#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!!! */
@ -27,6 +28,7 @@ enum model {
NSMutableAttributedString *pending_console_output;
NSRecursiveLock *console_output_lock;
NSTimer *console_output_timer;
NSTimer *hex_timer;
bool fullScreen;
bool in_sync_input;
@ -46,7 +48,7 @@ enum model {
bool oamUpdating;
NSMutableData *currentPrinterImageData;
enum {GBAccessoryNone, GBAccessoryPrinter} accessory;
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory;
bool rom_warning_issued;
@ -65,6 +67,12 @@ enum model {
size_t audioBufferNeeded;
bool borderModeChanged;
/* Link cable*/
Document *master;
Document *slave;
signed linkOffset;
bool linkCableBit;
}
@property GBAudioClient *audioClient;
@ -80,6 +88,10 @@ enum model {
- (void) gotNewSample:(GB_sample_t *)sample;
- (void) rumbleChanged:(double)amp;
- (void) loadBootROM:(GB_boot_rom_t)type;
- (void)linkCableBitStart:(bool)bit;
- (bool)linkCableBitEnd;
- (void)infraredStateChanged:(bool)state;
@end
static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
@ -137,6 +149,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);
@ -149,6 +171,26 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self rumbleChanged:amp];
}
static void linkCableBitStart(GB_gameboy_t *gb, bool bit_to_send)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self linkCableBitStart:bit_to_send];
}
static bool linkCableBitEnd(GB_gameboy_t *gb)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
return [self linkCableBitEnd];
}
static void infraredStateChanged(GB_gameboy_t *gb, bool on)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self infraredStateChanged:on];
}
@implementation Document
{
GB_gameboy_t gb;
@ -244,6 +286,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput);
GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]);
GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]);
GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]);
GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]);
[self updatePalette];
GB_set_rgb_encode_callback(&gb, rgbEncode);
@ -253,6 +297,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
GB_apu_set_sample_callback(&gb, audioCallback);
GB_set_rumble_callback(&gb, rumbleCallback);
GB_set_infrared_callback(&gb, infraredStateChanged);
[self updateRumbleMode];
}
@ -324,9 +369,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[_view setRumble:amp];
}
- (void) run
- (void) preRun
{
running = true;
GB_set_pixels_output(&gb, self.view.pixels);
GB_set_sample_rate(&gb, 96000);
self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
@ -357,7 +401,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
[self.audioClient start];
}
NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
/* Clear pending alarms, don't play alarms while playing */
@ -377,19 +421,58 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
}
}
}
while (running) {
if (rewind) {
rewind = false;
GB_rewind_pop(&gb);
if (!GB_rewind_pop(&gb)) {
rewind = self.view.isRewinding;
}
static unsigned *multiplication_table_for_frequency(unsigned frequency)
{
unsigned *ret = malloc(sizeof(*ret) * 0x100);
for (unsigned i = 0; i < 0x100; i++) {
ret[i] = i * frequency;
}
return ret;
}
- (void) run
{
assert(!master);
running = true;
[self preRun];
if (slave) {
[slave preRun];
unsigned *masterTable = multiplication_table_for_frequency(GB_get_clock_rate(&gb));
unsigned *slaveTable = multiplication_table_for_frequency(GB_get_clock_rate(&slave->gb));
while (running) {
if (linkOffset <= 0) {
linkOffset += slaveTable[GB_run(&gb)];
}
else {
linkOffset -= masterTable[GB_run(&slave->gb)];
}
}
else {
GB_run(&gb);
free(masterTable);
free(slaveTable);
[slave postRun];
}
else {
while (running) {
if (rewind) {
rewind = false;
GB_rewind_pop(&gb);
if (!GB_rewind_pop(&gb)) {
rewind = self.view.isRewinding;
}
}
else {
GB_run(&gb);
}
}
}
[self postRun];
stopping = false;
}
- (void)postRun
{
[hex_timer invalidate];
[audioLock lock];
memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer));
@ -404,12 +487,13 @@ 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];
friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""];
friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName];
notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName];
notification.identifier = self.fileName;
@ -419,18 +503,31 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"];
}
[_view setRumble:0];
stopping = false;
}
- (void) start
{
if (running) return;
self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0;
if (master) {
[master start];
return;
}
if (running) return;
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}
- (void) stop
{
if (master) {
if (!master->running) return;
GB_debugger_set_disabled(&gb, true);
if (GB_debugger_is_stopped(&gb)) {
[self interruptDebugInputRead];
}
[master stop];
GB_debugger_set_disabled(&gb, false);
return;
}
if (!running) return;
GB_debugger_set_disabled(&gb, true);
if (GB_debugger_is_stopped(&gb)) {
@ -507,6 +604,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)togglePause:(id)sender
{
if (master) {
[master togglePause:sender];
return;
}
if (running) {
[self stop];
}
@ -568,6 +669,17 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [[self.fileURL path] lastPathComponent]];
self.debuggerSplitView.dividerColor = [NSColor clearColor];
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)
@ -579,6 +691,16 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
name:@"GBColorCorrectionChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateLightTemperature)
name:@"GBLightTemperatureChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateInterferenceVolume)
name:@"GBInterferenceVolumeChanged"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateFrameBlendingMode)
name:@"GBFrameBlendingModeChanged"
@ -726,6 +848,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)close
{
[self disconnectLinkCable];
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"];
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"];
[self stop];
@ -737,9 +860,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{
[self log:"^C\n"];
GB_debugger_break(&gb);
if (!running) {
[self start];
}
[self start];
[self.consoleWindow makeKeyAndOrderFront:nil];
[self.consoleInput becomeFirstResponder];
}
@ -758,10 +879,13 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
if ([anItem action] == @selector(mute:)) {
[(NSMenuItem*)anItem setState:!self.audioClient.isPlaying];
[(NSMenuItem *)anItem setState:!self.audioClient.isPlaying];
}
else if ([anItem action] == @selector(togglePause:)) {
[(NSMenuItem*)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))];
if (master) {
[(NSMenuItem *)anItem setState:(!master->running) || (GB_debugger_is_stopped(&gb)) || (GB_debugger_is_stopped(&gb))];
}
[(NSMenuItem *)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))];
return !GB_debugger_is_stopped(&gb);
}
else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) {
@ -778,6 +902,13 @@ 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(connectLinkCable:)) {
[(NSMenuItem*)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
[(NSMenuItem *)anItem representedObject] == slave];
}
else if ([anItem action] == @selector(toggleCheats:)) {
[(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)];
}
@ -1064,6 +1195,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{
while (!GB_is_inited(&gb));
bool was_running = running && !GB_debugger_is_stopped(&gb);
if (master) {
was_running |= master->running;
}
if (was_running) {
[self stop];
}
@ -1674,6 +1808,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)disconnectAllAccessories:(id)sender
{
[self disconnectLinkCable];
[self performAtomicBlock:^{
accessory = GBAccessoryNone;
GB_disconnect_serial(&gb);
@ -1682,12 +1817,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (IBAction)connectPrinter:(id)sender
{
[self disconnectLinkCable];
[self performAtomicBlock:^{
accessory = GBAccessoryPrinter;
GB_connect_printer(&gb, printImage);
}];
}
- (IBAction)connectWorkboy:(id)sender
{
[self disconnectLinkCable];
[self performAtomicBlock:^{
accessory = GBAccessoryWorkboy;
GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime);
}];
}
- (void) updateHighpassFilter
{
if (GB_is_inited(&gb)) {
@ -1702,6 +1847,20 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
}
}
- (void) updateLightTemperature
{
if (GB_is_inited(&gb)) {
GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]);
}
}
- (void) updateInterferenceVolume
{
if (GB_is_inited(&gb)) {
GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]);
}
}
- (void) updateFrameBlendingMode
{
self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
@ -1798,4 +1957,83 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{
GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb));
}
- (void)disconnectLinkCable
{
bool wasRunning = self->running;
Document *partner = master ?: slave;
if (partner) {
[self stop];
partner->master = nil;
partner->slave = nil;
master = nil;
slave = nil;
if (wasRunning) {
[partner start];
[self start];
}
GB_set_turbo_mode(&gb, false, false);
GB_set_turbo_mode(&partner->gb, false, false);
partner->accessory = GBAccessoryNone;
accessory = GBAccessoryNone;
}
}
- (void)connectLinkCable:(NSMenuItem *)sender
{
[self disconnectAllAccessories:sender];
Document *partner = [sender representedObject];
[partner disconnectAllAccessories:sender];
bool wasRunning = self->running;
[self stop];
[partner stop];
GB_set_turbo_mode(&partner->gb, true, true);
slave = partner;
partner->master = self;
linkOffset = 0;
partner->accessory = GBAccessoryLinkCable;
accessory = GBAccessoryLinkCable;
GB_set_serial_transfer_bit_start_callback(&gb, linkCableBitStart);
GB_set_serial_transfer_bit_start_callback(&partner->gb, linkCableBitStart);
GB_set_serial_transfer_bit_end_callback(&gb, linkCableBitEnd);
GB_set_serial_transfer_bit_end_callback(&partner->gb, linkCableBitEnd);
if (wasRunning) {
[self start];
}
}
- (void)linkCableBitStart:(bool)bit
{
linkCableBit = bit;
}
-(bool)linkCableBitEnd
{
bool ret = GB_serial_get_data_bit(&self.partner->gb);
GB_serial_set_data_bit(&self.partner->gb, linkCableBit);
return ret;
}
- (void)infraredStateChanged:(bool)state
{
if (self.partner) {
GB_set_infrared_input(&self.partner->gb, state);
}
}
-(Document *)partner
{
return slave ?: master;
}
- (bool)isSlave
{
return master;
}
- (GB_gameboy_t *)gb
{
return &gb;
}
@end

View File

@ -59,6 +59,9 @@
<view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<connections>
<outlet property="document" destination="-2" id="Fvh-rD-z4r"/>
</connections>
</view>
</subviews>
</customView>
@ -800,13 +803,11 @@
<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">
<size key="minSize" width="32" height="32"/>
<size key="maxSize" width="32" height="32"/>
<connections>
<action selector="savePrinterFeed:" target="-2" id="Dm3-h0-ch4"/>
</connections>
</toolbarItem>
<toolbarItem implicitItemIdentifier="NSToolbarPrintItem" id="mtd-zS-DXa"/>
<toolbarItem implicitItemIdentifier="NSToolbarPrintItem" explicitItemIdentifier="Print" id="mtd-zS-DXa"/>
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="AoG-LH-J4b"/>
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="Q0x-n5-Q2Y"/>
</allowedToolbarItems>
@ -977,7 +978,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" widthSizable="YES" heightSizable="YES"/>
<autoresizingMask key="autoresizingMask"/>
<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"/>

View File

@ -17,7 +17,8 @@
@property (strong) IBOutlet NSMenuItem *bootROMsFolderItem;
@property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton;
@property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton;
@property (weak) IBOutlet NSSlider *temperatureSlider;
@property (weak) IBOutlet NSSlider *interferenceSlider;
@property (weak) IBOutlet NSPopUpButton *dmgPopupButton;
@property (weak) IBOutlet NSPopUpButton *sgbPopupButton;
@property (weak) IBOutlet NSPopUpButton *cgbPopupButton;

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
@ -25,6 +26,8 @@
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
NSPopUpButton *_preferredJoypadButton;
NSPopUpButton *_rumbleModePopupButton;
NSSlider *_temperatureSlider;
NSSlider *_interferenceSlider;
}
+ (NSArray *)filterList
@ -52,6 +55,11 @@
return filters;
}
- (NSWindowToolbarStyle)toolbarStyle
{
return NSWindowToolbarStylePreference;
}
- (void)close
{
joystick_configuration_state = -1;
@ -85,11 +93,34 @@
[_colorCorrectionPopupButton selectItemAtIndex:mode];
}
- (NSPopUpButton *)colorCorrectionPopupButton
{
return _colorCorrectionPopupButton;
}
- (void)setTemperatureSlider:(NSSlider *)temperatureSlider
{
_temperatureSlider = temperatureSlider;
[temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256];
}
- (NSSlider *)temperatureSlider
{
return _temperatureSlider;
}
- (void)setInterferenceSlider:(NSSlider *)interferenceSlider
{
_interferenceSlider = interferenceSlider;
[interferenceSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"] * 256];
}
- (NSSlider *)interferenceSlider
{
return _interferenceSlider;
}
- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton
{
_frameBlendingModePopupButton = frameBlendingModePopupButton;
@ -278,6 +309,21 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil];
}
- (IBAction)lightTemperatureChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBLightTemperature"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil];
}
- (IBAction)volumeTemperatureChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBInterferenceVolume"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil];
}
- (IBAction)franeBlendingModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])

View File

@ -1,6 +1,7 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
#import <JoyKit/JoyKit.h>
@class Document;
typedef enum {
GB_FRAME_BLENDING_MODE_DISABLED,
@ -13,6 +14,7 @@ typedef enum {
@interface GBView : NSView<JOYListener>
- (void) flip;
- (uint32_t *) pixels;
@property (weak) IBOutlet Document *document;
@property GB_gameboy_t *gb;
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled;

View File

@ -1,13 +1,107 @@
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <Carbon/Carbon.h>
#import "GBView.h"
#import "GBViewGL.h"
#import "GBViewMetal.h"
#import "GBButtons.h"
#import "NSString+StringForKey.h"
#import "Document.h"
#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];
@ -164,6 +258,9 @@
{
if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
GB_set_clock_multiplier(_gb, analogClockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier);
}
if (analogClockMultiplier == 1.0) {
analogClockMultiplierValid = false;
}
@ -172,10 +269,16 @@
if (underclockKeyDown && clockMultiplier > 0.5) {
clockMultiplier -= 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
}
if (!underclockKeyDown && clockMultiplier < 1.0) {
clockMultiplier += 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
}
}
current_buffer = (current_buffer + 1) % self.numberOfBuffers;
@ -188,11 +291,27 @@
-(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];
unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
@ -202,13 +321,20 @@
handled = true;
switch (button) {
case GBTurbo:
GB_set_turbo_mode(_gb, true, self.isRewinding);
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, true, false);
}
else {
GB_set_turbo_mode(_gb, true, self.isRewinding);
}
analogClockMultiplierValid = false;
break;
case GBRewind:
self.isRewinding = true;
GB_set_turbo_mode(_gb, false, false);
if (!self.document.partner) {
self.isRewinding = true;
GB_set_turbo_mode(_gb, false, false);
}
break;
case GBUnderclock:
@ -217,7 +343,17 @@
break;
default:
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
if (self.document.partner) {
if (player == 0) {
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true);
}
else {
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true);
}
}
else {
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
}
break;
}
}
@ -232,10 +368,22 @@
-(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];
unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
@ -245,7 +393,12 @@
handled = true;
switch (button) {
case GBTurbo:
GB_set_turbo_mode(_gb, false, false);
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false);
}
analogClockMultiplierValid = false;
break;
@ -259,7 +412,17 @@
break;
default:
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
if (self.document.partner) {
if (player == 0) {
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, false);
}
else {
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, false);
}
}
else {
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
}
break;
}
}
@ -300,13 +463,11 @@
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{
if (![self.window isMainWindow]) return;
if (controller != lastController) {
[self setRumble:0];
lastController = controller;
}
unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
IOPMAssertionID assertionID;
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
@ -332,33 +493,63 @@
usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
}
GB_gameboy_t *effectiveGB = _gb;
unsigned effectivePlayer = player;
if (player && self.document.partner) {
effectiveGB = self.document.partner.gb;
effectivePlayer = 0;
if (controller != self.document.partner.view->lastController) {
[self setRumble:0];
self.document.partner.view->lastController = controller;
}
}
else {
if (controller != lastController) {
[self setRumble:0];
lastController = controller;
}
}
switch (usage) {
case JOYButtonUsageNone: break;
case JOYButtonUsageA: GB_set_key_state_for_player(_gb, GB_KEY_A, player, button.isPressed); break;
case JOYButtonUsageB: GB_set_key_state_for_player(_gb, GB_KEY_B, player, button.isPressed); break;
case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break;
case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break;
case JOYButtonUsageC: break;
case JOYButtonUsageStart:
case JOYButtonUsageX: GB_set_key_state_for_player(_gb, GB_KEY_START, player, button.isPressed); break;
case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break;
case JOYButtonUsageSelect:
case JOYButtonUsageY: GB_set_key_state_for_player(_gb, GB_KEY_SELECT, player, button.isPressed); break;
case JOYButtonUsageY: GB_set_key_state_for_player(effectiveGB, GB_KEY_SELECT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageR2:
case JOYButtonUsageL2:
case JOYButtonUsageZ: {
self.isRewinding = button.isPressed;
if (button.isPressed) {
GB_set_turbo_mode(_gb, false, false);
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false);
}
}
break;
}
case JOYButtonUsageL1: GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break;
case JOYButtonUsageL1: {
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false); break;
}
else {
GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break;
}
}
case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break;
case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, button.isPressed); break;
case JOYButtonUsageDPadRight: GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, button.isPressed); break;
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(_gb, GB_KEY_UP, player, button.isPressed); break;
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, button.isPressed); break;
case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadRight: GB_set_key_state_for_player(effectiveGB, GB_KEY_RIGHT, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break;
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break;
default:
break;

View File

@ -12,7 +12,11 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
<connections>
<outlet property="linkCableMenuItem" destination="V4S-Fo-xJK" id="KL9-3K-64i"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
@ -373,12 +377,29 @@
<action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/>
</connections>
</menuItem>
<menuItem title="Game Link Cable &amp; Infrared" id="V4S-Fo-xJK">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Game Link Cable &amp; Infrared" id="6sJ-Wz-QLj">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PMY-5j-25T"/>
</connections>
</menu>
<connections>
<action selector="nop:" target="Voe-Tx-rLC" id="Bpa-0C-lkN"/>
</connections>
</menuItem>
<menuItem title="Game Boy Printer" id="zHR-Ha-pOR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<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

@ -73,21 +73,23 @@
<outlet property="frameBlendingModePopupButton" destination="lxk-db-Sxv" id="wzt-uo-TE6"/>
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
<outlet property="highpassFilterPopupButton" destination="T69-6N-dhT" id="0p6-4m-hb1"/>
<outlet property="interferenceSlider" destination="FpE-5i-j5L" id="hfH-e8-7cx"/>
<outlet property="playerListButton" destination="gWx-7h-0xq" id="zo6-82-JId"/>
<outlet property="preferredJoypadButton" destination="0Az-0R-oNw" id="7JM-tw-BhK"/>
<outlet property="rewindPopupButton" destination="7fg-Ww-JjR" id="Ka2-TP-B1x"/>
<outlet property="rumbleModePopupButton" destination="Ogs-xG-b4b" id="vuw-VN-MTp"/>
<outlet property="sgbPopupButton" destination="dza-T7-RkX" id="B0o-Nb-pIH"/>
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
<outlet property="temperatureSlider" destination="NuA-mL-AJZ" id="w11-n7-Bmj"/>
</connections>
<point key="canvasLocation" x="183" y="354"/>
</window>
<customView id="sRK-wO-K6R">
<rect key="frame" x="0.0" y="0.0" width="292" height="323"/>
<rect key="frame" x="0.0" y="0.0" width="292" height="378"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
<rect key="frame" x="18" y="286" width="256" height="17"/>
<rect key="frame" x="18" y="341" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5">
<font key="font" metaFont="system"/>
@ -96,7 +98,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
<rect key="frame" x="30" y="254" width="234" height="26"/>
<rect key="frame" x="30" y="308" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Nearest neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -133,7 +135,7 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
<rect key="frame" x="18" y="232" width="256" height="17"/>
<rect key="frame" x="18" y="286" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color correction:" id="5Si-hz-EK3">
<font key="font" metaFont="system"/>
@ -142,7 +144,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
<rect key="frame" x="30" y="200" width="234" height="26"/>
<rect key="frame" x="30" y="254" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -173,7 +175,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
<rect key="frame" x="32" y="149" width="229" height="22"/>
<rect key="frame" x="30" y="149" width="231" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -262,8 +264,25 @@
<action selector="changeAspectRatio:" target="QvC-M9-y7g" id="mQG-Ib-1jN"/>
</connections>
</button>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NuA-mL-AJZ">
<rect key="frame" x="30" y="207" width="230" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" minValue="-256" maxValue="256" tickMarkPosition="below" numberOfTickMarks="3" sliderType="linear" id="KX7-G9-k0O"/>
<connections>
<action selector="lightTemperatureChanged:" target="QvC-M9-y7g" id="he8-ib-I3Y"/>
</connections>
</slider>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cCm-Oa-FbN">
<rect key="frame" x="18" y="232" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Ambient light temperature:" id="Lso-GQ-pBl">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="-176" y="667.5"/>
<point key="canvasLocation" x="-176" y="667"/>
</customView>
<customView id="ymk-46-SX7">
<rect key="frame" x="0.0" y="0.0" width="292" height="320"/>
@ -428,11 +447,11 @@
<point key="canvasLocation" x="-176" y="848"/>
</customView>
<customView id="Zn1-Y5-RbR">
<rect key="frame" x="0.0" y="0.0" width="292" height="86"/>
<rect key="frame" x="0.0" y="0.0" width="292" height="134"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
<rect key="frame" x="30" y="17" width="233" height="26"/>
<rect key="frame" x="30" y="65" width="233" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -452,7 +471,7 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
<rect key="frame" x="18" y="49" width="256" height="17"/>
<rect key="frame" x="18" y="97" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass filter:" id="YLF-RL-b2D">
<font key="font" metaFont="system"/>
@ -460,8 +479,25 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GPt-9I-QBh">
<rect key="frame" x="18" y="43" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Interference volume:" id="I2Q-6U-uIx">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FpE-5i-j5L">
<rect key="frame" x="30" y="18" width="232" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="256" tickMarkPosition="below" sliderType="linear" id="Rbx-DU-xYf"/>
<connections>
<action selector="volumeTemperatureChanged:" target="QvC-M9-y7g" id="HFU-0q-hj1"/>
</connections>
</slider>
</subviews>
<point key="canvasLocation" x="-176" y="890"/>
<point key="canvasLocation" x="-176" y="914"/>
</customView>
<customView id="8TU-6J-NCg">
<rect key="frame" x="0.0" y="0.0" width="292" height="467"/>
@ -490,7 +526,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
<rect key="frame" x="1" y="1" width="238" height="209"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
<rect key="frame" x="0.0" y="0.0" width="238" height="209"/>

View File

@ -137,6 +137,45 @@ static double smooth(double x)
return 3*x*x - 2*x*x*x;
}
static signed interference(GB_gameboy_t *gb)
{
/* These aren't scientifically measured, but based on ear based on several recordings */
signed ret = 0;
if (gb->halted) {
if (gb->model != GB_MODEL_AGB) {
ret -= MAX_CH_AMP / 5;
}
else {
ret -= MAX_CH_AMP / 12;
}
}
if (gb->io_registers[GB_IO_LCDC] & 0x80) {
ret += MAX_CH_AMP / 7;
if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model != GB_MODEL_AGB) {
ret += MAX_CH_AMP / 14;
}
else if ((gb->io_registers[GB_IO_STAT] & 3) == 1) {
ret -= MAX_CH_AMP / 7;
}
}
if (gb->apu.global_enable) {
ret += MAX_CH_AMP / 10;
}
if (GB_is_cgb(gb) && gb->model < GB_MODEL_AGB && (gb->io_registers[GB_IO_RP] & 1)) {
ret += MAX_CH_AMP / 10;
}
if (!GB_is_cgb(gb)) {
ret /= 4;
}
ret += rand() % (MAX_CH_AMP / 12);
return ret;
}
static void render(GB_gameboy_t *gb)
{
GB_sample_t output = {0, 0};
@ -226,19 +265,21 @@ static void render(GB_gameboy_t *gb)
}
if (gb->apu_output.interference_volume) {
signed interference_bias = interference(gb);
int16_t interference_sample = (interference_bias - gb->apu_output.interference_highpass);
gb->apu_output.interference_highpass = gb->apu_output.interference_highpass * gb->apu_output.highpass_rate +
(1 - gb->apu_output.highpass_rate) * interference_sample;
interference_bias *= gb->apu_output.interference_volume;
filtered_output.left = MAX(MIN(filtered_output.left + interference_bias, 0x7FFF), -0x8000);
filtered_output.right = MAX(MIN(filtered_output.right + interference_bias, 0x7FFF), -0x8000);
}
assert(gb->apu_output.sample_callback);
gb->apu_output.sample_callback(gb, &filtered_output);
}
static uint16_t new_sweep_sample_length(GB_gameboy_t *gb)
{
uint16_t delta = gb->apu.shadow_sweep_sample_length >> (gb->io_registers[GB_IO_NR10] & 7);
if (gb->io_registers[GB_IO_NR10] & 8) {
return gb->apu.shadow_sweep_sample_length - delta;
}
return gb->apu.shadow_sweep_sample_length + delta;
}
static void update_square_sample(GB_gameboy_t *gb, unsigned index)
{
if (gb->apu.square_channels[index].current_sample_index & 0x80) return;
@ -338,6 +379,26 @@ static void tick_noise_envelope(GB_gameboy_t *gb)
}
}
static void trigger_sweep_calculation(GB_gameboy_t *gb)
{
if ((gb->io_registers[GB_IO_NR10] & 0x70) && gb->apu.square_sweep_countdown == 7) {
if (gb->io_registers[GB_IO_NR10] & 0x07) {
gb->apu.square_channels[GB_SQUARE_1].sample_length =
gb->apu.sweep_length_addend + gb->apu.shadow_sweep_sample_length + !!(gb->io_registers[GB_IO_NR10] & 0x8);
gb->apu.square_channels[GB_SQUARE_1].sample_length &= 0x7FF;
}
if (gb->apu.channel_1_restart_hold == 0) {
gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length;
gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7);
}
/* Recalculation and overflow check only occurs after a delay */
gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div;
gb->apu.unshifted_sweep = !(gb->io_registers[GB_IO_NR10] & 0x7);
gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7;
}
}
void GB_apu_div_event(GB_gameboy_t *gb)
{
if (!gb->apu.global_enable) return;
@ -405,29 +466,34 @@ void GB_apu_div_event(GB_gameboy_t *gb)
}
if ((gb->apu.div_divider & 3) == 3) {
if (!gb->apu.sweep_enabled) {
return;
}
if (gb->apu.square_sweep_countdown) {
if (!--gb->apu.square_sweep_countdown) {
if ((gb->io_registers[GB_IO_NR10] & 0x70) && (gb->io_registers[GB_IO_NR10] & 0x07)) {
gb->apu.square_channels[GB_SQUARE_1].sample_length =
gb->apu.shadow_sweep_sample_length =
gb->apu.new_sweep_sample_length;
}
if (gb->io_registers[GB_IO_NR10] & 0x70) {
/* Recalculation and overflow check only occurs after a delay */
gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div;
}
gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7);
if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8;
}
}
gb->apu.square_sweep_countdown++;
gb->apu.square_sweep_countdown &= 7;
trigger_sweep_calculation(gb);
}
}
static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset)
{
unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000;
bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1;
gb->apu.noise_channel.lfsr >>= 1;
if (new_high_bit) {
gb->apu.noise_channel.lfsr |= high_bit_mask;
}
else {
/* This code is not redundent, it's relevant when switching LFSR widths */
gb->apu.noise_channel.lfsr &= ~high_bit_mask;
}
gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
if (gb->apu.is_active[GB_NOISE]) {
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
gb->apu.noise_channel.current_volume : 0,
cycles_offset);
}
}
void GB_apu_run(GB_gameboy_t *gb)
{
@ -435,28 +501,58 @@ void GB_apu_run(GB_gameboy_t *gb)
uint8_t cycles = gb->apu.apu_cycles >> 2;
gb->apu.apu_cycles = 0;
if (!cycles) return;
bool start_ch4 = false;
if (gb->apu.channel_4_dmg_delayed_start) {
if (gb->apu.channel_4_dmg_delayed_start == cycles) {
gb->apu.channel_4_dmg_delayed_start = 0;
start_ch4 = true;
}
else if (gb->apu.channel_4_dmg_delayed_start > cycles) {
gb->apu.channel_4_dmg_delayed_start -= cycles;
}
else {
/* Split it into two */
cycles -= gb->apu.channel_4_dmg_delayed_start;
gb->apu.apu_cycles = gb->apu.channel_4_dmg_delayed_start * 2;
GB_apu_run(gb);
}
}
if (likely(!gb->stopped || GB_is_cgb(gb))) {
/* To align the square signal to 1MHz */
gb->apu.lf_div ^= cycles & 1;
gb->apu.noise_channel.alignment += cycles;
if (gb->apu.square_sweep_calculate_countdown) {
if (gb->apu.square_sweep_calculate_countdown &&
(((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.unshifted_sweep) ||
gb->apu.square_sweep_calculate_countdown <= (gb->model > GB_MODEL_CGB_C? 3 : 1))) { // Calculation is paused if the lower bits
if (gb->apu.square_sweep_calculate_countdown > cycles) {
gb->apu.square_sweep_calculate_countdown -= cycles;
}
else {
/* APU bug: sweep frequency is checked after adding the sweep delta twice */
gb->apu.new_sweep_sample_length = new_sweep_sample_length(gb);
if (gb->apu.new_sweep_sample_length > 0x7ff) {
if (gb->apu.channel_1_restart_hold == 0) {
gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length;
}
if (gb->io_registers[GB_IO_NR10] & 8) {
gb->apu.sweep_length_addend ^= 0x7FF;
}
if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) {
gb->apu.is_active[GB_SQUARE_1] = false;
update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles);
gb->apu.sweep_enabled = false;
}
gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8;
gb->apu.square_sweep_calculate_countdown = 0;
}
}
if (gb->apu.channel_1_restart_hold) {
if (gb->apu.channel_1_restart_hold > cycles) {
gb->apu.channel_1_restart_hold -= cycles;
}
else {
gb->apu.channel_1_restart_hold = 0;
}
}
UNROLL
for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) {
@ -499,39 +595,38 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.wave_channel.wave_form_just_read = false;
}
}
if (gb->apu.is_active[GB_NOISE]) {
// The noise channel can step even if inactive on the DMG
if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) {
uint8_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) {
cycles_left -= gb->apu.noise_channel.sample_countdown + 1;
gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3;
unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2;
if (!divisor) divisor = 2;
if (gb->apu.noise_channel.counter_countdown == 0) {
gb->apu.noise_channel.counter_countdown = divisor;
}
while (unlikely(cycles_left >= gb->apu.noise_channel.counter_countdown)) {
cycles_left -= gb->apu.noise_channel.counter_countdown;
gb->apu.noise_channel.counter_countdown = divisor + gb->apu.channel_4_delta;
gb->apu.channel_4_delta = 0;
bool old_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1;
gb->apu.noise_channel.counter++;
gb->apu.noise_channel.counter &= 0x3FFF;
bool new_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1;
/* Step LFSR */
unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000;
bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1;
gb->apu.noise_channel.lfsr >>= 1;
if (new_high_bit) {
gb->apu.noise_channel.lfsr |= high_bit_mask;
if (new_bit && !old_bit) {
step_lfsr(gb, cycles - cycles_left);
if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) {
gb->apu.pcm_mask[1] &= 0x0F;
}
}
else {
/* This code is not redundent, it's relevant when switching LFSR widths */
gb->apu.noise_channel.lfsr &= ~high_bit_mask;
}
gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) {
gb->apu.pcm_mask[1] &= 0x0F;
}
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
gb->apu.noise_channel.current_volume : 0,
0);
}
if (cycles_left) {
gb->apu.noise_channel.sample_countdown -= cycles_left;
gb->apu.noise_channel.counter_countdown -= cycles_left;
gb->apu.channel_4_countdown_reloaded = false;
}
else {
gb->apu.channel_4_countdown_reloaded = true;
}
}
}
@ -544,6 +639,9 @@ void GB_apu_run(GB_gameboy_t *gb)
render(gb);
}
}
if (start_ch4) {
GB_apu_write(gb, GB_IO_NR44, gb->io_registers[GB_IO_NR44] | 0x80);
}
}
void GB_apu_init(GB_gameboy_t *gb)
{
@ -596,6 +694,9 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) {
return 0xFF;
}
if (gb->model == GB_MODEL_AGB) {
return 0xFF;
}
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
}
@ -636,11 +737,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
break;
case GB_IO_NR52: {
uint8_t old_nrx1[] = {
gb->io_registers[GB_IO_NR11],
gb->io_registers[GB_IO_NR21],
gb->io_registers[GB_IO_NR31],
gb->io_registers[GB_IO_NR41]
uint8_t old_pulse_lengths[] = {
gb->apu.square_channels[0].pulse_length,
gb->apu.square_channels[1].pulse_length,
gb->apu.wave_channel.pulse_length,
gb->apu.noise_channel.pulse_length
};
if ((value & 0x80) && !gb->apu.global_enable) {
GB_apu_init(gb);
@ -652,35 +753,31 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
memset(&gb->apu, 0, sizeof(gb->apu));
memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10);
old_nrx1[0] &= 0x3F;
old_nrx1[1] &= 0x3F;
gb->apu.global_enable = false;
}
if (!GB_is_cgb(gb) && (value & 0x80)) {
GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]);
GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]);
GB_apu_write(gb, GB_IO_NR31, old_nrx1[2]);
GB_apu_write(gb, GB_IO_NR41, old_nrx1[3]);
gb->apu.square_channels[0].pulse_length = old_pulse_lengths[0];
gb->apu.square_channels[1].pulse_length = old_pulse_lengths[1];
gb->apu.wave_channel.pulse_length = old_pulse_lengths[2];
gb->apu.noise_channel.pulse_length = old_pulse_lengths[3];
}
}
break;
/* Square channels */
case GB_IO_NR10:
if (gb->apu.sweep_decreasing && !(value & 8)) {
case GB_IO_NR10:{
bool old_negate = gb->io_registers[GB_IO_NR10] & 8;
gb->io_registers[GB_IO_NR10] = value;
if (gb->apu.square_sweep_calculate_countdown == 0 &&
gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend + old_negate > 0x7FF &&
!(value & 8)) {
gb->apu.is_active[GB_SQUARE_1] = false;
update_sample(gb, GB_SQUARE_1, 0, 0);
gb->apu.sweep_enabled = false;
gb->apu.square_sweep_calculate_countdown = 0;
}
if ((value & 0x70) == 0) {
/* Todo: what happens if we set period to 0 while a calculate event is scheduled, and then
re-set it to non-zero? */
gb->apu.square_sweep_calculate_countdown = 0;
}
trigger_sweep_calculation(gb);
break;
}
case GB_IO_NR11:
case GB_IO_NR21: {
@ -724,7 +821,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR14:
case GB_IO_NR24: {
unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1;
bool was_active = gb->apu.is_active[index];
/* TODO: When the sample length changes right before being updated, the countdown should change to the
old length, but the current sample should not change. Because our write timing isn't accurate to
the T-cycle, we hack around it by stepping the sample index backwards. */
@ -739,13 +836,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
}
uint16_t old_sample_length = gb->apu.square_channels[index].sample_length;
gb->apu.square_channels[index].sample_length &= 0xFF;
gb->apu.square_channels[index].sample_length |= (value & 7) << 8;
if (index == GB_SQUARE_1) {
gb->apu.shadow_sweep_sample_length =
gb->apu.new_sweep_sample_length =
gb->apu.square_channels[0].sample_length;
}
if (value & 0x80) {
/* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by
turning the APU off. */
@ -753,8 +846,21 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div;
}
else {
unsigned extra_delay = 0;
if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */) {
if ((!(value & 4) && ((gb->io_registers[reg] & 4) || old_sample_length == 0x3FF)) ||
(old_sample_length == 0x7FF && gb->apu.square_channels[index].sample_length != 0x7FF)) {
gb->apu.square_channels[index].current_sample_index++;
gb->apu.square_channels[index].current_sample_index &= 0x7;
}
else if (gb->apu.square_channels[index].sample_length == 0x7FF &&
old_sample_length != 0x7FF &&
(gb->apu.square_channels[index].current_sample_index & 0x80)) {
extra_delay += 2;
}
}
/* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div;
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div + extra_delay;
}
gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4;
@ -779,19 +885,23 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
if (index == GB_SQUARE_1) {
gb->apu.sweep_decreasing = false;
gb->apu.shadow_sweep_sample_length = 0;
if (gb->io_registers[GB_IO_NR10] & 7) {
/* APU bug: if shift is nonzero, overflow check also occurs on trigger */
gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div;
gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div;
gb->apu.unshifted_sweep = false;
if (gb->model > GB_MODEL_CGB_C && !was_active) {
gb->apu.square_sweep_calculate_countdown += 2;
}
gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length;
gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7);
}
else {
gb->apu.square_sweep_calculate_countdown = 0;
gb->apu.sweep_length_addend = 0;
}
gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x77;
gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7);
if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8;
gb->apu.channel_1_restart_hold = 2 - gb->apu.lf_div + GB_is_cgb(gb) * 2;
gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7;
}
}
/* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
@ -937,58 +1047,73 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR43: {
gb->apu.noise_channel.narrow = value & 8;
unsigned divisor = (value & 0x07) << 1;
if (!divisor) divisor = 1;
gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1;
/* Todo: changing the frequency sometimes delays the next sample. This is probably
due to how the frequency is actually calculated in the noise channel, which is probably
not by calculating the effective sample length and counting simiarly to the other channels.
This is not emulated correctly. */
bool old_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1;
gb->io_registers[GB_IO_NR43] = value;
bool new_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1;
if (gb->apu.channel_4_countdown_reloaded) {
unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2;
if (!divisor) divisor = 2;
gb->apu.noise_channel.counter_countdown =
divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]);
gb->apu.channel_4_delta = 0;
}
/* Step LFSR */
if (new_bit && !old_bit) {
step_lfsr(gb, 0);
}
break;
}
case GB_IO_NR44: {
if (value & 0x80) {
gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div;
/* I'm COMPLETELY unsure about this logic, but it passes all relevant tests.
See comment in NR43. */
if ((gb->io_registers[GB_IO_NR43] & 7) && (gb->apu.noise_channel.alignment & 2) == 0) {
if ((gb->io_registers[GB_IO_NR43] & 7) == 1) {
gb->apu.noise_channel.sample_countdown += 2;
if (!GB_is_cgb(gb) && (gb->apu.noise_channel.alignment & 3) != 0) {
gb->apu.channel_4_dmg_delayed_start = 6;
}
else {
unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2;
if (!divisor) divisor = 2;
gb->apu.channel_4_delta = 0;
gb->apu.noise_channel.counter_countdown = divisor + 4;
if (divisor == 2) {
gb->apu.noise_channel.counter_countdown += 1 - gb->apu.lf_div;
}
else {
gb->apu.noise_channel.sample_countdown -= 2;
gb->apu.noise_channel.counter_countdown += (uint8_t[]){2, 1, 0, 3}[gb->apu.noise_channel.alignment & 3];
if (((gb->apu.noise_channel.alignment + 1) & 3) < 2) {
if ((gb->io_registers[GB_IO_NR43] & 0x07) == 1) {
gb->apu.noise_channel.counter_countdown -= 2;
gb->apu.channel_4_delta = 2;
}
else {
gb->apu.noise_channel.counter_countdown -= 4;
}
}
}
}
if (gb->apu.is_active[GB_NOISE]) {
gb->apu.noise_channel.sample_countdown += 2;
}
gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4;
gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4;
/* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously
started sound). The playback itself is not instant which is why we don't update the sample for other
cases. */
if (gb->apu.is_active[GB_NOISE]) {
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
gb->apu.noise_channel.current_volume : 0,
0);
}
gb->apu.noise_channel.lfsr = 0;
gb->apu.current_lfsr_sample = false;
gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7;
/* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously
started sound). The playback itself is not instant which is why we don't update the sample for other
cases. */
if (gb->apu.is_active[GB_NOISE]) {
update_sample(gb, GB_NOISE,
gb->apu.current_lfsr_sample ?
gb->apu.noise_channel.current_volume : 0,
0);
}
gb->apu.noise_channel.lfsr = 0;
gb->apu.current_lfsr_sample = false;
gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7;
if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) {
gb->apu.is_active[GB_NOISE] = true;
update_sample(gb, GB_NOISE, 0, 0);
}
if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) {
gb->apu.is_active[GB_NOISE] = true;
update_sample(gb, GB_NOISE, 0, 0);
}
if (gb->apu.noise_channel.pulse_length == 0) {
gb->apu.noise_channel.pulse_length = 0x40;
gb->apu.noise_channel.length_enabled = false;
if (gb->apu.noise_channel.pulse_length == 0) {
gb->apu.noise_channel.pulse_length = 0x40;
gb->apu.noise_channel.length_enabled = false;
}
}
}
@ -1062,3 +1187,8 @@ void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb)
gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */
}
}
void GB_set_interference_volume(GB_gameboy_t *gb, double volume)
{
gb->apu_output.interference_volume = volume;
}

View File

@ -64,10 +64,10 @@ typedef struct
uint8_t square_sweep_countdown; // In 128Hz
uint8_t square_sweep_calculate_countdown; // In 2 MHz
uint16_t new_sweep_sample_length;
uint16_t sweep_length_addend;
uint16_t shadow_sweep_sample_length;
bool sweep_enabled;
bool sweep_decreasing;
bool unshifted_sweep;
GB_PADDING(bool, sweep_decreasing);
struct {
uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks
@ -105,8 +105,9 @@ typedef struct
uint16_t lfsr;
bool narrow;
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length)
uint16_t sample_length; // From NR43, in APU ticks
uint8_t counter_countdown; // Counts from 0-7 to 0 to tick counter (Scaled from 512KHz to 2MHz)
uint8_t __padding;
uint16_t counter; // A bit from this 14-bit register ticks LFSR
bool length_enabled; // NR44
uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of
@ -120,6 +121,10 @@ typedef struct
uint8_t skip_div_event;
bool current_lfsr_sample;
uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch
uint8_t channel_1_restart_hold;
int8_t channel_4_delta;
bool channel_4_countdown_reloaded;
uint8_t channel_4_dmg_delayed_start;
} GB_apu_t;
typedef enum {
@ -149,12 +154,16 @@ typedef struct {
GB_sample_callback_t sample_callback;
bool rate_set_in_clocks;
double interference_volume;
double interference_highpass;
} GB_apu_output_t;
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
void GB_set_interference_volume(GB_gameboy_t *gb, double volume);
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
#ifdef GB_INTERNAL
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);

View File

@ -1,26 +1,26 @@
#include "gb.h"
static signed noise_seed = 0;
static uint32_t noise_seed = 0;
/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported.
/* This is not a complete emulation of the camera chip. Only the features used by the Game Boy Camera ROMs are supported.
We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */
static uint8_t generate_noise(uint8_t x, uint8_t y)
{
signed value = (x + y * 128 + noise_seed);
uint8_t *data = (uint8_t *) &value;
unsigned hash = 0;
uint32_t value = (x * 151 + y * 149) ^ noise_seed;
uint32_t hash = 0;
while ((signed *) data != &value + 1) {
hash ^= (*data << 8);
if (hash & 0x8000) {
hash ^= 0x8a00;
hash ^= *data;
}
data++;
while (value) {
hash <<= 1;
if (hash & 0x100) {
hash ^= 0x101;
}
if (value & 0x80000000) {
hash ^= 0xA1;
}
value <<= 1;
}
return (hash >> 8);
return hash;
}
static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y)

View File

@ -69,7 +69,7 @@ void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, u
cheat->enabled = enabled;
strncpy(cheat->description, description, sizeof(cheat->description));
cheat->description[sizeof(cheat->description) - 1] = 0;
gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(*cheat));
gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(gb->cheats[0]));
gb->cheats[gb->cheat_count - 1] = cheat;
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)];
@ -100,7 +100,7 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
gb->cheats = NULL;
}
else {
gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(*cheat));
gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(gb->cheats[0]));
}
break;
}
@ -222,7 +222,7 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des
}
else {
(*hash)->size++;
*hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
*hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
(*hash)->cheats[(*hash)->size - 1] = cheat;
}
}

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;
}
@ -473,7 +473,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
size_t length, bool *error,
uint16_t *watchpoint_address, uint8_t *watchpoint_new_value)
{
/* Disable watchpoints while evaulating expressions */
/* Disable watchpoints while evaluating expressions */
uint16_t n_watchpoints = gb->n_watchpoints;
gb->n_watchpoints = 0;
@ -1533,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");
@ -1778,10 +1778,17 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : "");
if (channel == GB_SQUARE_1) {
GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n",
gb->apu.sweep_enabled? "active" : "inactive",
gb->apu.sweep_decreasing? "decreasing" : "increasing",
gb->apu.square_sweep_calculate_countdown);
GB_log(gb, " Frequency sweep %s and %s\n",
((gb->io_registers[GB_IO_NR10] & 0x7) && (gb->io_registers[GB_IO_NR10] & 0x70))? "active" : "inactive",
(gb->io_registers[GB_IO_NR10] & 0x8) ? "decreasing" : "increasing");
if (gb->apu.square_sweep_calculate_countdown) {
GB_log(gb, " On going frequency calculation will be ready in %u APU ticks\n",
gb->apu.square_sweep_calculate_countdown);
}
else {
GB_log(gb, " Shadow frequency register: 0x%03x\n", gb->apu.shadow_sweep_sample_length);
GB_log(gb, " Sweep addend register: 0x%03x\n", gb->apu.sweep_length_addend);
}
}
if (gb->apu.square_channels[channel].length_enabled) {
@ -1814,10 +1821,10 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "\nCH4:\n");
GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
GB_log(gb, " Current volume: %u, current internal counter: 0x%04x (next increase in %u ticks)\n",
gb->apu.noise_channel.current_volume,
gb->apu.noise_channel.sample_length * 4 + 3,
gb->apu.noise_channel.sample_countdown);
gb->apu.noise_channel.counter,
gb->apu.noise_channel.counter_countdown);
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.noise_channel.volume_countdown,
@ -1889,6 +1896,29 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
return true;
}
static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
if (strlen(lstrip(arguments))) {
print_usage(gb, command);
return true;
}
if (!gb->undo_label) {
GB_log(gb, "No undo state available\n");
return true;
}
uint16_t pc = gb->pc;
GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size(gb));
GB_log(gb, "Reverted a \"%s\" command.\n", gb->undo_label);
if (pc != gb->pc) {
GB_cpu_disassemble(gb, gb->pc, 5);
}
gb->undo_label = NULL;
return true;
}
static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command);
#define HELP_NEWLINE "\n "
@ -1899,6 +1929,7 @@ static const debugger_command_t commands[] = {
{"next", 1, next, "Run the next instruction, skipping over function calls"},
{"step", 1, step, "Run the next instruction, stepping into function calls"},
{"finish", 1, finish, "Run until the current function returns"},
{"undo", 1, undo, "Reverts the last command"},
{"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"},
@ -2184,7 +2215,30 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
const debugger_command_t *command = find_command(command_string);
if (command) {
return command->implementation(gb, arguments, modifiers, command);
uint8_t *old_state = malloc(GB_get_save_state_size(gb));
GB_save_state_to_buffer(gb, old_state);
bool ret = command->implementation(gb, arguments, modifiers, command);
if (!ret) { // Command continues, save state in any case
free(gb->undo_state);
gb->undo_state = old_state;
gb->undo_label = command->command;
}
else {
uint8_t *new_state = malloc(GB_get_save_state_size(gb));
GB_save_state_to_buffer(gb, new_state);
if (memcmp(new_state, old_state, GB_get_save_state_size(gb)) != 0) {
// State changed, save the old state as the new undo state
free(gb->undo_state);
gb->undo_state = old_state;
gb->undo_label = command->command;
}
else {
// Nothing changed, just free the old state
free(old_state);
}
free(new_state);
}
return ret;
}
else {
GB_log(gb, "%s: no such command.\n", command_string);
@ -2260,6 +2314,11 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
void GB_debugger_run(GB_gameboy_t *gb)
{
if (gb->debug_disable) return;
if (!gb->undo_state) {
gb->undo_state = malloc(GB_get_save_state_size(gb));
GB_save_state_to_buffer(gb, gb->undo_state);
}
char *input = NULL;
if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) {

View File

@ -2,6 +2,7 @@
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include "gb.h"
/* FIFO functions */
@ -208,6 +209,26 @@ static void display_vblank(GB_gameboy_t *gb)
GB_timing_sync(gb);
}
static inline void temperature_tint(double temperature, double *r, double *g, double *b)
{
if (temperature >= 0) {
*r = 1;
*g = pow(1 - temperature, 0.375);
if (temperature >= 0.75) {
*b = 0;
}
else {
*b = sqrt(0.75 - temperature);
}
}
else {
*b = 1;
double squared = pow(temperature, 2);
*g = 0.125 * squared + 0.3 * temperature + 1.0;
*r = 0.21875 * squared + 0.5 * temperature + 1.0;
}
}
static inline uint8_t scale_channel(uint8_t x)
{
return (x << 3) | (x >> 2);
@ -215,12 +236,12 @@ static inline uint8_t scale_channel(uint8_t x)
static inline uint8_t scale_channel_with_curve(uint8_t x)
{
return (uint8_t[]){0,5,8,11,16,22,28,36,43,51,59,67,77,87,97,107,119,130,141,153,166,177,188,200,209,221,230,238,245,249,252,255}[x];
return (uint8_t[]){0,6,12,20,28,36,45,56,66,76,88,100,113,125,137,149,161,172,182,192,202,210,218,225,232,238,243,247,250,252,254,255}[x];
}
static inline uint8_t scale_channel_with_curve_agb(uint8_t x)
{
return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x];
return (uint8_t[]){0,3,8,14,20,26,33,40,47,54,62,70,78,86,94,103,112,120,129,138,147,157,166,176,185,195,205,215,225,235,245,255}[x];
}
static inline uint8_t scale_channel_with_curve_sgb(uint8_t x)
@ -240,13 +261,12 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
g = scale_channel(g);
b = scale_channel(b);
}
else if (GB_is_sgb(gb) || for_border) {
r = scale_channel_with_curve_sgb(r);
g = scale_channel_with_curve_sgb(g);
b = scale_channel_with_curve_sgb(b);
}
else {
if (GB_is_sgb(gb) || for_border) {
return gb->rgb_encode_callback(gb,
scale_channel_with_curve_sgb(r),
scale_channel_with_curve_sgb(g),
scale_channel_with_curve_sgb(b));
}
bool agb = gb->model == GB_MODEL_AGB;
r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
@ -301,6 +321,14 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
}
}
if (gb->light_temperature) {
double light_r, light_g, light_b;
temperature_tint(gb->light_temperature, &light_r, &light_g, &light_b);
r = round(light_r * r);
g = round(light_g * g);
b = round(light_b * b);
}
return gb->rgb_encode_callback(gb, r, g, b);
}
@ -324,6 +352,17 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m
}
}
void GB_set_light_temperature(GB_gameboy_t *gb, double temperature)
{
gb->light_temperature = temperature;
if (GB_is_cgb(gb)) {
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, false, i * 2);
GB_palette_changed(gb, true, i * 2);
}
}
}
/*
STAT interrupt is implemented based on this finding:
http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531
@ -430,6 +469,23 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
}
}
static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use)
{
/*
Based on Matt Currie's research here:
https://github.com/mattcurrie/mealybug-tearoom-tests/blob/master/the-comprehensive-game-boy-ppu-documentation.md#tile_sel-bit-4
*/
*should_use = true;
if (gb->io_registers[GB_IO_LCDC] & 0x10) {
*should_use = !(gb->current_tile & 0x80);
/* if (gb->model != GB_MODEL_CGB_D) */ return gb->current_tile;
// TODO: CGB D behaves differently
}
return gb->data_for_sel_glitch;
}
static void render_pixel_if_possible(GB_gameboy_t *gb)
{
GB_fifo_item_t *fifo_item = NULL;
@ -621,6 +677,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
break;
case GB_FETCHER_GET_TILE_DATA_LOWER: {
bool use_glitched = false;
if (gb->tile_sel_glitch) {
gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched);
}
uint8_t y_flip = 0;
uint16_t tile_address = 0;
uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb);
@ -638,20 +698,32 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
if (gb->current_tile_attributes & 0x40) {
y_flip = 0x7;
}
gb->current_tile_data[0] =
if (!use_glitched) {
gb->current_tile_data[0] =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[0] = 0xFF;
}
}
else {
gb->data_for_sel_glitch =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[0] = 0xFF;
if (gb->vram_ppu_blocked) {
gb->data_for_sel_glitch = 0xFF;
}
}
}
gb->fetcher_state++;
break;
case GB_FETCHER_GET_TILE_DATA_HIGH: {
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong.
Additionally, on CGB-D and newer mixing two tiles by changing the tileset
bit mid-fetching causes a glitched mixing of the two, in comparison to the
more logical DMG version. */
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
bool use_glitched = false;
if (gb->tile_sel_glitch) {
gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched);
}
uint16_t tile_address = 0;
uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb);
@ -669,10 +741,20 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
y_flip = 0x7;
}
gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1;
gb->current_tile_data[1] =
gb->vram[gb->last_tile_data_address];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[1] = 0xFF;
if (!use_glitched) {
gb->current_tile_data[1] =
gb->vram[gb->last_tile_data_address];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[1] = 0xFF;
}
}
else {
if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) {
gb->data_for_sel_glitch = gb->vram[gb->last_tile_data_address];
if (gb->vram_ppu_blocked) {
gb->data_for_sel_glitch = 0xFF;
}
}
}
}
if (gb->wx_triggered) {
@ -778,7 +860,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
GB_STATE(gb, display, 28);
GB_STATE(gb, display, 29);
GB_STATE(gb, display, 30);
// GB_STATE(gb, display, 31);
GB_STATE(gb, display, 31);
GB_STATE(gb, display, 32);
GB_STATE(gb, display, 33);
GB_STATE(gb, display, 34);
@ -810,13 +892,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
/* Handle mode 2 on the very first line 0 */
gb->current_line = 0;
gb->window_y = -1;
/* Todo: verify timings */
if (gb->io_registers[GB_IO_WY] == 0) {
gb->wy_triggered = true;
}
else {
gb->wy_triggered = false;
}
gb->wy_triggered = false;
gb->ly_for_comparison = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
@ -867,11 +943,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
/* Lines 0 - 143 */
gb->window_y = -1;
for (; gb->current_line < LINES; gb->current_line++) {
/* Todo: verify timings */
if ((gb->io_registers[GB_IO_WY] == gb->current_line ||
(gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) {
gb->wy_triggered = true;
}
gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed;
gb->accessed_oam_row = 0;
@ -951,6 +1022,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->cycles_for_line += 2;
GB_SLEEP(gb, display, 32, 2);
mode_3_start:
/* TODO: Timing seems incorrect, might need an access conflict handling. */
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
gb->io_registers[GB_IO_WY] == gb->current_line) {
gb->wy_triggered = true;
}
fifo_clear(&gb->bg_fifo);
fifo_clear(&gb->oam_fifo);
@ -1112,7 +1188,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
object->flags & 0x80,
gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0,
object->flags & 0x20);
gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1];
gb->n_visible_objs--;
}
@ -1128,6 +1205,14 @@ abort_fetching_object:
GB_SLEEP(gb, display, 21, 1);
}
/* TODO: Verify */
if (gb->fetcher_state == 4 || gb->fetcher_state == 5) {
gb->data_for_sel_glitch = gb->current_tile_data[0];
}
else {
gb->data_for_sel_glitch = gb->current_tile_data[1];
}
while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) {
/* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */
uint32_t *dest = NULL;
@ -1191,7 +1276,16 @@ abort_fetching_object:
if (gb->hdma_on_hblank) {
gb->hdma_starting = true;
}
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2);
/*
TODO: Verify double speed timing
TODO: Timing differs on a DMG
*/
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
(gb->io_registers[GB_IO_WY] == gb->current_line)) {
gb->wy_triggered = true;
}
GB_SLEEP(gb, display, 31, 2);
gb->mode_for_interrupt = 2;
// Todo: unverified timing
@ -1285,14 +1379,7 @@ abort_fetching_object:
gb->current_line = 0;
/* Todo: verify timings */
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
(gb->io_registers[GB_IO_WY] == 0)) {
gb->wy_triggered = true;
}
else {
gb->wy_triggered = false;
}
gb->wy_triggered = false;
// TODO: not the correct timing
gb->current_lcd_line = 0;

View File

@ -58,5 +58,6 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette
uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height);
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border);
void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode);
void GB_set_light_temperature(GB_gameboy_t *gb, double temperature);
bool GB_is_odd_frame(GB_gameboy_t *gb);
#endif /* display_h */

View File

@ -202,6 +202,9 @@ void GB_free(GB_gameboy_t *gb)
if (gb->nontrivial_jump_state) {
free(gb->nontrivial_jump_state);
}
if (gb->undo_state) {
free(gb->undo_state);
}
#ifndef GB_DISABLE_DEBUGGER
GB_debugger_clear_symbols(gb);
#endif
@ -302,6 +305,9 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path)
fread(gb->rom, 1, gb->rom_size, f);
fclose(f);
GB_configure_cart(gb);
gb->tried_loading_sgb_border = false;
gb->has_sgb_border = false;
load_default_border(gb);
return 0;
}
@ -534,6 +540,9 @@ error:
gb->rom_size = old_size;
}
fclose(f);
gb->tried_loading_sgb_border = false;
gb->has_sgb_border = false;
load_default_border(gb);
return -1;
}
@ -554,6 +563,9 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz
memset(gb->rom, 0xff, gb->rom_size);
memcpy(gb->rom, buffer, size);
GB_configure_cart(gb);
gb->tried_loading_sgb_border = false;
gb->has_sgb_border = false;
load_default_border(gb);
}
typedef struct {
@ -1073,17 +1085,6 @@ void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback)
void GB_set_infrared_input(GB_gameboy_t *gb, bool state)
{
gb->infrared_input = state;
gb->cycles_since_input_ir_change = 0;
gb->ir_queue_length = 0;
}
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change)
{
if (gb->ir_queue_length == GB_MAX_IR_QUEUE) {
GB_log(gb, "IR Queue is full\n");
return;
}
gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change};
}
void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback)
@ -1131,8 +1132,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)
@ -1444,6 +1446,10 @@ void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model)
gb->ram = realloc(gb->ram, gb->ram_size = 0x2000);
gb->vram = realloc(gb->vram, gb->vram_size = 0x2000);
}
if (gb->undo_state) {
free(gb->undo_state);
gb->undo_state = NULL;
}
GB_rewind_free(gb);
GB_reset(gb);
load_default_border(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;
@ -120,8 +125,6 @@ typedef enum {
GB_BORDER_ALWAYS,
} GB_border_mode_t;
#define GB_MAX_IR_QUEUE 256
enum {
/* Joypad and Serial */
GB_IO_JOYP = 0x00, // Joypad (R/W)
@ -267,7 +270,7 @@ typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update);
typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on);
typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, double rumble_amplitude);
typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send);
typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb);
@ -278,11 +281,6 @@ typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type);
typedef struct {
bool state;
uint64_t delay;
} GB_ir_queue_item_t;
struct GB_breakpoint_s;
struct GB_watchpoint_s;
@ -368,6 +366,10 @@ 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;
int32_t ir_sensor;
bool effective_ir_input;
);
/* DMA and HDMA */
@ -434,7 +436,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 +444,7 @@ struct GB_gameboy_internal_s {
bool huc3_alarm_enabled;
uint8_t huc3_read;
uint8_t huc3_access_flags;
bool mbc3_rtc_mapped;
);
@ -541,6 +544,7 @@ struct GB_gameboy_internal_s {
uint16_t last_tile_data_address;
uint16_t last_tile_index_address;
bool cgb_repeated_a_frame;
uint8_t data_for_sel_glitch;
);
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
@ -569,6 +573,7 @@ struct GB_gameboy_internal_s {
uint32_t sprite_palettes_rgb[0x20];
const GB_palette_t *dmg_palette;
GB_color_correction_mode_t color_correction_mode;
double light_temperature;
bool keys[4][GB_KEY_MAX];
GB_border_mode_t border_mode;
GB_sgb_border_t borrowed_border;
@ -603,11 +608,8 @@ struct GB_gameboy_internal_s {
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;
/* IR */
uint64_t cycles_since_ir_change; // In 8MHz units
uint64_t cycles_since_input_ir_change; // In 8MHz units
GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE];
size_t ir_queue_length;
GB_workboy_set_time_callback workboy_set_time_callback;
GB_workboy_get_time_callback workboy_get_time_callback;
/*** Debugger ***/
volatile bool debug_stopped, debug_disable;
@ -645,6 +647,10 @@ struct GB_gameboy_internal_s {
/* Ticks command */
uint64_t debugger_ticks;
/* Undo */
uint8_t *undo_state;
const char *undo_label;
/* Rewind */
#define GB_REWIND_FRAMES_PER_KEY 255
size_t rewind_buffer_length;
@ -682,6 +688,7 @@ struct GB_gameboy_internal_s {
/* Temporary state */
bool wx_just_changed;
bool tile_sel_glitch;
);
};
@ -759,7 +766,6 @@ void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output);
void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode);
void GB_set_infrared_input(GB_gameboy_t *gb, bool state);
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change); /* In 8MHz units*/
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
@ -793,9 +799,7 @@ void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callbac
void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback);
void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback);
#ifdef GB_INTERNAL
uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
#endif
void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier);
unsigned GB_get_screen_width(GB_gameboy_t *gb);

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;

View File

@ -113,11 +113,6 @@ static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr)
return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src);
}
static bool effective_ir_input(GB_gameboy_t *gb)
{
return gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir;
}
static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0x100 && !gb->boot_rom_finished) {
@ -173,7 +168,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
case 0xD: // RTC status
return 1;
case 0xE: // IR mode
return effective_ir_input(gb); // TODO: What are the other bits?
return gb->effective_ir_input; // TODO: What are the other bits?
default:
GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr);
return 1; // TODO: What happens in this case?
@ -183,7 +178,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) {
@ -191,21 +186,21 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
}
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
return 0xc0 | effective_ir_input(gb);
return 0xc0 | gb->effective_ir_input;
}
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 +208,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;
}
@ -424,9 +423,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
case GB_IO_RP: {
if (!gb->cgb_mode) return 0xFF;
/* You will read your own IR LED if it's on. */
uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C;
if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && effective_ir_input(gb)) {
ret |= 2;
uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x2E;
if (gb->model != GB_MODEL_CGB_E) {
ret |= 0x10;
}
if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model != GB_MODEL_AGB) {
ret &= ~2;
}
return ret;
}
@ -509,7 +511,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));
@ -645,14 +650,11 @@ static bool huc3_write(GB_gameboy_t *gb, uint8_t value)
// Not sure what writes here mean, they're always 0xFE
return true;
case 0xE: { // IR mode
bool old_input = effective_ir_input(gb);
gb->cart_ir = value & 1;
bool new_input = effective_ir_input(gb);
if (new_input != old_input) {
if (gb->cart_ir != (value & 1)) {
gb->cart_ir = value & 1;
if (gb->infrared_callback) {
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
gb->infrared_callback(gb, value & 1);
}
gb->cycles_since_ir_change = 0;
}
return true;
}
@ -677,32 +679,34 @@ 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) {
bool old_input = effective_ir_input(gb);
gb->cart_ir = value & 1;
bool new_input = effective_ir_input(gb);
if (new_input != old_input) {
if (gb->cart_ir != (value & 1)) {
gb->cart_ir = value & 1;
if (gb->infrared_callback) {
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
gb->infrared_callback(gb, value & 1);
}
gb->cycles_since_ir_change = 0;
}
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)
@ -891,6 +895,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
gb->display_cycles = 0;
gb->display_state = 0;
gb->double_speed_alignment = 0;
if (GB_is_sgb(gb)) {
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
@ -1096,15 +1101,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (!GB_is_cgb(gb)) {
return;
}
bool old_input = effective_ir_input(gb);
gb->io_registers[GB_IO_RP] = value;
bool new_input = effective_ir_input(gb);
if (new_input != old_input) {
if ((gb->io_registers[GB_IO_RP] ^ value) & 1) {
if (gb->infrared_callback) {
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
gb->infrared_callback(gb, value & 1);
}
gb->cycles_since_ir_change = 0;
}
gb->io_registers[GB_IO_RP] = value;
return;
}

View File

@ -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;
}
}

View File

@ -54,8 +54,8 @@ typedef struct
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

@ -25,14 +25,17 @@ void GB_handle_rumble(GB_gameboy_t *gb)
unsigned volume = (gb->io_registers[GB_IO_NR50] & 7) + 1 + ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
unsigned ch4_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 8) + !!(gb->io_registers[GB_IO_NR51] & 0x80));
unsigned ch1_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 1) + !!(gb->io_registers[GB_IO_NR51] & 0x10));
double ch4_rumble = (MIN(gb->apu.noise_channel.sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0;
unsigned ch4_divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 1;
if (!ch4_divisor) ch4_divisor = 1;
unsigned ch4_sample_length = (ch4_divisor << (gb->io_registers[GB_IO_NR43] >> 4)) - 1;
double ch4_rumble = (MIN(ch4_sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0;
ch4_rumble = MIN(ch4_rumble, 1.0);
ch4_rumble = MAX(ch4_rumble, 0.0);
double ch1_rumble = 0;
if (gb->apu.sweep_enabled && ((gb->io_registers[GB_IO_NR10] >> 4) & 7)) {
if ((gb->io_registers[GB_IO_NR10] & 0x7) && (gb->io_registers[GB_IO_NR10] & 0x70)) {
double sweep_speed = (gb->io_registers[GB_IO_NR10] & 7) / (double)((gb->io_registers[GB_IO_NR10] >> 4) & 7);
ch1_rumble = gb->apu.square_channels[GB_SQUARE_1].current_volume * ch1_volume / 32.0 * sweep_speed / 8.0 - 0.5;
ch1_rumble = MIN(ch1_rumble, 1.0);

View File

@ -150,7 +150,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t
{
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);
memmove(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

View File

@ -5,10 +5,16 @@
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use
#ifdef __cplusplus
/* For bsnes integration. C++ code does not need section information, and throws a fit over certain types such
as anonymous enums inside unions */
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__
#else
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) union {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]
#define GB_SECTION_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))
#endif
#define GB_aligned_double __attribute__ ((aligned (8))) double

View File

@ -21,10 +21,12 @@ typedef enum {
GB_CONFLICT_DMG_LCDC,
GB_CONFLICT_SGB_LCDC,
GB_CONFLICT_WX,
GB_CONFLICT_CGB_LCDC,
} GB_conflict_t;
/* Todo: How does double speed mode affect these? */
static const GB_conflict_t cgb_conflict_map[0x80] = {
[GB_IO_LCDC] = GB_CONFLICT_CGB_LCDC,
[GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
[GB_IO_LYC] = GB_CONFLICT_WRITE_CPU,
[GB_IO_STAT] = GB_CONFLICT_STAT_CGB,
@ -241,6 +243,36 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->wx_just_changed = false;
gb->pending_cycles = 3;
return;
case GB_CONFLICT_CGB_LCDC:
if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) {
// Todo: This is difference is because my timing is off in one of the models
if (gb->model > GB_MODEL_CGB_C) {
GB_advance_cycles(gb, gb->pending_cycles);
GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first
gb->tile_sel_glitch = true;
GB_advance_cycles(gb, 1);
gb->tile_sel_glitch = false;
GB_write_memory(gb, addr, value);
gb->pending_cycles = 3;
}
else {
GB_advance_cycles(gb, gb->pending_cycles - 1);
GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first
gb->tile_sel_glitch = true;
GB_advance_cycles(gb, 1);
gb->tile_sel_glitch = false;
GB_write_memory(gb, addr, value);
gb->pending_cycles = 4;
}
}
else {
GB_advance_cycles(gb, gb->pending_cycles);
GB_write_memory(gb, addr, value);
gb->pending_cycles = 4;
}
return;
}
}
@ -261,7 +293,20 @@ static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id)
}
GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */
gb->pending_cycles = 4;
}
static void cycle_oam_bug_pc(GB_gameboy_t *gb)
{
if (GB_is_cgb(gb)) {
/* Slight optimization */
gb->pending_cycles += 4;
return;
}
if (gb->pending_cycles) {
GB_advance_cycles(gb, gb->pending_cycles);
}
GB_trigger_oam_bug(gb, gb->pc); /* Todo: test T-cycle timing */
gb->pending_cycles = 4;
}
static void flush_pending_cycles(GB_gameboy_t *gb)
@ -287,6 +332,7 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode)
static void enter_stop_mode(GB_gameboy_t *gb)
{
GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0);
gb->stopped = true;
gb->oam_ppu_blocked = !gb->oam_read_blocked;
gb->vram_ppu_blocked = !gb->vram_read_blocked;
@ -295,14 +341,16 @@ static void enter_stop_mode(GB_gameboy_t *gb)
static void leave_stop_mode(GB_gameboy_t *gb)
{
/* The CPU takes more time to wake up then the other components */
for (unsigned i = 0x200; i--;) {
GB_advance_cycles(gb, 0x10);
}
gb->stopped = false;
gb->oam_ppu_blocked = false;
gb->vram_ppu_blocked = false;
gb->cgb_palettes_ppu_blocked = false;
/* The CPU takes more time to wake up then the other components */
for (unsigned i = 0x1FFF; i--;) {
GB_advance_cycles(gb, 0x10);
}
GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xF);
GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0);
}
static void stop(GB_gameboy_t *gb, uint8_t opcode)
@ -313,9 +361,11 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
GB_advance_cycles(gb, 0x4);
/* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */
if (gb->double_speed_alignment & 7) {
GB_advance_cycles(gb, 0x4);
needs_alignment = true;
GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n");
}
gb->cgb_double_speed ^= true;
@ -332,16 +382,14 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
else {
GB_timing_sync(gb);
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
/* HW Bug? When STOP is executed while a button is down, the CPU halts forever
yet the other hardware keeps running. */
gb->interrupt_enable = 0;
/* TODO: HW Bug? When STOP is executed while a button is down, the CPU enters halt
mode instead. Fine details not confirmed yet. */
gb->halted = true;
}
else {
enter_stop_mode(gb);
}
}
/* Todo: is PC being actually read? */
gb->pc++;
}
@ -1538,8 +1586,9 @@ void GB_cpu_run(GB_gameboy_t *gb)
gb->halted = false;
uint16_t call_addr = gb->pc;
cycle_no_access(gb);
cycle_no_access(gb);
gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++);
cycle_oam_bug_pc(gb);
gb->pc--;
GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */
cycle_no_access(gb);

View File

@ -89,15 +89,32 @@ void GB_timing_sync(GB_gameboy_t *gb)
}
#endif
static void GB_ir_run(GB_gameboy_t *gb)
#define IR_DECAY 31500
#define IR_THRESHOLD 19900
#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY
static void GB_ir_run(GB_gameboy_t *gb, uint32_t cycles)
{
if (gb->ir_queue_length == 0) return;
if (gb->cycles_since_input_ir_change >= gb->ir_queue[0].delay) {
gb->cycles_since_input_ir_change -= gb->ir_queue[0].delay;
gb->infrared_input = gb->ir_queue[0].state;
gb->ir_queue_length--;
memmove(&gb->ir_queue[0], &gb->ir_queue[1], sizeof(gb->ir_queue[0]) * (gb->ir_queue_length));
if (gb->model == GB_MODEL_AGB) return;
if (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1)) {
gb->ir_sensor += cycles;
if (gb->ir_sensor > IR_MAX) {
gb->ir_sensor = IR_MAX;
}
gb->effective_ir_input = gb->ir_sensor >= IR_THRESHOLD && gb->ir_sensor <= IR_THRESHOLD + IR_DECAY;
}
else {
if (gb->ir_sensor <= cycles) {
gb->ir_sensor = 0;
}
else {
gb->ir_sensor -= cycles;
}
gb->effective_ir_input = false;
}
}
static void advance_tima_state_machine(GB_gameboy_t *gb)
@ -140,7 +157,9 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles)
{
if (gb->stopped) {
gb->apu.apu_cycles += 4 << !gb->cgb_double_speed;
if (GB_is_cgb(gb)) {
gb->apu.apu_cycles += 4 << !gb->cgb_double_speed;
}
return;
}
@ -231,11 +250,11 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
}
// Not affected by speed boost
gb->double_speed_alignment += cycles;
if (gb->io_registers[GB_IO_LCDC] & 0x80) {
gb->double_speed_alignment += cycles;
}
gb->hdma_cycles += cycles;
gb->apu_output.sample_cycles += cycles;
gb->cycles_since_ir_change += cycles;
gb->cycles_since_input_ir_change += cycles;
gb->cycles_since_last_sync += cycles;
gb->cycles_since_run += cycles;
@ -252,7 +271,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
}
GB_apu_run(gb);
GB_display_run(gb, cycles);
GB_ir_run(gb);
GB_ir_run(gb, cycles);
}
/*
@ -291,8 +310,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

@ -16,7 +16,7 @@ endif
ifeq ($(PLATFORM),windows32)
_ := $(shell chcp 65001)
EXESUFFIX:=.exe
NATIVE_CC = clang -IWindows -Wno-deprecated-declarations
NATIVE_CC = clang -IWindows -Wno-deprecated-declarations --target=i386-pc-windows
else
EXESUFFIX:=
NATIVE_CC := cc
@ -36,7 +36,7 @@ ifeq ($(MAKECMDGOALS),)
MAKECMDGOALS := $(DEFAULT)
endif
VERSION := 0.13.2
include version.mk
export VERSION
CONF ?= debug
SDL_AUDIO_DRIVER ?= sdl
@ -76,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
@ -92,13 +99,18 @@ endif
# These must come before the -Wno- flags
WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option
WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context
WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation
# Only add this flag if the compiler supports it
ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0)
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
@ -117,8 +129,8 @@ GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl)
GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL)
endif
ifeq ($(PLATFORM),windows32)
CFLAGS += -IWindows -Drandom=rand
LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lSDL2main -Wl,/MANIFESTFILE:NUL
CFLAGS += -IWindows -Drandom=rand --target=i386-pc-windows
LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lshell32 -lole32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows
SDL_LDFLAGS := -lSDL2
GL_LDFLAGS := -lopengl32
else
@ -134,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
@ -232,24 +244,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
@ -277,14 +289,14 @@ $(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
$(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL \
@ -299,10 +311,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) -bundle -framework Cocoa -framework Quicklook
ifeq ($(CONF), release)
true -u -r -s QuickLook/exports.sym $@
endif
$(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.
@ -315,7 +324,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
@ -416,7 +425,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE
$(realpath $(PB12_COMPRESS)) < $< > $@
$(PB12_COMPRESS): BootROMs/pb12.c
$(NATIVE_CC) -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

@ -18,3 +18,21 @@ char *do_open_rom_dialog(void)
return NULL;
}
}
char *do_open_folder_dialog(void)
{
@autoreleasepool {
NSWindow *key = [NSApp keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
dialog.title = @"Select Boot ROMs Folder";
dialog.canChooseDirectories = true;
dialog.canChooseFiles = false;
[dialog runModal];
[key makeKeyAndOrderFront:nil];
NSString *ret = [[[dialog URLs] firstObject] path];
if (ret) {
return strdup(ret.UTF8String);
}
return NULL;
}
}

View File

@ -6,6 +6,7 @@
#include <string.h>
#define GTK_FILE_CHOOSER_ACTION_OPEN 0
#define GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER 2
#define GTK_RESPONSE_ACCEPT -3
#define GTK_RESPONSE_CANCEL -6
@ -111,3 +112,71 @@ lazy_error:
fprintf(stderr, "Failed to display GTK dialog\n");
return NULL;
}
char *do_open_folder_dialog(void)
{
static void *handle = NULL;
TRY_DLOPEN("libgtk-3.so");
TRY_DLOPEN("libgtk-3.so.0");
TRY_DLOPEN("libgtk-2.so");
TRY_DLOPEN("libgtk-2.so.0");
if (!handle) {
goto lazy_error;
}
LAZY(gtk_init_check);
LAZY(gtk_file_chooser_dialog_new);
LAZY(gtk_dialog_run);
LAZY(g_free);
LAZY(gtk_widget_destroy);
LAZY(gtk_file_chooser_get_filename);
LAZY(g_log_set_default_handler);
LAZY(gtk_file_filter_new);
LAZY(gtk_file_filter_add_pattern);
LAZY(gtk_file_filter_set_name);
LAZY(gtk_file_chooser_add_filter);
LAZY(gtk_events_pending);
LAZY(gtk_main_iteration);
/* Shut up GTK */
g_log_set_default_handler(nop, NULL);
gtk_init_check(0, 0);
void *dialog = gtk_file_chooser_dialog_new("Select Boot ROMs Folder",
0,
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL );
int res = gtk_dialog_run (dialog);
char *ret = NULL;
if (res == GTK_RESPONSE_ACCEPT) {
char *filename;
filename = gtk_file_chooser_get_filename(dialog);
ret = strdup(filename);
g_free(filename);
}
while (gtk_events_pending()) {
gtk_main_iteration();
}
gtk_widget_destroy(dialog);
while (gtk_events_pending()) {
gtk_main_iteration();
}
return ret;
lazy_error:
fprintf(stderr, "Failed to display GTK dialog\n");
return NULL;
}

View File

@ -2,5 +2,5 @@
#define open_rom_h
char *do_open_rom_dialog(void);
char *do_open_folder_dialog(void);
#endif /* open_rom_h */

View File

@ -1,10 +1,11 @@
#include <windows.h>
#include <shlobj.h>
#include "open_dialog.h"
char *do_open_rom_dialog(void)
{
OPENFILENAMEW dialog;
wchar_t filename[MAX_PATH] = {0};
static wchar_t filename[MAX_PATH] = {0};
memset(&dialog, 0, sizeof(dialog));
dialog.lStructSize = sizeof(dialog);
@ -25,3 +26,32 @@ char *do_open_rom_dialog(void)
return NULL;
}
char *do_open_folder_dialog(void)
{
BROWSEINFOW dialog;
memset(&dialog, 0, sizeof(dialog));
dialog.ulFlags = BIF_USENEWUI;
dialog.lpszTitle = L"Select Boot ROMs Folder";
OleInitialize(NULL);
LPITEMIDLIST list = SHBrowseForFolderW(&dialog);
static wchar_t filename[MAX_PATH] = {0};
if (list) {
if (!SHGetPathFromIDListW(list, filename)) {
OleUninitialize();
return NULL;
}
char *ret = malloc(MAX_PATH * 4);
WideCharToMultiByte(CP_UTF8, 0, filename, sizeof(filename), ret, MAX_PATH * 4, NULL, NULL);
CoTaskMemFree(list);
OleUninitialize();
return ret;
}
OleUninitialize();
return NULL;
}

View File

@ -1,3 +1 @@
_DeallocQuickLookGeneratorPluginType
_QuickLookGeneratorQueryInterface
_QuickLookGeneratorPluginFactory

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

@ -41,12 +41,12 @@ typedef struct __QuickLookGeneratorPluginType
// Forward declaration for the IUnknown implementation.
//
QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID);
void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance);
HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv);
void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID);
ULONG QuickLookGeneratorPluginAddRef(void *thisInstance);
ULONG QuickLookGeneratorPluginRelease(void *thisInstance);
static QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID);
static void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance);
static HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv);
extern void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID);
static ULONG QuickLookGeneratorPluginAddRef(void *thisInstance);
static ULONG QuickLookGeneratorPluginRelease(void *thisInstance);
// -----------------------------------------------------------------------------
// myInterfaceFtbl definition

View File

@ -33,21 +33,24 @@ Features currently supported only with the Cocoa version:
## Compatibility
SameBoy passes all of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), all of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) tests (Some tests require the original boot ROMs), and all of [Wilbert Pol's tests](https://github.com/wilbertpol/mooneye-gb/tree/master/tests/acceptance). SameBoy should work with most games and demos, please [report](https://github.com/LIJI32/SameBoy/issues/new) any broken ROM. The latest results for SameBoy's automatic tester are available [here](https://sameboy.github.io/automation/).
## Contributing
SameBoy is an open-source project licensed under the MIT license, and you're welcome to contribute by creating issues, implementing new features, improving emulation accuracy and fixing existing open issues. You can read the [contribution guidelines](CONTRIBUTING.md) to make sure your contributions are as effective as possible.
## Compilation
SameBoy requires the following tools and libraries to build:
* clang
* clang (Recommended; required for macOS) or GCC
* make
* Cocoa port: OS X SDK and Xcode command line tools
* macOS Cocoa port: macOS SDK and Xcode (For command line tools and ibtool)
* SDL port: libsdl2
* [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation
* [rgbds](https://github.com/gbdev/rgbds/releases/), for boot ROM compilation
On Windows, SameBoy also requires:
* Visual Studio (For headers, etc.)
* [GnuWin](http://gnuwin32.sourceforge.net/)
* Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively.
* Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ](https://github.com/LIJI32/SameBoy/blob/master/build-faq.md) for more details on Windows compilation)
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 is compiled and tested on macOS and Ubuntu.

View File

@ -1033,6 +1033,26 @@ uint8_t font[] = {
_, _, _, X, X, _,
_, _, _, _, X, _,
_, _, _, _, _, _,
/* Elipsis */
_, _, _, _, _, _,
_, _, _, _, _, _,
_, _, _, _, _, _,
_, _, _, _, _, _,
_, _, _, _, _, _,
_, _, _, _, _, _,
X, _, X, _, X, _,
_, _, _, _, _, _,
/* Mojibake */
X, X, X, X, X, _,
X, _, _, _, X, _,
X, _, _, _, X, _,
X, _, _, _, X, _,
X, _, _, _, X, _,
X, _, _, _, X, _,
X, _, _, _, X, _,
X, X, X, X, X, _,
};
const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' ';

View File

@ -12,5 +12,7 @@ extern const uint8_t font_max;
#define CTRL_STRING "\x80\x81\x82"
#define SHIFT_STRING "\x83"
#define CMD_STRING "\x84\x85"
#define ELLIPSIS_STRING "\x87"
#define MOJIBAKE_STRING "\x88"
#endif /* font_h */

266
SDL/gui.c
View File

@ -109,6 +109,8 @@ configuration_t configuration =
.model = MODEL_CGB,
.volume = 100,
.rumble_mode = GB_RUMBLE_ALL_GAMES,
.default_scale = 2,
.color_temperature = 10,
};
@ -170,8 +172,12 @@ void update_viewport(void)
}
}
/* Does NOT check for bounds! */
static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color)
static void rescale_window(void)
{
SDL_SetWindowSize(window, GB_get_screen_width(&gb) * configuration.default_scale, GB_get_screen_height(&gb) * configuration.default_scale);
}
static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color, uint32_t *mask_top, uint32_t *mask_bottom)
{
if (ch < ' ' || ch > font_max) {
ch = '?';
@ -181,7 +187,7 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne
for (unsigned y = GLYPH_HEIGHT; y--;) {
for (unsigned x = GLYPH_WIDTH; x--;) {
if (*(data++)) {
if (*(data++) && buffer >= mask_top && buffer < mask_bottom) {
(*buffer) = color;
}
buffer++;
@ -190,8 +196,8 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne
}
}
static unsigned scroll = 0;
static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color)
static signed scroll = 0;
static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color)
{
y -= scroll;
unsigned orig_x = x;
@ -204,17 +210,17 @@ static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned heig
continue;
}
if (x > width - GLYPH_WIDTH || y == 0 || y - y_offset > 144 - GLYPH_HEIGHT) {
if (x > width - GLYPH_WIDTH) {
break;
}
draw_char(&buffer[x + width * y], width, height, *string, color);
draw_char(&buffer[(signed)(x + width * y)], width, height, *string, color, &buffer[width * y_offset], &buffer[width * (y_offset + 144)]);
x += GLYPH_WIDTH;
string++;
}
}
static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border)
static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border)
{
draw_unbordered_text(buffer, width, height, x - 1, y, string, border);
draw_unbordered_text(buffer, width, height, x + 1, y, string, border);
@ -255,6 +261,10 @@ struct menu_item {
};
static const struct menu_item *current_menu = NULL;
static const struct menu_item *root_menu = NULL;
static unsigned menu_height;
static unsigned scrollbar_size;
static bool mouse_scroling = false;
static unsigned current_selection = 0;
static enum {
@ -296,6 +306,23 @@ static void open_rom(unsigned index)
}
}
static void recalculate_menu_height(void)
{
menu_height = 24;
scrollbar_size = 0;
if (gui_state == SHOWING_MENU) {
for (const struct menu_item *item = current_menu; item->string; item++) {
menu_height += 12;
if (item->backwards_handler) {
menu_height += 12;
}
}
}
if (menu_height > 144) {
scrollbar_size = 144 * 140 / menu_height;
}
}
static const struct menu_item paused_menu[] = {
{"Resume", NULL},
{"Open ROM", open_rom},
@ -316,6 +343,7 @@ static void return_to_root_menu(unsigned index)
current_menu = root_menu;
current_selection = 0;
scroll = 0;
recalculate_menu_height();
}
static void cycle_model(unsigned index)
@ -414,9 +442,50 @@ const char *current_rewind_string(unsigned index)
return "Custom";
}
const char *current_bootrom_string(unsigned index)
{
if (!configuration.bootrom_path[0]) {
return "Built-in Boot ROMs";
}
size_t length = strlen(configuration.bootrom_path);
static char ret[24] = {0,};
if (length <= 23) {
strcpy(ret, configuration.bootrom_path);
}
else {
memcpy(ret, configuration.bootrom_path, 11);
memcpy(ret + 12, configuration.bootrom_path + length - 11, 11);
}
for (unsigned i = 0; i < 24; i++) {
if (ret[i] < 0) {
ret[i] = MOJIBAKE_STRING[0];
}
}
if (length > 23) {
ret[11] = ELLIPSIS_STRING[0];
}
return ret;
}
static void toggle_bootrom(unsigned index)
{
if (configuration.bootrom_path[0]) {
configuration.bootrom_path[0] = 0;
}
else {
char *folder = do_open_folder_dialog();
if (!folder) return;
if (strlen(folder) < sizeof(configuration.bootrom_path) - 1) {
strcpy(configuration.bootrom_path, folder);
}
free(folder);
}
}
static const struct menu_item emulation_menu[] = {
{"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards},
{"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards},
{"Boot ROMs Folder:", toggle_bootrom, current_bootrom_string, toggle_bootrom},
{"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards},
{"Back", return_to_root_menu},
{NULL,}
@ -427,6 +496,7 @@ static void enter_emulation_menu(unsigned index)
current_menu = emulation_menu;
current_selection = 0;
scroll = 0;
recalculate_menu_height();
}
const char *current_scaling_mode(unsigned index)
@ -435,12 +505,45 @@ 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"}
[configuration.color_correction_mode];
}
const char *current_color_temperature(unsigned index)
{
return (const char *[]){"12000K",
"11450K",
"10900K",
"10350K",
"9800K",
"9250K",
"8700K",
"8150K",
"7600K",
"7050K",
"6500K (White)",
"5950K",
"5400K",
"4850K",
"4300K",
"3750K",
"3200K",
"2650K",
"2100K",
"1550K",
"1000K"}
[configuration.color_temperature];
}
const char *current_palette(unsigned index)
{
return (const char *[]){"Greyscale", "Lime (Game Boy)", "Olive (Pocket)", "Teal (Light)"}
@ -475,6 +578,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) {
@ -495,6 +624,20 @@ static void cycle_color_correction_backwards(unsigned index)
}
}
static void decrease_color_temperature(unsigned index)
{
if (configuration.color_temperature < 20) {
configuration.color_temperature++;
}
}
static void increase_color_temperature(unsigned index)
{
if (configuration.color_temperature > 0) {
configuration.color_temperature--;
}
}
static void cycle_palette(unsigned index)
{
if (configuration.dmg_palette == 3) {
@ -643,8 +786,10 @@ 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},
{"Ambient Light Temp.:", decrease_color_temperature, current_color_temperature, increase_color_temperature},
{"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards},
{"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards},
{"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards},
@ -657,6 +802,7 @@ static void enter_graphics_menu(unsigned index)
current_menu = graphics_menu;
current_selection = 0;
scroll = 0;
recalculate_menu_height();
}
const char *highpass_filter_string(unsigned index)
@ -706,9 +852,33 @@ void decrease_volume(unsigned index)
}
}
const char *interference_volume_string(unsigned index)
{
static char ret[5];
sprintf(ret, "%d%%", configuration.interference_volume);
return ret;
}
void increase_interference_volume(unsigned index)
{
configuration.interference_volume += 5;
if (configuration.interference_volume > 100) {
configuration.interference_volume = 100;
}
}
void decrease_interference_volume(unsigned index)
{
configuration.interference_volume -= 5;
if (configuration.interference_volume > 100) {
configuration.interference_volume = 0;
}
}
static const struct menu_item audio_menu[] = {
{"Highpass Filter:", cycle_highpass_filter, highpass_filter_string, cycle_highpass_filter_backwards},
{"Volume:", increase_volume, volume_string, decrease_volume},
{"Interference Volume:", increase_interference_volume, interference_volume_string, decrease_interference_volume},
{"Back", return_to_root_menu},
{NULL,}
};
@ -718,6 +888,7 @@ static void enter_audio_menu(unsigned index)
current_menu = audio_menu;
current_selection = 0;
scroll = 0;
recalculate_menu_height();
}
static void modify_key(unsigned index)
@ -759,6 +930,7 @@ static void enter_controls_menu(unsigned index)
current_menu = controls_menu;
current_selection = 0;
scroll = 0;
recalculate_menu_height();
}
static unsigned joypad_index = 0;
@ -776,7 +948,7 @@ const char *current_joypad_name(unsigned index)
// SDL returns a name with repeated and trailing spaces
while (*orig_name && i < sizeof(name) - 2) {
if (orig_name[0] != ' ' || orig_name[1] != ' ') {
name[i++] = *orig_name;
name[i++] = *orig_name > 0? *orig_name : MOJIBAKE_STRING[0];
}
orig_name++;
}
@ -894,6 +1066,7 @@ static void enter_joypad_menu(unsigned index)
current_menu = joypad_menu;
current_selection = 0;
scroll = 0;
recalculate_menu_height();
}
joypad_button_t get_joypad_button(uint8_t physical_button)
@ -978,6 +1151,7 @@ void run_gui(bool is_running)
gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE;
bool should_render = true;
current_menu = root_menu = is_running? paused_menu : nonpaused_menu;
recalculate_menu_height();
current_selection = 0;
scroll = 0;
do {
@ -1165,9 +1339,26 @@ void run_gui(bool is_running)
}
break;
}
case SDL_MOUSEWHEEL: {
if (menu_height > 144) {
scroll -= event.wheel.y;
if (scroll < 0) {
scroll = 0;
}
if (scroll >= menu_height - 144) {
scroll = menu_height - 144;
}
mouse_scroling = true;
should_render = true;
}
break;
}
case SDL_KEYDOWN:
if (event.key.keysym.scancode == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) {
if (event_hotkey_code(&event) == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) {
if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) {
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
@ -1176,7 +1367,7 @@ void run_gui(bool is_running)
}
update_viewport();
}
if (event.key.keysym.scancode == SDL_SCANCODE_O) {
if (event_hotkey_code(&event) == SDL_SCANCODE_O) {
if (event.key.keysym.mod & MODIFIER) {
char *filename = do_open_rom_dialog();
if (filename) {
@ -1202,7 +1393,11 @@ void run_gui(bool is_running)
}
}
else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
if (is_running) {
if (gui_state == SHOWING_MENU && current_menu != root_menu) {
return_to_root_menu(0);
should_render = true;
}
else if (is_running) {
return;
}
else {
@ -1213,18 +1408,22 @@ void run_gui(bool is_running)
gui_state = SHOWING_DROP_MESSAGE;
}
current_selection = 0;
mouse_scroling = false;
scroll = 0;
current_menu = root_menu;
recalculate_menu_height();
should_render = true;
}
}
else if (gui_state == SHOWING_MENU) {
if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) {
current_selection++;
mouse_scroling = false;
should_render = true;
}
else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) {
current_selection--;
mouse_scroling = false;
should_render = true;
}
else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && !current_menu[current_selection].backwards_handler) {
@ -1301,13 +1500,21 @@ void run_gui(bool is_running)
draw_text_centered(pixels, width, height, 8 + y_offset, "SameBoy", gui_palette_native[3], gui_palette_native[0], false);
unsigned i = 0, y = 24;
for (const struct menu_item *item = current_menu; item->string; item++, i++) {
if (i == current_selection) {
if (y < scroll) {
scroll = y - 4;
goto rerender;
if (i == current_selection && !mouse_scroling) {
if (i == 0) {
if (y < scroll) {
scroll = (y - 4) / 12 * 12;
goto rerender;
}
}
else {
if (y < scroll + 24) {
scroll = (y - 24) / 12 * 12;
goto rerender;
}
}
}
if (i == current_selection && i == 0 && scroll != 0) {
if (i == current_selection && i == 0 && scroll != 0 && !mouse_scroling) {
scroll = 0;
goto rerender;
}
@ -1324,19 +1531,38 @@ void run_gui(bool is_running)
i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE);
y += 12;
if (item->value_getter) {
draw_text_centered(pixels, width, height, y + y_offset, item->value_getter(i), gui_palette_native[3], gui_palette_native[0],
draw_text_centered(pixels, width, height, y + y_offset - 1, item->value_getter(i), gui_palette_native[3], gui_palette_native[0],
i == current_selection ? DECORATION_ARROWS : DECORATION_NONE);
y += 12;
}
}
if (i == current_selection) {
if (i == current_selection && !mouse_scroling) {
if (y > scroll + 144) {
scroll = y - 144;
scroll = (y - 144) / 12 * 12;
if (scroll > menu_height - 144) {
scroll = menu_height - 144;
}
goto rerender;
}
}
}
if (scrollbar_size) {
unsigned scrollbar_offset = (140 - scrollbar_size) * scroll / (menu_height - 144);
if (scrollbar_offset + scrollbar_size > 140) {
scrollbar_offset = 140 - scrollbar_size;
}
for (unsigned y = 0; y < 140; y++) {
uint32_t *pixel = pixels + x_offset + 156 + width * (y + y_offset + 2);
if (y >= scrollbar_offset && y < scrollbar_offset + scrollbar_size) {
pixel[0] = pixel[1]= gui_palette_native[2];
}
else {
pixel[0] = pixel[1]= gui_palette_native[1];
}
}
}
break;
case SHOWING_HELP:
draw_text(pixels, width, height, 2 + x_offset, 2 + y_offset, help[current_help_page], gui_palette_native[3], gui_palette_native[0]);

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,14 @@ typedef struct {
GB_border_mode_t border_mode;
uint8_t volume;
GB_rumble_mode_t rumble_mode;
uint8_t default_scale;
/* v0.14 */
unsigned padding;
uint8_t color_temperature;
char bootrom_path[4096];
uint8_t interference_volume;
} configuration_t;
extern configuration_t configuration;
@ -119,4 +128,13 @@ void connect_joypad(void);
joypad_button_t get_joypad_button(uint8_t physical_button);
joypad_axis_t get_joypad_axis(uint8_t physical_axis);
static SDL_Scancode event_hotkey_code(SDL_Event *event)
{
if (event->key.keysym.sym >= SDLK_a && event->key.keysym.sym < SDLK_z) {
return SDL_SCANCODE_A + event->key.keysym.sym - SDLK_a;
}
return event->key.keysym.scancode;
}
#endif

View File

@ -120,6 +120,8 @@ static void open_menu(void)
GB_audio_set_paused(false);
}
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0);
GB_set_interference_volume(&gb, configuration.interference_volume / 100.0);
GB_set_border_mode(&gb, configuration.border_mode);
update_palette();
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
@ -233,7 +235,7 @@ static void handle_events(GB_gameboy_t *gb)
};
case SDL_KEYDOWN:
switch (event.key.keysym.scancode) {
switch (event_hotkey_code(&event)) {
case SDL_SCANCODE_ESCAPE: {
open_menu();
break;
@ -241,7 +243,6 @@ static void handle_events(GB_gameboy_t *gb)
case SDL_SCANCODE_C:
if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) {
GB_debugger_break(gb);
}
break;
@ -448,8 +449,6 @@ static bool handle_pending_command(void)
static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type)
{
bool error = false;
start_capturing_logs();
static const char *const names[] = {
[GB_BOOT_ROM_DMG0] = "dmg0_boot.bin",
[GB_BOOT_ROM_DMG] = "dmg_boot.bin",
@ -460,8 +459,17 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type)
[GB_BOOT_ROM_CGB] = "cgb_boot.bin",
[GB_BOOT_ROM_AGB] = "agb_boot.bin",
};
GB_load_boot_rom(gb, resource_path(names[type]));
end_capturing_logs(true, error);
bool use_built_in = true;
if (configuration.bootrom_path[0]) {
static char path[4096];
snprintf(path, sizeof(path), "%s/%s", configuration.bootrom_path, names[type]);
use_built_in = GB_load_boot_rom(gb, path);
}
if (use_built_in) {
start_capturing_logs();
GB_load_boot_rom(gb, resource_path(names[type]));
end_capturing_logs(true, false);
}
}
static void run(void)
@ -497,6 +505,8 @@ restart:
GB_set_rumble_mode(&gb, configuration.rumble_mode);
GB_set_sample_rate(&gb, GB_audio_get_frequency());
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0);
GB_set_interference_volume(&gb, configuration.interference_volume / 100.0);
update_palette();
if ((unsigned)configuration.border_mode <= GB_BORDER_ALWAYS) {
GB_set_border_mode(&gb, configuration.border_mode);
@ -624,12 +634,49 @@ 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;
configuration.color_temperature %= 21;
configuration.bootrom_path[sizeof(configuration.bootrom_path) - 1] = 0;
}
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,36 +707,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);
/* Sanitize for stability */
configuration.color_correction_mode %= GB_COLOR_CORRECTION_REDUCE_CONTRAST +1;
configuration.scaling_mode %= GB_SDL_SCALING_MAX;
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;
}
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_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.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))
@ -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);

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
@ -65,7 +66,7 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]],
switch (*frame_blending_mode) {
default:
case DISABLED:
return scale(image, in.texcoords, input_resolution, *output_resolution);
return pow(scale(image, in.texcoords, input_resolution, *output_resolution), 1 / GAMMA);
case SIMPLE:
ratio = 0.5;
break;
@ -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

@ -31,16 +31,23 @@ static unsigned int test_length = 60 * 40;
GB_gameboy_t gb;
static unsigned int frames = 0;
const char bmp_header[] = {
0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00,
0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF,
0xFF, 0xFF, 0x01, 0x00, 0x20, 0x00, 0x03, 0x00,
0x00, 0x00, 0x02, 0x68, 0x01, 0x00, 0x12, 0x0B,
0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
static bool use_tga = false;
static const uint8_t bmp_header[] = {
0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00,
0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF,
0xFF, 0xFF, 0x01, 0x00, 0x20, 0x00, 0x03, 0x00,
0x00, 0x00, 0x02, 0x68, 0x01, 0x00, 0x12, 0x0B,
0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t tga_header[] = {
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x90, 0x00,
0x20, 0x28,
};
uint32_t bitmap[160*144];
@ -139,7 +146,12 @@ static void vblank(GB_gameboy_t *gb)
/* Let the test run for extra four seconds if the screen is off/disabled */
if (!is_screen_blank || frames >= test_length + 60 * 4) {
FILE *f = fopen(bmp_filename, "wb");
fwrite(&bmp_header, 1, sizeof(bmp_header), f);
if (use_tga) {
fwrite(&tga_header, 1, sizeof(tga_header), f);
}
else {
fwrite(&bmp_header, 1, sizeof(bmp_header), f);
}
fwrite(&bitmap, 1, sizeof(bitmap), f);
fclose(f);
if (!gb->boot_rom_finished) {
@ -215,6 +227,9 @@ static char *executable_relative_path(const char *filename)
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
{
if (use_tga) {
return (r << 16) | (g << 8) | (b);
}
return (r << 24) | (g << 16) | (b << 8);
}
@ -268,6 +283,12 @@ int main(int argc, char **argv)
dmg = true;
continue;
}
if (strcmp(argv[i], "--tga") == 0) {
fprintf(stderr, "Using TGA output\n");
use_tga = true;
continue;
}
if (strcmp(argv[i], "--start") == 0) {
fprintf(stderr, "Pushing Start and A\n");
@ -312,7 +333,7 @@ int main(int argc, char **argv)
size_t path_length = strlen(filename);
char bitmap_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .bmp + NULL */
replace_extension(filename, path_length, bitmap_path, ".bmp");
replace_extension(filename, path_length, bitmap_path, use_tga? ".tga" : ".bmp");
bmp_filename = &bitmap_path[0];
char log_path[path_length + 5];

View File

@ -1,7 +1,57 @@
# Attempting to build the Cocoa frontend fails with NSInternalInconsistencyException
# macOS Specific Issues
## Attempting to build the Cocoa frontend fails with NSInternalInconsistencyException
When building on macOS, the build system will make a native Cocoa app by default. In this case, the build system uses the Xcode `ibtool` command to build user interface files. If this command fails, you can fix this issue by starting Xcode and letting it install components. After this is done, you should be able to close Xcode and build successfully.
# Attempting to build the SDL frontend on macOS fails on linking
## Attempting to build the SDL frontend on macOS fails on linking
SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case.
SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case.
# Windows Build Process
## Tools and Libraries Installation
For the various tools and libraries, follow the below guide to ensure easy, proper configuration for the build environment:
### SDL2
For [libSDL2](https://libsdl.org/download-2.0.php), download the Visual C++ Development Library pack. Place the extracted files within a known folder for later. Both the `\x86\` and `\include\` paths will be needed.
The following examples will be referenced later:
- `C:\SDL2\lib\x86\*`
- `C:\SDL2\include\*`
### rgbds
After downloading [rgbds](https://github.com/gbdev/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`.
### GnuWin
Ensure that the `gnuwin32\bin\` directory is included in `%PATH%`. Like rgbds above, this may instead be manually included on the command line before installation: `set path=%path%;C:\path\to\gnuwin32\bin`.
## Building
Within a command prompt in the project directory:
```
vcvars32
set lib=%lib%;C:\SDL2\lib\x86
set include=%include%;C:\SDL2\include
make
```
Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `%PATH%` properly includes `rgbds` and `gnuwin32\bin`, and that the `lib` and `include` paths include the appropriate SDL2 directories.
## Common Errors
### Error -1073741819
If encountering an error that appears as follows:
``` make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819```
Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. This appears to be an issue with GnuWin.
### The system cannot find the file specified (`usr/bin/mkdir`)
If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. This happens because the Git for Windows version of `which` is used instead of the GnuWin one, and it returns a Unix-style path instead of a Windows one.

View File

@ -17,8 +17,6 @@ filter_out2 = $(call filter_out1,$(call filter_out1,$1))
unixpath = $(subst \,/,$1)
unixcygpath = /$(subst :,,$(call unixpath,$1))
CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\"
ifeq ($(platform),)
platform = unix
ifeq ($(shell uname -a),)
@ -51,7 +49,7 @@ ifeq ($(platform), win)
INCFLAGS += -I Windows
endif
CORE_DIR += ..
CORE_DIR = ../
TARGET_NAME = sameboy
LIBM = -lm
@ -90,18 +88,58 @@ else ifeq ($(platform), linux-portable)
TARGET := $(TARGET_NAME)_libretro.$(EXT)
fpic := -fPIC -nostdlib
SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T
LIBM :=
LIBM :=
# (armv7 a7, hard point, neon based) ###
# NESC, SNESC, C64 mini
else ifeq ($(platform), classic_armv7_a7)
TARGET := $(TARGET_NAME)_libretro.so
fpic := -fPIC
SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined
CFLAGS += -Ofast \
-flto=4 -fwhole-program -fuse-linker-plugin \
-fdata-sections -ffunction-sections -Wl,--gc-sections \
-fno-stack-protector -fno-ident -fomit-frame-pointer \
-falign-functions=1 -falign-jumps=1 -falign-loops=1 \
-fno-unwind-tables -fno-asynchronous-unwind-tables -fno-unroll-loops \
-fmerge-all-constants -fno-math-errno \
-marm -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard
CXXFLAGS += $(CFLAGS)
CPPFLAGS += $(CFLAGS)
ASFLAGS += $(CFLAGS)
HAVE_NEON = 1
ARCH = arm
BUILTIN_GPU = neon
USE_DYNAREC = 1
ifeq ($(shell echo `$(CC) -dumpversion` "< 4.9" | bc -l), 1)
CFLAGS += -march=armv7-a
else
CFLAGS += -march=armv7ve
# If gcc is 5.0 or later
ifeq ($(shell echo `$(CC) -dumpversion` ">= 5" | bc -l), 1)
LDFLAGS += -static-libgcc -static-libstdc++
endif
endif
#######################################
# Nintendo Switch (libtransistor)
else ifeq ($(platform), switch)
TARGET := $(TARGET_NAME)_libretro_$(platform).a
include $(LIBTRANSISTOR_HOME)/libtransistor.mk
CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls
STATIC_LINKING=1
# Nintendo Switch (libnx)
else ifeq ($(platform), libnx)
include $(DEVKITPRO)/libnx/switch_rules
TARGET := $(TARGET_NAME)_libretro_$(platform).a
DEFINES += -DSWITCH=1 -D__SWITCH__ -DARM
CFLAGS += $(DEFINES) -fPIE -I$(LIBNX)/include/ -ffunction-sections -fdata-sections -ftls-model=local-exec
CFLAGS += -march=armv8-a -mtune=cortex-a57 -mtp=soft -mcpu=cortex-a57+crc+fp+simd -ffast-math
CXXFLAGS := $(ASFLAGS) $(CFLAGS)
STATIC_LINKING = 1
# Nintendo WiiU
else ifeq ($(platform), wiiu)
TARGET := $(TARGET_NAME)_libretro_$(platform).a
CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT)
AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT)
CC ?= $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT)
AR ?= $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT)
CFLAGS += -DGEKKO -DHW_RVL -DWIIU -mwup -mcpu=750 -meabi -mhard-float -D__ppc__ -DMSB_FIRST -I$(DEVKITPRO)/libogc/include
CFLAGS += -U__INT32_TYPE__ -U __UINT32_TYPE__ -D__INT32_TYPE__=int
STATIC_LINKING = 1
@ -140,7 +178,7 @@ else ifeq ($(platform), emscripten)
fpic := -fPIC
SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined
else ifeq ($(platform), vita)
TARGET := $(TARGET_NAME)_vita.a
TARGET := $(TARGET_NAME)_libretro_vita.a
CC = arm-vita-eabi-gcc
AR = arm-vita-eabi-ar
CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls
@ -172,14 +210,14 @@ else ifneq (,$(findstring windows_msvc2017,$(platform)))
TargetArchMoniker = $(subst $(WinPartition)_,,$(PlatformSuffix))
CC = cl.exe
CXX = cl.exe
LD = link.exe
CC ?= cl.exe
CXX ?= cl.exe
LD ?= link.exe
reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>nul)))
fix_path = $(subst $(SPACE),\ ,$(subst \,/,$1))
ProgramFiles86w := $(shell cmd /c "echo %PROGRAMFILES(x86)%")
ProgramFiles86w := $(shell cmd //c "echo %PROGRAMFILES(x86)%")
ProgramFiles86 := $(shell cygpath "$(ProgramFiles86w)")
WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0)
@ -242,7 +280,7 @@ else ifneq (,$(findstring windows_msvc2017,$(platform)))
LDFLAGS += -DLL
else
CC = gcc
CC ?= gcc
TARGET := $(TARGET_NAME)_libretro.dll
SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined
endif
@ -262,6 +300,8 @@ endif
include Makefile.common
CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\"
OBJECTS := $(patsubst $(CORE_DIR)/%.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C))
OBJOUT = -o

View File

@ -1,3 +1,5 @@
include $(CORE_DIR)/version.mk
INCFLAGS := -I$(CORE_DIR)
SOURCES_C := $(CORE_DIR)/Core/gb.c \

View File

@ -8,7 +8,7 @@ include $(CORE_DIR)/libretro/Makefile.common
GENERATED_SOURCES := $(filter %_boot.c,$(SOURCES_C))
COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -Wno-multichar
COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -Wno-multichar -DANDROID
GIT_VERSION := " $(shell git rev-parse --short HEAD || echo unknown)"
ifneq ($(GIT_VERSION)," unknown")

View File

@ -85,6 +85,8 @@ static retro_audio_sample_t audio_sample_cb;
static retro_input_poll_t input_poll_cb;
static retro_input_state_t input_state_cb;
static bool libretro_supports_bitmasks = false;
static unsigned emulated_devices = 1;
static bool initialized = false;
static unsigned screen_layout = 0;
@ -119,24 +121,39 @@ static struct retro_rumble_interface rumble;
static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port)
{
uint16_t joypad_bits = 0;
input_poll_cb();
if (libretro_supports_bitmasks) {
joypad_bits = input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
}
else {
unsigned j;
for (j = 0; j < (RETRO_DEVICE_ID_JOYPAD_R3+1); j++) {
if (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, j)) {
joypad_bits |= (1 << j);
}
}
}
GB_set_key_state_for_player(gb, GB_KEY_RIGHT, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT));
GB_set_key_state_for_player(gb, GB_KEY_LEFT, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT));
GB_set_key_state_for_player(gb, GB_KEY_UP, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_UP));
GB_set_key_state_for_player(gb, GB_KEY_DOWN, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN));
GB_set_key_state_for_player(gb, GB_KEY_A, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_A));
GB_set_key_state_for_player(gb, GB_KEY_B, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_B));
GB_set_key_state_for_player(gb, GB_KEY_SELECT, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT));
GB_set_key_state_for_player(gb, GB_KEY_START, emulated_devices == 1 ? port : 0,
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START));
joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_START));
}
@ -196,6 +213,16 @@ static bool serial_end2(GB_gameboy_t *gb)
return ret;
}
static void infrared_callback1(GB_gameboy_t *gb, bool output)
{
GB_set_infrared_input(&gameboy[1], output);
}
static void infrared_callback2(GB_gameboy_t *gb, bool output)
{
GB_set_infrared_input(&gameboy[0], output);
}
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
{
return r <<16 | g <<8 | b;
@ -207,7 +234,7 @@ static retro_environment_t environ_cb;
static const struct retro_variable vars_single[] = {
{ "sameboy_color_correction_mode", "Color correction; emulate hardware|preserve brightness|reduce contrast|off|correct curves" },
{ "sameboy_high_pass_filter_mode", "High-pass filter; accurate|remove dc offset|off" },
{ "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" },
{ "sameboy_model", "Emulated model (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" },
{ "sameboy_border", "Display border; Super Game Boy only|always|never" },
{ "sameboy_rumble", "Enable rumble; rumble-enabled games|all games|never" },
{ NULL }
@ -219,8 +246,8 @@ static const struct retro_variable vars_dual[] = {
/*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/
{ "sameboy_screen_layout", "Screen layout; top-down|left-right" },
{ "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" },
{ "sameboy_model_1", "Emulated model for Game Boy #1; Auto|Game Boy|Game Boy Color|Game Boy Advance" },
{ "sameboy_model_2", "Emulated model for Game Boy #2; Auto|Game Boy|Game Boy Color|Game Boy Advance" },
{ "sameboy_model_1", "Emulated model for Game Boy #1 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" },
{ "sameboy_model_2", "Emulated model for Game Boy #2 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" },
{ "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; emulate hardware|preserve brightness|reduce contrast|off|correct curves" },
{ "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; emulate hardware|preserve brightness|reduce contrast|off|correct curves" },
{ "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; accurate|remove dc offset|off" },
@ -334,12 +361,16 @@ static void set_link_cable_state(bool state)
GB_set_serial_transfer_bit_end_callback(&gameboy[0], serial_end1);
GB_set_serial_transfer_bit_start_callback(&gameboy[1], serial_start2);
GB_set_serial_transfer_bit_end_callback(&gameboy[1], serial_end2);
GB_set_infrared_callback(&gameboy[0], infrared_callback1);
GB_set_infrared_callback(&gameboy[1], infrared_callback2);
}
else if (!state) {
GB_set_serial_transfer_bit_start_callback(&gameboy[0], NULL);
GB_set_serial_transfer_bit_end_callback(&gameboy[0], NULL);
GB_set_serial_transfer_bit_start_callback(&gameboy[1], NULL);
GB_set_serial_transfer_bit_end_callback(&gameboy[1], NULL);
GB_set_infrared_callback(&gameboy[0], NULL);
GB_set_infrared_callback(&gameboy[1], NULL);
}
}
@ -601,11 +632,7 @@ static void check_variables()
new_model = MODEL_AUTO;
}
if (new_model != model[0]) {
geometry_updated = true;
model[0] = new_model;
init_for_current_model(0);
}
model[0] = new_model;
}
var.key = "sameboy_border";
@ -747,10 +774,7 @@ static void check_variables()
new_model = MODEL_AUTO;
}
if (model[0] != new_model) {
model[0] = new_model;
init_for_current_model(0);
}
model[0] = new_model;
}
var.key = "sameboy_model_2";
@ -776,10 +800,7 @@ static void check_variables()
new_model = MODEL_AUTO;
}
if (model[1] != new_model) {
model[1] = new_model;
init_for_current_model(1);
}
model[1] = new_model;
}
var.key = "sameboy_screen_layout";
@ -850,6 +871,10 @@ void retro_init(void)
else {
log_cb = fallback_log;
}
if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) {
libretro_supports_bitmasks = true;
}
}
void retro_deinit(void)
@ -858,6 +883,8 @@ void retro_deinit(void)
free(frame_buf_copy);
frame_buf = NULL;
frame_buf_copy = NULL;
libretro_supports_bitmasks = false;
}
unsigned retro_api_version(void)
@ -947,10 +974,14 @@ void retro_set_video_refresh(retro_video_refresh_t cb)
void retro_reset(void)
{
check_variables();
for (int i = 0; i < emulated_devices; i++) {
init_for_current_model(i);
GB_reset(&gameboy[i]);
}
geometry_updated = true;
}
void retro_run(void)

File diff suppressed because it is too large Load Diff

1
version.mk Normal file
View File

@ -0,0 +1 @@
VERSION := 0.13.6