Merge remote-tracking branch 'origin/master' into gtk3
This commit is contained in:
commit
dac9d29ab2
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>
|
||||
@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
|
||||
|
||||
|
@ -50,6 +50,10 @@
|
||||
JOYAxes2DEmulateButtonsKey: @YES,
|
||||
JOYHatsEmulateButtonsKey: @YES,
|
||||
}];
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
|
||||
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)toggleDeveloperMode:(id)sender
|
||||
@ -77,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;
|
||||
@ -101,4 +124,12 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
|
||||
{
|
||||
[[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;
|
||||
@ -30,17 +31,18 @@
|
||||
@property (strong) IBOutlet NSTableView *spritesTableView;
|
||||
@property (strong) IBOutlet NSPanel *printerFeedWindow;
|
||||
@property (strong) IBOutlet NSImageView *feedImageView;
|
||||
@property (strong) IBOutlet NSButton *feedSaveButton;
|
||||
@property (strong) IBOutlet NSTextView *debuggerSideViewInput;
|
||||
@property (strong) IBOutlet NSTextView *debuggerSideView;
|
||||
@property (strong) IBOutlet GBSplitView *debuggerSplitView;
|
||||
@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
|
||||
|
||||
|
315
Cocoa/Document.m
315
Cocoa/Document.m
@ -8,6 +8,8 @@
|
||||
#include "GBMemoryByteArray.h"
|
||||
#include "GBWarningPopover.h"
|
||||
#include "GBCheatWindowController.h"
|
||||
#include "GBTerminalTextFieldCell.h"
|
||||
#include "BigSurToolbar.h"
|
||||
|
||||
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
|
||||
/* Todo: Split into category files! This is so messy!!! */
|
||||
@ -26,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;
|
||||
@ -45,7 +48,7 @@ enum model {
|
||||
bool oamUpdating;
|
||||
|
||||
NSMutableData *currentPrinterImageData;
|
||||
enum {GBAccessoryNone, GBAccessoryPrinter} accessory;
|
||||
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory;
|
||||
|
||||
bool rom_warning_issued;
|
||||
|
||||
@ -64,6 +67,12 @@ enum model {
|
||||
size_t audioBufferNeeded;
|
||||
|
||||
bool borderModeChanged;
|
||||
|
||||
/* Link cable*/
|
||||
Document *master;
|
||||
Document *slave;
|
||||
signed linkOffset;
|
||||
bool linkCableBit;
|
||||
}
|
||||
|
||||
@property GBAudioClient *audioClient;
|
||||
@ -79,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)
|
||||
@ -136,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);
|
||||
@ -148,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;
|
||||
@ -252,6 +295,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];
|
||||
}
|
||||
|
||||
@ -323,9 +367,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) {
|
||||
@ -356,20 +399,78 @@ 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];
|
||||
while (running) {
|
||||
if (rewind) {
|
||||
rewind = false;
|
||||
GB_rewind_pop(&gb);
|
||||
if (!GB_rewind_pop(&gb)) {
|
||||
rewind = self.view.isRewinding;
|
||||
|
||||
/* Clear pending alarms, don't play alarms while playing */
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
for (NSUserNotification *notification in [center scheduledNotifications]) {
|
||||
if ([notification.identifier isEqualToString:self.fileName]) {
|
||||
[center removeScheduledNotification:notification];
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_run(&gb);
|
||||
|
||||
for (NSUserNotification *notification in [center deliveredNotifications]) {
|
||||
if ([notification.identifier isEqualToString:self.fileName]) {
|
||||
[center removeDeliveredNotification:notification];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)];
|
||||
}
|
||||
}
|
||||
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));
|
||||
@ -381,19 +482,50 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
self.view.mouseHidingEnabled = NO;
|
||||
GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
|
||||
GB_save_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]);
|
||||
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;
|
||||
notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm];
|
||||
notification.soundName = NSUserNotificationDefaultSoundName;
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification];
|
||||
[[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)) {
|
||||
@ -470,6 +602,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (IBAction)togglePause:(id)sender
|
||||
{
|
||||
if (master) {
|
||||
[master togglePause:sender];
|
||||
return;
|
||||
}
|
||||
if (running) {
|
||||
[self stop];
|
||||
}
|
||||
@ -510,6 +646,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
self.debuggerSideViewInput.textColor = [NSColor whiteColor];
|
||||
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
|
||||
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
|
||||
((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(updateSideView)
|
||||
name:NSTextDidChangeNotification
|
||||
@ -527,16 +664,21 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
|
||||
|
||||
|
||||
[self.feedSaveButton removeFromSuperview];
|
||||
|
||||
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[self.fileURL path] lastPathComponent]];
|
||||
self.debuggerSplitView.dividerColor = [NSColor clearColor];
|
||||
|
||||
/* contentView.superview.subviews.lastObject is the titlebar view */
|
||||
NSView *titleView = self.printerFeedWindow.contentView.superview.subviews.lastObject;
|
||||
[titleView addSubview: self.feedSaveButton];
|
||||
self.feedSaveButton.frame = (NSRect){{268, 2}, {48, 17}};
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
self.memoryWindow.toolbarStyle = NSWindowToolbarStyleExpanded;
|
||||
self.printerFeedWindow.toolbarStyle = NSWindowToolbarStyleUnifiedCompact;
|
||||
[self.printerFeedWindow.toolbar removeItemAtIndex:1];
|
||||
self.printerFeedWindow.toolbar.items.firstObject.image =
|
||||
[NSImage imageWithSystemSymbolName:@"square.and.arrow.down"
|
||||
accessibilityDescription:@"Save"];
|
||||
self.printerFeedWindow.toolbar.items.lastObject.image =
|
||||
[NSImage imageWithSystemSymbolName:@"printer"
|
||||
accessibilityDescription:@"Print"];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(updateHighpassFilter)
|
||||
name:@"GBHighpassFilterChanged"
|
||||
@ -609,7 +751,6 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
{
|
||||
hex_controller = [[HFController alloc] init];
|
||||
[hex_controller setBytesPerColumn:1];
|
||||
[hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]];
|
||||
[hex_controller setEditMode:HFOverwriteMode];
|
||||
|
||||
[hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]];
|
||||
@ -695,6 +836,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];
|
||||
@ -706,9 +848,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];
|
||||
}
|
||||
@ -727,10 +867,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) {
|
||||
@ -747,6 +890,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)];
|
||||
}
|
||||
@ -972,6 +1122,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
[debugger_input_queue removeObjectAtIndex:0];
|
||||
}
|
||||
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
|
||||
if ((id)input == [NSNull null]) {
|
||||
return NULL;
|
||||
}
|
||||
return input? strdup([input UTF8String]): NULL;
|
||||
}
|
||||
|
||||
@ -1030,6 +1183,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];
|
||||
}
|
||||
@ -1596,13 +1752,24 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
scale:2.0];
|
||||
NSRect frame = self.printerFeedWindow.frame;
|
||||
frame.size = self.feedImageView.image.size;
|
||||
[self.printerFeedWindow setContentMaxSize:frame.size];
|
||||
frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height;
|
||||
[self.printerFeedWindow setMaxSize:frame.size];
|
||||
[self.printerFeedWindow setFrame:frame display:NO animate: self.printerFeedWindow.isVisible];
|
||||
[self.printerFeedWindow orderFront:NULL];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
- (void)printDocument:(id)sender
|
||||
{
|
||||
if (self.feedImageView.image.size.height == 0) {
|
||||
NSBeep(); return;
|
||||
}
|
||||
NSImageView *view = [[NSImageView alloc] initWithFrame:(NSRect){{0,0}, self.feedImageView.image.size}];
|
||||
view.image = self.feedImageView.image;
|
||||
[[NSPrintOperation printOperationWithView:view] runOperationModalForWindow:self.printerFeedWindow delegate:nil didRunSelector:NULL contextInfo:NULL];
|
||||
}
|
||||
|
||||
- (IBAction)savePrinterFeed:(id)sender
|
||||
{
|
||||
bool shouldResume = running;
|
||||
@ -1629,6 +1796,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (IBAction)disconnectAllAccessories:(id)sender
|
||||
{
|
||||
[self disconnectLinkCable];
|
||||
[self performAtomicBlock:^{
|
||||
accessory = GBAccessoryNone;
|
||||
GB_disconnect_serial(&gb);
|
||||
@ -1637,12 +1805,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (IBAction)connectPrinter:(id)sender
|
||||
{
|
||||
[self disconnectLinkCable];
|
||||
[self performAtomicBlock:^{
|
||||
accessory = GBAccessoryPrinter;
|
||||
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)) {
|
||||
@ -1753,4 +1931,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
|
||||
|
@ -19,7 +19,6 @@
|
||||
<outlet property="debuggerSplitView" destination="pUc-ZN-vl5" id="0sG-0D-cID"/>
|
||||
<outlet property="debuggerVerticalLine" destination="7bR-gM-1At" id="rfy-7Z-388"/>
|
||||
<outlet property="feedImageView" destination="Ar0-nN-eop" id="wHa-St-o4G"/>
|
||||
<outlet property="feedSaveButton" destination="RLc-0I-sYZ" id="Yy9-dG-xXY"/>
|
||||
<outlet property="gridButton" destination="fL6-2S-Rgd" id="jtV-jh-GHC"/>
|
||||
<outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/>
|
||||
<outlet property="memoryBankInput" destination="rdV-q6-hc6" id="KBx-9T-2mX"/>
|
||||
@ -60,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>
|
||||
@ -244,9 +246,9 @@
|
||||
<toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H">
|
||||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="100" height="25"/>
|
||||
<size key="maxSize" width="100" height="25"/>
|
||||
<size key="maxSize" width="130" height="25"/>
|
||||
<popUpButton key="view" verticalHuggingPriority="750" id="vfJ-vu-gqJ">
|
||||
<rect key="frame" x="0.0" y="14" width="100" height="25"/>
|
||||
<rect key="frame" x="0.0" y="14" width="128" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bpD-j9-omo">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -507,7 +509,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<tableView appearanceType="vibrantLight" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" headerView="of1-KC-dXC" id="TOc-XJ-w9w">
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" headerView="of1-KC-dXC" id="TOc-XJ-w9w">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="391"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
@ -786,9 +788,10 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
|
||||
<rect key="contentRect" x="0.0" y="0.0" width="320" height="288"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<value key="minSize" type="size" width="320" height="16"/>
|
||||
<view key="contentView" id="RRS-aa-bPT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ar0-nN-eop" customClass="GBImageView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
|
||||
@ -797,20 +800,25 @@
|
||||
</imageView>
|
||||
</subviews>
|
||||
</view>
|
||||
<toolbar key="toolbar" implicitIdentifier="1FF86A2B-6637-4EE6-A25A-7298D79AE84E" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="gH3-SH-7il">
|
||||
<allowedToolbarItems>
|
||||
<toolbarItem implicitItemIdentifier="15EB8D49-8C6E-42F2-9F7F-F7D7A0BBDAAF" label="Save" paletteLabel="Save" tag="-1" image="NSFolder" id="CBz-1N-o0Q">
|
||||
<connections>
|
||||
<action selector="savePrinterFeed:" target="-2" id="Dm3-h0-ch4"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarPrintItem" explicitItemIdentifier="Print" id="mtd-zS-DXa"/>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="AoG-LH-J4b"/>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="Q0x-n5-Q2Y"/>
|
||||
</allowedToolbarItems>
|
||||
<defaultToolbarItems>
|
||||
<toolbarItem reference="CBz-1N-o0Q"/>
|
||||
<toolbarItem reference="Q0x-n5-Q2Y"/>
|
||||
<toolbarItem reference="mtd-zS-DXa"/>
|
||||
</defaultToolbarItems>
|
||||
</toolbar>
|
||||
<point key="canvasLocation" x="-159" y="356"/>
|
||||
</window>
|
||||
<button verticalHuggingPriority="750" id="RLc-0I-sYZ">
|
||||
<rect key="frame" x="0.5" y="0.0" width="48" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Save" bezelStyle="texturedRounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="shw-MJ-B3T">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="miniSystem"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="savePrinterFeed:" target="-2" id="Y3g-fU-2te"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-507" y="397"/>
|
||||
</button>
|
||||
<window title="Cheats" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="4Yb-Np-JrF" customClass="NSPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<rect key="contentRect" x="0.0" y="0.0" width="692" height="272"/>
|
||||
@ -896,7 +904,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" id="C3V-Ep-bMj">
|
||||
<rect key="frame" x="202.5" y="12" width="82" height="23"/>
|
||||
<rect key="frame" x="202.5" y="12" width="83" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Import" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="mMP-KW-YNy">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -1069,6 +1077,7 @@
|
||||
</customObject>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSFolder" width="32" height="32"/>
|
||||
<image name="NSStopProgressFreestandingTemplate" width="14" height="14"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
@ -13,8 +13,14 @@ static inline double scale_channel(uint8_t x)
|
||||
|
||||
- (void)setObjectValue:(id)objectValue
|
||||
{
|
||||
|
||||
_integerValue = [objectValue integerValue];
|
||||
super.objectValue = [NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)];
|
||||
uint8_t r = _integerValue & 0x1F,
|
||||
g = (_integerValue >> 5) & 0x1F,
|
||||
b = (_integerValue >> 10) & 0x1F;
|
||||
super.objectValue = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)] attributes:@{
|
||||
NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor]
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSInteger)integerValue
|
||||
|
@ -1,6 +1,7 @@
|
||||
#import "GBPreferencesWindow.h"
|
||||
#import "NSString+StringForKey.h"
|
||||
#import "GBButtons.h"
|
||||
#import "BigSurToolbar.h"
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
@implementation GBPreferencesWindow
|
||||
@ -52,6 +53,11 @@
|
||||
return filters;
|
||||
}
|
||||
|
||||
- (NSWindowToolbarStyle)toolbarStyle
|
||||
{
|
||||
return NSWindowToolbarStylePreference;
|
||||
}
|
||||
|
||||
- (void)close
|
||||
{
|
||||
joystick_configuration_state = -1;
|
||||
@ -164,6 +170,20 @@
|
||||
return GBGameBoyButtonCount;
|
||||
}
|
||||
|
||||
- (unsigned) usesForKey:(unsigned) key
|
||||
{
|
||||
unsigned ret = 0;
|
||||
for (unsigned player = 4; player--;) {
|
||||
for (unsigned button = player == 0? GBButtonCount:GBGameBoyButtonCount; button--;) {
|
||||
NSNumber *other = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(button, player)];
|
||||
if (other && [other unsignedIntValue] == key) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
if ([tableColumn.identifier isEqualToString:@"keyName"]) {
|
||||
@ -176,6 +196,12 @@
|
||||
|
||||
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
|
||||
if (key) {
|
||||
if ([self usesForKey:[key unsignedIntValue]] > 1) {
|
||||
return [[NSAttributedString alloc] initWithString:[NSString displayStringForKeyCode: [key unsignedIntegerValue]]
|
||||
attributes:@{NSForegroundColorAttributeName: [NSColor colorWithRed:0.9375 green:0.25 blue:0.25 alpha:1.0],
|
||||
NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]
|
||||
}];
|
||||
}
|
||||
return [NSString displayStringForKeyCode: [key unsignedIntegerValue]];
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
|
||||
@interface GBTerminalTextFieldCell : NSTextFieldCell
|
||||
|
||||
@property GB_gameboy_t *gb;
|
||||
@end
|
||||
|
@ -2,6 +2,7 @@
|
||||
#import "GBTerminalTextFieldCell.h"
|
||||
|
||||
@interface GBTerminalTextView : NSTextView
|
||||
@property GB_gameboy_t *gb;
|
||||
@end
|
||||
|
||||
@implementation GBTerminalTextFieldCell
|
||||
@ -12,10 +13,12 @@
|
||||
- (NSTextView *)fieldEditorForView:(NSView *)controlView
|
||||
{
|
||||
if (field_editor) {
|
||||
field_editor.gb = self.gb;
|
||||
return field_editor;
|
||||
}
|
||||
field_editor = [[GBTerminalTextView alloc] init];
|
||||
[field_editor setFieldEditor:YES];
|
||||
field_editor.gb = self.gb;
|
||||
return field_editor;
|
||||
}
|
||||
|
||||
@ -26,6 +29,8 @@
|
||||
NSMutableOrderedSet *lines;
|
||||
NSUInteger current_line;
|
||||
bool reverse_search_mode;
|
||||
NSRange auto_complete_range;
|
||||
uintptr_t auto_complete_context;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@ -170,6 +175,7 @@
|
||||
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
|
||||
{
|
||||
reverse_search_mode = false;
|
||||
auto_complete_context = 0;
|
||||
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
|
||||
}
|
||||
|
||||
@ -188,6 +194,38 @@
|
||||
[attributes setObject:color forKey:NSForegroundColorAttributeName];
|
||||
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Todo: lazy design, make it use a delegate instead of having a gb reference*/
|
||||
|
||||
- (void)insertTab:(id)sender
|
||||
{
|
||||
if (auto_complete_context == 0) {
|
||||
NSRange selection = self.selectedRange;
|
||||
if (selection.length) {
|
||||
[self delete:nil];
|
||||
}
|
||||
auto_complete_range = NSMakeRange(selection.location, 0);
|
||||
}
|
||||
char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String);
|
||||
uintptr_t context = auto_complete_context;
|
||||
char *completion = GB_debugger_complete_substring(self.gb, substring, &context);
|
||||
free(substring);
|
||||
if (completion) {
|
||||
NSString *ns_completion = @(completion);
|
||||
free(completion);
|
||||
if (!ns_completion) {
|
||||
goto error;
|
||||
}
|
||||
self.selectedRange = auto_complete_range;
|
||||
auto_complete_range.length = ns_completion.length;
|
||||
[self replaceCharactersInRange:self.selectedRange withString:ns_completion];
|
||||
auto_complete_context = context;
|
||||
return;
|
||||
}
|
||||
error:
|
||||
auto_complete_context = context;
|
||||
NSBeep();
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -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;
|
||||
|
239
Cocoa/GBView.m
239
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];
|
||||
@ -115,7 +209,7 @@
|
||||
[NSCursor unhide];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[lastController setRumbleAmplitude:0];
|
||||
[self setRumble:0];
|
||||
[JOYController unregisterListener:self];
|
||||
}
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
@ -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) {
|
||||
[lastController setRumbleAmplitude:0];
|
||||
lastController = controller;
|
||||
}
|
||||
|
||||
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
if (self.document.partner) {
|
||||
player_count = 2;
|
||||
}
|
||||
|
||||
IOPMAssertionID assertionID;
|
||||
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
|
||||
@ -319,7 +480,9 @@
|
||||
![preferred_joypad isEqualToString:controller.uniqueID]) {
|
||||
continue;
|
||||
}
|
||||
[controller setPlayerLEDs:1 << player];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[controller setPlayerLEDs:1 << player];
|
||||
});
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
if (!mapping) {
|
||||
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
|
||||
@ -330,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;
|
||||
|
BIN
Cocoa/Joypad~dark.png
Normal file
BIN
Cocoa/Joypad~dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
BIN
Cocoa/Joypad~dark@2x.png
Normal file
BIN
Cocoa/Joypad~dark@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
@ -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>
|
||||
|
42
Cocoa/NSImageNamedDarkSupport.m
Normal file
42
Cocoa/NSImageNamedDarkSupport.m
Normal file
@ -0,0 +1,42 @@
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface NSImageRep(PrivateAPI)
|
||||
@property(setter=_setAppearanceName:) NSString *_appearanceName;
|
||||
@end
|
||||
|
||||
static NSImage * (*imageNamed)(Class self, SEL _cmd, NSString *name);
|
||||
|
||||
@implementation NSImage(DarkHooks)
|
||||
|
||||
+ (NSImage *)imageNamedWithDark:(NSImageName)name
|
||||
{
|
||||
NSImage *light = imageNamed(self, _cmd, name);
|
||||
if (@available(macOS 10.14, *)) {
|
||||
NSImage *dark = imageNamed(self, _cmd, [name stringByAppendingString:@"~dark"]);
|
||||
if (!dark) {
|
||||
return light;
|
||||
}
|
||||
NSImage *ret = [[NSImage alloc] initWithSize:light.size];
|
||||
for (NSImageRep *rep in light.representations) {
|
||||
[rep _setAppearanceName:NSAppearanceNameAqua];
|
||||
[ret addRepresentation:rep];
|
||||
}
|
||||
for (NSImageRep *rep in dark.representations) {
|
||||
[rep _setAppearanceName:NSAppearanceNameDarkAqua];
|
||||
[ret addRepresentation:rep];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return light;
|
||||
}
|
||||
|
||||
+(void)load
|
||||
{
|
||||
if (@available(macOS 10.14, *)) {
|
||||
imageNamed = (void *)[self methodForSelector:@selector(imageNamed:)];
|
||||
method_setImplementation(class_getClassMethod(self, @selector(imageNamed:)),
|
||||
[self methodForSelector:@selector(imageNamedWithDark:)]);
|
||||
}
|
||||
}
|
||||
@end
|
@ -492,7 +492,7 @@
|
||||
<rect key="frame" x="1" y="1" width="238" height="209"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" appearanceType="vibrantLight" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="238" height="209"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
@ -507,8 +507,8 @@
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mqT-jD-eXS">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
@ -520,8 +520,8 @@
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" identifier="keyValue" title="Text Cell" id="tn8-0i-1q1">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
@ -603,13 +603,13 @@
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ogs-xG-b4b">
|
||||
<rect key="frame" x="30" y="58" width="245" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="jki-7x-bnM" id="o9b-MH-8kd">
|
||||
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="jki-7x-bnM" id="o9b-MH-8kd">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="8p7-je-0Fh">
|
||||
<items>
|
||||
<menuItem title="Never" id="jki-7x-bnM"/>
|
||||
<menuItem title="For rumble-enabled cartridges" state="on" tag="1" id="58e-Tp-TWd"/>
|
||||
<menuItem title="Never" state="on" id="jki-7x-bnM"/>
|
||||
<menuItem title="For rumble-enabled Game Paks" tag="1" id="58e-Tp-TWd"/>
|
||||
<menuItem title="Always" tag="2" id="qVe-2b-W1P"/>
|
||||
</items>
|
||||
</menu>
|
||||
|
BIN
Cocoa/Speaker~dark.png
Normal file
BIN
Cocoa/Speaker~dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
Cocoa/Speaker~dark@2x.png
Normal file
BIN
Cocoa/Speaker~dark@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
324
Core/apu.c
324
Core/apu.c
@ -230,15 +230,6 @@ static void render(GB_gameboy_t *gb)
|
||||
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 +329,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.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 +416,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 +451,57 @@ 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.square_sweep_calculate_countdown <= 7)) { // 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 +544,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 +588,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 +643,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 +686,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 +702,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: {
|
||||
@ -741,11 +787,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
||||
|
||||
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. */
|
||||
@ -779,19 +820,19 @@ 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 + 7 - gb->apu.lf_div;
|
||||
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 = 4 - gb->apu.lf_div;
|
||||
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 +978,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
15
Core/apu.h
15
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;
|
||||
GB_PADDING(bool, sweep_enabled);
|
||||
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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
293
Core/debugger.c
293
Core/debugger.c
@ -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;
|
||||
|
||||
@ -689,6 +689,7 @@ exit:
|
||||
|
||||
struct debugger_command_s;
|
||||
typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command);
|
||||
typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context);
|
||||
|
||||
typedef struct debugger_command_s {
|
||||
const char *command;
|
||||
@ -697,6 +698,8 @@ typedef struct debugger_command_s {
|
||||
const char *help_string; // Null if should not appear in help
|
||||
const char *arguments_format; // For usage message
|
||||
const char *modifiers_format; // For usage message
|
||||
debugger_completer_imp_t *argument_completer;
|
||||
debugger_completer_imp_t *modifiers_completer;
|
||||
} debugger_command_t;
|
||||
|
||||
static const char *lstrip(const char *str)
|
||||
@ -832,6 +835,36 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||
{
|
||||
size_t length = strlen(string);
|
||||
const char *suggestions[] = {"on", "off"};
|
||||
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||
return strdup(suggestions[(*context)++] + length);
|
||||
}
|
||||
(*context)++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Enable or disable software breakpoints */
|
||||
static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
NO_MODIFIERS
|
||||
if (strcmp(lstrip(arguments), "on") == 0 || !strlen(lstrip(arguments))) {
|
||||
gb->has_software_breakpoints = true;
|
||||
}
|
||||
else if (strcmp(lstrip(arguments), "off") == 0) {
|
||||
gb->has_software_breakpoints = false;
|
||||
}
|
||||
else {
|
||||
print_usage(gb, command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Find the index of the closest breakpoint equal or greater to addr */
|
||||
static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
|
||||
{
|
||||
@ -856,6 +889,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
|
||||
return (uint16_t) min;
|
||||
}
|
||||
|
||||
static inline bool is_legal_symbol_char(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return true;
|
||||
if (c >= 'A' && c <= 'Z') return true;
|
||||
if (c >= 'a' && c <= 'z') return true;
|
||||
if (c == '_') return true;
|
||||
if (c == '.') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context)
|
||||
{
|
||||
const char *symbol_prefix = string;
|
||||
while (*string) {
|
||||
if (!is_legal_symbol_char(*string)) {
|
||||
symbol_prefix = string + 1;
|
||||
}
|
||||
string++;
|
||||
}
|
||||
|
||||
if (*symbol_prefix == '$') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct {
|
||||
uint16_t bank;
|
||||
uint32_t symbol;
|
||||
} *context = (void *)_context;
|
||||
|
||||
|
||||
size_t length = strlen(symbol_prefix);
|
||||
while (context->bank < 0x200) {
|
||||
if (gb->bank_symbols[context->bank] == NULL ||
|
||||
context->symbol >= gb->bank_symbols[context->bank]->n_symbols) {
|
||||
context->bank++;
|
||||
context->symbol = 0;
|
||||
continue;
|
||||
}
|
||||
const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name;
|
||||
if (memcmp(symbol_prefix, candidate, length) == 0) {
|
||||
return strdup(candidate + length);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||
{
|
||||
size_t length = strlen(string);
|
||||
const char *suggestions[] = {"j"};
|
||||
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||
return strdup(suggestions[(*context)++] + length);
|
||||
}
|
||||
(*context)++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
bool is_jump_to = true;
|
||||
@ -1023,6 +1115,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
|
||||
return (uint16_t) min;
|
||||
}
|
||||
|
||||
static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||
{
|
||||
size_t length = strlen(string);
|
||||
const char *suggestions[] = {"r", "rw", "w"};
|
||||
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||
return strdup(suggestions[(*context)++] + length);
|
||||
}
|
||||
(*context)++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
if (strlen(lstrip(arguments)) == 0) {
|
||||
@ -1260,6 +1365,19 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to)
|
||||
return _should_break(gb, full_addr, jump_to);
|
||||
}
|
||||
|
||||
static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||
{
|
||||
size_t length = strlen(string);
|
||||
const char *suggestions[] = {"a", "b", "d", "o", "x"};
|
||||
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||
return strdup(suggestions[(*context)++] + length);
|
||||
}
|
||||
(*context)++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
if (strlen(lstrip(arguments)) == 0) {
|
||||
@ -1415,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");
|
||||
@ -1660,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) {
|
||||
@ -1696,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,
|
||||
@ -1723,6 +1848,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||
{
|
||||
size_t length = strlen(string);
|
||||
const char *suggestions[] = {"c", "f", "l"};
|
||||
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||
return strdup(suggestions[(*context)++] + length);
|
||||
}
|
||||
(*context)++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
|
||||
@ -1758,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 "
|
||||
@ -1768,9 +1929,10 @@ 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 (Experimental)"},
|
||||
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"},
|
||||
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
|
||||
"used"},
|
||||
{"registers", 1, registers, "Print values of processor registers and other important registers"},
|
||||
@ -1779,29 +1941,33 @@ static const debugger_command_t commands[] = {
|
||||
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
|
||||
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
|
||||
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
|
||||
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"},
|
||||
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
|
||||
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
|
||||
{"palettes", 3, palettes, "Displays the current CGB palettes"},
|
||||
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer},
|
||||
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
|
||||
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
|
||||
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
|
||||
"jumping to the target.",
|
||||
"<expression>[ if <condition expression>]", "j"},
|
||||
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"},
|
||||
"<expression>[ if <condition expression>]", "j",
|
||||
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
|
||||
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
|
||||
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
|
||||
"Default watchpoint type is write-only.",
|
||||
"<expression>[ if <condition expression>]", "(r|w|rw)"},
|
||||
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"},
|
||||
"<expression>[ if <condition expression>]", "(r|w|rw)",
|
||||
.argument_completer = symbol_completer, .modifiers_completer = rw_completer
|
||||
},
|
||||
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||
{"list", 1, list, "List all set breakpoints and watchpoints"},
|
||||
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
|
||||
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
|
||||
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
|
||||
"<expression>", "format"},
|
||||
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
|
||||
{"eval", 2, }, /* Alias */
|
||||
{"examine", 2, examine, "Examine values at address", "<expression>", "count"},
|
||||
{"examine", 2, examine, "Examine values at address", "<expression>", "count", .argument_completer = symbol_completer},
|
||||
{"x", 1, }, /* Alias */
|
||||
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count"},
|
||||
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count", .argument_completer = symbol_completer},
|
||||
|
||||
|
||||
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
|
||||
@ -2049,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);
|
||||
@ -2057,6 +2246,63 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns true if debugger waits for more commands */
|
||||
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context)
|
||||
{
|
||||
char *command_string = input;
|
||||
char *arguments = strchr(input, ' ');
|
||||
if (arguments) {
|
||||
/* Actually "split" the string. */
|
||||
arguments[0] = 0;
|
||||
arguments++;
|
||||
}
|
||||
|
||||
char *modifiers = strchr(command_string, '/');
|
||||
if (modifiers) {
|
||||
/* Actually "split" the string. */
|
||||
modifiers[0] = 0;
|
||||
modifiers++;
|
||||
}
|
||||
|
||||
const debugger_command_t *command = find_command(command_string);
|
||||
if (command && command->implementation == help && arguments) {
|
||||
command_string = arguments;
|
||||
arguments = NULL;
|
||||
}
|
||||
|
||||
/* No commands and no modifiers, complete the command */
|
||||
if (!arguments && !modifiers) {
|
||||
size_t length = strlen(command_string);
|
||||
if (*context >= sizeof(commands) / sizeof(commands[0])) {
|
||||
return NULL;
|
||||
}
|
||||
for (const debugger_command_t *command = &commands[*context]; command->command; command++) {
|
||||
(*context)++;
|
||||
if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */
|
||||
return strdup(command->command + length);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (command) {
|
||||
if (arguments) {
|
||||
if (command->argument_completer) {
|
||||
return command->argument_completer(gb, arguments, context);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (modifiers) {
|
||||
if (command->modifiers_completer) {
|
||||
return command->modifiers_completer(gb, modifiers, context);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
JUMP_TO_NONE,
|
||||
JUMP_TO_BREAK,
|
||||
@ -2068,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) {
|
||||
|
@ -34,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G
|
||||
void
|
||||
#endif
|
||||
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
|
||||
|
||||
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */
|
||||
void GB_debugger_load_symbol_file_from_buffer(GB_gameboy_t *gb, const char *buffer, size_t size);
|
||||
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path);
|
||||
const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);
|
||||
|
101
Core/display.c
101
Core/display.c
@ -128,7 +128,7 @@ static void display_vblank(GB_gameboy_t *gb)
|
||||
|
||||
bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80;
|
||||
|
||||
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
|
||||
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) {
|
||||
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
|
||||
if (!GB_is_sgb(gb)) {
|
||||
uint32_t color = 0;
|
||||
@ -430,6 +430,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;
|
||||
@ -603,14 +620,15 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
||||
/* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */
|
||||
gb->fetcher_y = y;
|
||||
}
|
||||
gb->current_tile = gb->vram[map + x + y / 8 * 32];
|
||||
gb->last_tile_index_address = map + x + y / 8 * 32;
|
||||
gb->current_tile = gb->vram[gb->last_tile_index_address];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->current_tile = 0xFF;
|
||||
}
|
||||
if (GB_is_cgb(gb)) {
|
||||
/* The CGB actually accesses both the tile index AND the attributes in the same T-cycle.
|
||||
This probably means the CGB has a 16-bit data bus for the VRAM. */
|
||||
gb->current_tile_attributes = gb->vram[map + x + y / 8 * 32 + 0x2000];
|
||||
This probably means the CGB has a 16-bit data bus for the VRAM. */
|
||||
gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->current_tile_attributes = 0xFF;
|
||||
}
|
||||
@ -620,6 +638,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);
|
||||
@ -637,20 +659,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);
|
||||
|
||||
@ -667,10 +701,21 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
||||
if (gb->current_tile_attributes & 0x40) {
|
||||
y_flip = 0x7;
|
||||
}
|
||||
gb->current_tile_data[1] =
|
||||
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->current_tile_data[1] = 0xFF;
|
||||
gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1;
|
||||
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) {
|
||||
@ -794,6 +839,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
while (true) {
|
||||
GB_SLEEP(gb, display, 1, LCDC_PERIOD);
|
||||
display_vblank(gb);
|
||||
gb->cgb_repeated_a_frame = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -851,8 +897,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
GB_SLEEP(gb, display, 37, 2);
|
||||
|
||||
gb->cgb_palettes_blocked = true;
|
||||
gb->cycles_for_line += 3;
|
||||
GB_SLEEP(gb, display, 38, 3);
|
||||
gb->cycles_for_line += (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3;
|
||||
GB_SLEEP(gb, display, 38, (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3);
|
||||
|
||||
gb->vram_read_blocked = true;
|
||||
gb->vram_write_blocked = true;
|
||||
@ -975,7 +1021,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
bool should_activate_window = false;
|
||||
if (gb->io_registers[GB_IO_WX] == 0) {
|
||||
static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14};
|
||||
if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) {
|
||||
if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7] && !GB_is_cgb(gb)) {
|
||||
should_activate_window = true;
|
||||
}
|
||||
}
|
||||
@ -1109,7 +1155,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--;
|
||||
}
|
||||
|
||||
@ -1125,6 +1172,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;
|
||||
@ -1238,11 +1293,17 @@ abort_fetching_object:
|
||||
}
|
||||
}
|
||||
else {
|
||||
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
|
||||
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
|
||||
gb->is_odd_frame ^= true;
|
||||
display_vblank(gb);
|
||||
}
|
||||
if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) {
|
||||
gb->cgb_repeated_a_frame = true;
|
||||
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
|
||||
}
|
||||
else {
|
||||
gb->cgb_repeated_a_frame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
69
Core/gb.c
69
Core/gb.c
@ -62,6 +62,9 @@ static char *default_input_callback(GB_gameboy_t *gb)
|
||||
{
|
||||
char *expression = NULL;
|
||||
size_t size = 0;
|
||||
if (gb->debug_stopped) {
|
||||
printf(">");
|
||||
}
|
||||
|
||||
if (getline(&expression, &size, stdin) == -1) {
|
||||
/* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */
|
||||
@ -77,6 +80,12 @@ static char *default_input_callback(GB_gameboy_t *gb)
|
||||
if (expression[length - 1] == '\n') {
|
||||
expression[length - 1] = 0;
|
||||
}
|
||||
|
||||
if (expression[0] == '\x03') {
|
||||
gb->debug_stopped = true;
|
||||
free(expression);
|
||||
return strdup("");
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
@ -193,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
|
||||
@ -564,6 +576,8 @@ typedef struct __attribute__((packed)) {
|
||||
uint64_t last_rtc_second;
|
||||
uint16_t minutes;
|
||||
uint16_t days;
|
||||
uint16_t alarm_minutes, alarm_days;
|
||||
uint8_t alarm_enabled;
|
||||
} GB_huc3_rtc_time_t;
|
||||
|
||||
typedef union {
|
||||
@ -612,12 +626,18 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
|
||||
__builtin_bswap64(gb->last_rtc_second),
|
||||
__builtin_bswap16(gb->huc3_minutes),
|
||||
__builtin_bswap16(gb->huc3_days),
|
||||
__builtin_bswap16(gb->huc3_alarm_minutes),
|
||||
__builtin_bswap16(gb->huc3_alarm_days),
|
||||
gb->huc3_alarm_enabled,
|
||||
};
|
||||
#else
|
||||
GB_huc3_rtc_time_t rtc_save = {
|
||||
gb->last_rtc_second,
|
||||
gb->huc3_minutes,
|
||||
gb->huc3_days,
|
||||
gb->huc3_alarm_minutes,
|
||||
gb->huc3_alarm_days,
|
||||
gb->huc3_alarm_enabled,
|
||||
};
|
||||
#endif
|
||||
memcpy(buffer, &rtc_save, sizeof(rtc_save));
|
||||
@ -666,12 +686,18 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path)
|
||||
__builtin_bswap64(gb->last_rtc_second),
|
||||
__builtin_bswap16(gb->huc3_minutes),
|
||||
__builtin_bswap16(gb->huc3_days),
|
||||
__builtin_bswap16(gb->huc3_alarm_minutes),
|
||||
__builtin_bswap16(gb->huc3_alarm_days),
|
||||
gb->huc3_alarm_enabled,
|
||||
};
|
||||
#else
|
||||
GB_huc3_rtc_time_t rtc_save = {
|
||||
gb->last_rtc_second,
|
||||
gb->huc3_minutes,
|
||||
gb->huc3_days,
|
||||
gb->huc3_alarm_minutes,
|
||||
gb->huc3_alarm_days,
|
||||
gb->huc3_alarm_enabled,
|
||||
};
|
||||
#endif
|
||||
|
||||
@ -726,10 +752,16 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t
|
||||
gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second);
|
||||
gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes);
|
||||
gb->huc3_days = __builtin_bswap16(rtc_save.days);
|
||||
gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes);
|
||||
gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days);
|
||||
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
|
||||
#else
|
||||
gb->last_rtc_second = rtc_save.last_rtc_second;
|
||||
gb->huc3_minutes = rtc_save.minutes;
|
||||
gb->huc3_days = rtc_save.days;
|
||||
gb->huc3_alarm_minutes = rtc_save.alarm_minutes;
|
||||
gb->huc3_alarm_days = rtc_save.alarm_days;
|
||||
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
|
||||
#endif
|
||||
if (gb->last_rtc_second > time(NULL)) {
|
||||
/* We must reset RTC here, or it will not advance. */
|
||||
@ -802,6 +834,7 @@ reset_rtc:
|
||||
gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
|
||||
gb->huc3_days = 0xFFFF;
|
||||
gb->huc3_minutes = 0xFFF;
|
||||
gb->huc3_alarm_enabled = false;
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
@ -827,10 +860,16 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path)
|
||||
gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second);
|
||||
gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes);
|
||||
gb->huc3_days = __builtin_bswap16(rtc_save.days);
|
||||
gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes);
|
||||
gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days);
|
||||
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
|
||||
#else
|
||||
gb->last_rtc_second = rtc_save.last_rtc_second;
|
||||
gb->huc3_minutes = rtc_save.minutes;
|
||||
gb->huc3_days = rtc_save.days;
|
||||
gb->huc3_alarm_minutes = rtc_save.alarm_minutes;
|
||||
gb->huc3_alarm_days = rtc_save.alarm_days;
|
||||
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
|
||||
#endif
|
||||
if (gb->last_rtc_second > time(NULL)) {
|
||||
/* We must reset RTC here, or it will not advance. */
|
||||
@ -902,6 +941,7 @@ reset_rtc:
|
||||
gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
|
||||
gb->huc3_days = 0xFFFF;
|
||||
gb->huc3_minutes = 0xFFF;
|
||||
gb->huc3_alarm_enabled = false;
|
||||
exit:
|
||||
fclose(f);
|
||||
return;
|
||||
@ -1036,17 +1076,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)
|
||||
@ -1094,8 +1123,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)
|
||||
@ -1407,6 +1437,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);
|
||||
@ -1568,3 +1602,14 @@ void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t
|
||||
gb->boot_rom_load_callback = callback;
|
||||
request_boot_rom(gb);
|
||||
}
|
||||
|
||||
unsigned GB_time_to_alarm(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->cartridge_type->mbc_type != GB_HUC3) return 0;
|
||||
if (!gb->huc3_alarm_enabled) return 0;
|
||||
if (!(gb->huc3_alarm_days & 0x2000)) return 0;
|
||||
unsigned current_time = (gb->huc3_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_minutes * 60 + (time(NULL) % 60);
|
||||
unsigned alarm_time = (gb->huc3_alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_alarm_minutes * 60;
|
||||
if (current_time > alarm_time) return 0;
|
||||
return alarm_time - current_time;
|
||||
}
|
||||
|
52
Core/gb.h
52
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 */
|
||||
@ -418,7 +420,8 @@ struct GB_gameboy_internal_s {
|
||||
struct {
|
||||
uint8_t bank_low:6;
|
||||
uint8_t bank_high:3;
|
||||
uint8_t mode:1;
|
||||
bool mode:1;
|
||||
bool ir_mode:1;
|
||||
} huc1;
|
||||
|
||||
struct {
|
||||
@ -433,12 +436,15 @@ 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;
|
||||
uint16_t huc3_alarm_minutes, huc3_alarm_days;
|
||||
bool huc3_alarm_enabled;
|
||||
uint8_t huc3_read;
|
||||
uint8_t huc3_access_flags;
|
||||
bool mbc3_rtc_mapped;
|
||||
);
|
||||
|
||||
|
||||
@ -535,6 +541,10 @@ struct GB_gameboy_internal_s {
|
||||
uint8_t window_tile_x;
|
||||
uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases.
|
||||
bool is_odd_frame;
|
||||
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 */
|
||||
@ -596,12 +606,9 @@ struct GB_gameboy_internal_s {
|
||||
GB_icd_vreset_callback_t icd_vreset_callback;
|
||||
GB_read_memory_callback_t read_memory_callback;
|
||||
GB_boot_rom_load_callback_t boot_rom_load_callback;
|
||||
|
||||
/* 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_print_image_callback_t printer_callback;
|
||||
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;
|
||||
@ -610,7 +617,7 @@ struct GB_gameboy_internal_s {
|
||||
/* Breakpoints */
|
||||
uint16_t n_breakpoints;
|
||||
struct GB_breakpoint_s *breakpoints;
|
||||
bool has_jump_to_breakpoints;
|
||||
bool has_jump_to_breakpoints, has_software_breakpoints;
|
||||
void *nontrivial_jump_state;
|
||||
bool non_trivial_jump_breakpoint_occured;
|
||||
|
||||
@ -639,6 +646,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;
|
||||
@ -676,6 +687,7 @@ struct GB_gameboy_internal_s {
|
||||
|
||||
/* Temporary state */
|
||||
bool wx_just_changed;
|
||||
bool tile_sel_glitch;
|
||||
);
|
||||
};
|
||||
|
||||
@ -753,7 +765,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);
|
||||
@ -778,15 +789,16 @@ void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data);
|
||||
|
||||
void GB_disconnect_serial(GB_gameboy_t *gb);
|
||||
|
||||
/* For cartridges with an alarm clock */
|
||||
unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm
|
||||
|
||||
/* For integration with SFC/SNES emulators */
|
||||
void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback);
|
||||
void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback);
|
||||
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;
|
||||
@ -135,7 +134,10 @@ void GB_configure_cart(GB_gameboy_t *gb)
|
||||
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
|
||||
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
|
||||
}
|
||||
gb->mbc_ram = malloc(gb->mbc_ram_size);
|
||||
|
||||
if (gb->mbc_ram_size) {
|
||||
gb->mbc_ram = malloc(gb->mbc_ram_size);
|
||||
}
|
||||
|
||||
/* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */
|
||||
memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);
|
||||
|
263
Core/memory.c
263
Core/memory.c
@ -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) {
|
||||
@ -146,6 +141,18 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr)
|
||||
if (gb->vram_read_blocked) {
|
||||
return 0xFF;
|
||||
}
|
||||
if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) {
|
||||
if (addr & 0x1000) {
|
||||
addr = gb->last_tile_index_address;
|
||||
}
|
||||
else if (gb->last_tile_data_address & 0x1000) {
|
||||
/* TODO: This is case is more complicated then the rest and differ between revisions
|
||||
It's probably affected by how VRAM is layed out, might be easier after a decap is done*/
|
||||
}
|
||||
else {
|
||||
addr = gb->last_tile_data_address;
|
||||
}
|
||||
}
|
||||
return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000];
|
||||
}
|
||||
|
||||
@ -161,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?
|
||||
@ -171,26 +178,29 @@ 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) return 0xFF;
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) {
|
||||
return 0xc0 | effective_ir_input(gb);
|
||||
gb->cartridge_type->mbc_type != GB_HUC3) {
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
|
||||
return 0xc0 | gb->effective_ir_input;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 &&
|
||||
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;
|
||||
}
|
||||
|
||||
@ -198,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;
|
||||
}
|
||||
@ -409,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;
|
||||
}
|
||||
@ -494,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));
|
||||
@ -522,9 +542,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
break;
|
||||
case GB_HUC1:
|
||||
switch (addr & 0xF000) {
|
||||
case 0x0000: case 0x1000: gb->huc1.mode = (value & 0xF) == 0xE; break;
|
||||
case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break;
|
||||
case 0x2000: case 0x3000: gb->huc1.bank_low = value; break;
|
||||
case 0x4000: case 0x5000: gb->huc1.bank_high = value; break;
|
||||
case 0x6000: case 0x7000: gb->huc1.mode = value; break;
|
||||
}
|
||||
break;
|
||||
case GB_HUC3:
|
||||
@ -547,80 +568,110 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
//GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr);
|
||||
return;
|
||||
}
|
||||
/* TODO: not verified */
|
||||
if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) {
|
||||
if (addr & 0x1000) {
|
||||
addr = gb->last_tile_index_address;
|
||||
}
|
||||
else if (gb->last_tile_data_address & 0x1000) {
|
||||
/* TODO: This is case is more complicated then the rest and differ between revisions
|
||||
It's probably affected by how VRAM is layed out, might be easier after a decap is done */
|
||||
}
|
||||
else {
|
||||
addr = gb->last_tile_data_address;
|
||||
}
|
||||
}
|
||||
gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value;
|
||||
}
|
||||
|
||||
static bool huc3_write(GB_gameboy_t *gb, uint8_t value)
|
||||
{
|
||||
switch (gb->huc3_mode) {
|
||||
case 0xB: // RTC Write
|
||||
switch (value >> 4) {
|
||||
case 1:
|
||||
if (gb->huc3_access_index < 3) {
|
||||
gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF;
|
||||
}
|
||||
else if (gb->huc3_access_index < 7) {
|
||||
gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF;
|
||||
}
|
||||
else {
|
||||
// GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index);
|
||||
}
|
||||
gb->huc3_access_index++;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if (gb->huc3_access_index < 3) {
|
||||
gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4));
|
||||
gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4));
|
||||
}
|
||||
else if (gb->huc3_access_index < 7) {
|
||||
gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4));
|
||||
gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4));
|
||||
}
|
||||
else if (gb->huc3_access_index >= 0x58 && gb->huc3_access_index <= 0x5a) {
|
||||
gb->huc3_alarm_minutes &= ~(0xF << ((gb->huc3_access_index - 0x58) * 4));
|
||||
gb->huc3_alarm_minutes |= ((value & 0xF) << ((gb->huc3_access_index - 0x58) * 4));
|
||||
}
|
||||
else if (gb->huc3_access_index >= 0x5b && gb->huc3_access_index <= 0x5e) {
|
||||
gb->huc3_alarm_days &= ~(0xF << ((gb->huc3_access_index - 0x5b) * 4));
|
||||
gb->huc3_alarm_days |= ((value & 0xF) << ((gb->huc3_access_index - 0x5b) * 4));
|
||||
}
|
||||
else if (gb->huc3_access_index == 0x5f) {
|
||||
gb->huc3_alarm_enabled = value & 1;
|
||||
}
|
||||
else {
|
||||
// GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3_access_index);
|
||||
}
|
||||
if ((value >> 4) == 3) {
|
||||
gb->huc3_access_index++;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
gb->huc3_access_index &= 0xF0;
|
||||
gb->huc3_access_index |= value & 0xF;
|
||||
break;
|
||||
case 5:
|
||||
gb->huc3_access_index &= 0x0F;
|
||||
gb->huc3_access_index |= (value & 0xF) << 4;
|
||||
break;
|
||||
case 6:
|
||||
gb->huc3_access_flags = (value & 0xF);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
case 0xD: // RTC status
|
||||
// Not sure what writes here mean, they're always 0xFE
|
||||
return true;
|
||||
case 0xE: { // IR mode
|
||||
if (gb->cart_ir != (value & 1)) {
|
||||
gb->cart_ir = value & 1;
|
||||
if (gb->infrared_callback) {
|
||||
gb->infrared_callback(gb, value & 1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case 0xC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
case 0: // Disabled
|
||||
case 0xA: // RAM
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
{
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
switch (gb->huc3_mode) {
|
||||
case 0xB: // RTC Write
|
||||
switch (value >> 4) {
|
||||
case 1:
|
||||
if (gb->huc3_access_index < 3) {
|
||||
gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF;
|
||||
}
|
||||
else if (gb->huc3_access_index < 7) {
|
||||
gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF;
|
||||
}
|
||||
else {
|
||||
GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index);
|
||||
}
|
||||
gb->huc3_access_index++;
|
||||
return;
|
||||
case 2:
|
||||
case 3:
|
||||
if (gb->huc3_access_index < 3) {
|
||||
gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4));
|
||||
gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4));
|
||||
}
|
||||
else if (gb->huc3_access_index < 7) {
|
||||
gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4));
|
||||
gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4));
|
||||
}
|
||||
if ((value >> 4) == 3) {
|
||||
gb->huc3_access_index++;
|
||||
}
|
||||
return;
|
||||
case 4:
|
||||
gb->huc3_access_index &= 0xF0;
|
||||
gb->huc3_access_index |= value & 0xF;
|
||||
return;
|
||||
case 5:
|
||||
gb->huc3_access_index &= 0x0F;
|
||||
gb->huc3_access_index |= (value & 0xF) << 4;
|
||||
return;
|
||||
case 6:
|
||||
gb->huc3_access_flags = (value & 0xF);
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
case 0xD: // RTC status
|
||||
// Not sure what writes here mean, they're always 0xFE
|
||||
return;
|
||||
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->infrared_callback) {
|
||||
gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change);
|
||||
}
|
||||
gb->cycles_since_ir_change = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
GB_log(gb, "Unsupported HuC-3 mode %x write: [%04x] = %02x\n", gb->huc3_mode, addr, value);
|
||||
return;
|
||||
case 0: // Disabled
|
||||
case 0xA: // RAM
|
||||
break;
|
||||
}
|
||||
if (huc3_write(gb, value)) return;
|
||||
}
|
||||
|
||||
if (gb->camera_registers_mapped) {
|
||||
@ -628,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.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->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
|
||||
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)
|
||||
@ -1047,15 +1100,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;
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,8 @@ static void handle_command(GB_gameboy_t *gb)
|
||||
image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
|
||||
}
|
||||
|
||||
if (gb->printer.callback) {
|
||||
gb->printer.callback(gb, image, gb->printer.image_offset / 160,
|
||||
if (gb->printer_callback) {
|
||||
gb->printer_callback(gb, image, gb->printer.image_offset / 160,
|
||||
gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7,
|
||||
gb->printer.command_data[3] & 0x7F);
|
||||
}
|
||||
@ -189,13 +189,13 @@ static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received)
|
||||
|
||||
static void serial_start(GB_gameboy_t *gb, bool bit_received)
|
||||
{
|
||||
gb->printer.byte_being_recieved <<= 1;
|
||||
gb->printer.byte_being_recieved |= bit_received;
|
||||
gb->printer.bits_recieved++;
|
||||
if (gb->printer.bits_recieved == 8) {
|
||||
byte_reieve_completed(gb, gb->printer.byte_being_recieved);
|
||||
gb->printer.bits_recieved = 0;
|
||||
gb->printer.byte_being_recieved = 0;
|
||||
gb->printer.byte_being_received <<= 1;
|
||||
gb->printer.byte_being_received |= bit_received;
|
||||
gb->printer.bits_received++;
|
||||
if (gb->printer.bits_received == 8) {
|
||||
byte_reieve_completed(gb, gb->printer.byte_being_received);
|
||||
gb->printer.bits_received = 0;
|
||||
gb->printer.byte_being_received = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,5 +212,5 @@ void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback)
|
||||
memset(&gb->printer, 0, sizeof(gb->printer));
|
||||
GB_set_serial_transfer_bit_start_callback(gb, serial_start);
|
||||
GB_set_serial_transfer_bit_end_callback(gb, serial_end);
|
||||
gb->printer.callback = callback;
|
||||
gb->printer_callback = callback;
|
||||
}
|
||||
|
@ -48,13 +48,14 @@ typedef struct
|
||||
uint8_t image[160 * 200];
|
||||
uint16_t image_offset;
|
||||
|
||||
GB_print_image_callback_t callback;
|
||||
/* TODO: Delete me. */
|
||||
uint64_t padding;
|
||||
|
||||
uint8_t compression_run_lenth;
|
||||
bool compression_run_is_compressed;
|
||||
|
||||
uint8_t bits_recieved;
|
||||
uint8_t byte_being_recieved;
|
||||
uint8_t bits_received;
|
||||
uint8_t byte_being_received;
|
||||
bool bit_to_send;
|
||||
} GB_printer_t;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -40,7 +40,6 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
|
||||
if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
|
||||
}
|
||||
|
||||
|
||||
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
|
||||
goto error;
|
||||
}
|
||||
@ -116,13 +115,21 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
|
||||
}
|
||||
|
||||
/* Best-effort read function for maximum future compatibility. */
|
||||
static bool read_section(FILE *f, void *dest, uint32_t size)
|
||||
static bool read_section(FILE *f, void *dest, uint32_t size, bool fix_broken_windows_saves)
|
||||
{
|
||||
uint32_t saved_size = 0;
|
||||
if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fix_broken_windows_saves) {
|
||||
if (saved_size < 4) {
|
||||
return false;
|
||||
}
|
||||
saved_size -= 4;
|
||||
fseek(f, 4, SEEK_CUR);
|
||||
}
|
||||
|
||||
if (saved_size <= size) {
|
||||
if (fread(dest, 1, saved_size, f) != saved_size) {
|
||||
return false;
|
||||
@ -139,11 +146,21 @@ static bool read_section(FILE *f, void *dest, uint32_t size)
|
||||
}
|
||||
#undef DUMP_SECTION
|
||||
|
||||
static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
|
||||
static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
|
||||
{
|
||||
if (gb->magic != save->magic) {
|
||||
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
|
||||
return false;
|
||||
if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) {
|
||||
/* This is a save state with a bad printer struct from a 32-bit OS */
|
||||
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
|
||||
incorrect RAM amount if it's a CGB instance */
|
||||
if (GB_is_cgb(save)) {
|
||||
save->ram_size = 0x2000 * 8; // Incorrect RAM size
|
||||
}
|
||||
else {
|
||||
save->ram_size = gb->ram_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->version != save->version) {
|
||||
@ -202,7 +219,7 @@ static void sanitize_state(GB_gameboy_t *gb)
|
||||
}
|
||||
}
|
||||
|
||||
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
||||
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
|
||||
|
||||
int GB_load_state(GB_gameboy_t *gb, const char *path)
|
||||
{
|
||||
@ -219,7 +236,18 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
|
||||
return errno;
|
||||
}
|
||||
|
||||
bool fix_broken_windows_saves = false;
|
||||
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
|
||||
if (save.magic == 0) {
|
||||
/* Potentially legacy, broken Windows save state */
|
||||
fseek(f, 4, SEEK_SET);
|
||||
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
|
||||
fix_broken_windows_saves = true;
|
||||
}
|
||||
if (gb->magic != save.magic) {
|
||||
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
|
||||
return false;
|
||||
}
|
||||
if (!READ_SECTION(&save, f, core_state)) goto error;
|
||||
if (!READ_SECTION(&save, f, dma )) goto error;
|
||||
if (!READ_SECTION(&save, f, mbc )) goto error;
|
||||
@ -229,24 +257,13 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
|
||||
if (!READ_SECTION(&save, f, rtc )) goto error;
|
||||
if (!READ_SECTION(&save, f, video )) goto error;
|
||||
|
||||
if (save.ram_size == 0) {
|
||||
/* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
|
||||
incorrect RAM amount if it's a CGB instance */
|
||||
if (GB_is_cgb(&save)) {
|
||||
save.ram_size = 0x2000 * 8; // Incorrect RAM size
|
||||
}
|
||||
else {
|
||||
save.ram_size = gb->ram_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!verify_state_compatibility(gb, &save)) {
|
||||
if (!verify_and_update_state_compatibility(gb, &save)) {
|
||||
errno = -1;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
|
||||
if (!read_section(f, gb->sgb, sizeof(*gb->sgb), false)) goto error;
|
||||
}
|
||||
|
||||
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
|
||||
@ -297,7 +314,7 @@ static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, siz
|
||||
return length;
|
||||
}
|
||||
|
||||
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size)
|
||||
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size, bool fix_broken_windows_saves)
|
||||
{
|
||||
uint32_t saved_size = 0;
|
||||
if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
|
||||
@ -306,6 +323,14 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
|
||||
|
||||
if (saved_size > *buffer_length) return false;
|
||||
|
||||
if (fix_broken_windows_saves) {
|
||||
if (saved_size < 4) {
|
||||
return false;
|
||||
}
|
||||
saved_size -= 4;
|
||||
*buffer += 4;
|
||||
}
|
||||
|
||||
if (saved_size <= size) {
|
||||
if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
|
||||
return false;
|
||||
@ -322,15 +347,27 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
|
||||
return true;
|
||||
}
|
||||
|
||||
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
||||
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
|
||||
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
|
||||
{
|
||||
GB_gameboy_t save;
|
||||
|
||||
/* Every unread value should be kept the same. */
|
||||
memcpy(&save, gb, sizeof(save));
|
||||
|
||||
bool fix_broken_windows_saves = false;
|
||||
|
||||
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
|
||||
if (save.magic == 0) {
|
||||
/* Potentially legacy, broken Windows save state*/
|
||||
buffer -= GB_SECTION_SIZE(header) - 4;
|
||||
length += GB_SECTION_SIZE(header) - 4;
|
||||
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
|
||||
fix_broken_windows_saves = true;
|
||||
}
|
||||
if (gb->magic != save.magic) {
|
||||
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
|
||||
return false;
|
||||
}
|
||||
if (!READ_SECTION(&save, buffer, length, core_state)) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, dma )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
|
||||
@ -339,13 +376,14 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
|
||||
if (!READ_SECTION(&save, buffer, length, apu )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, rtc )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, video )) return -1;
|
||||
|
||||
|
||||
if (!verify_state_compatibility(gb, &save)) {
|
||||
if (!verify_and_update_state_compatibility(gb, &save)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1;
|
||||
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb), false)) return -1;
|
||||
}
|
||||
|
||||
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
|
||||
|
@ -5,10 +5,16 @@
|
||||
|
||||
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use
|
||||
|
||||
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end
|
||||
#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,34 @@ 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->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->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 +291,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)
|
||||
@ -789,6 +832,13 @@ LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL
|
||||
LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a)
|
||||
LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a)
|
||||
|
||||
// fire the debugger if software breakpoints are enabled
|
||||
static void ld_b_b(GB_gameboy_t *gb, uint8_t opcode)
|
||||
{
|
||||
if (gb->has_software_breakpoints) {
|
||||
gb->debug_stopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_a_r(GB_gameboy_t *gb, uint8_t opcode)
|
||||
{
|
||||
@ -1459,7 +1509,7 @@ static GB_opcode_t *opcodes[256] = {
|
||||
jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl,
|
||||
jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */
|
||||
jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf,
|
||||
nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */
|
||||
ld_b_b, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */
|
||||
ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a,
|
||||
ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */
|
||||
ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a,
|
||||
@ -1531,8 +1581,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)
|
||||
@ -234,8 +251,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
||||
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 +267,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 +306,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
|
2
HexFiend/HFFunctions.h
vendored
2
HexFiend/HFFunctions.h
vendored
@ -4,7 +4,7 @@
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#define HFDEFAULT_FONT (@"Monaco")
|
||||
#define HFDEFAULT_FONTSIZE ((CGFloat)10.)
|
||||
#define HFDEFAULT_FONTSIZE ((CGFloat)11.)
|
||||
|
||||
#define HFZeroRange (HFRange){0, 0}
|
||||
|
||||
|
10
HexFiend/HFLineCountingRepresenter.m
vendored
10
HexFiend/HFLineCountingRepresenter.m
vendored
@ -57,8 +57,8 @@ static CGFloat maximumDigitAdvanceForFont(NSFont *font) {
|
||||
interiorShadowEdge = NSMaxXEdge;
|
||||
|
||||
_borderedEdges = (1 << NSMaxXEdge);
|
||||
_borderColor = [[NSColor darkGrayColor] retain];
|
||||
_backgroundColor = [[NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
|
||||
_borderColor = [[NSColor controlShadowColor] retain];
|
||||
_backgroundColor = [[NSColor windowBackgroundColor] retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -82,9 +82,9 @@ static CGFloat maximumDigitAdvanceForFont(NSFont *font) {
|
||||
lineNumberFormat = (HFLineNumberFormat)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
|
||||
|
||||
_borderedEdges = [coder decodeObjectForKey:@"HFBorderedEdges"] ? (NSInteger)[coder decodeInt64ForKey:@"HFBorderedEdges"] : 0;
|
||||
_borderColor = [[coder decodeObjectForKey:@"HFBorderColor"] ?: [NSColor darkGrayColor] retain];
|
||||
_backgroundColor = [[coder decodeObjectForKey:@"HFBackgroundColor"] ?: [NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
|
||||
|
||||
_borderColor = [[NSColor controlShadowColor] retain];
|
||||
_backgroundColor = [[NSColor windowBackgroundColor] retain];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
56
HexFiend/HFLineCountingView.m
vendored
56
HexFiend/HFLineCountingView.m
vendored
@ -119,20 +119,6 @@
|
||||
[super viewWillMoveToWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)drawGradientWithClip:(NSRect)clip {
|
||||
[_representer.backgroundColor set];
|
||||
NSRectFill(clip);
|
||||
|
||||
NSInteger shadowEdge = _representer.interiorShadowEdge;
|
||||
|
||||
if (shadowEdge >= 0) {
|
||||
const CGFloat shadowWidth = 6;
|
||||
NSWindow *window = self.window;
|
||||
BOOL drawActive = (window == nil || [window isKeyWindow] || [window isMainWindow]);
|
||||
HFDrawShadow([[NSGraphicsContext currentContext] graphicsPort], self.bounds, shadowWidth, shadowEdge, drawActive, clip);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawDividerWithClip:(NSRect)clipRect {
|
||||
USE(clipRect);
|
||||
|
||||
@ -267,7 +253,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
|
||||
NSUInteger glyphCount;
|
||||
[textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
|
||||
if (previousTextStorageCharacterCount == 0) {
|
||||
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, nil];
|
||||
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, nil];
|
||||
[textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
|
||||
[atts release];
|
||||
}
|
||||
@ -305,7 +291,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
|
||||
[mutableStyle setAlignment:NSRightTextAlignment];
|
||||
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
||||
[mutableStyle release];
|
||||
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
[paragraphStyle release];
|
||||
}
|
||||
|
||||
@ -456,12 +442,12 @@ static inline int common_prefix_length(const char *a, const char *b) {
|
||||
}
|
||||
}
|
||||
|
||||
if (! textAttributes) {
|
||||
if (!textAttributes) {
|
||||
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
[mutableStyle setAlignment:NSRightTextAlignment];
|
||||
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
||||
[mutableStyle release];
|
||||
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
[paragraphStyle release];
|
||||
[textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])];
|
||||
}
|
||||
@ -489,27 +475,28 @@ static inline int common_prefix_length(const char *a, const char *b) {
|
||||
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
|
||||
|
||||
CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
|
||||
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
|
||||
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1.5;
|
||||
NSRect textRect = self.bounds;
|
||||
textRect.size.width -= 5;
|
||||
textRect.origin.y -= verticalOffset;
|
||||
textRect.size.height += verticalOffset;
|
||||
|
||||
if (! textAttributes) {
|
||||
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
[mutableStyle setAlignment:NSRightTextAlignment];
|
||||
[mutableStyle setMinimumLineHeight:_lineHeight];
|
||||
[mutableStyle setMaximumLineHeight:_lineHeight];
|
||||
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
||||
[mutableStyle release];
|
||||
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
[paragraphStyle release];
|
||||
}
|
||||
textRect.size.height += verticalOffset + _lineHeight;
|
||||
|
||||
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
[mutableStyle setAlignment:NSRightTextAlignment];
|
||||
[mutableStyle setMinimumLineHeight:_lineHeight];
|
||||
[mutableStyle setMaximumLineHeight:_lineHeight];
|
||||
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
||||
[mutableStyle release];
|
||||
NSColor *color = [[NSColor textColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
|
||||
color = [NSColor colorWithRed:color.redComponent green:color.greenComponent blue:color.blueComponent alpha:0.75];
|
||||
NSDictionary *_textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:_font.fontName size:9], NSFontAttributeName, color, NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
[paragraphStyle release];
|
||||
|
||||
|
||||
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
|
||||
[string drawInRect:textRect withAttributes:textAttributes];
|
||||
[string drawInRect:textRect withAttributes:_textAttributes];
|
||||
[string release];
|
||||
[_textAttributes release];
|
||||
}
|
||||
|
||||
- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
|
||||
@ -533,7 +520,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
|
||||
[mutableStyle setMaximumLineHeight:_lineHeight];
|
||||
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
||||
[mutableStyle release];
|
||||
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
||||
[paragraphStyle release];
|
||||
}
|
||||
|
||||
@ -568,7 +555,7 @@ static inline int common_prefix_length(const char *a, const char *b) {
|
||||
#if TIME_LINE_NUMBERS
|
||||
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
||||
#endif
|
||||
NSInteger drawingMode = (useStringDrawingPath ? 1 : 3);
|
||||
NSInteger drawingMode = 3; // (useStringDrawingPath ? 1 : 3);
|
||||
switch (drawingMode) {
|
||||
// Drawing can't be done right if fonts are wider than expected, but all
|
||||
// of these have rather nasty behavior in that case. I've commented what
|
||||
@ -606,7 +593,6 @@ static inline int common_prefix_length(const char *a, const char *b) {
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)clipRect {
|
||||
[self drawGradientWithClip:clipRect];
|
||||
[self drawDividerWithClip:clipRect];
|
||||
[self drawLineNumbersWithClip:clipRect];
|
||||
}
|
||||
|
18
HexFiend/HFRepresenterTextView.m
vendored
18
HexFiend/HFRepresenterTextView.m
vendored
@ -441,7 +441,7 @@ enum LineCoverage_t {
|
||||
- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect {
|
||||
NSRect caretRect = NSIntersectionRect(caretRectToDraw, clipRect);
|
||||
if (! NSIsEmptyRect(caretRect)) {
|
||||
[[NSColor blackColor] set];
|
||||
[[NSColor controlTextColor] set];
|
||||
NSRectFill(caretRect);
|
||||
lastDrawnCaretRect = caretRect;
|
||||
}
|
||||
@ -456,12 +456,18 @@ enum LineCoverage_t {
|
||||
|
||||
/* This is the color when we are not in the key window */
|
||||
- (NSColor *)inactiveTextSelectionColor {
|
||||
if (@available(macOS 10.14, *)) {
|
||||
return [NSColor unemphasizedSelectedTextBackgroundColor];
|
||||
}
|
||||
return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
|
||||
}
|
||||
|
||||
/* This is the color when we are not the first responder, but we are in the key window */
|
||||
- (NSColor *)secondaryTextSelectionColor {
|
||||
return [[self primaryTextSelectionColor] blendedColorWithFraction:.66 ofColor:[NSColor colorWithCalibratedWhite:.8f alpha:1]];
|
||||
if (@available(macOS 10.14, *)) {
|
||||
return [NSColor unemphasizedSelectedTextBackgroundColor];
|
||||
}
|
||||
return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
|
||||
}
|
||||
|
||||
- (NSColor *)textSelectionColor {
|
||||
@ -826,7 +832,7 @@ enum LineCoverage_t {
|
||||
- (HFTextVisualStyleRun *)styleRunForByteAtIndex:(NSUInteger)byteIndex {
|
||||
HFTextVisualStyleRun *run = [[HFTextVisualStyleRun alloc] init];
|
||||
[run setRange:NSMakeRange(0, NSUIntegerMax)];
|
||||
[run setForegroundColor:[NSColor blackColor]];
|
||||
[run setForegroundColor:[NSColor textColor]];
|
||||
return [run autorelease];
|
||||
}
|
||||
|
||||
@ -902,8 +908,8 @@ static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count)
|
||||
guideIndex++;
|
||||
}
|
||||
if (rectIndex > 0) {
|
||||
[[NSColor colorWithCalibratedWhite:(CGFloat).8 alpha:1] set];
|
||||
NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositePlusDarker);
|
||||
[[NSColor gridColor] set];
|
||||
NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositeSourceOver);
|
||||
}
|
||||
FREE_ARRAY(lineRects);
|
||||
}
|
||||
@ -1344,7 +1350,7 @@ static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count)
|
||||
[self _drawDefaultLineBackgrounds:clip withLineHeight:[self lineHeight] maxLines:ll2l(HFRoundUpToNextMultipleSaturate(byteCount, bytesPerLine) / bytesPerLine)];
|
||||
[self drawSelectionIfNecessaryWithClip:clip];
|
||||
|
||||
NSColor *textColor = [NSColor blackColor];
|
||||
NSColor *textColor = [NSColor textColor];
|
||||
[textColor set];
|
||||
|
||||
if (! antialias) {
|
||||
|
44
HexFiend/HFStatusBarRepresenter.m
vendored
44
HexFiend/HFStatusBarRepresenter.m
vendored
@ -29,7 +29,7 @@
|
||||
- (void)_sharedInitStatusBarView {
|
||||
NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
|
||||
[style setAlignment:NSCenterTextAlignment];
|
||||
cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor colorWithCalibratedWhite:(CGFloat).15 alpha:1], NSForegroundColorAttributeName, [NSFont labelFontOfSize:10], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil];
|
||||
cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor windowFrameTextColor], NSForegroundColorAttributeName, [NSFont labelFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil];
|
||||
cell = [[NSCell alloc] initTextCell:@""];
|
||||
[cell setAlignment:NSCenterTextAlignment];
|
||||
[cell setBackgroundStyle:NSBackgroundStyleRaised];
|
||||
@ -62,51 +62,24 @@
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)drawDividerWithClip:(NSRect)clipRect {
|
||||
[[NSColor lightGrayColor] set];
|
||||
NSRect bounds = [self bounds];
|
||||
NSRect lineRect = bounds;
|
||||
lineRect.size.height = 1;
|
||||
NSRectFill(NSIntersectionRect(lineRect, clipRect));
|
||||
}
|
||||
|
||||
|
||||
- (NSGradient *)getGradient:(BOOL)active {
|
||||
static NSGradient *sActiveGradient;
|
||||
static NSGradient *sInactiveGradient;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sActiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
|
||||
[NSColor colorWithCalibratedWhite:.89 alpha:1.], 0.00,
|
||||
[NSColor colorWithCalibratedWhite:.77 alpha:1.], 0.9,
|
||||
[NSColor colorWithCalibratedWhite:.82 alpha:1.], 1.0,
|
||||
nil];
|
||||
|
||||
sInactiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
|
||||
[NSColor colorWithCalibratedWhite:.93 alpha:1.], 0.00,
|
||||
[NSColor colorWithCalibratedWhite:.87 alpha:1.], 0.9,
|
||||
[NSColor colorWithCalibratedWhite:.90 alpha:1.], 1.0,
|
||||
nil];
|
||||
});
|
||||
return active ? sActiveGradient : sInactiveGradient;
|
||||
}
|
||||
|
||||
|
||||
- (void)drawRect:(NSRect)clip {
|
||||
USE(clip);
|
||||
NSRect bounds = [self bounds];
|
||||
// [[NSColor colorWithCalibratedWhite:(CGFloat).91 alpha:1] set];
|
||||
// NSRectFill(clip);
|
||||
|
||||
NSWindow *window = [self window];
|
||||
BOOL drawActive = (window == nil || [window isMainWindow] || [window isKeyWindow]);
|
||||
[[self getGradient:drawActive] drawInRect:bounds angle:90.];
|
||||
|
||||
[self drawDividerWithClip:clip];
|
||||
NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - cellSize.height / 2), NSWidth(bounds), cellSize.height);
|
||||
[cell drawWithFrame:cellRect inView:self];
|
||||
}
|
||||
|
||||
- (void)setFrame:(NSRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
[self.window setContentBorderThickness:frame.origin.y + frame.size.height forEdge:NSMinYEdge];
|
||||
}
|
||||
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
USE(event);
|
||||
HFStatusBarMode newMode = ([representer statusMode] + 1) % HFSTATUSMODECOUNT;
|
||||
@ -122,6 +95,7 @@
|
||||
- (void)viewDidMoveToWindow {
|
||||
HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
|
||||
registeredForAppNotifications = YES;
|
||||
[self.window setContentBorderThickness:self.frame.origin.y + self.frame.size.height forEdge:NSMinYEdge];
|
||||
[super viewDidMoveToWindow];
|
||||
}
|
||||
|
||||
|
12
HexFiend/HFTextRepresenter.m
vendored
12
HexFiend/HFTextRepresenter.m
vendored
@ -20,9 +20,13 @@
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
|
||||
NSColor *color1 = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
|
||||
NSColor *color2 = [NSColor colorWithCalibratedRed:.87 green:.89 blue:1. alpha:1.];
|
||||
_rowBackgroundColors = [@[color1, color2] retain];
|
||||
if (@available(macOS 10.14, *)) {
|
||||
_rowBackgroundColors = [[NSColor alternatingContentBackgroundColors] retain];
|
||||
} else {
|
||||
NSColor *color1 = [NSColor windowBackgroundColor];
|
||||
NSColor *color2 = [NSColor colorWithDeviceWhite:0.96 alpha:1];
|
||||
_rowBackgroundColors = [@[color1, color2] retain];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -237,7 +241,7 @@
|
||||
FOREACH(HFRangeWrapper *, wrapper, selectedRanges) {
|
||||
HFRange selectedRange = [wrapper HFRange];
|
||||
BOOL clippedRangeIsVisible;
|
||||
NSRange clippedSelectedRange;
|
||||
NSRange clippedSelectedRange = {0,};
|
||||
/* Necessary because zero length ranges do not intersect anything */
|
||||
if (selectedRange.length == 0) {
|
||||
/* Remember that {6, 0} is considered a subrange of {3, 3} */
|
||||
|
@ -39,8 +39,6 @@ static bool axesEmulateButtons = false;
|
||||
static bool axes2DEmulateButtons = false;
|
||||
static bool hatsEmulateButtons = false;
|
||||
|
||||
static NSLock *globalPWMThreadLock;
|
||||
|
||||
@interface JOYController ()
|
||||
+ (void)controllerAdded:(IOHIDDeviceRef) device;
|
||||
+ (void)controllerRemoved:(IOHIDDeviceRef) device;
|
||||
@ -95,7 +93,9 @@ static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef
|
||||
static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type,
|
||||
uint32_t reportID, uint8_t *report, CFIndex reportLength)
|
||||
{
|
||||
[(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]];
|
||||
if (reportLength) {
|
||||
[(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]];
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
@ -120,7 +120,7 @@ typedef struct __attribute__((packed)) {
|
||||
uint8_t dutyLength;
|
||||
uint8_t enabled;
|
||||
uint8_t dutyOff;
|
||||
uint8_t dutyOnn;
|
||||
uint8_t dutyOn;
|
||||
} __attribute__((packed)) led[5];
|
||||
uint8_t padding3[13];
|
||||
} JOYDualShock3Output;
|
||||
@ -152,19 +152,20 @@ typedef union {
|
||||
bool _isSwitch; // Does this controller use the Switch protocol?
|
||||
bool _isDualShock3; // Does this controller use DS3 outputs?
|
||||
JOYVendorSpecificOutput _lastVendorSpecificOutput;
|
||||
NSLock *_rumblePWMThreadLock;
|
||||
volatile double _rumblePWMRatio;
|
||||
volatile double _rumbleAmplitude;
|
||||
bool _physicallyConnected;
|
||||
bool _logicallyConnected;
|
||||
bool _rumblePWMThreadRunning;
|
||||
volatile bool _forceStopPWMThread;
|
||||
|
||||
NSDictionary *_hacks;
|
||||
NSMutableData *_lastReport;
|
||||
|
||||
// Used when creating inputs
|
||||
JOYElement *_previousAxisElement;
|
||||
|
||||
|
||||
uint8_t _playerLEDs;
|
||||
double _sentRumbleAmp;
|
||||
unsigned _rumbleCounter;
|
||||
bool _deviceCantSendReports;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks
|
||||
@ -342,6 +343,7 @@ typedef union {
|
||||
_logicallyConnected = true;
|
||||
_device = (IOHIDDeviceRef)CFRetain(device);
|
||||
_serialSuffix = suffix;
|
||||
_playerLEDs = -1;
|
||||
|
||||
IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self);
|
||||
IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
@ -355,7 +357,6 @@ typedef union {
|
||||
_axes2DEmulatedButtons = [NSMutableDictionary dictionary];
|
||||
_hatEmulatedButtons = [NSMutableDictionary dictionary];
|
||||
_iokitToJOY = [NSMutableDictionary dictionary];
|
||||
_rumblePWMThreadLock = [[NSLock alloc] init];
|
||||
|
||||
|
||||
//NSMutableArray *axes3d = [NSMutableArray array];
|
||||
@ -365,18 +366,19 @@ typedef union {
|
||||
_isDualShock3 = [_hacks[JOYIsDualShock3] boolValue];
|
||||
|
||||
NSDictionary *customReports = hacks[JOYCustomReports];
|
||||
|
||||
_lastReport = [NSMutableData dataWithLength:MAX(
|
||||
MAX(
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue],
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue]
|
||||
),
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue]
|
||||
)];
|
||||
IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self);
|
||||
|
||||
if (hacks[JOYCustomReports]) {
|
||||
_multiElements = [NSMutableDictionary dictionary];
|
||||
_fullReportElements = [NSMutableDictionary dictionary];
|
||||
_lastReport = [NSMutableData dataWithLength:MAX(
|
||||
MAX(
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue],
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue]
|
||||
),
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue]
|
||||
)];
|
||||
IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self);
|
||||
|
||||
|
||||
for (NSNumber *_reportID in customReports) {
|
||||
signed reportID = [_reportID intValue];
|
||||
@ -481,11 +483,11 @@ typedef union {
|
||||
_lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){
|
||||
.reportID = 1,
|
||||
.led = {
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32},
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32},
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32},
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOnn = 0x32},
|
||||
{.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOnn = 0},
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
|
||||
{.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
|
||||
{.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0},
|
||||
}
|
||||
};
|
||||
|
||||
@ -552,16 +554,17 @@ typedef union {
|
||||
- (void)gotReport:(NSData *)report
|
||||
{
|
||||
JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)];
|
||||
if (!element) return;
|
||||
[element updateValue:report];
|
||||
|
||||
NSArray<JOYElement *> *subElements = _multiElements[element];
|
||||
if (subElements) {
|
||||
for (JOYElement *subElement in subElements) {
|
||||
[self _elementChanged:subElement];
|
||||
if (element) {
|
||||
[element updateValue:report];
|
||||
|
||||
NSArray<JOYElement *> *subElements = _multiElements[element];
|
||||
if (subElements) {
|
||||
for (JOYElement *subElement in subElements) {
|
||||
[self _elementChanged:subElement];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
[self updateRumble];
|
||||
}
|
||||
|
||||
- (void)elementChanged:(IOHIDElementRef)element
|
||||
@ -694,8 +697,9 @@ typedef union {
|
||||
}
|
||||
}
|
||||
_physicallyConnected = false;
|
||||
[self _forceStopPWMThread]; // Stop the rumble thread.
|
||||
[exposedControllers removeObject:self];
|
||||
[self setRumbleAmplitude:0];
|
||||
[self updateRumble];
|
||||
_device = nil;
|
||||
}
|
||||
|
||||
@ -703,19 +707,29 @@ typedef union {
|
||||
{
|
||||
if (!report.length) return;
|
||||
if (!_device) return;
|
||||
IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length);
|
||||
if (_deviceCantSendReports) return;
|
||||
/* Some Macs fail to send reports to some devices, specifically the DS3, returning the bogus(?) error code 1 after
|
||||
freezing for 5 seconds. Stop sending reports if that's the case. */
|
||||
if (IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length) == 1) {
|
||||
_deviceCantSendReports = true;
|
||||
NSLog(@"This Mac appears to be incapable of sending output reports to %@", self);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPlayerLEDs:(uint8_t)mask
|
||||
{
|
||||
mask &= 0xF;
|
||||
if (mask == _playerLEDs) {
|
||||
return;
|
||||
}
|
||||
_playerLEDs = mask;
|
||||
if (_isSwitch) {
|
||||
_lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs
|
||||
_lastVendorSpecificOutput.switchPacket.sequence++;
|
||||
_lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
|
||||
_lastVendorSpecificOutput.switchPacket.command = 0x30; // LED
|
||||
_lastVendorSpecificOutput.switchPacket.commandData[0] = mask;
|
||||
//[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]];
|
||||
[self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
|
||||
}
|
||||
else if (_isDualShock3) {
|
||||
_lastVendorSpecificOutput.ds3Output.reportID = 1;
|
||||
@ -724,101 +738,81 @@ typedef union {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pwmThread
|
||||
- (void)updateRumble
|
||||
{
|
||||
unsigned rumbleCounter = 0;
|
||||
while (self.connected && !_forceStopPWMThread) {
|
||||
if ([_rumbleElement setValue:rumbleCounter < round(_rumblePWMRatio * PWM_RESOLUTION)]) {
|
||||
break;
|
||||
if (!self.connected) {
|
||||
return;
|
||||
}
|
||||
if (!_rumbleElement && !_isSwitch && !_isDualShock3) {
|
||||
return;
|
||||
}
|
||||
if (_rumbleElement.max == 1 && _rumbleElement.min == 0) {
|
||||
double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION);
|
||||
if (ampToSend != _sentRumbleAmp) {
|
||||
[_rumbleElement setValue:ampToSend];
|
||||
_sentRumbleAmp = ampToSend;
|
||||
}
|
||||
rumbleCounter += round(_rumblePWMRatio * PWM_RESOLUTION);
|
||||
if (rumbleCounter >= PWM_RESOLUTION) {
|
||||
rumbleCounter -= PWM_RESOLUTION;
|
||||
_rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION);
|
||||
if (_rumbleCounter >= PWM_RESOLUTION) {
|
||||
_rumbleCounter -= PWM_RESOLUTION;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (_rumbleAmplitude == _sentRumbleAmp) {
|
||||
return;
|
||||
}
|
||||
_sentRumbleAmp = _rumbleAmplitude;
|
||||
if (_isSwitch) {
|
||||
double frequency = 144;
|
||||
double amp = _rumbleAmplitude;
|
||||
|
||||
uint8_t highAmp = amp * 0x64;
|
||||
uint8_t lowAmp = amp * 0x32 + 0x40;
|
||||
if (frequency < 0) frequency = 0;
|
||||
if (frequency > 1252) frequency = 1252;
|
||||
uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0);
|
||||
|
||||
uint16_t highFreq = (encodedFrequency - 0x60) * 4;
|
||||
uint8_t lowFreq = encodedFrequency - 0x40;
|
||||
|
||||
//if (frequency < 82 || frequency > 312) {
|
||||
if (amp) {
|
||||
highAmp = 0;
|
||||
}
|
||||
|
||||
if (frequency < 40 || frequency > 626) {
|
||||
lowAmp = 0;
|
||||
}
|
||||
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF;
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1);
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq;
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp;
|
||||
|
||||
|
||||
_lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only
|
||||
_lastVendorSpecificOutput.switchPacket.sequence++;
|
||||
_lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
|
||||
_lastVendorSpecificOutput.switchPacket.command = 0; // LED
|
||||
[self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
|
||||
}
|
||||
else if (_isDualShock3) {
|
||||
_lastVendorSpecificOutput.ds3Output.reportID = 1;
|
||||
_lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xff : 0;
|
||||
_lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xff);
|
||||
[self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]];
|
||||
}
|
||||
else {
|
||||
[_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min];
|
||||
}
|
||||
}
|
||||
[_rumblePWMThreadLock lock];
|
||||
[_rumbleElement setValue:0];
|
||||
_rumblePWMThreadRunning = false;
|
||||
_forceStopPWMThread = false;
|
||||
[_rumblePWMThreadLock unlock];
|
||||
}
|
||||
|
||||
- (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */
|
||||
{
|
||||
double frequency = 144; // I have no idea what I'm doing.
|
||||
|
||||
if (amp < 0) amp = 0;
|
||||
if (amp > 1) amp = 1;
|
||||
if (_isSwitch) {
|
||||
if (amp == 0) {
|
||||
amp = 1;
|
||||
frequency = 0;
|
||||
}
|
||||
|
||||
uint8_t highAmp = amp * 0x64;
|
||||
uint8_t lowAmp = amp * 0x32 + 0x40;
|
||||
if (frequency < 0) frequency = 0;
|
||||
if (frequency > 1252) frequency = 1252;
|
||||
uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0);
|
||||
|
||||
uint16_t highFreq = (encodedFrequency - 0x60) * 4;
|
||||
uint8_t lowFreq = encodedFrequency - 0x40;
|
||||
|
||||
//if (frequency < 82 || frequency > 312) {
|
||||
if (amp) {
|
||||
highAmp = 0;
|
||||
}
|
||||
|
||||
if (frequency < 40 || frequency > 626) {
|
||||
lowAmp = 0;
|
||||
}
|
||||
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF;
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1);
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq;
|
||||
_lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp;
|
||||
|
||||
|
||||
_lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only
|
||||
_lastVendorSpecificOutput.switchPacket.sequence++;
|
||||
_lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
|
||||
_lastVendorSpecificOutput.switchPacket.command = 0; // LED
|
||||
[self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
|
||||
}
|
||||
else if (_isDualShock3) {
|
||||
_lastVendorSpecificOutput.ds3Output.reportID = 1;
|
||||
_lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = amp? 0xff : 0;
|
||||
_lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = amp * 0xff;
|
||||
[self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]];
|
||||
}
|
||||
else {
|
||||
if (_rumbleElement.max == 1 && _rumbleElement.min == 0) {
|
||||
[_rumblePWMThreadLock lock];
|
||||
_rumblePWMRatio = amp;
|
||||
if (!_rumblePWMThreadRunning) { // PWM thread not running, start it.
|
||||
if (amp != 0) {
|
||||
/* TODO: The PWM thread does not handle correctly the case of having a multi-port controller where more
|
||||
than one controller uses rumble. At least make sure any sibling controllers don't have their
|
||||
PWM thread running. */
|
||||
|
||||
[globalPWMThreadLock lock];
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
if (controller != self && controller->_device == _device) {
|
||||
[controller _forceStopPWMThread];
|
||||
}
|
||||
}
|
||||
_rumblePWMRatio = amp;
|
||||
_rumblePWMThreadRunning = true;
|
||||
[self performSelectorInBackground:@selector(pwmThread) withObject:nil];
|
||||
[globalPWMThreadLock unlock];
|
||||
}
|
||||
}
|
||||
[_rumblePWMThreadLock unlock];
|
||||
}
|
||||
else {
|
||||
[_rumbleElement setValue:amp * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min];
|
||||
}
|
||||
}
|
||||
_rumbleAmplitude = amp;
|
||||
}
|
||||
|
||||
- (bool)isConnected
|
||||
@ -826,16 +820,6 @@ typedef union {
|
||||
return _logicallyConnected && _physicallyConnected;
|
||||
}
|
||||
|
||||
- (void)_forceStopPWMThread
|
||||
{
|
||||
[_rumblePWMThreadLock lock];
|
||||
if (_rumblePWMThreadRunning) {
|
||||
_forceStopPWMThread = true;
|
||||
}
|
||||
[_rumblePWMThreadLock unlock];
|
||||
while (_rumblePWMThreadRunning);
|
||||
}
|
||||
|
||||
+ (void)controllerAdded:(IOHIDDeviceRef) device
|
||||
{
|
||||
NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
|
||||
@ -893,7 +877,6 @@ typedef union {
|
||||
|
||||
controllers = [NSMutableDictionary dictionary];
|
||||
exposedControllers = [NSMutableArray array];
|
||||
globalPWMThreadLock = [[NSLock alloc] init];
|
||||
NSArray *array = @[
|
||||
CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
|
||||
CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
|
||||
|
46
Makefile
46
Makefile
@ -16,8 +16,10 @@ endif
|
||||
ifeq ($(PLATFORM),windows32)
|
||||
_ := $(shell chcp 65001)
|
||||
EXESUFFIX:=.exe
|
||||
NATIVE_CC = clang -IWindows -Wno-deprecated-declarations --target=i386-pc-windows
|
||||
else
|
||||
EXESUFFIX:=
|
||||
NATIVE_CC := cc
|
||||
endif
|
||||
|
||||
PB12_COMPRESS := build/pb12$(EXESUFFIX)
|
||||
@ -34,7 +36,7 @@ ifeq ($(MAKECMDGOALS),)
|
||||
MAKECMDGOALS := $(DEFAULT)
|
||||
endif
|
||||
|
||||
VERSION := 0.12.3
|
||||
include version.mk
|
||||
export VERSION
|
||||
CONF ?= debug
|
||||
SDL_AUDIO_DRIVER ?= sdl
|
||||
@ -74,6 +76,13 @@ LDFLAGS += -march=native -mtune=native
|
||||
CFLAGS += -march=native -mtune=native
|
||||
endif
|
||||
|
||||
ifeq ($(CONF),fat_release)
|
||||
override CONF := release
|
||||
FAT_FLAGS += -arch x86_64 -arch arm64
|
||||
endif
|
||||
|
||||
|
||||
|
||||
# Set compilation and linkage flags based on target, platform and configuration
|
||||
|
||||
OPEN_DIALOG = OpenDialog/gtk.c
|
||||
@ -97,6 +106,11 @@ ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2>
|
||||
WARNINGS += -Wpartial-availability
|
||||
endif
|
||||
|
||||
# GCC's implementation of this warning has false positives, so we skip it
|
||||
ifneq ($(shell $(CC) --version 2>&1 | grep "gcc"), )
|
||||
WARNINGS += -Wno-maybe-uninitialized
|
||||
endif
|
||||
|
||||
CFLAGS += $(WARNINGS)
|
||||
|
||||
CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES
|
||||
@ -142,8 +156,8 @@ GTK3_CFLAGS += -Wno-unused
|
||||
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 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows
|
||||
SDL_LDFLAGS := -lSDL2
|
||||
GL_LDFLAGS := -lopengl32
|
||||
else
|
||||
@ -160,9 +174,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
|
||||
@ -271,11 +285,11 @@ $(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 $@
|
||||
|
||||
# GTK3 requires special CFLAGS
|
||||
$(OBJ)/gtk3/%.c.o: gtk3/%.c
|
||||
@ -284,16 +298,16 @@ $(OBJ)/gtk3/%.c.o: gtk3/%.c
|
||||
|
||||
$(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
|
||||
|
||||
@ -321,13 +335,13 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \
|
||||
|
||||
$(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit
|
||||
$(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit
|
||||
ifeq ($(CONF), release)
|
||||
$(STRIP) $@
|
||||
endif
|
||||
|
||||
$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib
|
||||
ibtool --compile $@ $^
|
||||
ibtool --compile $@ $^ 2>&1 | cat -
|
||||
|
||||
# Quick Look generator
|
||||
|
||||
@ -343,7 +357,7 @@ $(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL
|
||||
# once in the QL Generator. It should probably become a dylib instead.
|
||||
$(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK_OBJECTS)
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $^ -o $@ $(LDFLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook
|
||||
$(CC) $^ -o $@ $(LDFLAGS) $(FAT_FLAGS) -Wl,-exported_symbols_list,QuickLook/exports.sym -bundle -framework Cocoa -framework Quicklook
|
||||
|
||||
# cgb_boot_fast.bin is not a standard boot ROM, we don't expect it to exist in the user-provided
|
||||
# boot ROM directory.
|
||||
@ -356,7 +370,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
|
||||
@ -458,10 +472,10 @@ $(OBJ)/%.2bpp: %.png
|
||||
rgbgfx -h -u -o $@ $<
|
||||
|
||||
$(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS)
|
||||
$(PB12_COMPRESS) < $< > $@
|
||||
$(realpath $(PB12_COMPRESS)) < $< > $@
|
||||
|
||||
$(PB12_COMPRESS): BootROMs/pb12.c
|
||||
$(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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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.
|
||||
|
95
SDL/gui.c
95
SDL/gui.c
@ -108,10 +108,12 @@ configuration_t configuration =
|
||||
.rewind_length = 60 * 2,
|
||||
.model = MODEL_CGB,
|
||||
.volume = 100,
|
||||
.rumble_mode = GB_RUMBLE_ALL_GAMES,
|
||||
.default_scale = 2,
|
||||
};
|
||||
|
||||
|
||||
static const char *help[] ={
|
||||
static const char *help[] = {
|
||||
"Drop a ROM to play.\n"
|
||||
"\n"
|
||||
"Keyboard Shortcuts:\n"
|
||||
@ -169,6 +171,11 @@ void update_viewport(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void rescale_window(void)
|
||||
{
|
||||
SDL_SetWindowSize(window, GB_get_screen_width(&gb) * configuration.default_scale, GB_get_screen_height(&gb) * configuration.default_scale);
|
||||
}
|
||||
|
||||
/* Does NOT check for bounds! */
|
||||
static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color)
|
||||
{
|
||||
@ -434,6 +441,12 @@ const char *current_scaling_mode(unsigned index)
|
||||
[configuration.scaling_mode];
|
||||
}
|
||||
|
||||
const char *current_default_scale(unsigned index)
|
||||
{
|
||||
return (const char *[]){"1x", "2x", "3x", "4x", "5x", "6x", "7x", "8x"}
|
||||
[configuration.default_scale - 1];
|
||||
}
|
||||
|
||||
const char *current_color_correction_mode(unsigned index)
|
||||
{
|
||||
return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness", "Reduce Contrast"}
|
||||
@ -474,6 +487,32 @@ void cycle_scaling_backwards(unsigned index)
|
||||
render_texture(NULL, NULL);
|
||||
}
|
||||
|
||||
void cycle_default_scale(unsigned index)
|
||||
{
|
||||
if (configuration.default_scale == GB_SDL_DEFAULT_SCALE_MAX) {
|
||||
configuration.default_scale = 1;
|
||||
}
|
||||
else {
|
||||
configuration.default_scale++;
|
||||
}
|
||||
|
||||
rescale_window();
|
||||
update_viewport();
|
||||
}
|
||||
|
||||
void cycle_default_scale_backwards(unsigned index)
|
||||
{
|
||||
if (configuration.default_scale == 1) {
|
||||
configuration.default_scale = GB_SDL_DEFAULT_SCALE_MAX;
|
||||
}
|
||||
else {
|
||||
configuration.default_scale--;
|
||||
}
|
||||
|
||||
rescale_window();
|
||||
update_viewport();
|
||||
}
|
||||
|
||||
static void cycle_color_correction(unsigned index)
|
||||
{
|
||||
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) {
|
||||
@ -642,6 +681,7 @@ const char *blending_mode_string(unsigned index)
|
||||
|
||||
static const struct menu_item graphics_menu[] = {
|
||||
{"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards},
|
||||
{"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards},
|
||||
{"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards},
|
||||
{"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards},
|
||||
{"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards},
|
||||
@ -763,6 +803,7 @@ static void enter_controls_menu(unsigned index)
|
||||
static unsigned joypad_index = 0;
|
||||
static SDL_Joystick *joystick = NULL;
|
||||
static SDL_GameController *controller = NULL;
|
||||
SDL_Haptic *haptic = NULL;
|
||||
|
||||
const char *current_joypad_name(unsigned index)
|
||||
{
|
||||
@ -792,6 +833,12 @@ static void cycle_joypads(unsigned index)
|
||||
if (joypad_index >= SDL_NumJoysticks()) {
|
||||
joypad_index = 0;
|
||||
}
|
||||
|
||||
if (haptic) {
|
||||
SDL_HapticClose(haptic);
|
||||
haptic = NULL;
|
||||
}
|
||||
|
||||
if (controller) {
|
||||
SDL_GameControllerClose(controller);
|
||||
controller = NULL;
|
||||
@ -806,14 +853,22 @@ static void cycle_joypads(unsigned index)
|
||||
else {
|
||||
joystick = SDL_JoystickOpen(joypad_index);
|
||||
}
|
||||
}
|
||||
if (joystick) {
|
||||
haptic = SDL_HapticOpenFromJoystick(joystick);
|
||||
}}
|
||||
|
||||
static void cycle_joypads_backwards(unsigned index)
|
||||
{
|
||||
joypad_index++;
|
||||
joypad_index--;
|
||||
if (joypad_index >= SDL_NumJoysticks()) {
|
||||
joypad_index = SDL_NumJoysticks() - 1;
|
||||
}
|
||||
|
||||
if (haptic) {
|
||||
SDL_HapticClose(haptic);
|
||||
haptic = NULL;
|
||||
}
|
||||
|
||||
if (controller) {
|
||||
SDL_GameControllerClose(controller);
|
||||
controller = NULL;
|
||||
@ -828,7 +883,9 @@ static void cycle_joypads_backwards(unsigned index)
|
||||
else {
|
||||
joystick = SDL_JoystickOpen(joypad_index);
|
||||
}
|
||||
}
|
||||
if (joystick) {
|
||||
haptic = SDL_HapticOpenFromJoystick(joystick);
|
||||
}}
|
||||
|
||||
static void detect_joypad_layout(unsigned index)
|
||||
{
|
||||
@ -837,9 +894,36 @@ static void detect_joypad_layout(unsigned index)
|
||||
joypad_axis_temp = -1;
|
||||
}
|
||||
|
||||
static void cycle_rumble_mode(unsigned index)
|
||||
{
|
||||
if (configuration.rumble_mode == GB_RUMBLE_ALL_GAMES) {
|
||||
configuration.rumble_mode = GB_RUMBLE_DISABLED;
|
||||
}
|
||||
else {
|
||||
configuration.rumble_mode++;
|
||||
}
|
||||
}
|
||||
|
||||
static void cycle_rumble_mode_backwards(unsigned index)
|
||||
{
|
||||
if (configuration.rumble_mode == GB_RUMBLE_DISABLED) {
|
||||
configuration.rumble_mode = GB_RUMBLE_ALL_GAMES;
|
||||
}
|
||||
else {
|
||||
configuration.rumble_mode--;
|
||||
}
|
||||
}
|
||||
|
||||
const char *current_rumble_mode(unsigned index)
|
||||
{
|
||||
return (const char *[]){"Disabled", "Rumble Game Paks Only", "All Games"}
|
||||
[configuration.rumble_mode];
|
||||
}
|
||||
|
||||
static const struct menu_item joypad_menu[] = {
|
||||
{"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards},
|
||||
{"Configure layout", detect_joypad_layout},
|
||||
{"Rumble Mode:", cycle_rumble_mode, current_rumble_mode, cycle_rumble_mode_backwards},
|
||||
{"Back", return_to_root_menu},
|
||||
{NULL,}
|
||||
};
|
||||
@ -893,6 +977,9 @@ void connect_joypad(void)
|
||||
joystick = SDL_JoystickOpen(0);
|
||||
}
|
||||
}
|
||||
if (joystick) {
|
||||
haptic = SDL_HapticOpenFromJoystick(joystick);
|
||||
}
|
||||
}
|
||||
|
||||
void run_gui(bool is_running)
|
||||
|
@ -21,6 +21,7 @@ extern SDL_Window *window;
|
||||
extern SDL_Renderer *renderer;
|
||||
extern SDL_Texture *texture;
|
||||
extern SDL_PixelFormat *pixel_format;
|
||||
extern SDL_Haptic *haptic;
|
||||
extern shader_t shader;
|
||||
|
||||
enum scaling_mode {
|
||||
@ -40,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;
|
||||
@ -105,6 +107,9 @@ typedef struct {
|
||||
uint8_t dmg_palette;
|
||||
GB_border_mode_t border_mode;
|
||||
uint8_t volume;
|
||||
GB_rumble_mode_t rumble_mode;
|
||||
|
||||
uint8_t default_scale;
|
||||
} configuration_t;
|
||||
|
||||
extern configuration_t configuration;
|
||||
|
62
SDL/main.c
62
SDL/main.c
@ -367,6 +367,11 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
||||
return SDL_MapRGB(pixel_format, r, g, b);
|
||||
}
|
||||
|
||||
static void rumble(GB_gameboy_t *gb, double amp)
|
||||
{
|
||||
SDL_HapticRumblePlay(haptic, amp, 250);
|
||||
}
|
||||
|
||||
static void debugger_interrupt(int ignore)
|
||||
{
|
||||
if (!GB_is_inited(&gb)) return;
|
||||
@ -488,6 +493,8 @@ restart:
|
||||
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
|
||||
GB_set_pixels_output(&gb, active_pixel_buffer);
|
||||
GB_set_rgb_encode_callback(&gb, rgb_encode);
|
||||
GB_set_rumble_callback(&gb, rumble);
|
||||
GB_set_rumble_mode(&gb, configuration.rumble_mode);
|
||||
GB_set_sample_rate(&gb, GB_audio_get_sample_rate());
|
||||
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
|
||||
update_palette();
|
||||
@ -617,12 +624,47 @@ int main(int argc, char **argv)
|
||||
|
||||
SDL_Init(SDL_INIT_EVERYTHING);
|
||||
|
||||
strcpy(prefs_path, resource_path("prefs.bin"));
|
||||
if (access(prefs_path, R_OK | W_OK) != 0) {
|
||||
char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
|
||||
snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
|
||||
SDL_free(prefs_dir);
|
||||
}
|
||||
|
||||
FILE *prefs_file = fopen(prefs_path, "rb");
|
||||
if (prefs_file) {
|
||||
fread(&configuration, 1, sizeof(configuration), prefs_file);
|
||||
fclose(prefs_file);
|
||||
|
||||
/* Sanitize for stability */
|
||||
configuration.color_correction_mode %= GB_COLOR_CORRECTION_REDUCE_CONTRAST +1;
|
||||
configuration.scaling_mode %= GB_SDL_SCALING_MAX;
|
||||
configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1;
|
||||
configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1;
|
||||
configuration.highpass_mode %= GB_HIGHPASS_MAX;
|
||||
configuration.model %= MODEL_MAX;
|
||||
configuration.sgb_revision %= SGB_MAX;
|
||||
configuration.dmg_palette %= 3;
|
||||
configuration.border_mode %= GB_BORDER_ALWAYS + 1;
|
||||
configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1;
|
||||
}
|
||||
|
||||
if (configuration.model >= MODEL_MAX) {
|
||||
configuration.model = MODEL_CGB;
|
||||
}
|
||||
|
||||
if (configuration.default_scale == 0) {
|
||||
configuration.default_scale = 2;
|
||||
}
|
||||
|
||||
atexit(save_configuration);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
160 * configuration.default_scale, 144 * configuration.default_scale, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
SDL_SetWindowMinimumSize(window, 160, 144);
|
||||
|
||||
if (fullscreen) {
|
||||
@ -653,24 +695,6 @@ int main(int argc, char **argv)
|
||||
|
||||
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
||||
|
||||
strcpy(prefs_path, resource_path("prefs.bin"));
|
||||
if (access(prefs_path, R_OK | W_OK) != 0) {
|
||||
char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
|
||||
snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
|
||||
SDL_free(prefs_dir);
|
||||
}
|
||||
|
||||
FILE *prefs_file = fopen(prefs_path, "rb");
|
||||
if (prefs_file) {
|
||||
fread(&configuration, 1, sizeof(configuration), prefs_file);
|
||||
fclose(prefs_file);
|
||||
}
|
||||
if (configuration.model >= MODEL_MAX) {
|
||||
configuration.model = MODEL_CGB;
|
||||
}
|
||||
|
||||
atexit(save_configuration);
|
||||
|
||||
if (!init_shader_with_name(&shader, configuration.filter)) {
|
||||
init_shader_with_name(&shader, "NearestNeighbor");
|
||||
}
|
||||
|
@ -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,13 +37,13 @@ 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
|
||||
{filter}
|
||||
|
||||
#define BLEND_BIAS (1.0/3.0)
|
||||
#define BLEND_BIAS (2.0/5.0)
|
||||
|
||||
enum frame_blending_mode {
|
||||
DISABLED,
|
||||
@ -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];
|
||||
|
7
Windows/unistd.h
Normal file
7
Windows/unistd.h
Normal file
@ -0,0 +1,7 @@
|
||||
#include <io.h>
|
||||
#define STDIN_FILENO 0
|
||||
#define STDOUT_FILENO 1
|
||||
#define STDERR_FILENO 2
|
||||
|
||||
#define read(...) _read(__VA_ARGS__)
|
||||
#define write(...) _write(__VA_ARGS__)
|
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,47 +420,12 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
|
||||
}
|
||||
}
|
||||
|
||||
static void init_for_current_model(unsigned id)
|
||||
static void retro_set_memory_maps(void)
|
||||
{
|
||||
unsigned i = id;
|
||||
enum model effective_model;
|
||||
|
||||
effective_model = model[i];
|
||||
if (effective_model == MODEL_AUTO) {
|
||||
effective_model = auto_model;
|
||||
}
|
||||
|
||||
|
||||
if (GB_is_inited(&gameboy[i])) {
|
||||
GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]);
|
||||
}
|
||||
else {
|
||||
GB_init(&gameboy[i], libretro_to_internal_model[effective_model]);
|
||||
}
|
||||
|
||||
GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load);
|
||||
|
||||
/* When running multiple devices they are assumed to use the same resolution */
|
||||
|
||||
GB_set_pixels_output(&gameboy[i],
|
||||
(uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i));
|
||||
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
|
||||
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
|
||||
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
|
||||
GB_set_rumble_callback(&gameboy[i], rumble_callback);
|
||||
|
||||
/* todo: attempt to make these more generic */
|
||||
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);
|
||||
if (emulated_devices == 2) {
|
||||
GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2);
|
||||
if (link_cable_emulation) {
|
||||
set_link_cable_state(true);
|
||||
}
|
||||
}
|
||||
|
||||
struct retro_memory_descriptor descs[11];
|
||||
size_t size;
|
||||
uint16_t bank;
|
||||
unsigned i;
|
||||
|
||||
|
||||
/* todo: add netplay awareness for this so achievements can be granted on the respective client */
|
||||
@ -489,6 +485,45 @@ static void init_for_current_model(unsigned id)
|
||||
mmaps.descriptors = descs;
|
||||
mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
|
||||
}
|
||||
|
||||
static void init_for_current_model(unsigned id)
|
||||
{
|
||||
unsigned i = id;
|
||||
enum model effective_model;
|
||||
|
||||
effective_model = model[i];
|
||||
if (effective_model == MODEL_AUTO) {
|
||||
effective_model = auto_model;
|
||||
}
|
||||
|
||||
|
||||
if (GB_is_inited(&gameboy[i])) {
|
||||
GB_switch_model_and_reset(&gameboy[i], libretro_to_internal_model[effective_model]);
|
||||
}
|
||||
else {
|
||||
GB_init(&gameboy[i], libretro_to_internal_model[effective_model]);
|
||||
}
|
||||
|
||||
GB_set_boot_rom_load_callback(&gameboy[i], boot_rom_load);
|
||||
|
||||
/* When running multiple devices they are assumed to use the same resolution */
|
||||
|
||||
GB_set_pixels_output(&gameboy[i],
|
||||
(uint32_t *)(frame_buf + GB_get_screen_width(&gameboy[0]) * GB_get_screen_height(&gameboy[0]) * i));
|
||||
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
|
||||
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
|
||||
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
|
||||
GB_set_rumble_callback(&gameboy[i], rumble_callback);
|
||||
|
||||
/* todo: attempt to make these more generic */
|
||||
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);
|
||||
if (emulated_devices == 2) {
|
||||
GB_set_vblank_callback(&gameboy[1], (GB_vblank_callback_t) vblank2);
|
||||
if (link_cable_emulation) {
|
||||
set_link_cable_state(true);
|
||||
}
|
||||
}
|
||||
|
||||
/* Let's be extremely nitpicky about how devices and descriptors are set */
|
||||
if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) {
|
||||
@ -597,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";
|
||||
@ -743,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";
|
||||
@ -772,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";
|
||||
@ -846,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)
|
||||
@ -854,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)
|
||||
@ -943,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)
|
||||
@ -1070,6 +1105,9 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
}
|
||||
|
||||
check_variables();
|
||||
|
||||
retro_set_memory_maps();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
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