Merge remote-tracking branch 'origin/master' into GnomeThumbnailer
This commit is contained in:
commit
15fbf0994a
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
79
CONTRIBUTING.md
Normal 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.
|
@ -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
|
||||
|
||||
|
@ -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
30
Cocoa/BigSurToolbar.h
Normal 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
|
@ -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
|
||||
|
||||
|
280
Cocoa/Document.m
280
Cocoa/Document.m
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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;
|
||||
|
@ -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])
|
||||
|
@ -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;
|
||||
|
233
Cocoa/GBView.m
233
Cocoa/GBView.m
@ -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;
|
||||
|
@ -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 & Infrared" id="V4S-Fo-xJK">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Game Link Cable & 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>
|
||||
|
@ -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"/>
|
||||
|
402
Core/apu.c
402
Core/apu.c
@ -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;
|
||||
}
|
||||
|
19
Core/apu.h
19
Core/apu.h
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
171
Core/display.c
171
Core/display.c
@ -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;
|
||||
|
@ -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 */
|
||||
|
30
Core/gb.c
30
Core/gb.c
@ -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);
|
||||
|
38
Core/gb.h
38
Core/gb.h
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
169
Core/workboy.c
Normal 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
118
Core/workboy.h
Normal 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
|
55
Makefile
55
Makefile
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -2,5 +2,5 @@
|
||||
#define open_rom_h
|
||||
|
||||
char *do_open_rom_dialog(void);
|
||||
|
||||
char *do_open_folder_dialog(void);
|
||||
#endif /* open_rom_h */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,3 +1 @@
|
||||
_DeallocQuickLookGeneratorPluginType
|
||||
_QuickLookGeneratorQueryInterface
|
||||
_QuickLookGeneratorPluginFactory
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
15
README.md
15
README.md
@ -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.
|
||||
|
20
SDL/font.c
20
SDL/font.c
@ -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 + ' ';
|
||||
|
@ -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
266
SDL/gui.c
@ -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]);
|
||||
|
18
SDL/gui.h
18
SDL/gui.h
@ -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
|
||||
|
91
SDL/main.c
91
SDL/main.c
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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];
|
||||
|
56
build-faq.md
56
build-faq.md
@ -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.
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
include $(CORE_DIR)/version.mk
|
||||
|
||||
INCFLAGS := -I$(CORE_DIR)
|
||||
|
||||
SOURCES_C := $(CORE_DIR)/Core/gb.c \
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
1393
libretro/libretro.h
1393
libretro/libretro.h
File diff suppressed because it is too large
Load Diff
1
version.mk
Normal file
1
version.mk
Normal file
@ -0,0 +1 @@
|
||||
VERSION := 0.13.6
|
Loading…
Reference in New Issue
Block a user