Compare commits

..

590 Commits

Author SHA1 Message Date
Lior Halphon
b7f03dea8d Add CGB-A support 2022-01-05 21:55:46 +02:00
Lior Halphon
ab1d4cd26e More DMA write conflicts 2022-01-05 21:40:10 +02:00
Lior Halphon
5e7bb0f891 DMA write conflict revision differences 2022-01-04 19:59:46 +02:00
Lior Halphon
79ec22b28e Clang hates this 2022-01-03 17:55:00 +02:00
Lior Halphon
5c17c0ec3b unreachable fun 2022-01-03 17:17:35 +02:00
Lior Halphon
cd16431699 OMA DMA improvements (WIP) 2022-01-03 16:51:45 +02:00
Lior Halphon
81e45b00b9 Minor Cocoa bug fixes 2022-01-03 16:51:24 +02:00
Lior Halphon
c7298ae5a6 Fix a silly underflow 2022-01-03 16:51:05 +02:00
Lior Halphon
8e675295a8 Enable LTO in Windows builds, fix missing MGB boot in SDL 2021-12-31 00:40:34 +02:00
Lior Halphon
bc073e3d09 Expose PC 2021-12-30 23:53:24 +02:00
Lior Halphon
52c5610528 Cocoa audio driver goes brrrr 2021-12-29 17:21:06 +02:00
Lior Halphon
38eafeb0cf Never deadlock ever again thanks 2021-12-29 17:03:44 +02:00
Lior Halphon
97652b7460 Cocoa audio bugfix 2021-12-29 16:53:28 +02:00
Lior Halphon
59c315a5dd Minor free optimization 2021-12-29 16:33:04 +02:00
Lior Halphon
b72c2ea225 DMG palette getter 2021-12-29 13:08:46 +02:00
Lior Halphon
769aac93c0 Lazy APU, extra ~17% speed up 2021-12-29 00:48:44 +02:00
Lior Halphon
db50462710 More accurate fix 2021-12-26 23:24:08 +02:00
Lior Halphon
59dfb1a85a It's not verified because it's wrong 2021-12-26 21:57:40 +02:00
Lior Halphon
6ffe924637 This is probably better but still wrong 2021-12-26 21:43:54 +02:00
Lior Halphon
d178ece909 Disabled an incorrectly emulated portion of the TILE_SET glitch 2021-12-26 19:57:18 +02:00
Lior Halphon
6e7ba7589c Fixed blurred unfiltered screenshots 2021-12-26 18:38:08 +02:00
Lior Halphon
66f7babe86 Cache the clock rate 2021-12-26 15:50:24 +02:00
Lior Halphon
c53d99dbc4 Abolished slow double use 2021-12-26 15:20:46 +02:00
Lior Halphon
c5f6be1e64 Several likely/unlikely optimization, saving on a memset 2021-12-26 02:38:54 +02:00
Lior Halphon
69de3f0fae Implement a PPU fast path, up to 34% performance boost 2021-12-26 01:47:59 +02:00
Lior Halphon
f3277ab8d3 Sorry C++ users 2021-12-20 18:59:51 +02:00
Lior Halphon
e9906e44cd Sure, why not 2021-12-19 21:46:22 +02:00
Lior Halphon
f866441481 Improved emulation of channel 3 wave RAM read glitch 2021-12-19 19:27:40 +02:00
Lior Halphon
e9629407a5 Fix potential alignment issues 2021-12-19 00:54:29 +02:00
Lior Halphon
cdc3321c36 Add an API to allow illegal inputs 2021-12-19 00:28:24 +02:00
Lior Halphon
eaccd792ed Fixes to safe reads, closes #422 2021-12-18 14:56:33 +02:00
Lior Halphon
5127cb0022 Direct access to registers (#422) 2021-12-18 14:51:14 +02:00
Lior Halphon
c63ddbe771 Lag frame detection API (#422) 2021-12-18 01:25:06 +02:00
Lior Halphon
c3d9141b7c Replace the term sprite with object for consistency 2021-12-17 21:16:23 +02:00
Lior Halphon
c1ae129ed4 Allow hiding background/object "layers" (#422) 2021-12-17 21:12:26 +02:00
Lior Halphon
f78fac12c2 Fixed several issues involving LY change timing, as well as an LYC issue in models prior to CGB-D 2021-12-14 20:27:38 +02:00
Lior Halphon
7e5e672988 RTC speed multiplier, for TAS syncing (#422) 2021-12-11 02:51:21 +02:00
Lior Halphon
a30247cf16 LCD line callback (for #422) 2021-12-10 19:49:52 +02:00
Lior Halphon
7508ddb0cf Execute callback (for #422) 2021-12-10 19:42:47 +02:00
Lior Halphon
e087bd5218 The GBS visualizer should use custom color palettes 2021-12-10 02:06:12 +02:00
Lior Halphon
9e57201b08 Accurate IF clear timing 2021-12-05 16:18:54 +02:00
Lior Halphon
25e3414974 Redesigned vblank callback scheduling scheme, should be more regular and less prune to various sorts of frontend DOS 2021-12-04 15:04:46 +02:00
Lior Halphon
4b3c77bfa5 oops 2021-12-02 11:54:26 +02:00
Lior Halphon
8660e20eeb New inputs API 2021-12-02 11:23:44 +02:00
Lior Halphon
b770bbea2e Fix save state issue that caused vblank callbacks timings to differ 2021-12-02 11:21:12 +02:00
Lior Halphon
486f8a2c10
Merge pull request #420 from SnowyMouse/cgb_mode
Add GB_is_cgb_in_cgb_mode
2021-11-26 14:10:35 +02:00
Lior Halphon
06b744259b Add memory write callback, optimize memory access with likely/unlikely 2021-11-26 14:09:41 +02:00
Lior Halphon
bdbe02b043 Add a safe memory read API 2021-11-26 13:54:28 +02:00
Lior Halphon
33090a5cc0 Fix an oops from the last commit 2021-11-26 13:38:52 +02:00
Snowy
d0a9d2f72a Add GB_is_cgb_in_cgb_mode 2021-11-25 17:16:11 -06:00
Lior Halphon
f1e5e04198 ...even when timekeeping is disabled 2021-11-25 21:46:51 +02:00
Lior Halphon
d0d39015ee Let update_input_hint_callback get called during turbo 2021-11-25 21:17:49 +02:00
Lior Halphon
f08f16346e Fix #293 2021-11-24 23:13:52 +02:00
Lior Halphon
d94c8b9125 Switch Pro Controller motion controls 2021-11-22 23:29:10 +02:00
Lior Halphon
d15eaf4134 Mouse controls for MBC7 2021-11-14 21:43:31 +02:00
Lior Halphon
ae930472f0 Units info 2021-11-14 13:18:58 +02:00
Lior Halphon
7a78649e21 Implement motion controls in JoyKit, implement accel/gyro in DualSense and DualShock 4, implement motion controls in Cocoa 2021-11-13 19:23:45 +02:00
Lior Halphon
06ce30d3a8 Map joysticks to motion controls 2021-11-12 18:10:03 +02:00
Lior Halphon
c6f39bc60b Initial MBC7 support 2021-11-12 17:44:51 +02:00
Lior Halphon
02f55d12d3 Maybe one day GCC will stop being shit at handling __attribute__s 2021-11-07 14:13:52 +02:00
Lior Halphon
1650820edb Clean up endian-related code 2021-11-07 13:57:43 +02:00
Lior Halphon
18e7a3f4fa Cleanup, better symbol handling, improves LTO 2021-11-07 13:39:18 +02:00
Lior Halphon
fbf1bb7f98 Save state compatibility breaking cleanup 2021-11-07 12:56:46 +02:00
Lior Halphon
5565c2540b Register name and info update 2021-11-06 13:34:34 +02:00
Lior Halphon
4a7afb246d Fix some oopsies 2021-11-05 21:45:54 +02:00
Lior Halphon
178860e715 Custom palette and editor 2021-11-05 19:07:27 +02:00
Lior Halphon
f237b1e9b9 CGB-0 support 2021-11-04 00:35:44 +02:00
Lior Halphon
6cd13be624 Add CGB-B support 2021-10-30 20:58:57 +03:00
Lior Halphon
b54365fc40 Merge branch 'v0.14.x' 2021-10-30 18:40:27 +03:00
Lior Halphon
72b6d6c532 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-10-30 18:31:47 +03:00
Lior Halphon
43831d0bc1 Update version to 0.14.7 2021-10-30 16:10:26 +03:00
Lior Halphon
0f6a0186cd Cherry picking conflicts 2021-10-30 16:09:13 +03:00
Lior Halphon
deb037d87d Detect missing ANSI support on Windows 2021-10-30 16:08:35 +03:00
Lior Halphon
4498d16bed Improved sanitation for save states for better security and stability 2021-10-30 16:08:35 +03:00
Lior Halphon
8d319c65c2 Use a monospaced font in the palette viewer 2021-10-30 16:08:35 +03:00
Snowy
0d7cc66ffd Change y to a signed value 2021-10-30 16:08:35 +03:00
Lior Halphon
0dcd233cbb Writes to SVBK should work before the boot ROM is disabled 2021-10-30 16:08:35 +03:00
Lior Halphon
fd8c9bba5d Detect missing ANSI support on Windows 2021-10-30 16:03:33 +03:00
Lior Halphon
1f7b20251b Improved sanitation for save states for better security and stability 2021-10-30 16:03:13 +03:00
Lior Halphon
93ef8d7db8
Merge pull request #411 from SnowyMouse/sprite_h
Fix partially offscreen sprites not being returned in GB_get_oam_info
2021-10-27 01:44:26 +03:00
Lior Halphon
739a9eb2bf Use a monospaced font in the palette viewer 2021-10-27 01:43:36 +03:00
Lior Halphon
e6c4ceaf5a Add CGB-D support 2021-10-27 01:40:28 +03:00
Snowy
2ec573c84a Change y to a signed value 2021-10-24 11:15:28 -05:00
Lior Halphon
5b9746084d Writes to SVBK should work before the boot ROM is disabled 2021-10-23 23:51:48 +03:00
Lior Halphon
18007f0e53 MGB support 2021-10-23 23:28:54 +03:00
Lior Halphon
5808d4485f Drop BOOLs 2021-10-23 13:36:58 +03:00
Lior Halphon
fc10a90dec Screenshots in the Cocoa frontend 2021-10-23 13:26:44 +03:00
Lior Halphon
3f954f1d0c Update version 2021-10-20 23:37:16 +03:00
Lior Halphon
0e6b9da42d Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-10-20 23:36:48 +03:00
Lior Halphon
1376c386a2 Slightly altered merge of #408 2021-10-20 21:49:02 +03:00
Lior Halphon
2f2e469790
Merge pull request #407 from SnowyMouse/master
Handle missing background.bmp
2021-10-20 20:34:13 +03:00
Lior Halphon
e04dcd90d6 Fix APU command 2021-10-19 01:56:10 +03:00
Lior Halphon
94776fcf8c Better (But imperfect) emulation of the wave RAM address bug glitch 2021-10-19 01:53:24 +03:00
Lior Halphon
de16ab5d08 Why was this under APU 2021-10-17 20:05:49 +03:00
Lior Halphon
886363b398 Now this glitch makes more sense 2021-10-17 12:52:08 +03:00
Lior Halphon
7ef198ec50 More accurate channel 3 restarts 2021-10-17 02:06:33 +03:00
Lior Halphon
f1b8164613 Force the user selected model on reset (which can change by a save state) 2021-10-17 02:06:13 +03:00
Lior Halphon
40fc477b56 Fix an oopsie 2021-10-10 20:05:57 +03:00
Lior Halphon
4ce643d5eb Fix timer bug; fixes #409 2021-10-10 16:53:07 +03:00
Lior Halphon
01f80a3f3e Adjust border fade delay to match SGB2 2021-10-10 14:24:14 +03:00
Lior Halphon
8f9365251d Timing adjustments 2021-10-10 13:30:30 +03:00
Lior Halphon
b580e63c37 Fix 7-part SGB commands 2021-10-10 02:55:12 +03:00
Lior Halphon
d263a8f6f8 Fix sanity 2021-10-09 23:12:49 +03:00
Lior Halphon
46e1b79b63 Madden 96 needs even more time 2021-10-09 23:07:15 +03:00
Lior Halphon
6b947c46bc Correct blank image detection on SGB 2021-10-09 22:41:43 +03:00
Lior Halphon
893d7d162a Don't try to render tiles 100-3FF 2021-10-09 22:34:43 +03:00
Lior Halphon
85da5b64d3 Some games like racing the SGB 2021-10-09 22:02:40 +03:00
Lior Halphon
a7c8b702da Some games like to race with the SGB border fade 2021-10-09 21:08:17 +03:00
Lior Halphon
f1761340fc Fix ATTR_SET command 2021-10-09 18:45:08 +03:00
Lior Halphon
3b1094058b Add SGB and CGB flags to the tester 2021-10-09 15:57:15 +03:00
Lior Halphon
004b004f98 Fix inverted key buttons in the SDL menu, fixes #401 2021-10-09 15:27:18 +03:00
Lior Halphon
191f7cee02 Improved emulation of SGB multiplayer, fixes #405 2021-10-09 14:52:28 +03:00
Lior Halphon
164a870189 Cleanup uses of gb->registers 2021-10-09 14:09:51 +03:00
Lior Halphon
24af1c5a31 Newly discovered OAM corruption trigger 2021-10-09 14:05:29 +03:00
Lior Halphon
c05c3c2abd Improved accuracy of mid-line SCX writes 2021-10-08 19:36:05 +03:00
Lior Halphon
d4999fbbdb Give APU tests more time 2021-10-07 18:30:09 +03:00
Lior Halphon
ef053ff113 APU regression fix 2021-10-07 18:25:54 +03:00
Lior Halphon
d1caeafe5e Better handling of tiny ROMs 2021-10-05 19:53:43 +03:00
Lior Halphon
9a957674d9 Fix broken action 2021-10-05 19:53:19 +03:00
Lior Halphon
345e51647f API issue – RTC data should not be wiped after GB_reset 2021-09-30 21:23:52 +03:00
Lior Halphon
8068ff41fb Fix potential future compatibility issue, update workflow environments 2021-09-29 21:57:39 +03:00
Snowy
20d580881a Handle missing background.bmp 2021-09-26 23:08:13 -05:00
Lior Halphon
1050a7a533 More size changes for Big Bloat 2021-09-26 00:10:19 +03:00
Lior Halphon
6e2abe23ef Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-09-25 21:55:29 +03:00
Lior Halphon
7aca04f4c4 Things need to be slightly bigger in Big Sur 2021-09-25 21:55:12 +03:00
Lior Halphon
24a7467735
Merge pull request #406 from Talkashie/master
Fixed a typo in the debugger
2021-09-24 20:33:56 +03:00
Lior Halphon
84c5f8b92d Fix #402 2021-09-22 20:53:04 +03:00
Lior Halphon
51be70275d Fix broken wave RAM initialization in the boot ROM 2021-09-22 17:59:20 +03:00
Talkashie
2dc0e14d0a Update debugger.c
Fixed a typo
2021-09-13 21:20:19 -05:00
Lior Halphon
c25c94d142 Minor improvements to console behavior on startup 2021-09-10 14:17:07 +03:00
Lior Halphon
c5d91fc448 New console readline-like interface for the SDL port 2021-09-09 00:13:09 +03:00
Lior Halphon
336bc65dbf Remove Cocoa spam from the SDL port 2021-09-09 00:11:49 +03:00
Lior Halphon
d8a9f12a4f Smooth scrolling in the SDL port on macOS 2021-09-06 15:21:02 +03:00
Lior Halphon
b27bd4eed7 Remove redundant condition 2021-09-04 18:15:22 +03:00
Lior Halphon
e5454a39b7 Block wave RAM writes on the AGB 2021-09-04 14:18:46 +03:00
Lior Halphon
0ca1ee6a35 Minor APU clean ups 2021-09-02 01:29:38 +03:00
Lior Halphon
eefaac3d04 NR32 was treated as the wrong value on APU reset 2021-08-31 23:28:05 +03:00
Lior Halphon
3c6d094763 Update version to 0.14.5 2021-08-01 16:19:40 +03:00
Lior Halphon
50bf8c4919 Fixed a bug that sometimes prevented GBS files from being opened 2021-08-01 15:11:33 +03:00
Lior Halphon
de500cd397 Update version to 0.14.4 2021-07-30 19:09:18 +03:00
Lior Halphon
c459058156 That was silly 2021-07-30 14:10:04 +03:00
Lior Halphon
48397683b8
Merge pull request #389 from tommitytom/master
Fix Windows build in clang-cl
2021-07-30 13:50:26 +03:00
Lior Halphon
0b2411ecc6 Merge branch 'libretro-core-options' 2021-07-29 23:29:47 +03:00
Lior Halphon
c7e8d7fa13 Minor cleanup 2021-07-29 23:19:13 +03:00
Lior Halphon
6138833b28 Style fixes 2021-07-29 23:03:36 +03:00
Lior Halphon
690a263648 Major improvements to JoyKit, fixing Xbox and 8BitDo controllers as well as analog mappings in PS controllers in some situations 2021-07-29 22:43:55 +03:00
Lior Halphon
0ff882f3bc Actually do what the previous commit claimed to do 2021-07-28 00:47:19 +03:00
Lior Halphon
b454ee28db Fix an issue where SameBoot gave DMG games the wrong palette and needlessly drew the DMG boot tilemap 2021-07-27 22:18:28 +03:00
Lior Halphon
4d1a28f1d1 Improved OAM bug accuracy in several read edge cases 2021-07-25 16:34:34 +03:00
Lior Halphon
1d7692cff5 Fix blurry VRAM viewer grid lines 2021-07-11 23:12:46 +03:00
Lior Halphon
a5325d3374 Improved ticks command, more accurate speed switch timings, better odd-mode warnings 2021-07-11 21:49:58 +03:00
Lior Halphon
6f6f72dcbd More accurate emulation of STOP 2021-07-11 12:11:12 +03:00
Lior Halphon
efb644bc72 MBC5 RAM enable is 8 bit 2021-07-10 15:02:15 +03:00
Ryunam
75ec1c0334 [Libretro] Fix small typo in palette description 2021-06-27 11:22:27 +02:00
Ryunam
e1453f1961 [Libretro] Upgrade Core Options to v1.3 2021-06-26 23:40:22 +02:00
Lior Halphon
278224299f Fixed double->single speed switch causing misaligned CPU timing 2021-06-26 13:55:34 +03:00
Lior Halphon
94add1d172 Add "Harsh Reality" color correction mode 2021-06-25 19:57:56 +03:00
Lior Halphon
a2d34c9bd9 Add -s/--stop-debugger flag to SDL, closes #392 2021-06-25 17:12:05 +03:00
Lior Halphon
ceacc226bc Fixed Switch Pro Controller in USB mode 2021-06-23 21:21:53 +03:00
Lior Halphon
23e8cc58c5 Vblank should occur 1 T-cycle later 2021-06-19 02:14:16 +03:00
Lior Halphon
b4709fd66b Disabled an accuracy-improvement-attempt that caused audio regressions until the proper behavior is well understood, fixes #390 2021-06-18 01:36:29 +03:00
Lior Halphon
339613263c Fixed a bug that prevented STAT interrupt blocking from functioning correctly in the transition to VBlank while the OAM interrupt was disabled 2021-06-18 01:20:05 +03:00
Tom Yaxley
a12ec3c8c8 Fix Windows build in clang-cl 2021-06-06 12:04:47 +10:00
Lior Halphon
7a6ae2d951 Improved DualSense LEDs, fix several analog controls issues 2021-06-04 22:21:41 +03:00
Lior Halphon
e71d3a7d3c First-tier support for DualSense controllers with rumble and LED support 2021-06-04 18:17:14 +03:00
Lior Halphon
b444ecd1ee Fix configuration of analog shoulder buttons for analog turbo/slow motion when using a PS5 controller 2021-06-01 00:46:06 +03:00
Lior Halphon
1e5e236e84 Correct default mapping for PS5 and PS4 controllers 2021-06-01 00:33:25 +03:00
Lior Halphon
ebb0cb5e81 Added optional OSD (SDL) 2021-05-30 23:39:59 +03:00
Lior Halphon
3ed18a76da Added optional OSD (Cocoa) 2021-05-30 20:55:04 +03:00
Lior Halphon
033f025851 Added volume control to the Cocoa port 2021-05-21 18:12:29 +03:00
Lior Halphon
75d3470d55 That code made very little sense 2021-05-19 00:15:02 +03:00
Lior Halphon
fcbbecea17 Fix #386 2021-05-18 20:21:21 +03:00
Lior Halphon
2afeb7dee3 Place a cap on the GBS file size 2021-05-17 17:11:41 +03:00
Lior Halphon
ea67a7e3f0
Merge pull request #379 from jprjr/gbs-buffer
gbs: function to load from memory buffer
2021-05-17 16:53:12 +03:00
Lior Halphon
9b2dfe7ae2
Style fixes 2021-05-17 16:52:55 +03:00
Lior Halphon
e9ab7fa7df
Merge pull request #382 from Mailaender/patch-1
Fixed the desktop categories
2021-05-17 16:44:53 +03:00
Lior Halphon
c944142b36 Fall back to .snX if no .sX save state found 2021-05-07 00:33:04 +03:00
Lior Halphon
a4a8ad00d5 Display usage on invalid options 2021-05-06 00:26:45 +03:00
Lior Halphon
0dff3ef144 A flag to disable OpenGL, better and more stable handling of no-OpenGL mode 2021-05-06 00:23:46 +03:00
Lior Halphon
1d0366052d Updater support 2021-04-25 22:28:24 +03:00
Matthias Mailänder
898ef2c981
Fix the desktop categories. 2021-04-23 20:43:34 +02:00
Lior Halphon
ea05a0c765 Don't save 0x6000 for MBC3 in BESS 2021-04-23 21:05:33 +03:00
Lior Halphon
ac5b0aca2c RTC accuracy fix 2021-04-23 21:01:17 +03:00
John Regan
0e8d8effdf gbs: function to load from memory buffer 2021-04-20 08:38:53 -04:00
Lior Halphon
a2d3b8c174 Support for non-standard GBS files with a loading address at 0 2021-04-19 20:58:27 +03:00
Lior Halphon
c29edc1963 Handle loading errors 2021-04-19 20:57:28 +03:00
Lior Halphon
2971b17701 Add support for ugetab's GBS extensions, fixes #377 2021-04-19 00:32:10 +03:00
Lior Halphon
5f2e893828 Allow GBS files with loading addresses 0x6E-0x3FF, fixes #376 2021-04-19 00:08:21 +03:00
Lior Halphon
d9b9385eb4 Typo fix 2021-04-17 18:13:19 +03:00
Lior Halphon
939817df73 Update version, finalize BESS 1.0 2021-04-17 16:59:22 +03:00
Lior Halphon
e8158be454 Merge branch 'bess' into gbs 2021-04-17 16:57:05 +03:00
Lior Halphon
9fcdc082d2 Fix an SDL crash, minor tweak to BESS SGB 2021-04-17 16:37:55 +03:00
Lior Halphon
817c4a7752 Merge branch 'bess' into gbs 2021-04-16 16:35:21 +03:00
Lior Halphon
87a2d48675 Redo TPP1 saving, fix RTC and HUC3 in BESS 2021-04-16 00:35:54 +03:00
Lior Halphon
f0a6488546 Added optional INFO block 2021-04-15 21:57:38 +03:00
Lior Halphon
2078c2a8fb Use semantic popup icons instead of always using error 2021-04-15 02:42:31 +03:00
Lior Halphon
98a39ae49a ATTR_CHR does not seem to wrap around screen (only lines/columns) 2021-04-14 23:39:07 +03:00
Lior Halphon
b325148544 Update and clarify specification 2021-04-14 23:37:00 +03:00
Lior Halphon
ba6e22dfc0 Merge branch 'bess' into gbs 2021-04-14 16:44:51 +03:00
Lior Halphon
dd86077410 Use the older, more available API 2021-04-14 15:24:06 +03:00
Lior Halphon
8a84a5897e Allow drag&drop of state files 2021-04-14 15:20:01 +03:00
Lior Halphon
c1509b6339 KEY0 info 2021-04-13 23:34:49 +03:00
Lior Halphon
6f0b640702 More clarifications 2021-04-13 22:32:45 +03:00
Lior Halphon
79f109b463 Clarify MBC block 2021-04-13 22:08:25 +03:00
Lior Halphon
43fb86320e Hard fail on unexpected SGB blocks 2021-04-13 22:05:13 +03:00
Lior Halphon
0af4f1fa4d Clarify SGB multiplayer, handle count = 0 2021-04-13 21:33:13 +03:00
Lior Halphon
24915e41eb TPP1 in BESS 2021-04-13 20:56:09 +03:00
Lior Halphon
976f5e4d02 Merge branch 'master' into bess 2021-04-13 20:50:29 +03:00
Lior Halphon
fada772cb1 Don't use BESS for internal in-memory saves 2021-04-13 20:35:07 +03:00
Lior Halphon
dfdbff7304 Allow writes to the $a000-$bfff range in the MBC block 2021-04-13 16:01:44 +03:00
Lior Halphon
6ee488688b Update spec 2021-04-13 01:11:06 +03:00
Lior Halphon
a3a73602fc ATF is only 0xFD2 bytes, not 0xFE0 2021-04-13 01:09:29 +03:00
Lior Halphon
5b993ed775 Add HuC3 to BESS 2021-04-12 23:36:42 +03:00
Lior Halphon
4346b063f5 Wording 2021-04-12 22:48:05 +03:00
Lior Halphon
9c1889f450 Actually update spec 2021-04-12 22:43:23 +03:00
Lior Halphon
9a1f962281 Spec update 2021-04-12 22:39:13 +03:00
Lior Halphon
251dd15ff9 Fixed a bug where the screen would not redraw when certain controllers are rumbling in specific strengths in the Cocoa port 2021-04-11 23:36:42 +03:00
Lior Halphon
80f422d0ca Respect TPP1 feature flags for rumble and RTC 2021-04-11 23:16:31 +03:00
Lior Halphon
763de9d2e0 Fix Rumble support in TPP1 2021-04-11 22:52:34 +03:00
Lior Halphon
42471095e4 Normalize invalid weekdays only after a $11 command 2021-04-11 22:38:25 +03:00
Lior Halphon
0c5e15b49d Correct emulation of count overflow in ATTR_CHR, fixes #372 2021-04-11 02:38:58 +03:00
Lior Halphon
f24489b983 TPP1 support 2021-04-10 23:56:41 +03:00
Lior Halphon
44c75ae7be Remove commented out code 2021-04-10 18:43:24 +03:00
Lior Halphon
ad05eb6d0a GCC build fix 2021-04-10 16:15:40 +03:00
Lior Halphon
955ef59140 Merge branch 'bess' into gbs 2021-04-10 16:12:35 +03:00
Lior Halphon
f89c80caa5 Merge branch 'master' into bess 2021-04-10 16:12:03 +03:00
Lior Halphon
d0bbf383d6 Another cheat bugfix 2021-04-10 16:10:23 +03:00
Lior Halphon
6ddfcc9725 Added visualizer to the GBS player, various GBS UI improvements 2021-04-10 16:10:10 +03:00
Lior Halphon
77384a5f6a Merge branch 'bess' into gbs 2021-04-09 23:12:03 +03:00
Lior Halphon
20ffa27dd4 Forgot to commit the document update 2021-04-07 21:45:43 +03:00
Lior Halphon
1c31812ffd BESS format updates 2021-04-06 01:02:49 +03:00
Lior Halphon
7a558492b6 Merge branch 'master' into bess 2021-04-05 23:10:00 +03:00
Lior Halphon
e6fa2336da Fix a potential crash/corruption when modifying cheats 2021-04-05 23:09:32 +03:00
Lior Halphon
f67d3947d6 UI Updates 2021-04-05 23:08:43 +03:00
Lior Halphon
bb3a73ff88 Cocoa GBS Player 2021-04-03 01:29:43 +03:00
Lior Halphon
9996c7b4a2 Add GBS APIs 2021-04-03 01:29:06 +03:00
Lior Halphon
39c71b40e7 Fix memory leak 2021-04-02 19:07:28 +03:00
Lior Halphon
328e2d9e40 Merge branch 'master' into gbs 2021-04-02 19:06:15 +03:00
Lior Halphon
6b8eb8063a Fix a bug where SameBoy would start in "faux turbo mode" 2021-04-02 02:54:14 +03:00
Lior Halphon
b7348b5478 Add BESS format documentation 2021-04-01 00:16:28 +03:00
Lior Halphon
e460b0a7b4 Change the border format to SNES-style 2021-03-31 00:54:55 +03:00
Lior Halphon
9314bcf98d
Merge pull request #371 from Jan200101/patch-1
Correct usage of PREFIX with DATA_DIR
2021-03-30 20:45:21 +03:00
Jan
48ec3e6413
Correct usage of PREFIX with DATA_DIR
slipped through in #370
DATA_DIR is not relative to PREFIX so having it is problematic
2021-03-30 17:29:55 +02:00
Lior Halphon
8adaba237e SGB support in BESS, BE fixes, changes to SGB save state format on BE machines 2021-03-29 02:47:57 +03:00
Lior Halphon
b9030bb2d0
Merge pull request #370 from Jan200101/patch-1
replace PREFIX/share/sameboy with DATA_DIR
2021-03-25 20:57:30 +02:00
Jan
aca2fd04b1
replace PREFIX/share/sameboy with DATA_DIR 2021-03-25 19:17:45 +01:00
Lior Halphon
659f954028 RTC support 2021-03-25 00:07:37 +02:00
Lior Halphon
a52302f2f6 Make NAME come before CORE 2021-03-25 00:07:37 +02:00
Lior Halphon
75bc1e9a86 Initial BESS support, no SGB nor RTC yet 2021-03-25 00:07:37 +02:00
Lior Halphon
925bd863c0 Better errnos 2021-03-22 00:18:49 +02:00
Lior Halphon
da1003263f Redo save states to remove severe code duplication between buffers and files 2021-03-21 20:32:30 +02:00
Lior Halphon
ad54dc57b0 Improved time syncing when turning the LCD on and off, fixes #193 2021-03-21 15:15:04 +02:00
Lior Halphon
5c1b89e82d Update version 2021-03-01 23:27:40 +02:00
Lior Halphon
5a966bba91 Register ISX files on FreeDesktop 2021-03-01 23:21:07 +02:00
Lior Halphon
f50d9310a7 This shouldn't have been here 2021-03-01 23:00:11 +02:00
Lior Halphon
4d67fa8e80 Close all related windows when closing a document 2021-03-01 22:58:52 +02:00
Lior Halphon
ea97c1dc0b Fix an APU regression that caused some games in DMG mode to play in the wrong pitch 2021-03-01 21:44:54 +02:00
Lior Halphon
d2ed1343e5 Add missing mkdir 2021-02-28 20:41:58 +02:00
Lior Halphon
c6ea57209f
Merge pull request #356 from RobLoach/libretro-updates
libretro: Sync updates from libretro
2021-02-28 20:14:15 +02:00
Rob Loach
f21fd33cc3
libretro: Remove APP_STL 2021-02-28 13:13:40 -05:00
Rob Loach
975d379d76
libretro: Remove empty CFLAGS 2021-02-28 13:13:12 -05:00
Lior Halphon
2d593a95e3 Update version to 0.14.1 2021-02-28 17:15:19 +02:00
Lior Halphon
57080c48bc No need for -1 2021-02-28 16:50:46 +02:00
Rob Loach
6a995bfe10
libretro: Sync updates from libretro 2021-02-28 09:45:18 -05:00
Lior Halphon
ec7d756e3b
Merge pull request #351 from NieDzejkob/symbol-off-by-one
Fix off-by-one in symbol search
2021-02-28 15:27:50 +02:00
Lior Halphon
0fbd714d4a
Merge pull request #352 from NieDzejkob/value-to-string-oob
value_to_string: use snprintf
2021-02-28 15:23:29 +02:00
Lior Halphon
81bfea9ba2
Coding style, ensuring string termination. 2021-02-28 15:23:14 +02:00
Lior Halphon
d2eb8e0996 Addresses issues mentioned by #355 2021-02-28 15:17:00 +02:00
Lior Halphon
5cc2dcc864 Fix #353 better 2021-02-28 14:55:30 +02:00
Lior Halphon
d50514ede9 Fix #353 2021-02-28 14:51:58 +02:00
Lior Halphon
5cd920d363 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-02-28 03:41:13 +02:00
Lior Halphon
1fdb4f09b4 Fix a sweep regression in DMG/SGB mode and CGB-C mode 2021-02-28 03:40:58 +02:00
Jakub Kądziołka
c9665d0449
value_to_string: use snprintf
Currently, value_to_string and debugger_value_to_string use an
error-prone calculation to avoid overflow. This was once adjusted
already, and one of the codepaths is still vulnerable. Put this in a
symfile:

    01:5678 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

and execute `p 1:$5679`. On Linux, the canary terminates the process.
2021-02-27 19:33:31 +01:00
Lior Halphon
ec2661ac03
Merge pull request #350 from NieDzejkob/destdir-mkdir
Fix DESTDIR installation
2021-02-27 20:29:22 +02:00
Jakub Kądziołka
e8bfc4050e
Fix off-by-one in symbol search
Before this commit, printing an address that's after every symbol in a
bank would not show it relative to the last symbol.
2021-02-27 19:29:06 +01:00
Jakub Kądziołka
0a4cb8148f
Fix DESTDIR installation
Installing into a fresh DESTDIR would error out due to non-existent bin
directory
2021-02-27 18:33:22 +01:00
Lior Halphon
54d733f356
Merge pull request #349 from NieDzejkob/memmove-fix
Use memmove for overlapping copy
2021-02-27 19:31:06 +02:00
Jakub Kądziołka
1dae345b24
Use memmove for overlapping copy 2021-02-27 18:29:59 +01:00
Lior Halphon
e57b5dd57e Update version and copyright date 2021-02-27 19:08:32 +02:00
Lior Halphon
a59cd856c7 Fix make install issues 2021-02-27 18:39:14 +02:00
Lior Halphon
cd2e4b3cef Fixes and improvements to XDG installation 2021-02-27 17:34:11 +02:00
Lior Halphon
3c0f4d458d Update Windows icon 2021-02-27 15:51:56 +02:00
Lior Halphon
aebc11744c Update readme 2021-02-27 15:37:40 +02:00
Lior Halphon
2b263937da Allow make install under FreeDesktop 2021-02-27 14:32:07 +02:00
Lior Halphon
0a983b788e Update icon 2021-02-27 04:13:31 +02:00
Lior Halphon
ce44773caa Make the printer not deadlock after a sudden termination 2021-02-26 16:40:35 +02:00
Lior Halphon
cb721dae5d Make the automation use the accurate RTC mode 2021-02-26 01:09:40 +02:00
Lior Halphon
34b0404ffa Add RTC setting to libretro 2021-02-26 01:07:46 +02:00
Lior Halphon
bae91cdb1d Add RTC option to the SDL port, fix a bug where rewind setting didn't update 2021-02-26 01:04:24 +02:00
Lior Halphon
72cb391612 Slightly improve MBC3 accuracy 2021-02-26 00:52:18 +02:00
Lior Halphon
71c6fa45e0 Accurate RTC emulation 2021-02-26 00:40:18 +02:00
Lior Halphon
a13469c4e2 Fix PAL SGB in the Cocoa port 2021-02-25 22:42:02 +02:00
Lior Halphon
e08df2a089 Add accurate RTC emulation mode 2021-02-25 22:12:14 +02:00
Lior Halphon
807712b9c2 Allow creating sav files from the tester (Fixes #311) 2021-02-25 18:04:52 +02:00
Lior Halphon
9fa564f97c Fix #336 2021-02-25 17:12:01 +02:00
Lior Halphon
6ec4583aa0 Tell GCC to calm down 2021-02-25 15:52:48 +02:00
Lior Halphon
fa5420136e I hate the audio thread 2021-02-25 15:43:52 +02:00
Lior Halphon
4c05ebcea6 Redo the volume envelope with better timings, locking emulation and zombie mode edge cases. Fixes #344 2021-02-25 15:43:38 +02:00
Lior Halphon
8809d8ac2f More correct emulation of manual clocking of channels 1 and 2 2021-02-22 15:27:36 +02:00
Lior Halphon
71c88323b7 Rename UNROLL to unrolled (unrolled for) 2021-02-22 14:45:30 +02:00
Lior Halphon
9da0449797 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-02-22 13:49:08 +02:00
Lior Halphon
393269ae1f Emulate volume envelope locking 2021-02-22 13:48:56 +02:00
Lior Halphon
d50fdc52ea Further accuracy improvements to the audio envelope 2021-02-22 01:58:43 +02:00
Lior Halphon
6d2d88648e Improved emulation of the volume envelope 2021-02-22 01:10:14 +02:00
Lior Halphon
759ff1927e
Merge pull request #345 from jkotlinski/fail-nicely-on-missing-window
exit with error message instead of crash when a window cannot be opened
2021-02-14 02:33:58 +02:00
Johan Kotlinski
1a87c452b7 exit with error message instead of crash when a window cannot be opened 2021-02-14 01:31:49 +01:00
Lior Halphon
e7a5be70c2 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-02-13 23:00:30 +02:00
Lior Halphon
95051d1c1c Improved emulation of the NRx2 write glitch (Zombie mode) on models prior to CGB-D 2021-02-13 23:00:20 +02:00
Lior Halphon
d343f0c969
Merge pull request #330 from jverkoey/array
Add type annotations to GBImageView's grid arrays.
2021-02-13 14:38:07 +02:00
Lior Halphon
3316954d14
Merge pull request #326 from jverkoey/deprecations
Resolve various deprecation warnings.
2021-02-13 14:15:48 +02:00
Lior Halphon
8ad08c1b35 Fix more audio deadlocks 2021-02-01 23:11:42 +02:00
Lior Halphon
301c0f41c2 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-01-31 19:17:56 +02:00
Lior Halphon
6798b1f11a Use a slider for temperature in the SDL GUI 2021-01-31 19:17:48 +02:00
Lior Halphon
2d76698279 Emulation of NR43 bit 3 glitch on CGB-C and older 2021-01-31 19:17:26 +02:00
Lior Halphon
d67580c964 Oops, that was reversed 2021-01-31 17:16:59 +02:00
Lior Halphon
fbfa20a2cd
Merge pull request #337 from phobos2390/fix_for_signed_array_index
Fix for tolower extension signed char issue
2021-01-27 22:04:15 +02:00
phobos2390
bbfd16f63d Fix for tolower extension signed char issue 2021-01-25 23:37:46 -07:00
Lior Halphon
ef9671010b More NR43 obscurities 2021-01-24 20:57:46 +02:00
Lior Halphon
8e1e889ce0 Add a TODO 2021-01-16 15:31:09 +02:00
Lior Halphon
aa421258b8 Update the model enum so comparisons work correctly for SGB PAL and no-SFC SGBs 2021-01-16 14:51:06 +02:00
Lior Halphon
13a1e9d332 Timing fix 2021-01-16 14:43:32 +02:00
Lior Halphon
0056cc2d61 Revert "Further NR43 write glitch emulation" for now
This reverts commit e384707615.
2021-01-16 14:42:13 +02:00
Jeff Verkoeyen
557f554270 [Sameboy] Add type annotations to GBImageView's grid arrays. 2021-01-15 13:21:28 -05:00
Jeff Verkoeyen
8f91533a9a Revert nil check changes. 2021-01-15 12:49:24 -05:00
Lior Halphon
931045fd9b
Merge pull request #325 from jverkoey/master
Annotate all Cocoa properties as nonatomic.
2021-01-15 14:15:09 +02:00
Lior Halphon
0b8ee0585a
Merge pull request #329 from jverkoey/spritebug
Fix broken sprite rendering in the VRAM viewer due to mis-calculation of image data size.
2021-01-15 13:39:07 +02:00
Jeff Verkoeyen
1707c8818a Fix broken sprite rendering in the VRAM viewer due to mis-calculation of image data size. 2021-01-15 00:41:21 -05:00
Jeff Verkoeyen
60f226321d Resolve various deprecation warnings. 2021-01-13 14:52:18 -05:00
Jeff Verkoeyen
6dca01ad27 Annotate properties as nonatomic. 2021-01-13 14:12:34 -05:00
Lior Halphon
e384707615 Further NR43 write glitch emulation 2021-01-10 17:20:25 +02:00
Lior Halphon
07e76a4ecf Oh boy, looks like my CGB-B is unique 2021-01-09 23:28:30 +02:00
Lior Halphon
1b3f52e8c0 Improved emulation of NR43 writes on different revisions 2021-01-09 21:21:22 +02:00
Lior Halphon
2aa171e0ea Better sample alignment on pre-CGB-D models 2021-01-09 16:26:56 +02:00
Lior Halphon
96736fe7c5 Fix false positives in odd-mode detection 2021-01-09 00:59:12 +02:00
Lior Halphon
c496797fce Merge branch 'master' of https://github.com/LIJI32/SameBoy 2021-01-09 00:31:26 +02:00
Lior Halphon
c0582fd994 More accurate emulation of NR10 writes 2021-01-09 00:31:16 +02:00
Lior Halphon
8420fb7364
Merge pull request #319 from MaddTheSane/qlTrim
Fix visibility of a few functions in the QuickLook plug-in.
2021-01-05 21:25:08 +02:00
C.W. Betts
e4c7333a1a Fix visibility of a few functions in the QuickLook plug-in. 2021-01-04 01:08:31 -07:00
Lior Halphon
29a3b18186 Better camera noise on frontends without camera support 2021-01-03 16:52:18 +02:00
Lior Halphon
f9b13c66b1 Emulation of a newly discovered revision specific APU quirk 2021-01-03 13:49:36 +02:00
Lior Halphon
ecace40fb0 Minor APU bug fix 2021-01-02 18:27:21 +02:00
Lior Halphon
a9c337264e Fix the last remaining APU test 2021-01-02 16:23:34 +02:00
Lior Halphon
b54a72d9b9 Fixing a bug where where zero-shift sweep wouldn't tick 2021-01-02 14:56:45 +02:00
Lior Halphon
5c854dbdca Interference emulation 2020-12-31 00:07:04 +02:00
Lior Halphon
8e858c1bf1 Capitalization 2020-12-27 01:02:50 +02:00
Lior Halphon
e535d97e84 Fix GCC9 build break 2020-12-27 00:23:16 +02:00
Lior Halphon
9e808b255c Escape now returns to the previous menu if used from a submenu in the SDL port 2020-12-27 00:03:40 +02:00
Lior Halphon
1d34637bda Fix it harder 2020-12-26 23:56:26 +02:00
Lior Halphon
47ebc31733 Fixed a bug where the SDL and libretro frontend would not update the border when loading a new ROM 2020-12-26 23:52:41 +02:00
Lior Halphon
3dbd2eac91 Something went wrong with the color temperature commit somehow 2020-12-26 23:33:01 +02:00
Lior Halphon
7fc59b5cf4 Let the SDL port choose a boot ROMs folder 2020-12-26 15:10:11 +02:00
Lior Halphon
e1f797c212 Improved scrolling 2020-12-26 13:13:43 +02:00
Lior Halphon
c471696fbb Scrollbar and mouse wheel support 2020-12-26 03:20:53 +02:00
Lior Halphon
6d5ce6c54d Better scrolling a spacing in the SDL UI 2020-12-26 01:45:03 +02:00
Lior Halphon
544d39f19d Further improvements to STOP timing 2020-12-26 00:19:48 +02:00
Lior Halphon
4bbd27735f Fix a regression in speed switch timing, reset DIV on speed switch, better odd-mode detection and avoidance 2020-12-25 20:41:00 +02:00
Lior Halphon
159d9d0348 Color temperature control 2020-12-25 14:17:57 +02:00
Lior Halphon
b5a611c5db More accurate color correction curves 2020-12-24 23:17:20 +02:00
Lior Halphon
84b388692a Merge branch 'master' of https://github.com/LIJI32/SameBoy 2020-12-24 23:14:00 +02:00
Lior Halphon
66f62d696c More window fixes 2020-12-24 20:50:47 +02:00
Lior Halphon
aa2bdf2a1c Better support for non-QWERTY Latin layouts 2020-12-23 23:50:19 +02:00
Lior Halphon
8f64f49c3b More accurate emulation of window timing, actual correct fix of #123 2020-12-23 23:49:57 +02:00
Lior Halphon
b0eb87dfab
Merge pull request #315 from messmerd/line_ending_fix
Prevent shader compilation issues due to line endings on Windows machines
2020-12-19 11:36:49 +02:00
Dalton Messmer
8a13b7be24
Add .gitattributes line ending settings
Always use LF line endings for shaders
2020-12-19 00:58:19 -05:00
Lior Halphon
7a3ebb708c LCDC write timing regression fix 2020-12-12 22:55:14 +02:00
Lior Halphon
db483ce95f Warn about potential odd-mode triggers 2020-12-12 20:40:35 +02:00
Lior Halphon
555835549a More accurate pausing behavior, including revision differences 2020-12-12 20:35:18 +02:00
Lior Halphon
770885440f Minor changes to debugger output 2020-12-12 19:09:53 +02:00
Lior Halphon
4f408eae7c Whoops 2020-12-12 18:13:55 +02:00
Lior Halphon
dffc12331b Emulate the delayed NR44 write on the DMG 2020-12-12 18:11:35 +02:00
Lior Halphon
6b30de5fb1 Fixed dark colors on Metal without frame blending 2020-12-12 16:02:46 +02:00
Lior Halphon
7de6194e28 Redo channel 4's timing accurately, emulate NR43 write quirks 2020-12-12 16:02:25 +02:00
Lior Halphon
1baa0446a9 More sweep improvements 2020-12-01 22:37:13 +02:00
Lior Halphon
13bc8679f9 Correct preservation of NRx1's state on pre-CGB models 2020-12-01 14:18:19 +02:00
Lior Halphon
74cf452a48 Further accuracy improvements to sweep; passes Blargg's APU tests again, this time for real 2020-12-01 14:17:35 +02:00
Lior Halphon
0485124076 Redo channel 1 sweep based on DMG schematics; emulates two newly discovered behaviors and also fixes #309 2020-11-28 19:31:25 +02:00
Lior Halphon
67c0e03f3b Fix a window bug in CGB mode, fixes #123 2020-11-22 00:21:19 +02:00
Lior Halphon
bbf609f46b Add TGA output option to the tester, closes #310 2020-11-21 21:05:03 +02:00
Lior Halphon
027cecde24 Added debugger "undo" command. Closes #156 2020-11-21 16:19:58 +02:00
Lior Halphon
bdd27ce50d IR support in the libretro port 2020-11-21 15:36:21 +02:00
Lior Halphon
1d9ac5ccc3 More accurate IR emulation, simplify API 2020-11-21 00:52:54 +02:00
Lior Halphon
b7f3454763 More accurate emulation of the IR port 2020-11-20 22:12:15 +02:00
Lior Halphon
cd2310f0a7 Wave RAM reads 0xFF while active on AGBs 2020-11-20 19:39:54 +02:00
Lior Halphon
7fdc58a07e Implement CGB-mode TILE_SEL mixing, fixes cgb-acid-hell and m3_lcdc_tile_sel_change2, closes #308 2020-11-20 16:24:16 +02:00
Lior Halphon
c36bdc22f6 More accurate interrupt emulation 2020-11-14 13:55:39 +02:00
Lior Halphon
60b8978762 Local link cable and infrared emulation in the Cocoa port 2020-11-13 23:07:35 +02:00
Lior Halphon
88198e64f4 Minor bug fixes 2020-11-13 23:02:17 +02:00
Lior Halphon
03cbab2f85
Windows is no longer officially supported in the standalone builds 2020-10-15 19:21:37 +03:00
Lior Halphon
1936dd7f67
Merge pull request #304 from JL2210/rgbds-link
Update RGBDS links in README and build-faq
2020-10-14 01:22:21 +03:00
James Larrowe
6e0c09f78c Update RGBDS links in README and build-faq
The repo's owner has changed twice since this link was used;
once from bentley to the neutral rednex organization, and then
from rednex to gbdev
2020-10-13 18:19:29 -04:00
Lior Halphon
919a88ec23
Merge pull request #301 from SimpleTease1/patch1
libretro changes
2020-10-11 02:42:02 +03:00
SimpleTease
eb295de218 shared version.mk 2020-10-10 23:33:58 +00:00
SimpleTease
696bebc673 libretro: joypad bitmasks 2020-10-10 17:14:53 +00:00
SimpleTease
714227883f cross-compile friendly 2020-10-10 13:46:44 +00:00
Lior Halphon
526c2e029a
Fix #296 2020-10-10 14:50:11 +03:00
SimpleTease
2bfca48e0f libretro: fix core version 2020-10-10 04:02:21 +00:00
SimpleTease
cd526d960e libretro: changing model requires manual game restart 2020-10-10 04:00:30 +00:00
radius
8dc60d0b87 update makefile 2020-10-10 03:52:22 +00:00
twinaphex
efe8d6b643 Update Makefile 2020-10-10 01:22:51 +00:00
slash0042
faeb1d2e18 Add libnx port 2020-10-10 01:02:22 +00:00
Lior Halphon
8b45c32d77 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2020-10-09 16:40:00 +03:00
Lior Halphon
c35fe8b517 Make gb.h compatible with C++ again for bsnes integration. Fixed #300 2020-10-09 16:39:23 +03:00
Lior Halphon
195329463b
Merge pull request #294 from yo-wotop/master
Windows Updates
2020-10-09 16:03:57 +03:00
Lior Halphon
99ec5b32fc
Update build-faq.md 2020-10-09 16:03:32 +03:00
Lior Halphon
64963e1746
Update README.md 2020-10-09 15:57:23 +03:00
yo
38afb187cf Resolving some comments and clarifying some language 2020-10-06 23:03:39 -07:00
yo
0b5853070a Updated instructions for Windows building 2020-10-05 14:37:49 -07:00
yo
28234da2d2 Updated instructions for Windows building 2020-10-05 14:34:00 -07:00
yo
04e5f1b8cf Updated for Windows clang and SDL2 changes 2020-10-05 14:33:36 -07:00
Lior Halphon
2a5aed626d
Update README.md 2020-09-29 20:50:14 +03:00
Lior Halphon
1e9e961e9c
Create CONTRIBUTING.md 2020-09-29 20:43:47 +03:00
Lior Halphon
7c92845882 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2020-09-19 19:31:31 +03:00
Lior Halphon
7ff3556bc3 Workboy emulation (Cocoa only) 2020-09-19 19:31:24 +03:00
Lior Halphon
e35c22d405 Fix a potential single byte overflow 2020-09-17 23:47:35 +03:00
Lior Halphon
faf91508e2
Yes, I *do* mean -Wno-maybe-uninitialized! 2020-09-17 23:25:56 +03:00
Lior Halphon
abea3888db
Fix compilation under GCC 9 2020-09-17 23:18:16 +03:00
Lior Halphon
e0d6aa59ad
Merge pull request #289 from NieDzejkob/warning-fix
pb12: check the return value of write
2020-09-17 21:22:22 +03:00
Jakub Kądziołka
9c50a992af
pb12: check the return value of write 2020-09-17 20:01:21 +02:00
Lior Halphon
2e4a638077 Update version to 0.13.6 2020-08-22 14:21:54 +03:00
Lior Halphon
abce93640c Set a default value in the struct (instead of just during sanitation) 2020-08-22 14:20:44 +03:00
Lior Halphon
ceec044a22 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2020-08-22 14:17:20 +03:00
Lior Halphon
ebcc0d18ce
Merge pull request #281 from nadiaholmquist/feature/sdl-scale
SDL: Make default window scale configurable
2020-08-22 14:17:09 +03:00
Lior Halphon
e307de8064
Style fixes 2020-08-22 14:16:07 +03:00
Lior Halphon
dab1c1bcfa Fix RTC drift 2020-08-22 14:10:02 +03:00
Lior Halphon
832dc127a4 Fix Quick Look preview on Big Sur 2020-08-22 14:02:41 +03:00
Lior Halphon
c2410a4ffc Update UI for Big Sur 2020-08-22 13:06:27 +03:00
Lior Halphon
5cffdbcd27 Prevent asking for notification permissions until used by an HuC-3 game 2020-08-22 00:56:12 +03:00
Nadia Holmquist Pedersen
cc37632714 Remove printf 2020-08-19 06:15:36 +02:00
Lior Halphon
766529d7be Update version to 0.13.5 2020-08-06 21:01:55 +03:00
Lior Halphon
bce4bfba61 Fix major battery save regressions introduced by the last release, fixes #282 2020-08-06 19:34:57 +03:00
Nadia Holmquist Pedersen
012b9a2ba0 SDL: Make default window scale configurable 2020-08-06 03:08:19 +02:00
Lior Halphon
5b2eec214b Update version to 0.13.4 2020-08-05 21:17:22 +03:00
Lior Halphon
3f97b8eaa8 Even more regressions 2020-08-05 02:10:21 +03:00
Lior Halphon
d3664d5da0 Fix more RTC regressions 2020-08-05 01:39:19 +03:00
Lior Halphon
289853445f Fix long loading times when loading save states with bad local RTC times 2020-08-04 21:32:33 +03:00
Lior Halphon
b0d118f246 Fix broken and regressed MBC3 RTC emulation. Fixes #273, fixes #276, fixes #280 2020-08-04 21:32:27 +03:00
Lior Halphon
68b8d4bb1e
Merge pull request #279 from renatoliveira/cartridge-patch
Cartridge patch
2020-08-01 19:22:34 +03:00
Renato Oliveira
445aa74b14
Fix "cartrdige" to "cartridge" in comments 2020-08-01 12:57:10 -03:00
Renato Oliveira
ac3b09966c
Fix "Cartrdige" to "Cartridge" 2020-08-01 12:55:53 -03:00
Lior Halphon
4248ba8512
Merge pull request #272 from lpla/patch-2
Fix `make: write error` on macOS when `make CONF=native_release`
2020-06-25 20:54:58 +03:00
Leopoldo Pla
44ff0563c0
Fixes #259 2020-06-25 19:50:24 +02:00
Lior Halphon
a7942d6a1f Allow building fat x86-64 and ARM64 macOS binaries 2020-06-25 19:51:58 +03:00
Lior Halphon
64f381fa23 Update version to 0.13.3 2020-06-24 23:34:33 +03:00
Lior Halphon
a2e656a7c2 Fixed boot ROM regression; CGB games were given the wrong palettes 2020-06-24 20:34:52 +03:00
Lior Halphon
92c3eafc72 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2020-06-24 19:15:29 +03:00
Lior Halphon
cfcdce81ba Fix color in OpenGL when frame blending is disabled 2020-06-24 19:15:20 +03:00
Lior Halphon
ac521641ec
Merge pull request #271 from JL2210/old-gcc
Compatibility hacks for old compilers
2020-06-22 23:23:47 +03:00
James Larrowe
87d25c0896 Compatibility hacks for old compilers
GCC versions below 4.8.1 didn't have __builtin_bswap16, so provide
a suitable replacement.
2020-06-22 20:14:32 +00:00
Lior Halphon
e88a48e0a1 Use gamma-corrected mixing in shaders 2020-06-19 23:18:38 +03:00
Lior Halphon
096eb78be7 Update version to 0.13.2 2020-06-11 13:39:25 +03:00
Lior Halphon
4f42f4f718 Minor layout fixes 2020-06-11 00:38:53 +03:00
Lior Halphon
7eb4a92d2a Improve print window, add a button for actual physical printing 2020-06-10 23:28:33 +03:00
Lior Halphon
006ee4f6cb What rhymes with "willows" and breaks when you edit a Makefile? 2020-06-10 23:00:41 +03:00
Lior Halphon
b6b56d0766 Fix various save state compatibility issues between Windows and non-Windows, and a potential crash 2020-06-10 22:46:19 +03:00
Lior Halphon
edf7762408 Improved Dark Mode support, improved Hex Fiend's general system-native appearance 2020-06-10 01:10:11 +03:00
Lior Halphon
4a51f5c956 Cherry-picking libretro memory map bugfix (Closes #227, #205). Fixing libretro build with modern macOS SDKs. 2020-06-09 20:09:50 +03:00
Lior Halphon
c07588e3bd Console auto complete 2020-06-05 02:10:05 +03:00
Lior Halphon
ef203cf0e5 Update version to 0.13.1 2020-06-03 21:18:09 +03:00
Lior Halphon
b7a9039e50 Sanitize SDL preferences for cross-version stability 2020-06-03 21:06:47 +03:00
Lior Halphon
6a3cd371d0 Fix potential memory corruption when execution malformed ROMs 2020-06-03 20:54:06 +03:00
Lior Halphon
9e8b4345c0 Update version to 0.13 2020-05-31 21:55:04 +03:00
Lior Halphon
9521729e4e Fixed Windows build 2020-05-31 21:54:54 +03:00
Lior Halphon
08efb46d41 Made the command line debugger output “>” before inputs, added special magic sequence to break the debugger from stdin 2020-05-31 20:32:00 +03:00
Lior Halphon
0c0ca8e862 Last resort for Macs that can’t send reports to certain devices 2020-05-31 01:41:27 +03:00
Lior Halphon
97e844a0b7 GB_debugger_break is for external APIs, not available on libretro builds 2020-05-31 01:01:06 +03:00
Lior Halphon
5f4aee8ca1
Merge pull request #261 from JL2210/ld-b-b
Add ld b, b breakpoint
2020-05-31 00:55:17 +03:00
Lior Halphon
f1ea39f1c6
Spacing 2020-05-31 00:54:49 +03:00
Lior Halphon
fd97e11919
Spacing 2020-05-31 00:54:13 +03:00
James Larrowe
6fcf77c7f6 Make no argument for softbreak be equivalent to "on" 2020-05-30 16:46:17 -04:00
James Larrowe
abdece7737 add debugger command to enable and disable 2020-05-30 16:35:07 -04:00
James Larrowe
f105f28017 Add ld b,b breakpoint
Signed-off-by: James Larrowe <larrowe.semaj11@gmail.com>
2020-05-30 15:54:51 -04:00
Lior Halphon
83b959c126 Delay requests to show notifications 2020-05-30 22:46:06 +03:00
Lior Halphon
c665fcb2ed Minor fixes 2020-05-30 22:20:45 +03:00
Lior Halphon
c9b401135f Actually, don’t use rumble threads at all, because IOHIDDeviceSetReport seems to queue stuff despite being blocking 2020-05-30 22:18:32 +03:00
Lior Halphon
af5cb72edc Restore Switch LED support 2020-05-30 21:31:00 +03:00
Lior Halphon
e678b50101 Force all controllers to use a rumble thread 2020-05-30 20:15:13 +03:00
Lior Halphon
59b94b92ca Make sure reports are only sent from one thread 2020-05-30 18:43:09 +03:00
Lior Halphon
4d91081046 Do not send LED updates if nothing changed 2020-05-30 17:58:02 +03:00
Lior Halphon
fa7232944f Better emulation of CGB’s first frame behavior 2020-05-30 01:25:21 +03:00
Lior Halphon
ffa569deeb Partial emulation of reading VRAM right after mode 3 2020-05-29 23:10:23 +03:00
Lior Halphon
29b64d7545 Slightly reduce the scanline-ish LCD effect 2020-05-29 16:51:20 +03:00
Lior Halphon
17dfe0dd6a Fix minor CGB-C regression 2020-05-29 16:30:40 +03:00
Lior Halphon
f1442b0ea6 Attempt to add rumble support to SDL. Who knows it might work. 2020-05-24 23:04:36 +03:00
Lior Halphon
7af66387de HuC-3 alarm clock emulation 2020-05-23 14:50:54 +03:00
Lior Halphon
369410f370 Fix HuC-1 regression 2020-05-23 00:09:30 +03:00
Lior Halphon
08ca56eec7 Cleanup 2020-05-23 00:05:43 +03:00
Lior Halphon
ce9114ed55 Fix IR bugs 2020-05-19 01:24:09 +03:00
Lior Halphon
933b622886 Allow more GameShark cheats 2020-05-19 01:24:02 +03:00
Lior Halphon
157123e118 Fix clearing OAM and initializeing wave RAM 2020-05-17 19:24:11 +03:00
Lior Halphon
a588993f28 Add an HuC command required by Pocket Family 2 2020-05-17 00:10:43 +03:00
Lior Halphon
a9023d08c6 Emulate HuC-3’s IR and RTC 2020-05-16 23:27:17 +03:00
Lior Halphon
2cc980755e HuC1 IR support 2020-05-13 22:21:31 +03:00
Lior Halphon
060136306b Merge branch 'sdl_audio_fix' 2020-05-10 22:17:58 +03:00
Lior Halphon
1b7c3c4c7c Minor fixes, style update 2020-05-10 22:16:49 +03:00
Lior Halphon
0200596391 Fix #256 2020-05-10 22:05:47 +03:00
Lior Halphon
ca24f54bee Merge branch 'master' of https://github.com/LIJI32/SameBoy 2020-05-10 00:38:06 +03:00
Lior Halphon
3cba3e8e27 Emulate CGB-C PCM read glitch, fix a potential noise volume envelope bug 2020-05-10 00:37:52 +03:00
Rupert Carmichael
5f2c7b966f Pre-buffer audio samples before passing to SDL's queue 2020-05-09 11:49:20 -04:00
Lior Halphon
e4aa212f9a
Merge pull request #254 from NieDzejkob/bootrom-2bpp
Fix segfault, save some bytes.
2020-05-09 15:32:19 +03:00
Jakub Kądziołka
24220defd6
Save 16 bytes in the CGB boot ROM 2020-05-09 13:11:51 +02:00
Lior Halphon
620ee3cf51 Make the libretro frontend not crash on rumble-less frontends 2020-05-07 23:43:49 +03:00
Lior Halphon
ccc7d2e887 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2020-05-07 22:47:52 +03:00
Lior Halphon
e063671816 No need to use malloc here, the buffer never gets large 2020-05-07 22:46:06 +03:00
Lior Halphon
c4a8669501
Merge pull request #253 from FredrIQ/multiaddr
Allow more than 1 symbol per debug address
2020-05-07 02:09:09 +03:00
Lior Halphon
8625b23c0d Whoops 2020-05-07 01:32:03 +03:00
Fredrik Ljungdahl
99ec31dfdc Allow more than 1 symbol per debug address 2020-05-07 00:12:35 +02:00
Lior Halphon
8f69703726 Merge branch 'bootrom-2bpp' 2020-05-06 23:30:56 +03:00
Lior Halphon
7cff35368d Port to C to remove the Python dep, remove leftovers 2020-05-06 23:30:01 +03:00
Lior Halphon
184743637e Fix silly regression 2020-05-06 01:10:46 +03:00
Lior Halphon
730567dc60 Proper color mixing 2020-05-06 01:06:22 +03:00
Lior Halphon
a3f261184d Optimize more 2020-05-05 01:44:48 +03:00
Lior Halphon
f46f138e9f Clear VRAM correctly 2020-05-04 23:54:43 +03:00
Lior Halphon
260f61f33a This window shouldn’t be resizeable 2020-05-04 22:48:00 +03:00
Lior Halphon
72a90ba91c Hacky color blending 2020-05-04 02:17:03 +03:00
Lior Halphon
2225fd114c Handle 2bpp palettes 2020-05-04 02:07:19 +03:00
Jakub Kądziołka
b057e0d10a
Save 4 more bytes in the CGB boot ROM 2020-05-03 23:07:53 +02:00
Jakub Kądziołka
cb738190be
Add a 2bpp CGB boot ROM logo, pending palettes 2020-05-03 22:45:23 +02:00
Jakub Kądziołka
19126df7f4
Save 8 bytes in the CGB boot ROM 2020-05-03 22:41:56 +02:00
Lior Halphon
6910c3d24b Complete DualShock 3 support 2020-05-03 20:23:37 +03:00
Lior Halphon
bb37f8d2f0 Optimize Joypad initialization 2020-05-02 23:04:12 +03:00
Lior Halphon
9413d68976 Add support for wired Switch Pro Controller 2020-05-02 22:14:53 +03:00
Lior Halphon
78e2b94cb5 Rewrite the "Sub Elements" design into a more powerful Custom Report design that can overwrite an entire report structure of a sepcific report by its ID 2020-05-02 20:55:54 +03:00
Lior Halphon
e5302a9b1e Set sane libretro defaults, add border settings (Closes #203), general libretro cleanup 2020-05-01 23:42:08 +03:00
Lior Halphon
c492022ae6 Fix a deadlock 2020-05-01 19:31:04 +03:00
Lior Halphon
69fb2ad0a3 Fix rumble on WUP-028 on ports other than 1 2020-05-01 18:46:00 +03:00
Lior Halphon
7e124e169e Avoid races 2020-05-01 18:44:26 +03:00
Lior Halphon
2854578527 Less ugly hacks 2020-05-01 18:26:48 +03:00
Lior Halphon
021cdb402d Various hacks for stopping the PWM thread when needed, important if we have a WUP-028 connected with more than one controller 2020-05-01 18:16:33 +03:00
Lior Halphon
4bf252800e Improve PWM quality, fix a crash 2020-05-01 18:08:44 +03:00
Lior Halphon
5a56c3b882 Style fixes 2020-05-01 16:03:26 +03:00
Lior Halphon
ea18ba9335 Add rumble settings to libretro 2020-05-01 15:50:22 +03:00
Lior Halphon
5da80062d9 Fix WUP-028 on Catalina, make controller configuration compatible between macOS versions 2020-05-01 00:34:00 +03:00
Lior Halphon
40562b1c54 Revert "Fix WUP-028 on Catalina, make controller configuration compatible between macOS versions"
This reverts commit 160282c42a.
2020-05-01 00:25:40 +03:00
Lior Halphon
160282c42a Fix WUP-028 on Catalina, make controller configuration compatible between macOS versions 2020-04-30 23:56:14 +03:00
Lior Halphon
60ad3160cf Fix an XIB oops 2020-04-29 23:52:28 +03:00
Lior Halphon
6bcaffe27d Fix sendReport on JOYMultiplayerControlle 2020-04-29 23:47:18 +03:00
Lior Halphon
4c1f073d20 Fix error report 2020-04-29 20:59:19 +03:00
Lior Halphon
0f73282e4e Actually allow it 2020-04-29 20:54:28 +03:00
Lior Halphon
03ea6dc708 Make builds possible without Xcode 2020-04-29 20:44:55 +03:00
Lior Halphon
9f876e380c Offical WUP-028s require an activation sequence 2020-04-29 20:08:00 +03:00
Lior Halphon
66112af37e Fix PWM performence issue 2020-04-29 19:53:47 +03:00
Lior Halphon
5c9d50e25f Fix job 2020-04-29 17:02:20 +03:00
Lior Halphon
05cf3656b8 Fix libretro 2020-04-29 16:58:38 +03:00
Lior Halphon
0c91502859 Remove log 2020-04-29 16:52:32 +03:00
Lior Halphon
4b24178553 Rumble mode selection 2020-04-29 16:50:31 +03:00
Lior Halphon
4c443d51ce Minor JoyKit improvements 2020-04-29 16:06:38 +03:00
Lior Halphon
6448a692e2 Add smart rumble to games without a rumblepak 2020-04-29 16:06:11 +03:00
Lior Halphon
151d58eb60 setRumble should be double 2020-04-29 16:05:31 +03:00
Lior Halphon
5a14eb93c6 Merge branch 'master' into JoyKit
# Conflicts:
#	Cocoa/Document.m
#	Cocoa/GBView.h
#	Cocoa/GBView.m
#	Cocoa/joypad.m
#	Cocoa/main.m
#	Core/display.c
#	Core/gb.h
#	Core/save_state.c
2020-04-29 15:52:03 +03:00
Lior Halphon
8f6047fdca Prevent -Wall from overriding -Wno flags 2020-04-28 21:53:37 +03:00
Lior Halphon
1e54c55c11 Making libretro compile without warnings with GCC 2020-04-28 21:44:29 +03:00
Lior Halphon
c64d5b58b6 Make failed builds easier to read 2020-04-28 20:01:22 +03:00
Lior Halphon
8d016f19d2 Move the audio code to a different file 2020-04-28 20:01:22 +03:00
Lior Halphon
110cedeaac Even better hueristics 2020-04-25 23:26:17 +03:00
Lior Halphon
d1e3ad7790 Better hueristics for wrong MBC type 2020-04-25 23:18:03 +03:00
Lior Halphon
0534b091a5 Fix SDL 2020-04-25 23:11:29 +03:00
Lior Halphon
9e99ce434e Allow loading .RAM files 2020-04-25 23:09:08 +03:00
Lior Halphon
ca567bee79 Fix Linux build break 2020-04-25 22:54:50 +03:00
Lior Halphon
152924e13f Add support to the ISX format, including symbols 2020-04-25 22:48:48 +03:00
Lior Halphon
36aa3f31b9 -q was not enough 2020-04-25 18:11:01 +03:00
Lior Halphon
f65dc73632 -q was not enough 2020-04-25 18:09:04 +03:00
Lior Halphon
9fbafab67f Use grep -q, put macOS first, restore -j 2020-04-25 18:04:27 +03:00
Lior Halphon
65fb6afd60 Make fixes 2020-04-25 17:57:19 +03:00
Lior Halphon
09e7068658 Fixing another duh 2020-04-25 17:22:43 +03:00
Lior Halphon
aa9ccc724f Fixing a duh 2020-04-25 17:20:06 +03:00
Lior Halphon
7760e11544 Better error handling 2020-04-25 17:12:53 +03:00
Lior Halphon
a35164dc0a Fixed unused variable on Linux 2020-04-25 17:06:24 +03:00
Lior Halphon
e819b91a97 Rename job, temporarily disable -j 2020-04-25 17:03:45 +03:00
Lior Halphon
bf67811392 Sanity test against a few test ROMs 2020-04-25 16:59:47 +03:00
Lior Halphon
8e702f1452 Also test libretro's buildability 2020-04-25 15:13:04 +03:00
Lior Halphon
bb5c9f7fc6 Fix libretro build 2020-04-25 15:12:10 +03:00
Lior Halphon
66112f4930 That wasn't enough to fix it, I'll just disable this warning 2020-04-25 14:55:51 +03:00
Lior Halphon
c62704e26b Minor fix for GCC's LTO 2020-04-25 14:51:17 +03:00
Lior Halphon
1e7737a239 Limit unroll to GCC 8 2020-04-25 14:46:01 +03:00
Lior Halphon
c2a395006e Update docs 2020-04-25 14:45:52 +03:00
Lior Halphon
097705456c Show compiler version 2020-04-25 14:05:35 +03:00
Lior Halphon
7e908fef0e The macOS environment doesn't come with GCC, it'll just test Clang again 2020-04-25 14:04:51 +03:00
Lior Halphon
17c97c3c2b Use brew's SDL2 on macOS 2020-04-25 13:59:31 +03:00
Lior Halphon
385cd1b8c7 Fix chmod 2020-04-25 13:52:18 +03:00
Lior Halphon
2df6d266bd Add a GitHub action to avoid breaking builds 2020-04-25 13:50:35 +03:00
Lior Halphon
ddad913e06 OK this time it will work. 2020-04-24 21:59:51 +03:00
Lior Halphon
8ac029d3fe Truly truly fix #249, fix #251 2020-04-24 21:06:44 +03:00
Lior Halphon
198942b273 Truly fix #249, fix #251 2020-04-24 21:00:30 +03:00
Lior Halphon
634dcefd01 Typo 2020-04-24 20:44:25 +03:00
Lior Halphon
0cf168f32b Fixing inconsistent style 2020-04-24 20:37:57 +03:00
Lior Halphon
d38fd41b0e Reorder flags so -Wpartial-availablility is affected by -Wno-unknown-warning -Wno-unknown-warning-option, fixes #249, fixes #251 2020-04-24 20:18:56 +03:00
Lior Halphon
db9410caa5 Minor UI fix 2020-04-11 21:48:47 +03:00
Lior Halphon
32a0dc0e43 Rename the "Developer" menu to "Develop", like first party Mac apps 2020-04-11 20:44:15 +03:00
Lior Halphon
0737655753 Merge branch 'master' into JoyKit 2020-01-03 23:42:25 +02:00
Lior Halphon
0ece21bca7 Replace the SDL-derived controller support with my own JoyKit framework. Adds rumble support, LED support, better manual and automatic configurations, analog speed controls. 2019-10-19 19:26:04 +03:00
239 changed files with 20841 additions and 12005 deletions

4
.gitattributes vendored
View File

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

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> Blargg's Test ROMs by Shay Green <gblargg@gmail.com>
<!--
The MIT License (MIT)
Copyright (c) 2015-2019 Lior Halphon Acid2 tests by Matt Currie under MIT:
MIT License
Copyright (c) 2020 Matt Currie
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -11,20 +13,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in all
all copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
THE SOFTWARE. SOFTWARE.
Author: Maximilian Mader
-->
<interface>
<!-- interface-requires gtk+ 3.0 -->
</interface>

BIN
.github/actions/cgb-acid2.gbc vendored Normal file

Binary file not shown.

BIN
.github/actions/cgb_sound.gb vendored Normal file

Binary file not shown.

BIN
.github/actions/dmg-acid2.gb vendored Normal file

Binary file not shown.

BIN
.github/actions/dmg_sound-2.gb vendored Executable file

Binary file not shown.

23
.github/actions/install_deps.sh vendored Executable file
View File

@ -0,0 +1,23 @@
case `echo $1 | cut -d '-' -f 1` in
ubuntu)
sudo apt-get -qq update
sudo apt-get install -yq bison libpng-dev pkg-config libsdl2-dev
(
cd `mktemp -d`
curl -L https://github.com/rednex/rgbds/archive/v0.4.0.zip > rgbds.zip
unzip rgbds.zip
cd rgbds-*
make -sj
sudo make install
cd ..
rm -rf *
)
;;
macos)
brew install rgbds sdl2
;;
*)
echo "Unsupported OS"
exit 1
;;
esac

BIN
.github/actions/oam_bug-2.gb vendored Executable file

Binary file not shown.

33
.github/actions/sanity_tests.sh vendored Executable file
View File

@ -0,0 +1,33 @@
set -e
./build/bin/tester/sameboy_tester --jobs 5 \
--length 45 .github/actions/cgb_sound.gb \
--length 10 .github/actions/cgb-acid2.gbc \
--length 10 .github/actions/dmg-acid2.gb \
--dmg --length 45 .github/actions/dmg_sound-2.gb \
--dmg --length 20 .github/actions/oam_bug-2.gb
mv .github/actions/dmg{,-mode}-acid2.bmp
./build/bin/tester/sameboy_tester \
--dmg --length 10 .github/actions/dmg-acid2.gb
set +e
FAILED_TESTS=`
shasum .github/actions/*.bmp | grep -E -v \(\
5283564df0cf5bb78a7a90aff026c1a4692fd39e\ \ .github/actions/cgb-acid2.bmp\|\
dbcc438dcea13b5d1b80c5cd06bda2592cc5d9e0\ \ .github/actions/cgb_sound.bmp\|\
0caadf9634e40247ae9c15ff71992e8f77bbf89e\ \ .github/actions/dmg-acid2.bmp\|\
a732077f98f43d9231453b1764d9f797a836924d\ \ .github/actions/dmg-mode-acid2.bmp\|\
c9e944b7e01078bdeba1819bc2fa9372b111f52d\ \ .github/actions/dmg_sound-2.bmp\|\
f0172cc91867d3343fbd113a2bb98100074be0de\ \ .github/actions/oam_bug-2.bmp\
\)`
if [ -n "$FAILED_TESTS" ] ; then
echo "Failed the following tests:"
echo $FAILED_TESTS | tr " " "\n" | grep -o -E "[^/]+\.bmp" | sed s/.bmp// | sort
exit 1
fi
echo Passed all tests

36
.github/workflows/sanity.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: "Bulidability and Sanity"
on: push
jobs:
sanity:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, ubuntu-18.04]
cc: [gcc, clang]
include:
- os: macos-latest
cc: clang
extra_target: cocoa
exclude:
- os: macos-latest
cc: gcc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Install deps
shell: bash
run: |
./.github/actions/install_deps.sh ${{ matrix.os }}
- name: Build
run: |
${{ matrix.cc }} -v; (make -j sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }} || (echo "==== Build Failed ==="; make sdl tester libretro ${{ matrix.extra_target }} CONF=release CC=${{ matrix.cc }}))
- name: Sanity tests
shell: bash
run: |
./.github/actions/sanity_tests.sh
- name: Upload binaries
uses: actions/upload-artifact@v1
with:
name: sameboy-canary-${{ matrix.os }}-${{ matrix.cc }}
path: build/bin

9
.gitignore vendored
View File

@ -1,10 +1 @@
build build
# intermediate source files generated at build time
gtk3/sameboy-gtk3-resources.c
gtk3/resources/gtk3/
gtk3/resources/ui/#*#
# temporary backup file
*.*~

217
BESS.md Normal file
View File

@ -0,0 +1,217 @@
# BESS Best Effort Save State 1.0
## Motivation
BESS is a save state format specification designed to allow different emulators, as well as majorly different versions of the same emulator, to import save states from other BESS-compliant save states. BESS works by appending additional, implementation-agnostic information about the emulation state. This allows a single save state file to be read as both a fully-featured, implementation specific save state which includes detailed timing information; and as a portable "best effort" save state that represents a state accurately enough to be restored in casual use-cases.
## Specification
Every integer used in the BESS specification is stored in Little Endian encoding.
### BESS footer
BESS works by appending a detectable footer at the end of an existing save state format. The footer uses the following format:
| Offset from end of file | Content |
|-------------------------|-------------------------------------------------------|
| -8 | Offset to the first BESS Block, from the file's start |
| -4 | The ASCII string 'BESS' |
### BESS blocks
BESS uses a block format where each block contains the following header:
| Offset | Content |
|--------|---------------------------------------|
| 0 | A four-letter ASCII identifier |
| 4 | Length of the block, excluding header |
Every block is followed by another block, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure).
#### NAME block
The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first.
The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII.
#### INFO block
The INFO block uses the `'INFO'` identifier, and is an optional block that contains information about the ROM this save state originates from. When used, this block should come before `CORE` but after `NAME`. This block is 0x12 bytes long, and it follows this structure:
| Offset | Content |
|--------|--------------------------------------------------|
| 0x00 | Bytes 0x134-0x143 from the ROM (Title) |
| 0x10 | Bytes 0x14E-0x14F from the ROM (Global checksum) |
#### CORE block
The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, unless the `NAME` or `INFO` blocks exist then it must come directly after them. An implementation should not enforce block order on blocks unknown to it for future compatibility.
The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows:
| Offset | Content |
|--------|----------------------------------------|
| 0x00 | Major BESS version as a 16-bit integer |
| 0x02 | Minor BESS version as a 16-bit integer |
Both major and minor versions should be 1. Implementations are expected to reject incompatible majors, but still attempt to read newer minor versions.
| Offset | Content |
|--------|----------------------------------------|
| 0x04 | A four-character ASCII model identifier |
BESS uses a four-character string to identify Game Boy models:
* The first letter represents mutually-incompatible families of models and is required. The allowed values are `'G'` for the original Game Boy family, `'S'` for the Super Game Boy family, and `'C'` for the Game Boy Color and Advance family.
* The second letter represents a specific model within the family, and is optional (If an implementation does not distinguish between specific models in a family, a space character may be used). The allowed values for family G are `'D'` for DMG and `'M'` for MGB; the allowed values for family S are `'N'` for NTSC, `'P'` for PAL, and `'2'` for SGB2; and the allowed values for family C are `'C'` for CGB, and `'A'` for the various GBA line models.
* The third letter represents a specific CPU revision within a model, and is optional (If an implementation does not distinguish between revisions, a space character may be used). The allowed values for model GD (DMG) are `'0'` and `'A'`, through `'C'`; the allowed values for model CC (CGB) are `'0'` and `'A'`, through `'E'`; the allowed values for model CA (AGB, AGS, GBP) are `'0'`, `'A'` and `'B'`; and for every other model this value must be a space character.
* The last character is used for padding and must be a space character.
For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E.
| Offset | Content |
|--------|--------------------------------------------------------|
| 0x08 | The value of the PC register |
| 0x0A | The value of the AF register |
| 0x0C | The value of the BC register |
| 0x0E | The value of the DE register |
| 0x10 | The value of the HL register |
| 0x12 | The value of the SP register |
| 0x14 | The value of IME (0 or 1) |
| 0x15 | The value of the IE register |
| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) |
| 0x17 | Reserved, must be 0 |
| 0x18 | The values of every memory-mapped register (128 bytes) |
The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note:
* Unused registers have Don't-Care values which should be ignored
* Unused register bits have Don't-Care values which should be ignored
* If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode
* Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode.
* Object priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect
* If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored.
* BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid.
* Implementations should not start a serial transfer when writing the value of SB
* Similarly, no value of NRx4 should trigger a sound pulse on save state load
* And similarly again, implementations should not trigger DMA transfers when writing the values of DMA or HDMA5
* The value store for DIV will be used to set the internal divisor to `DIV << 8`
* Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers)
| Offset | Content |
|--------|--------------------------------------------------------------------|
| 0x98 | The size of RAM (32-bit integer) |
| 0x9C | The offset of RAM from file start (32-bit integer) |
| 0xA0 | The size of VRAM (32-bit integer) |
| 0xA4 | The offset of VRAM from file start (32-bit integer) |
| 0xA8 | The size of MBC RAM (32-bit integer) |
| 0xAC | The offset of MBC RAM from file start (32-bit integer) |
| 0xB0 | The size of OAM (=0xA0, 32-bit integer) |
| 0xB4 | The offset of OAM from file start (32-bit integer) |
| 0xB8 | The size of HRAM (=0x7F, 32-bit integer) |
| 0xBC | The offset of HRAM from file start (32-bit integer) |
| 0xC0 | The size of background palettes (=0x40 or 0, 32-bit integer) |
| 0xC4 | The offset of background palettes from file start (32-bit integer) |
| 0xC8 | The size of object palettes (=0x40 or 0, 32-bit integer) |
| 0xCC | The offset of object palettes from file start (32-bit integer) |
The contents of large buffers are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. Background and object palette sizes must be 0 for models prior to Game Boy Color.
An implementation needs handle size mismatches gracefully. For example, if too large MBC RAM size is specified, the superfluous data should be ignored. On the other hand, if a too small VRAM size is specified (For example, if it's a save state from an emulator emulating a CGB in DMG mode, and it didn't save the second CGB VRAM bank), the implementation is expected to set that extra bank to all zeros.
#### XOAM block
The XOAM block uses the `'XOAM'` identifier, and is an optional block that contains the data of extra OAM (addresses `0xFEA0-0xFEFF`). This block length must be `0x60`. Implementations that do not emulate this extra range are free to ignore the excess bytes, and to not create this block.
#### MBC block
The MBC block uses the `'MBC '` identifier, and is an optional block that is only used when saving states of ROMs that use an MBC. The length of this block is variable and must be divisible by 3.
This block contains an MBC-specific number of 3-byte-long pairs that represent the values of each MBC register. For example, for MBC5 the contents would look like:
| Offset | Content |
|--------|---------------------------------------|
| 0x0 | The value 0x0000 as a 16-bit integer |
| 0x2 | 0x0A if RAM is enabled, 0 otherwise |
| 0x3 | The value 0x2000 as a 16-bit integer |
| 0x5 | The lower 8 bits of the ROM bank |
| 0x6 | The value 0x3000 as a 16-bit integer |
| 0x8 | The bit 9 of the ROM bank |
| 0x9 | The value 0x4000 as a 16-bit integer |
| 0xB | The current RAM bank |
An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. Implementations must perform the writes in order (i.e. not reverse, sorted, or any other transformation on their order)
#### RTC block
The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB.
The length of this block is 0x30 bytes long and it follows the following structure:
| Offset | Content |
|--------|------------------------------------------------------------------------|
| 0x00 | Current seconds (1 byte), followed by 3 bytes of padding |
| 0x04 | Current minutes (1 byte), followed by 3 bytes of padding |
| 0x08 | Current hours (1 byte), followed by 3 bytes of padding |
| 0x0C | Current days (1 byte), followed by 3 bytes of padding |
| 0x10 | Current high/overflow/running (1 byte), followed by 3 bytes of padding |
| 0x14 | Latched seconds (1 byte), followed by 3 bytes of padding |
| 0x18 | Latched minutes (1 byte), followed by 3 bytes of padding |
| 0x1C | Latched hours (1 byte), followed by 3 bytes of padding |
| 0x20 | Latched days (1 byte), followed by 3 bytes of padding |
| 0x24 | Latched high/overflow/running (1 byte), followed by 3 bytes of padding |
| 0x28 | UNIX timestamp at the time of the save state (64-bit) |
#### HUC3 block
The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is used while emulating an HuC3 cartridge to store RTC and alarm information. The contents of this block are identical to HuC3 RTC saves from SameBoy.
The length of this block is 0x11 bytes long and it follows the following structure:
| Offset | Content |
|--------|-------------------------------------------------------|
| 0x00 | UNIX timestamp at the time of the save state (64-bit) |
| 0x08 | RTC minutes (16-bit) |
| 0x0A | RTC days (16-bit) |
| 0x0C | Scheduled alarm time minutes (16-bit) |
| 0x0E | Scheduled alarm time days (16-bit) |
| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) |
#### SGB block
The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing.
The length of this block is 0x39 bytes, but implementations should allow and ignore excess data in this block for extensions. The block follows the following structure:
| Offset | Content |
|--------|--------------------------------------------------------------------------------------------------------------------------|
| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) |
| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) |
| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) |
| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) |
| 0x10 | The size of the border palettes (=0x80, 32-bit integer) |
| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) |
| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) |
| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) |
| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) |
| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) |
| 0x28 | The size of the attribute map (=0x168, 32-bit integer) |
| 0x2C | The offset of the attribute map (32-bit integer) |
| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) |
| 0x34 | The offset of the attribute files (32-bit integer) |
| 0x38 | Multiplayer status (1 byte); high nibble is player count (1, 2 or 4), low nibble is current player (Where Player 1 is 0) |
If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default.
#### END block
The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. The length of the END block must be 0.
## Validation and Failures
Other than previously specified required fail conditions, an implementation is free to decide what format errors should abort the loading of a save file. Structural errors (e.g. a block with an invalid length, a file offset that is outside the file's range, or a missing END block) should be considered as irrecoverable errors. Other errors that are considered fatal by SameBoy's implementation:
* Duplicate CORE block
* A known block, other than NAME, appearing before CORE
* An invalid length for the XOAM, RTC, SGB or HUC3 blocks
* An invalid length of MBC (not a multiple of 3)
* A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block
* An SGB block on a save state targeting another model
* An END block with non-zero length

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 479 B

2
BootROMs/cgb0_boot.asm Normal file
View File

@ -0,0 +1,2 @@
CGB0 EQU 1
include "cgb_boot.asm"

File diff suppressed because it is too large Load Diff

View File

@ -115,7 +115,11 @@ Start:
call WaitBFrames call WaitBFrames
; Set registers to match the original DMG boot ; Set registers to match the original DMG boot
IF DEF(MGB)
ld hl, $FFB0
ELSE
ld hl, $01B0 ld hl, $01B0
ENDC
push hl push hl
pop af pop af
ld hl, $014D ld hl, $014D

2
BootROMs/mgb_boot.asm Normal file
View File

@ -0,0 +1,2 @@
MGB EQU 1
include "dmg_boot.asm"

102
BootROMs/pb12.c Normal file
View File

@ -0,0 +1,102 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
void opts(uint8_t byte, uint8_t *options)
{
*(options++) = byte | ((byte << 1) & 0xff);
*(options++) = byte & (byte << 1);
*(options++) = byte | ((byte >> 1) & 0xff);
*(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];
size_t size = read(STDIN_FILENO, &source, sizeof(source));
unsigned pos = 0;
assert(size <= 0x4000);
while (size && source[size - 1] == 0) {
size--;
}
uint8_t literals[8];
size_t literals_size = 0;
unsigned bits = 0;
unsigned control = 0;
unsigned prev[2] = {-1, -1}; // Unsigned to allow "not set" values
while (true) {
uint8_t byte = 0;
if (pos == size){
if (bits == 0) break;
}
else {
byte = source[pos++];
}
if (byte == prev[0] || byte == prev[1]) {
bits += 2;
control <<= 1;
control |= 1;
control <<= 1;
if (byte == prev[1]) {
control |= 1;
}
}
else {
bits += 2;
control <<= 2;
uint8_t options[4];
opts(prev[1], options);
bool found = false;
for (unsigned i = 0; i < 4; i++) {
if (options[i] == byte) {
// 01 = modify
control |= 1;
bits += 2;
control <<= 2;
control |= i;
found = true;
break;
}
}
if (!found) {
literals[literals_size++] = byte;
}
}
prev[0] = prev[1];
prev[1] = byte;
if (bits >= 8) {
uint8_t outctl = control >> (bits - 8);
assert(outctl != 1);
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_all(STDOUT_FILENO, &end_byte, 1);
return 0;
}

View File

@ -1,330 +0,0 @@
/*
PB8 compressor and decompressor
Copyright 2019 Damian Yerrick
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
// For setting stdin/stdout to binary mode
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
#include <unistd.h>
#define fd_isatty isatty
#elif defined (_WIN32)
#include <io.h>
#include <fcntl.h>
#define fd_isatty _isatty
#endif
/*
; The logo is compressed using PB8, a form of RLE with unary-coded
; run lengths. Each block representing 8 bytes consists of a control
; byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat
; previous, followed by the literals in that block.
SameBoyLogo_dst = $8080
SameBoyLogo_length = (128 * 24) / 64
LoadTileset:
ld hl, SameBoyLogo
ld de, SameBoyLogo_dst
ld c, SameBoyLogo_length
.pb8BlockLoop:
; Register map for PB8 decompression
; HL: source address in boot ROM
; DE: destination address in VRAM
; A: Current literal value
; B: Repeat bits, terminated by 1000...
; C: Number of 8-byte blocks left in this block
; Source address in HL lets the repeat bits go straight to B,
; bypassing A and avoiding spilling registers to the stack.
ld b, [hl]
inc hl
; Shift a 1 into lower bit of shift value. Once this bit
; reaches the carry, B becomes 0 and the byte is over
scf
rl b
.pb8BitLoop:
; If not a repeat, load a literal byte
jr c,.pb8Repeat
ld a, [hli]
.pb8Repeat:
; Decompressed data uses colors 0 and 1, so write once, inc twice
ld [de], a
inc de
inc de
sla b
jr nz, .pb8BitLoop
dec c
jr nz, .pb8BlockLoop
ret
*/
/* Compressor and decompressor *************************************/
/**
* Compresses an input stream to PB8 data on an output stream.
* @param infp input stream
* @param outfp output stream
* @param blocklength size of an independent input block in bytes
* @return 0 for reaching infp end of file, or EOF for error
*/
int pb8(FILE *infp, FILE *outfp, size_t blocklength) {
blocklength >>= 3; // convert bytes to blocks
assert(blocklength > 0);
while (1) {
int last_byte = EOF; // value that never occurs in a file
for (size_t blkleft = blocklength; blkleft > 0; --blkleft) {
unsigned int control_byte = 0x0001;
unsigned char literals[8];
size_t nliterals = 0;
while (control_byte < 0x100) {
int c = fgetc(infp);
if (c == EOF) break;
control_byte <<= 1;
if (c == last_byte) {
control_byte |= 0x01;
} else {
literals[nliterals++] = last_byte = c;
}
}
if (control_byte > 1) {
// Fill partial block with repeats
while (control_byte < 0x100) {
control_byte = (control_byte << 1) | 1;
}
// Write control byte and check for write failure
int ok = fputc(control_byte & 0xFF, outfp);
if (ok == EOF) return EOF;
size_t ok2 = fwrite(literals, 1, nliterals, outfp);
if (ok2 < nliterals) return EOF;
}
// If finished, return success or failure
if (ferror(infp) || ferror(outfp)) return EOF;
if (feof(infp)) return 0;
} // End 8-byte block
} // End packet, resetting last_byte
}
/**
* Decompresses PB8 data on an input stream to an output stream.
* @param infp input stream
* @param outfp output stream
* @return 0 for reaching infp end of file, or EOF for error
*/
int unpb8(FILE *infp, FILE *outfp) {
int last_byte = 0;
while (1) {
int control_byte = fgetc(infp);
if (control_byte == EOF) {
return feof(infp) ? 0 : EOF;
}
control_byte &= 0xFF;
for (size_t bytesleft = 8; bytesleft > 0; --bytesleft) {
if (!(control_byte & 0x80)) {
last_byte = fgetc(infp);
if (last_byte == EOF) return EOF; // read error
}
control_byte <<= 1;
int ok = fputc(last_byte, outfp);
if (ok == EOF) return EOF;
}
}
}
/* CLI frontend ****************************************************/
static inline void set_fd_binary(unsigned int fd) {
#ifdef _WIN32
_setmode(fd, _O_BINARY);
#else
(void) fd;
#endif
}
static const char *usage_msg =
"usage: pb8 [-d] [-l blocklength] [infile [outfile]]\n"
"Compresses a file using RLE with unary run and literal lengths.\n"
"\n"
"options:\n"
" -d decompress\n"
" -l blocklength allow RLE packets to span up to blocklength\n"
" input bytes (multiple of 8; default 8)\n"
" -h, -?, --help show this usage page\n"
" --version show copyright info\n"
"\n"
"If infile is - or missing, it is standard input.\n"
"If outfile is - or missing, it is standard output.\n"
"You cannot compress to or decompress from a terminal.\n"
;
static const char *version_msg =
"PB8 compressor (C version) v0.01\n"
"Copyright 2019 Damian Yerrick <https://pineight.com/contact/>\n"
"This software is provided 'as-is', without any express or implied\n"
"warranty.\n"
;
static const char *toomanyfilenames_msg =
"pb8: too many filenames; try pb8 --help\n";
int main(int argc, char **argv) {
const char *infilename = NULL;
const char *outfilename = NULL;
bool decompress = false;
size_t blocklength = 8;
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-' && argv[i][1] != 0) {
if (!strcmp(argv[i], "--help")) {
fputs(usage_msg, stdout);
return 0;
}
if (!strcmp(argv[i], "--version")) {
fputs(version_msg, stdout);
return 0;
}
// -t1 or -t 1
int argtype = argv[i][1];
switch (argtype) {
case 'h':
case '?':
fputs(usage_msg, stdout);
return 0;
case 'd':
decompress = true;
break;
case 'l': {
const char *argvalue = argv[i][2] ? argv[i] + 2 : argv[++i];
const char *endptr = NULL;
unsigned long tvalue = strtoul(argvalue, (char **)&endptr, 10);
if (endptr == argvalue || tvalue == 0 || tvalue > SIZE_MAX) {
fprintf(stderr, "pb8: block length %s not a positive integer\n",
argvalue);
return EXIT_FAILURE;
}
if (tvalue % 8 != 0) {
fprintf(stderr, "pb8: block length %s not a multiple of 8\n",
argvalue);
return EXIT_FAILURE;
}
blocklength = tvalue;
} break;
default:
fprintf(stderr, "pb8: unknown option -%c\n", argtype);
return EXIT_FAILURE;
}
} else if (!infilename) {
infilename = argv[i];
} else if (!outfilename) {
outfilename = argv[i];
} else {
fputs(toomanyfilenames_msg, stderr);
return EXIT_FAILURE;
}
}
if (infilename && !strcmp(infilename, "-")) {
infilename = NULL;
}
if (!infilename && decompress && fd_isatty(0)) {
fputs("pb8: cannot decompress from terminal; try redirecting stdin\n",
stderr);
return EXIT_FAILURE;
}
if (outfilename && !strcmp(outfilename, "-")) {
outfilename = NULL;
}
if (!outfilename && !decompress && fd_isatty(1)) {
fputs("pb8: cannot compress to terminal; try redirecting stdout or pb8 --help\n",
stderr);
return EXIT_FAILURE;
}
FILE *infp = NULL;
if (infilename) {
infp = fopen(infilename, "rb");
if (!infp) {
fprintf(stderr, "pb8: error opening %s ", infilename);
perror("for reading");
return EXIT_FAILURE;
}
} else {
infp = stdin;
set_fd_binary(0);
}
FILE *outfp = NULL;
if (outfilename) {
outfp = fopen(outfilename, "wb");
if (!outfp) {
fprintf(stderr, "pb8: error opening %s ", outfilename);
perror("for writing");
fclose(infp);
return EXIT_FAILURE;
}
} else {
outfp = stdout;
set_fd_binary(1);
}
int compfailed = 0;
int has_ferror = 0;
if (decompress) {
compfailed = unpb8(infp, outfp);
} else {
compfailed = pb8(infp, outfp, blocklength);
}
fflush(outfp);
if (ferror(infp)) {
fprintf(stderr, "pb8: error reading %s\n",
infilename ? infilename : "<stdin>");
has_ferror = EOF;
}
fclose(infp);
if (ferror(outfp)) {
fprintf(stderr, "pb8: error writing %s\n",
outfilename ? outfilename : "<stdout>");
has_ferror = EOF;
}
fclose(outfp);
if (compfailed && !has_ferror) {
fputs("pb8: unknown compression failure\n", stderr);
}
return (compfailed || has_ferror) ? EXIT_FAILURE : EXIT_SUCCESS;
}

79
CONTRIBUTING.md Normal file
View File

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

View File

@ -1,15 +1,25 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
@interface AppDelegate : NSObject <NSApplicationDelegate> @interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate, WebUIDelegate, WebPolicyDelegate, WebFrameLoadDelegate>
@property IBOutlet NSWindow *preferencesWindow; @property (nonatomic, strong) IBOutlet NSWindow *preferencesWindow;
@property (strong) IBOutlet NSView *graphicsTab; @property (nonatomic, strong) IBOutlet NSView *graphicsTab;
@property (strong) IBOutlet NSView *emulationTab; @property (nonatomic, strong) IBOutlet NSView *emulationTab;
@property (strong) IBOutlet NSView *audioTab; @property (nonatomic, strong) IBOutlet NSView *audioTab;
@property (strong) IBOutlet NSView *controlsTab; @property (nonatomic, strong) IBOutlet NSView *controlsTab;
@property (nonatomic, strong) IBOutlet NSView *updatesTab;
- (IBAction)showPreferences: (id) sender; - (IBAction)showPreferences: (id) sender;
- (IBAction)toggleDeveloperMode:(id)sender; - (IBAction)toggleDeveloperMode:(id)sender;
- (IBAction)switchPreferencesTab:(id)sender; - (IBAction)switchPreferencesTab:(id)sender;
@property (nonatomic, weak) IBOutlet NSMenuItem *linkCableMenuItem;
@property (nonatomic, strong) IBOutlet NSWindow *updateWindow;
@property (nonatomic, strong) IBOutlet WebView *updateChanges;
@property (nonatomic, strong) IBOutlet NSProgressIndicator *updatesSpinner;
@property (strong) IBOutlet NSButton *updatesButton;
@property (strong) IBOutlet NSTextField *updateProgressLabel;
@property (strong) IBOutlet NSButton *updateProgressButton;
@property (strong) IBOutlet NSWindow *updateProgressWindow;
@property (strong) IBOutlet NSProgressIndicator *updateProgressSpinner;
@end @end

View File

@ -3,11 +3,34 @@
#include "GBView.h" #include "GBView.h"
#include <Core/gb.h> #include <Core/gb.h>
#import <Carbon/Carbon.h> #import <Carbon/Carbon.h>
#import <JoyKit/JoyKit.h>
#import <WebKit/WebKit.h>
#define UPDATE_SERVER "https://sameboy.github.io"
static uint32_t color_to_int(NSColor *color)
{
color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
return (((unsigned)(color.redComponent * 0xFF)) << 16) |
(((unsigned)(color.greenComponent * 0xFF)) << 8) |
((unsigned)(color.blueComponent * 0xFF));
}
@implementation AppDelegate @implementation AppDelegate
{ {
NSWindow *preferences_window; NSWindow *preferences_window;
NSArray<NSView *> *preferences_tabs; NSArray<NSView *> *preferences_tabs;
NSString *_lastVersion;
NSString *_updateURL;
NSURLSessionDownloadTask *_updateTask;
enum {
UPDATE_DOWNLOADING,
UPDATE_EXTRACTING,
UPDATE_WAIT_INSTALL,
UPDATE_INSTALLING,
UPDATE_FAILED,
} _updateState;
NSString *_downloadDirectory;
} }
- (void) applicationDidFinishLaunching:(NSNotification *)notification - (void) applicationDidFinishLaunching:(NSNotification *)notification
@ -42,7 +65,32 @@
@"GBDMGModel": @(GB_MODEL_DMG_B), @"GBDMGModel": @(GB_MODEL_DMG_B),
@"GBCGBModel": @(GB_MODEL_CGB_E), @"GBCGBModel": @(GB_MODEL_CGB_E),
@"GBSGBModel": @(GB_MODEL_SGB2), @"GBSGBModel": @(GB_MODEL_SGB2),
@"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY),
@"GBVolume": @(1.0),
@"GBMBC7JoystickOverride": @NO,
@"GBMBC7AllowMouse": @YES,
}]; }];
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
JOYAxes2DEmulateButtonsKey: @YES,
JOYHatsEmulateButtonsKey: @YES,
}];
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
}
[self askAutoUpdates];
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]) {
[self checkForUpdates];
}
if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) {
[NSApp activateIgnoringOtherApps:true];
}
} }
- (IBAction)toggleDeveloperMode:(id)sender - (IBAction)toggleDeveloperMode:(id)sender
@ -61,7 +109,7 @@
NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame]; NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame];
new.origin.x = old.origin.x; new.origin.x = old.origin.x;
new.origin.y = old.origin.y + (old.size.height - new.size.height); new.origin.y = old.origin.y + (old.size.height - new.size.height);
[_preferencesWindow setFrame:new display:YES animate:_preferencesWindow.visible]; [_preferencesWindow setFrame:new display:true animate:_preferencesWindow.visible];
[_preferencesWindow.contentView addSubview:tab]; [_preferencesWindow.contentView addSubview:tab];
} }
@ -71,9 +119,28 @@
[(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; [(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]];
} }
if (anItem == self.linkCableMenuItem) {
return [[NSDocumentController sharedDocumentController] documents].count > 1;
}
return true; 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 - (IBAction) showPreferences: (id) sender
{ {
NSArray *objects; NSArray *objects;
@ -81,17 +148,306 @@
[[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects]; [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects];
NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject]; NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject];
_preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier]; _preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier];
preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab]; preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab, self.updatesTab];
[self switchPreferencesTab:first_toolbar_item]; [self switchPreferencesTab:first_toolbar_item];
[_preferencesWindow center]; [_preferencesWindow center];
#ifndef UPDATE_SUPPORT
[_preferencesWindow.toolbar removeItemAtIndex:4];
#endif
} }
[_preferencesWindow makeKeyAndOrderFront:self]; [_preferencesWindow makeKeyAndOrderFront:self];
} }
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
{ {
[self askAutoUpdates];
/* Bring an existing panel to the foreground */
for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
if ([window isKindOfClass:[NSOpenPanel class]]) {
[(NSOpenPanel *)window makeKeyAndOrderFront:nil];
return true;
}
}
[[NSDocumentController sharedDocumentController] openDocument:self]; [[NSDocumentController sharedDocumentController] openDocument:self];
return YES; return true;
} }
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true];
}
- (void)updateFound
{
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/raw_changes"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSColor *linkColor = [NSColor colorWithRed:0.125 green:0.325 blue:1.0 alpha:1.0];
if (@available(macOS 10.10, *)) {
linkColor = [NSColor linkColor];
}
NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSRange cutoffRange = [changes rangeOfString:@"<!--(" GB_VERSION ")-->"];
if (cutoffRange.location != NSNotFound) {
changes = [changes substringToIndex:cutoffRange.location];
}
NSString *html = [NSString stringWithFormat:@"<!DOCTYPE html><html><head><title></title>"
"<style>html {background-color:transparent; color: #%06x; line-height:1.5} a:link, a:visited{color:#%06x; text-decoration:none}</style>"
"</head><body>%@</body></html>",
color_to_int([NSColor textColor]),
color_to_int(linkColor),
changes];
if ([(NSHTTPURLResponse *)response statusCode] == 200) {
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *objects;
[[NSBundle mainBundle] loadNibNamed:@"UpdateWindow" owner:self topLevelObjects:&objects];
self.updateChanges.preferences.standardFontFamily = [NSFont systemFontOfSize:0].familyName;
self.updateChanges.preferences.fixedFontFamily = @"Menlo";
self.updateChanges.drawsBackground = false;
[self.updateChanges.mainFrame loadHTMLString:html baseURL:nil];
});
}
}] resume];
}
- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
{
// Disable reload context menu
if ([defaultMenuItems count] <= 2) {
return nil;
}
return defaultMenuItems;
}
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sender.mainFrame.frameView.documentView.enclosingScrollView.drawsBackground = true;
sender.mainFrame.frameView.documentView.enclosingScrollView.backgroundColor = [NSColor textBackgroundColor];
sender.policyDelegate = self;
[self.updateWindow center];
[self.updateWindow makeKeyAndOrderFront:nil];
});
}
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
[listener ignore];
[[NSWorkspace sharedWorkspace] openURL:[request URL]];
}
- (void)checkForUpdates
{
#ifdef UPDATE_SUPPORT
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.updatesSpinner stopAnimation:nil];
[self.updatesButton setEnabled:true];
});
if ([(NSHTTPURLResponse *)response statusCode] == 200) {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray <NSString *> *components = [string componentsSeparatedByString:@"|"];
if (components.count != 2) return;
_lastVersion = components[0];
_updateURL = components[1];
if (![@GB_VERSION isEqualToString:_lastVersion] &&
![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) {
[self updateFound];
}
}
}] resume];
#endif
}
- (IBAction)userCheckForUpdates:(id)sender
{
if (self.updateWindow) {
[self.updateWindow makeKeyAndOrderFront:sender];
}
else {
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"GBSkippedVersion"];
[self checkForUpdates];
[sender setEnabled:false];
[self.updatesSpinner startAnimation:sender];
}
}
- (void)askAutoUpdates
{
#ifdef UPDATE_SUPPORT
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAskedAutoUpdates"]) {
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"Should SameBoy check for updates when launched?";
alert.informativeText = @"SameBoy is frequently updated with new features, accuracy improvements, and bug fixes. This setting can always be changed in the preferences window.";
[alert addButtonWithTitle:@"Check on Launch"];
[alert addButtonWithTitle:@"Don't Check on Launch"];
[[NSUserDefaults standardUserDefaults] setBool:[alert runModal] == NSAlertFirstButtonReturn forKey:@"GBAutoUpdatesEnabled"];
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBAskedAutoUpdates"];
}
#endif
}
- (IBAction)skipVersion:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:_lastVersion forKey:@"GBSkippedVersion"];
[self.updateWindow performClose:sender];
}
- (IBAction)installUpdate:(id)sender
{
[self.updateProgressSpinner startAnimation:nil];
self.updateProgressButton.title = @"Cancel";
self.updateProgressButton.enabled = true;
self.updateProgressLabel.stringValue = @"Downloading update...";
_updateState = UPDATE_DOWNLOADING;
_updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
_updateTask = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Extracting update...";
_updateState = UPDATE_EXTRACTING;
});
_downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:[[NSBundle mainBundle] bundleURL]
create:true
error:nil] path];
NSTask *unzipTask;
if (!_downloadDirectory) {
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to extract update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
return;
}
unzipTask = [[NSTask alloc] init];
unzipTask.launchPath = @"/usr/bin/unzip";
unzipTask.arguments = @[location.path, @"-d", _downloadDirectory];
[unzipTask launch];
[unzipTask waitUntilExit];
if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) {
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to extract update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
return;
}
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Update ready, save your game progress and click Install.";
_updateState = UPDATE_WAIT_INSTALL;
self.updateProgressButton.title = @"Install";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
}];
[_updateTask resume];
self.updateProgressWindow.preventsApplicationTerminationWhenModal = false;
[self.updateWindow beginSheet:self.updateProgressWindow completionHandler:^(NSModalResponse returnCode) {
[self.updateWindow close];
}];
}
- (void)performUpgrade
{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Instaling update...";
_updateState = UPDATE_INSTALLING;
self.updateProgressButton.enabled = false;
[self.updateProgressSpinner startAnimation:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *executablePath = [[NSBundle mainBundle] executablePath];
NSString *contentsPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents"];
NSString *contentsTempPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"TempContents"];
NSString *updateContentsPath = [_downloadDirectory stringByAppendingPathComponent:@"SameBoy.app/Contents"];
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtPath:contentsPath toPath:contentsTempPath error:&error];
if (error) {
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
_downloadDirectory = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to install update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
return;
}
[[NSFileManager defaultManager] moveItemAtPath:updateContentsPath toPath:contentsPath error:&error];
if (error) {
[[NSFileManager defaultManager] moveItemAtPath:contentsTempPath toPath:contentsPath error:nil];
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
_downloadDirectory = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to install update.";
_updateState = UPDATE_FAILED;
self.updateProgressButton.title = @"Close";
self.updateProgressButton.enabled = true;
[self.updateProgressSpinner stopAnimation:nil];
});
return;
}
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
[[NSFileManager defaultManager] removeItemAtPath:contentsTempPath error:nil];
_downloadDirectory = nil;
atexit_b(^{
execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL);
});
dispatch_async(dispatch_get_main_queue(), ^{
[NSApp terminate:nil];
});
});
}
- (IBAction)updateAction:(id)sender
{
switch (_updateState) {
case UPDATE_DOWNLOADING:
[_updateTask cancelByProducingResumeData:nil];
_updateTask = nil;
[self.updateProgressWindow close];
break;
case UPDATE_WAIT_INSTALL:
[self performUpgrade];
break;
case UPDATE_EXTRACTING:
case UPDATE_INSTALLING:
break;
case UPDATE_FAILED:
[self.updateProgressWindow close];
break;
}
}
- (void)dealloc
{
if (_downloadDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
}
}
- (IBAction)nop:(id)sender
{
}
@end @end

Binary file not shown.

30
Cocoa/BigSurToolbar.h Normal file
View File

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

View File

@ -2,45 +2,60 @@
#include "GBView.h" #include "GBView.h"
#include "GBImageView.h" #include "GBImageView.h"
#include "GBSplitView.h" #include "GBSplitView.h"
#include "GBVisualizerView.h"
#include "GBOSDView.h"
@class GBCheatWindowController; @class GBCheatWindowController;
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate> @interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
@property (strong) IBOutlet GBView *view; @property (nonatomic, readonly) GB_gameboy_t *gb;
@property (strong) IBOutlet NSTextView *consoleOutput; @property (nonatomic, strong) IBOutlet GBView *view;
@property (strong) IBOutlet NSPanel *consoleWindow; @property (nonatomic, strong) IBOutlet NSTextView *consoleOutput;
@property (strong) IBOutlet NSTextField *consoleInput; @property (nonatomic, strong) IBOutlet NSPanel *consoleWindow;
@property (strong) IBOutlet NSWindow *mainWindow; @property (nonatomic, strong) IBOutlet NSTextField *consoleInput;
@property (strong) IBOutlet NSView *memoryView; @property (nonatomic, strong) IBOutlet NSWindow *mainWindow;
@property (strong) IBOutlet NSPanel *memoryWindow; @property (nonatomic, strong) IBOutlet NSView *memoryView;
@property (readonly) GB_gameboy_t *gameboy; @property (nonatomic, strong) IBOutlet NSPanel *memoryWindow;
@property (strong) IBOutlet NSTextField *memoryBankInput; @property (nonatomic, readonly) GB_gameboy_t *gameboy;
@property (strong) IBOutlet NSToolbarItem *memoryBankItem; @property (nonatomic, strong) IBOutlet NSTextField *memoryBankInput;
@property (strong) IBOutlet GBImageView *tilesetImageView; @property (nonatomic, strong) IBOutlet NSToolbarItem *memoryBankItem;
@property (strong) IBOutlet NSPopUpButton *tilesetPaletteButton; @property (nonatomic, strong) IBOutlet GBImageView *tilesetImageView;
@property (strong) IBOutlet GBImageView *tilemapImageView; @property (nonatomic, strong) IBOutlet NSPopUpButton *tilesetPaletteButton;
@property (strong) IBOutlet NSPopUpButton *tilemapPaletteButton; @property (nonatomic, strong) IBOutlet GBImageView *tilemapImageView;
@property (strong) IBOutlet NSPopUpButton *tilemapMapButton; @property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapPaletteButton;
@property (strong) IBOutlet NSPopUpButton *TilemapSetButton; @property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapMapButton;
@property (strong) IBOutlet NSButton *gridButton; @property (nonatomic, strong) IBOutlet NSPopUpButton *TilemapSetButton;
@property (strong) IBOutlet NSTabView *vramTabView; @property (nonatomic, strong) IBOutlet NSButton *gridButton;
@property (strong) IBOutlet NSPanel *vramWindow; @property (nonatomic, strong) IBOutlet NSTabView *vramTabView;
@property (strong) IBOutlet NSTextField *vramStatusLabel; @property (nonatomic, strong) IBOutlet NSPanel *vramWindow;
@property (strong) IBOutlet NSTableView *paletteTableView; @property (nonatomic, strong) IBOutlet NSTextField *vramStatusLabel;
@property (strong) IBOutlet NSTableView *spritesTableView; @property (nonatomic, strong) IBOutlet NSTableView *paletteTableView;
@property (strong) IBOutlet NSPanel *printerFeedWindow; @property (nonatomic, strong) IBOutlet NSTableView *objectsTableView;
@property (strong) IBOutlet NSImageView *feedImageView; @property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow;
@property (strong) IBOutlet NSButton *feedSaveButton; @property (nonatomic, strong) IBOutlet NSImageView *feedImageView;
@property (strong) IBOutlet NSTextView *debuggerSideViewInput; @property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput;
@property (strong) IBOutlet NSTextView *debuggerSideView; @property (nonatomic, strong) IBOutlet NSTextView *debuggerSideView;
@property (strong) IBOutlet GBSplitView *debuggerSplitView; @property (nonatomic, strong) IBOutlet GBSplitView *debuggerSplitView;
@property (strong) IBOutlet NSBox *debuggerVerticalLine; @property (nonatomic, strong) IBOutlet NSBox *debuggerVerticalLine;
@property (strong) IBOutlet NSPanel *cheatsWindow; @property (nonatomic, strong) IBOutlet NSPanel *cheatsWindow;
@property (strong) IBOutlet GBCheatWindowController *cheatWindowController; @property (nonatomic, strong) IBOutlet GBCheatWindowController *cheatWindowController;
@property (nonatomic, readonly) Document *partner;
@property (nonatomic, readonly) bool isSlave;
@property (strong) IBOutlet NSView *gbsPlayerView;
@property (strong) IBOutlet NSTextField *gbsTitle;
@property (strong) IBOutlet NSTextField *gbsAuthor;
@property (strong) IBOutlet NSTextField *gbsCopyright;
@property (strong) IBOutlet NSPopUpButton *gbsTracks;
@property (strong) IBOutlet NSButton *gbsPlayPauseButton;
@property (strong) IBOutlet NSButton *gbsRewindButton;
@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton;
@property (strong) IBOutlet GBVisualizerView *gbsVisualizer;
@property (strong) IBOutlet GBOSDView *osdView;
-(uint8_t) readMemory:(uint16_t) addr; -(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
-(void) performAtomicBlock: (void (^)())block; -(void) performAtomicBlock: (void (^)())block;
-(void) connectLinkCable:(NSMenuItem *)sender;
-(int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound;
@end @end

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@
<customObject id="-2" userLabel="File's Owner" customClass="Document"> <customObject id="-2" userLabel="File's Owner" customClass="Document">
<connections> <connections>
<outlet property="TilemapSetButton" destination="k4c-Vg-MBu" id="Ak1-6d-B1N"/> <outlet property="TilemapSetButton" destination="k4c-Vg-MBu" id="Ak1-6d-B1N"/>
<outlet property="cheatController" destination="v7q-gT-jHT" id="yg2-23-kOX"/>
<outlet property="cheatWindowController" destination="v7q-gT-jHT" id="UNn-g4-2kP"/> <outlet property="cheatWindowController" destination="v7q-gT-jHT" id="UNn-g4-2kP"/>
<outlet property="cheatsWindow" destination="4Yb-Np-JrF" id="YCZ-cj-gn5"/> <outlet property="cheatsWindow" destination="4Yb-Np-JrF" id="YCZ-cj-gn5"/>
<outlet property="consoleInput" destination="l22-S8-uji" id="Heu-am-YgB"/> <outlet property="consoleInput" destination="l22-S8-uji" id="Heu-am-YgB"/>
@ -20,16 +19,16 @@
<outlet property="debuggerSplitView" destination="pUc-ZN-vl5" id="0sG-0D-cID"/> <outlet property="debuggerSplitView" destination="pUc-ZN-vl5" id="0sG-0D-cID"/>
<outlet property="debuggerVerticalLine" destination="7bR-gM-1At" id="rfy-7Z-388"/> <outlet property="debuggerVerticalLine" destination="7bR-gM-1At" id="rfy-7Z-388"/>
<outlet property="feedImageView" destination="Ar0-nN-eop" id="wHa-St-o4G"/> <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="gridButton" destination="fL6-2S-Rgd" id="jtV-jh-GHC"/>
<outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/> <outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/>
<outlet property="memoryBankInput" destination="rdV-q6-hc6" id="KBx-9T-2mX"/> <outlet property="memoryBankInput" destination="rdV-q6-hc6" id="KBx-9T-2mX"/>
<outlet property="memoryBankItem" destination="bWC-FW-IYP" id="Lf2-dh-z32"/> <outlet property="memoryBankItem" destination="bWC-FW-IYP" id="Lf2-dh-z32"/>
<outlet property="memoryView" destination="8hr-8o-3rN" id="fF0-rh-8ND"/> <outlet property="memoryView" destination="8hr-8o-3rN" id="fF0-rh-8ND"/>
<outlet property="memoryWindow" destination="mRm-dL-mCj" id="VPR-lu-vtI"/> <outlet property="memoryWindow" destination="mRm-dL-mCj" id="VPR-lu-vtI"/>
<outlet property="objectsTableView" destination="TOc-XJ-w9w" id="O4R-4Z-9hU"/>
<outlet property="osdView" destination="MX4-l2-7NE" id="Am7-fq-uvu"/>
<outlet property="paletteTableView" destination="gfC-d3-dmq" id="fTC-eL-Qg3"/> <outlet property="paletteTableView" destination="gfC-d3-dmq" id="fTC-eL-Qg3"/>
<outlet property="printerFeedWindow" destination="NdE-0B-WCf" id="yVK-cS-NOJ"/> <outlet property="printerFeedWindow" destination="NdE-0B-WCf" id="yVK-cS-NOJ"/>
<outlet property="spritesTableView" destination="TOc-XJ-w9w" id="O4R-4Z-9hU"/>
<outlet property="tilemapImageView" destination="LlK-tV-bjv" id="nSY-Xd-BjZ"/> <outlet property="tilemapImageView" destination="LlK-tV-bjv" id="nSY-Xd-BjZ"/>
<outlet property="tilemapMapButton" destination="YIJ-Qc-SIZ" id="BB7-Gg-7XP"/> <outlet property="tilemapMapButton" destination="YIJ-Qc-SIZ" id="BB7-Gg-7XP"/>
<outlet property="tilemapPaletteButton" destination="loB-0k-Qff" id="2Or-7l-6vn"/> <outlet property="tilemapPaletteButton" destination="loB-0k-Qff" id="2Or-7l-6vn"/>
@ -61,9 +60,16 @@
<view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView"> <view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/> <rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<connections>
<outlet property="document" destination="-2" id="Fvh-rD-z4r"/>
</connections>
</view> </view>
</subviews> </subviews>
</customView> </customView>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MX4-l2-7NE" customClass="GBOSDView">
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</customView>
</subviews> </subviews>
</view> </view>
<connections> <connections>
@ -117,7 +123,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S"> <clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/> <rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl"> <textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/> <rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
@ -154,7 +160,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC"> <clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC">
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/> <rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textView ambiguous="YES" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" spellingCorrection="YES" id="w0g-eK-jM4"> <textView ambiguous="YES" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" spellingCorrection="YES" id="w0g-eK-jM4">
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/> <rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
@ -188,7 +194,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg"> <clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg">
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/> <rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp"> <textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp">
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/> <rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
@ -245,9 +251,9 @@
<toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H"> <toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H">
<nil key="toolTip"/> <nil key="toolTip"/>
<size key="minSize" width="100" height="25"/> <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"> <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"/> <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"> <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"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -313,7 +319,7 @@
</window> </window>
<menuItem title="Cartridge RAM" id="ylM-ah-PNQ"/> <menuItem title="Cartridge RAM" id="ylM-ah-PNQ"/>
<window title="VRAM Viewer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="mbr-db-iZh" customClass="NSPanel"> <window title="VRAM Viewer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="mbr-db-iZh" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/> <windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="512" height="432"/> <rect key="contentRect" x="0.0" y="0.0" width="512" height="432"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/> <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
@ -496,7 +502,7 @@
</subviews> </subviews>
</view> </view>
</tabViewItem> </tabViewItem>
<tabViewItem label="Sprites" identifier="" id="a08-eg-Maw"> <tabViewItem label="Objects" identifier="" id="a08-eg-Maw">
<view key="view" id="EiO-p0-3xn"> <view key="view" id="EiO-p0-3xn">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/> <rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -508,7 +514,7 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/> <rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <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"/> <rect key="frame" x="0.0" y="0.0" width="512" height="391"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
@ -764,7 +770,7 @@
<segments> <segments>
<segment label="Tileset" selected="YES"/> <segment label="Tileset" selected="YES"/>
<segment label="Tilemap" tag="1"/> <segment label="Tilemap" tag="1"/>
<segment label="Sprites"/> <segment label="Objects"/>
<segment label="Palettes"/> <segment label="Palettes"/>
</segments> </segments>
</segmentedCell> </segmentedCell>
@ -787,9 +793,10 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="320" height="288"/> <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"/> <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"> <view key="contentView" id="RRS-aa-bPT">
<rect key="frame" x="0.0" y="0.0" width="320" height="288"/> <rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ar0-nN-eop" customClass="GBImageView"> <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"/> <rect key="frame" x="0.0" y="0.0" width="320" height="288"/>
@ -798,20 +805,25 @@
</imageView> </imageView>
</subviews> </subviews>
</view> </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"/> <point key="canvasLocation" x="-159" y="356"/>
</window> </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"> <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"/> <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="692" height="272"/> <rect key="contentRect" x="0.0" y="0.0" width="692" height="272"/>
@ -897,7 +909,7 @@
</connections> </connections>
</textField> </textField>
<button verticalHuggingPriority="750" id="C3V-Ep-bMj"> <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"/> <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"> <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"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -992,7 +1004,7 @@
</buttonCell> </buttonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn> </tableColumn>
<tableColumn width="50" minWidth="40" maxWidth="1000" id="9DZ-oW-Scx"> <tableColumn width="60" minWidth="40" maxWidth="1000" id="9DZ-oW-Scx">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Enabled"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Enabled">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -1017,7 +1029,7 @@
</textFieldCell> </textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn> </tableColumn>
<tableColumn editable="NO" width="144" minWidth="40" maxWidth="1000" id="ACq-gU-K36"> <tableColumn editable="NO" width="134" minWidth="40" maxWidth="1000" id="ACq-gU-K36">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Action"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Action">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -1070,6 +1082,7 @@
</customObject> </customObject>
</objects> </objects>
<resources> <resources>
<image name="NSFolder" width="32" height="32"/>
<image name="NSStopProgressFreestandingTemplate" width="14" height="14"/> <image name="NSStopProgressFreestandingTemplate" width="14" height="14"/>
</resources> </resources>
</document> </document>

View File

@ -2,9 +2,9 @@
#import <Core/gb.h> #import <Core/gb.h>
@interface GBAudioClient : NSObject @interface GBAudioClient : NSObject
@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer); @property (nonatomic, strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer);
@property (readonly) UInt32 rate; @property (nonatomic, readonly) UInt32 rate;
@property (readonly, getter=isPlaying) bool playing; @property (nonatomic, readonly, getter=isPlaying) bool playing;
-(void) start; -(void) start;
-(void) stop; -(void) stop;
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block -(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block

View File

@ -26,8 +26,7 @@ static OSStatus render(
-(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block -(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
andSampleRate:(UInt32) rate andSampleRate:(UInt32) rate
{ {
if(!(self = [super init])) if (!(self = [super init])) {
{
return nil; return nil;
} }
@ -91,7 +90,7 @@ static OSStatus render(
{ {
OSErr err = AudioOutputUnitStart(audioUnit); OSErr err = AudioOutputUnitStart(audioUnit);
NSAssert1(err == noErr, @"Error starting unit: %hd", err); NSAssert1(err == noErr, @"Error starting unit: %hd", err);
_playing = YES; _playing = true;
} }
@ -99,10 +98,11 @@ static OSStatus render(
-(void) stop -(void) stop
{ {
AudioOutputUnitStop(audioUnit); AudioOutputUnitStop(audioUnit);
_playing = NO; _playing = false;
} }
-(void) dealloc { -(void) dealloc
{
[self stop]; [self stop];
AudioUnitUninitialize(audioUnit); AudioUnitUninitialize(audioUnit);
AudioComponentInstanceDispose(audioUnit); AudioComponentInstanceDispose(audioUnit);

View File

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

View File

@ -19,6 +19,11 @@ typedef enum : NSUInteger {
extern NSString const *GBButtonNames[GBButtonCount]; extern NSString const *GBButtonNames[GBButtonCount];
static inline NSString *n2s(uint64_t number)
{
return [NSString stringWithFormat:@"%llx", number];
}
static inline NSString *button_to_preference_name(GBButton button, unsigned player) static inline NSString *button_to_preference_name(GBButton button, unsigned player)
{ {
if (player) { if (player) {

View File

@ -1,5 +1,5 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@interface GBCheatTextFieldCell : NSTextFieldCell @interface GBCheatTextFieldCell : NSTextFieldCell
@property bool usesAddressFormat; @property (nonatomic) bool usesAddressFormat;
@end @end

View File

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

View File

@ -3,15 +3,14 @@
#import "Document.h" #import "Document.h"
@interface GBCheatWindowController : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate> @interface GBCheatWindowController : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate>
@property (weak) IBOutlet NSTableView *cheatsTable; @property (nonatomic, weak) IBOutlet NSTableView *cheatsTable;
@property (weak) IBOutlet NSTextField *addressField; @property (nonatomic, weak) IBOutlet NSTextField *addressField;
@property (weak) IBOutlet NSTextField *valueField; @property (nonatomic, weak) IBOutlet NSTextField *valueField;
@property (weak) IBOutlet NSTextField *oldValueField; @property (nonatomic, weak) IBOutlet NSTextField *oldValueField;
@property (weak) IBOutlet NSButton *oldValueCheckbox; @property (nonatomic, weak) IBOutlet NSButton *oldValueCheckbox;
@property (weak) IBOutlet NSTextField *descriptionField; @property (nonatomic, weak) IBOutlet NSTextField *descriptionField;
@property (weak) IBOutlet NSTextField *importCodeField; @property (nonatomic, weak) IBOutlet NSTextField *importCodeField;
@property (weak) IBOutlet NSTextField *importDescriptionField; @property (nonatomic, weak) IBOutlet NSTextField *importDescriptionField;
@property (weak) IBOutlet Document *document; @property (nonatomic, weak) IBOutlet Document *document;
- (void)cheatsUpdated;
@end @end

View File

@ -52,7 +52,7 @@
if (row >= cheatCount) { if (row >= cheatCount) {
switch (columnIndex) { switch (columnIndex) {
case 0: case 0:
return @(YES); return @YES;
case 1: case 1:
return @NO; return @NO;
@ -67,7 +67,7 @@
switch (columnIndex) { switch (columnIndex) {
case 0: case 0:
return @(NO); return @NO;
case 1: case 1:
return @(cheats[row]->enabled); return @(cheats[row]->enabled);
@ -231,4 +231,10 @@
[self.cheatsTable reloadData]; [self.cheatsTable reloadData];
} }
- (void)cheatsUpdated
{
[self.cheatsTable reloadData];
[self tableViewSelectionDidChange:nil];
}
@end @end

View File

@ -14,7 +14,13 @@ static inline double scale_channel(uint8_t x)
- (void)setObjectValue:(id)objectValue - (void)setObjectValue:(id)objectValue
{ {
_integerValue = [objectValue integerValue]; _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],
NSFontAttributeName: [NSFont userFixedPitchFontOfSize:12]
}];
} }
- (NSInteger)integerValue - (NSInteger)integerValue
@ -30,13 +36,14 @@ static inline double scale_channel(uint8_t x)
- (NSColor *) backgroundColor - (NSColor *) backgroundColor
{ {
/* Todo: color correction */
uint16_t color = self.integerValue; uint16_t color = self.integerValue;
return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0]; return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0];
} }
- (BOOL)drawsBackground - (BOOL)drawsBackground
{ {
return YES; return true;
} }
@end @end

9
Cocoa/GBHueSliderCell.h Normal file
View File

@ -0,0 +1,9 @@
#import <Cocoa/Cocoa.h>
@interface NSSlider (GBHueSlider)
-(NSColor *)colorValue;
@end
@interface GBHueSliderCell : NSSliderCell
-(NSColor *)colorValue;
@end

113
Cocoa/GBHueSliderCell.m Normal file
View File

@ -0,0 +1,113 @@
#import "GBHueSliderCell.h"
@interface NSSliderCell(privateAPI)
- (double)_normalizedDoubleValue;
@end
@implementation GBHueSliderCell
{
bool _drawingTrack;
}
-(NSColor *)colorValue
{
double hue = self.doubleValue / 360.0;
double r = 0, g = 0, b =0 ;
double t = fmod(hue * 6, 1);
switch ((int)(hue * 6) % 6) {
case 0:
r = 1;
g = t;
break;
case 1:
r = 1 - t;
g = 1;
break;
case 2:
g = 1;
b = t;
break;
case 3:
g = 1 - t;
b = 1;
break;
case 4:
b = 1;
r = t;
break;
case 5:
b = 1 - t;
r = 1;
break;
}
return [NSColor colorWithRed:r green:g blue:b alpha:1.0];
}
-(void)drawKnob:(NSRect)knobRect
{
[super drawKnob:knobRect];
NSRect peekRect = knobRect;
peekRect.size.width /= 2;
peekRect.size.height = peekRect.size.width;
peekRect.origin.x += peekRect.size.width / 2;
peekRect.origin.y += peekRect.size.height / 2;
NSColor *color = self.colorValue;
if (!self.enabled) {
color = [color colorWithAlphaComponent:0.5];
}
[color setFill];
NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:peekRect];
[path fill];
[[NSColor colorWithWhite:0 alpha:0.25] setStroke];
[path setLineWidth:0.5];
[path stroke];
}
-(double)_normalizedDoubleValue
{
if (_drawingTrack) return 0;
return [super _normalizedDoubleValue];
}
-(void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped
{
if (!self.enabled) {
[super drawBarInside:rect flipped:flipped];
return;
}
_drawingTrack = true;
[super drawBarInside:rect flipped:flipped];
_drawingTrack = false;
NSGradient *gradient = [[NSGradient alloc] initWithColors:@[
[NSColor redColor],
[NSColor yellowColor],
[NSColor greenColor],
[NSColor cyanColor],
[NSColor blueColor],
[NSColor magentaColor],
[NSColor redColor],
]];
rect.origin.y += rect.size.height / 2 - 0.5;
rect.size.height = 1;
rect.size.width -= 2;
rect.origin.x += 1;
[[NSColor redColor] set];
NSRectFill(rect);
rect.size.width -= self.knobThickness + 2;
rect.origin.x += self.knobThickness / 2 - 1;
[gradient drawInRect:rect angle:0];
}
@end
@implementation NSSlider (GBHueSlider)
- (NSColor *)colorValue
{
return ((GBHueSliderCell *)self.cell).colorValue;
}
@end

View File

@ -3,17 +3,17 @@
@protocol GBImageViewDelegate; @protocol GBImageViewDelegate;
@interface GBImageViewGridConfiguration : NSObject @interface GBImageViewGridConfiguration : NSObject
@property NSColor *color; @property (nonatomic, strong) NSColor *color;
@property NSUInteger size; @property (nonatomic) NSUInteger size;
- (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size; - (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size;
@end @end
@interface GBImageView : NSImageView @interface GBImageView : NSImageView
@property (nonatomic) NSArray *horizontalGrids; @property (nonatomic, strong) NSArray<GBImageViewGridConfiguration *> *horizontalGrids;
@property (nonatomic) NSArray *verticalGrids; @property (nonatomic, strong) NSArray<GBImageViewGridConfiguration *> *verticalGrids;
@property (nonatomic) bool displayScrollRect; @property (nonatomic) bool displayScrollRect;
@property NSRect scrollRect; @property NSRect scrollRect;
@property (weak) IBOutlet id<GBImageViewDelegate> delegate; @property (nonatomic, weak) IBOutlet id<GBImageViewDelegate> delegate;
@end @end
@protocol GBImageViewDelegate <NSObject> @protocol GBImageViewDelegate <NSObject>

View File

@ -1,9 +0,0 @@
#import <AppKit/AppKit.h>
@protocol GBJoystickListener <NSObject>
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state;
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value;
- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) value;
@end

View File

@ -12,6 +12,6 @@ typedef enum {
@interface GBMemoryByteArray : HFByteArray @interface GBMemoryByteArray : HFByteArray
- (instancetype) initWithDocument:(Document *)document; - (instancetype) initWithDocument:(Document *)document;
@property uint16_t selectedBank; @property (nonatomic) uint16_t selectedBank;
@property GB_memory_mode_t mode; @property (nonatomic) GB_memory_mode_t mode;
@end @end

6
Cocoa/GBOSDView.h Normal file
View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
@interface GBOSDView : NSView
@property bool usesSGBScale;
- (void)displayText:(NSString *)text;
@end

104
Cocoa/GBOSDView.m Normal file
View File

@ -0,0 +1,104 @@
#import "GBOSDView.h"
@implementation GBOSDView
{
bool _usesSGBScale;
NSString *_text;
double _animation;
NSTimer *_timer;
}
- (void)setUsesSGBScale:(bool)usesSGBScale
{
_usesSGBScale = usesSGBScale;
[self setNeedsDisplay:true];
}
- (bool)usesSGBScale
{
return _usesSGBScale;
}
- (void)displayText:(NSString *)text
{
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]) return;
dispatch_async(dispatch_get_main_queue(), ^{
if (![_text isEqualToString:text]) {
[self setNeedsDisplay:true];
}
_text = text;
self.alphaValue = 1.0;
_animation = 2.5;
// Longer strings should appear longer
if ([_text rangeOfString:@"\n"].location != NSNotFound) {
_animation += 4;
}
[_timer invalidate];
self.hidden = false;
_timer = [NSTimer scheduledTimerWithTimeInterval:0.025 target:self selector:@selector(animate) userInfo:nil repeats:true];
});
}
- (void)animate
{
_animation -= 0.1;
if (_animation < 1.0) {
self.alphaValue = _animation;
};
if (_animation == 0) {
self.hidden = true;
[_timer invalidate];
_text = nil;
}
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
if (!_text.length) return;
double fontSize = 8;
NSSize size = self.frame.size;
if (_usesSGBScale) {
fontSize *= MIN(size.width / 256, size.height / 224);
}
else {
fontSize *= MIN(size.width / 160, size.height / 144);
}
NSFont *font = [NSFont boldSystemFontOfSize:fontSize];
/* The built in stroke attribute uses an inside stroke, which is typographically terrible.
We'll use a naïve manual stroke instead which looks better. */
NSDictionary *attributes = @{
NSFontAttributeName: font,
NSForegroundColorAttributeName: [NSColor blackColor],
};
NSAttributedString *text = [[NSAttributedString alloc] initWithString:_text attributes:attributes];
[text drawAtPoint:NSMakePoint(fontSize + 1, fontSize + 0)];
[text drawAtPoint:NSMakePoint(fontSize - 1, fontSize + 0)];
[text drawAtPoint:NSMakePoint(fontSize + 0, fontSize + 1)];
[text drawAtPoint:NSMakePoint(fontSize + 0, fontSize - 1)];
// The uses of sqrt(2)/2, which is more correct, results in severe ugly-looking rounding errors
if (self.window.screen.backingScaleFactor > 1) {
[text drawAtPoint:NSMakePoint(fontSize + 0.5, fontSize + 0.5)];
[text drawAtPoint:NSMakePoint(fontSize - 0.5, fontSize + 0.5)];
[text drawAtPoint:NSMakePoint(fontSize - 0.5, fontSize - 0.5)];
[text drawAtPoint:NSMakePoint(fontSize + 0.5, fontSize - 0.5)];
}
attributes = @{
NSFontAttributeName: font,
NSForegroundColorAttributeName: [NSColor whiteColor],
};
text = [[NSAttributedString alloc] initWithString:_text attributes:attributes];
[text drawAtPoint:NSMakePoint(fontSize, fontSize)];
}
@end

View File

@ -2,5 +2,5 @@
#import "GBGLShader.h" #import "GBGLShader.h"
@interface GBOpenGLView : NSOpenGLView @interface GBOpenGLView : NSOpenGLView
@property GBGLShader *shader; @property (nonatomic) GBGLShader *shader;
@end @end

View File

@ -4,7 +4,8 @@
@implementation GBOpenGLView @implementation GBOpenGLView
- (void)drawRect:(NSRect)dirtyRect { - (void)drawRect:(NSRect)dirtyRect
{
if (!self.shader) { if (!self.shader) {
self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]]; self.shader = [[GBGLShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]];
} }
@ -33,6 +34,6 @@
- (void) filterChanged - (void) filterChanged
{ {
self.shader = nil; self.shader = nil;
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
} }
@end @end

View File

@ -0,0 +1,18 @@
#import <AppKit/AppKit.h>
#import <Core/gb.h>
@interface GBPaletteEditorController : NSObject<NSTableViewDataSource, NSTableViewDelegate>
@property (weak) IBOutlet NSColorWell *colorWell0;
@property (weak) IBOutlet NSColorWell *colorWell1;
@property (weak) IBOutlet NSColorWell *colorWell2;
@property (weak) IBOutlet NSColorWell *colorWell3;
@property (weak) IBOutlet NSColorWell *colorWell4;
@property (weak) IBOutlet NSButton *disableLCDColorCheckbox;
@property (weak) IBOutlet NSButton *manualModeCheckbox;
@property (weak) IBOutlet NSSlider *brightnessSlider;
@property (weak) IBOutlet NSSlider *hueSlider;
@property (weak) IBOutlet NSSlider *hueStrengthSlider;
@property (weak) IBOutlet NSTableView *themesList;
@property (weak) IBOutlet NSMenu *menu;
+ (const GB_palette_t *)userPalette;
@end

View File

@ -0,0 +1,378 @@
#import "GBPaletteEditorController.h"
#import "GBHueSliderCell.h"
#import <Core/gb.h>
#define MAGIC 'SBPL'
typedef struct __attribute__ ((packed)) {
uint32_t magic;
bool manual:1;
bool disabled_lcd_color:1;
unsigned padding:6;
struct GB_color_s colors[5];
int32_t brightness_bias;
uint32_t hue_bias;
uint32_t hue_bias_strength;
} theme_t;
static double blend(double from, double to, double position)
{
return from * (1 - position) + to * position;
}
@implementation NSColor (GBColor)
- (struct GB_color_s)gbColor
{
NSColor *sRGB = [self colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
return (struct GB_color_s){round(sRGB.redComponent * 255), round(sRGB.greenComponent * 255), round(sRGB.blueComponent * 255)};
}
- (uint32_t)intValue
{
struct GB_color_s color = self.gbColor;
return (color.r << 0) | (color.g << 8) | (color.b << 16) | 0xFF000000;
}
@end
@implementation GBPaletteEditorController
- (NSArray<NSColorWell *> *)colorWells
{
return @[_colorWell0, _colorWell1, _colorWell2, _colorWell3, _colorWell4];
}
- (void)updateEnabledControls
{
if (self.manualModeCheckbox.state) {
_brightnessSlider.enabled = false;
_hueSlider.enabled = false;
_hueStrengthSlider.enabled = false;
_colorWell1.enabled = true;
_colorWell2.enabled = true;
_colorWell3.enabled = true;
if (!(_colorWell4.enabled = self.disableLCDColorCheckbox.state)) {
_colorWell4.color = _colorWell3.color;
}
}
else {
_colorWell1.enabled = false;
_colorWell2.enabled = false;
_colorWell3.enabled = false;
_colorWell4.enabled = true;
_brightnessSlider.enabled = true;
_hueSlider.enabled = true;
_hueStrengthSlider.enabled = true;
[self updateAutoColors];
}
}
- (NSColor *)autoColorAtPositon:(double)position
{
NSColor *first = [_colorWell0.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
NSColor *second = [_colorWell4.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
double brightness = 1 / pow(4, (_brightnessSlider.doubleValue - 128) / 128.0);
position = pow(position, brightness);
NSColor *hue = _hueSlider.colorValue;
double bias = _hueStrengthSlider.doubleValue / 256.0;
double red = 1 / pow(4, (hue.redComponent * 2 - 1) * bias);
double green = 1 / pow(4, (hue.greenComponent * 2 - 1) * bias);
double blue = 1 / pow(4, (hue.blueComponent * 2 - 1) * bias);
NSColor *ret = [NSColor colorWithRed:blend(first.redComponent, second.redComponent, pow(position, red))
green:blend(first.greenComponent, second.greenComponent, pow(position, green))
blue:blend(first.blueComponent, second.blueComponent, pow(position, blue))
alpha:1.0];
return ret;
}
- (IBAction)updateAutoColors:(id)sender
{
if (!self.manualModeCheckbox.state) {
[self updateAutoColors];
}
else {
[self savePalette:sender];
}
}
- (void)updateAutoColors
{
if (_disableLCDColorCheckbox.state) {
_colorWell1.color = [self autoColorAtPositon:8 / 25.0];
_colorWell2.color = [self autoColorAtPositon:16 / 25.0];
_colorWell3.color = [self autoColorAtPositon:24 / 25.0];
}
else {
_colorWell1.color = [self autoColorAtPositon:1 / 3.0];
_colorWell2.color = [self autoColorAtPositon:2 / 3.0];
_colorWell3.color = _colorWell4.color;
}
[self savePalette:nil];
}
- (IBAction)disabledLCDColorCheckboxChanged:(id)sender
{
[self updateEnabledControls];
}
- (IBAction)manualModeChanged:(id)sender
{
[self updateEnabledControls];
}
- (IBAction)updateColor4:(id)sender
{
if (!self.disableLCDColorCheckbox.state) {
self.colorWell4.color = self.colorWell3.color;
}
[self savePalette:self];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
if (themes.count == 0) {
[defaults setObject:@"Untitled Palette" forKey:@"GBCurrentTheme"];
[self savePalette:nil];
return 1;
}
return themes.count;
}
-(void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *oldName = [self tableView:tableView objectValueForTableColumn:tableColumn row:row];
if ([oldName isEqualToString:object]) {
return;
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
NSString *newName = object;
unsigned i = 2;
if (!newName.length) {
newName = @"Untitled Palette";
}
while (themes[newName]) {
newName = [NSString stringWithFormat:@"%@ %d", object, i];
}
themes[newName] = themes[oldName];
[themes removeObjectForKey:oldName];
if ([oldName isEqualToString:[defaults stringForKey:@"GBCurrentTheme"]]) {
[defaults setObject:newName forKey:@"GBCurrentTheme"];
}
[defaults setObject:themes forKey:@"GBThemes"];
[tableView reloadData];
[self awakeFromNib];
}
- (IBAction)deleteTheme:(id)sender
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *name = [defaults stringForKey:@"GBCurrentTheme"];
NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
[themes removeObjectForKey:name];
[defaults setObject:themes forKey:@"GBThemes"];
[_themesList reloadData];
[self awakeFromNib];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSString *name = [self tableView:nil objectValueForTableColumn:nil row:_themesList.selectedRow];
[[NSUserDefaults standardUserDefaults] setObject:name forKey:@"GBCurrentTheme"];
[self loadPalette];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
}
- (void)tableViewSelectionIsChanging:(NSNotification *)notification
{
[self tableViewSelectionDidChange:notification];
}
- (void)awakeFromNib
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
NSString *theme = [defaults stringForKey:@"GBCurrentTheme"];
if (theme && themes[theme]) {
unsigned index = [[themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] indexOfObject:theme];
[_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:false];
}
else {
[_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:false];
}
[self tableViewSelectionDidChange:nil];
}
- (IBAction)addTheme:(id)sender
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
NSString *newName = @"Untitled Palette";
unsigned i = 2;
while (themes[newName]) {
newName = [NSString stringWithFormat:@"Untitled Palette %d", i++];
}
[defaults setObject:newName forKey:@"GBCurrentTheme"];
[self savePalette:sender];
[_themesList reloadData];
[self awakeFromNib];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
return [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)][row];
}
- (void)loadPalette
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *theme = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]];
NSArray *colors = theme[@"Colors"];
if (colors.count == 5) {
unsigned i = 0;
for (NSNumber *color in colors) {
uint32_t c = [color unsignedIntValue];
self.colorWells[i++].color = [NSColor colorWithRed:(c & 0xFF) / 255.0
green:((c >> 8) & 0xFF) / 255.0
blue:((c >> 16) & 0xFF) / 255.0
alpha:1.0];
}
}
_disableLCDColorCheckbox.state = [theme[@"DisabledLCDColor"] boolValue];
_manualModeCheckbox.state = [theme[@"Manual"] boolValue];
_brightnessSlider.doubleValue = [theme[@"BrightnessBias"] doubleValue] * 128 + 128;
_hueSlider.doubleValue = [theme[@"HueBias"] doubleValue] * 360;
_hueStrengthSlider.doubleValue = [theme[@"HueBiasStrength"] doubleValue] * 256;
[self updateEnabledControls];
}
- (IBAction)savePalette:(id)sender
{
NSDictionary *theme = @{
@"Colors":
@[@(_colorWell0.color.intValue),
@(_colorWell1.color.intValue),
@(_colorWell2.color.intValue),
@(_colorWell3.color.intValue),
@(_colorWell4.color.intValue)],
@"DisabledLCDColor": _disableLCDColorCheckbox.state? @YES : @NO,
@"Manual": _manualModeCheckbox.state? @YES : @NO,
@"BrightnessBias": @((_brightnessSlider.doubleValue - 128) / 128.0),
@"HueBias": @(_hueSlider.doubleValue / 360.0),
@"HueBiasStrength": @(_hueStrengthSlider.doubleValue / 256.0)
};
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
themes[[defaults stringForKey:@"GBCurrentTheme"]] = theme;
[defaults setObject:themes forKey:@"GBThemes"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
}
+ (const GB_palette_t *)userPalette
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
switch ([defaults integerForKey:@"GBColorPalette"]) {
case 1: return &GB_PALETTE_DMG;
case 2: return &GB_PALETTE_MGB;
case 3: return &GB_PALETTE_GBL;
default: return &GB_PALETTE_GREY;
case -1: {
static GB_palette_t customPalette;
NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]][@"Colors"];
if (colors.count == 5) {
unsigned i = 0;
for (NSNumber *color in colors) {
uint32_t c = [color unsignedIntValue];
customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16};
}
}
return &customPalette;
}
}
}
- (IBAction)export:(id)sender
{
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel setAllowedFileTypes:@[@"sbp"]];
savePanel.nameFieldStringValue = [NSString stringWithFormat:@"%@.sbp", [[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"]];
if ([savePanel runModal] == NSModalResponseOK) {
theme_t theme = {0,};
theme.magic = MAGIC;
theme.manual = _manualModeCheckbox.state;
theme.disabled_lcd_color = _disableLCDColorCheckbox.state;
unsigned i = 0;
for (NSColorWell *well in self.colorWells) {
theme.colors[i++] = well.color.gbColor;
}
theme.brightness_bias = (_brightnessSlider.doubleValue - 128) * (0x40000000 / 128);
theme.hue_bias = round(_hueSlider.doubleValue * (0x80000000 / 360.0));
theme.hue_bias_strength = (_hueStrengthSlider.doubleValue) * (0x80000000 / 256);
size_t size = sizeof(theme);
if (theme.manual) {
size = theme.disabled_lcd_color? 5 + 5 * sizeof(theme.colors[0]) : 5 + 4 * sizeof(theme.colors[0]);
}
[[NSData dataWithBytes:&theme length:size] writeToURL:savePanel.URL atomically:false];
}
}
- (IBAction)import:(id)sender
{
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setAllowedFileTypes:@[@"sbp"]];
if ([openPanel runModal] == NSModalResponseOK) {
NSData *data = [NSData dataWithContentsOfURL:openPanel.URL];
theme_t theme = {0,};
memcpy(&theme, data.bytes, MIN(sizeof(theme), data.length));
if (theme.magic != MAGIC) {
NSBeep();
return;
}
_manualModeCheckbox.state = theme.manual;
_disableLCDColorCheckbox.state = theme.disabled_lcd_color;
unsigned i = 0;
for (NSColorWell *well in self.colorWells) {
well.color = [NSColor colorWithRed:theme.colors[i].r / 255.0
green:theme.colors[i].g / 255.0
blue:theme.colors[i].b / 255.0
alpha:1.0];
i++;
}
if (!theme.disabled_lcd_color) {
_colorWell4.color = _colorWell3.color;
}
_brightnessSlider.doubleValue = theme.brightness_bias / (0x40000000 / 128.0) + 128;
_hueSlider.doubleValue = theme.hue_bias / (0x80000000 / 360.0);
_hueStrengthSlider.doubleValue = theme.hue_bias_strength / (0x80000000 / 256.0);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
NSString *baseName = openPanel.URL.lastPathComponent.stringByDeletingPathExtension;
NSString *newName = baseName;
i = 2;
while (themes[newName]) {
newName = [NSString stringWithFormat:@"%@ %d", baseName, i++];
}
[defaults setObject:newName forKey:@"GBCurrentTheme"];
[self savePalette:sender];
[self awakeFromNib];
}
}
- (IBAction)done:(NSButton *)sender
{
[sender.window.sheetParent endSheet:sender.window];
}
- (instancetype)init
{
static id singleton = nil;
if (singleton) return singleton;
return (singleton = [super init]);
}
@end

View File

@ -1,25 +1,37 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "GBJoystickListener.h" #import <JoyKit/JoyKit.h>
#import "GBPaletteEditorController.h"
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, GBJoystickListener>
@property IBOutlet NSTableView *controlsTableView;
@property IBOutlet NSPopUpButton *graphicsFilterPopupButton;
@property (strong) IBOutlet NSButton *aspectRatioCheckbox;
@property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
@property (strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton;
@property (strong) IBOutlet NSPopUpButton *colorPalettePopupButton;
@property (strong) IBOutlet NSPopUpButton *displayBorderPopupButton;
@property (strong) IBOutlet NSPopUpButton *rewindPopupButton;
@property (strong) IBOutlet NSButton *configureJoypadButton;
@property (strong) IBOutlet NSButton *skipButton;
@property (strong) IBOutlet NSMenuItem *bootROMsFolderItem;
@property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton;
@property (weak) IBOutlet NSPopUpButton *dmgPopupButton;
@property (weak) IBOutlet NSPopUpButton *sgbPopupButton;
@property (weak) IBOutlet NSPopUpButton *cgbPopupButton;
@property (weak) IBOutlet NSPopUpButton *preferredJoypadButton;
@property (weak) IBOutlet NSPopUpButton *playerListButton;
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, JOYListener>
@property (nonatomic, strong) IBOutlet NSTableView *controlsTableView;
@property (nonatomic, strong) IBOutlet NSPopUpButton *graphicsFilterPopupButton;
@property (nonatomic, strong) IBOutlet NSButton *analogControlsCheckbox;
@property (nonatomic, strong) IBOutlet NSButton *aspectRatioCheckbox;
@property (nonatomic, strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *displayBorderPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *rewindPopupButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *rtcPopupButton;
@property (nonatomic, strong) IBOutlet NSButton *configureJoypadButton;
@property (nonatomic, strong) IBOutlet NSButton *skipButton;
@property (nonatomic, strong) IBOutlet NSMenuItem *bootROMsFolderItem;
@property (nonatomic, strong) IBOutlet NSPopUpButtonCell *bootROMsButton;
@property (nonatomic, strong) IBOutlet NSPopUpButton *rumbleModePopupButton;
@property (nonatomic, weak) IBOutlet NSSlider *temperatureSlider;
@property (nonatomic, weak) IBOutlet NSSlider *interferenceSlider;
@property (nonatomic, weak) IBOutlet NSPopUpButton *dmgPopupButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *sgbPopupButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *cgbPopupButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton;
@property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton;
@property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox;
@property (weak) IBOutlet NSSlider *volumeSlider;
@property (weak) IBOutlet NSButton *OSDCheckbox;
@property (weak) IBOutlet NSButton *screenshotFilterCheckbox;
@property (weak) IBOutlet GBPaletteEditorController *paletteEditorController;
@property (strong) IBOutlet NSWindow *paletteEditor;
@property (weak) IBOutlet NSButton *joystickMBC7Checkbox;
@property (weak) IBOutlet NSButton *mouseMBC7Checkbox;
@end @end

View File

@ -1,6 +1,8 @@
#import "GBPreferencesWindow.h" #import "GBPreferencesWindow.h"
#import "NSString+StringForKey.h" #import "NSString+StringForKey.h"
#import "GBButtons.h" #import "GBButtons.h"
#import "BigSurToolbar.h"
#import "GBViewMetal.h"
#import <Carbon/Carbon.h> #import <Carbon/Carbon.h>
@implementation GBPreferencesWindow @implementation GBPreferencesWindow
@ -9,7 +11,7 @@
NSInteger button_being_modified; NSInteger button_being_modified;
signed joystick_configuration_state; signed joystick_configuration_state;
NSString *joystick_being_configured; NSString *joystick_being_configured;
signed last_axis; bool joypad_wait;
NSPopUpButton *_graphicsFilterPopupButton; NSPopUpButton *_graphicsFilterPopupButton;
NSPopUpButton *_highpassFilterPopupButton; NSPopUpButton *_highpassFilterPopupButton;
@ -18,11 +20,22 @@
NSPopUpButton *_colorPalettePopupButton; NSPopUpButton *_colorPalettePopupButton;
NSPopUpButton *_displayBorderPopupButton; NSPopUpButton *_displayBorderPopupButton;
NSPopUpButton *_rewindPopupButton; NSPopUpButton *_rewindPopupButton;
NSPopUpButton *_rtcPopupButton;
NSButton *_aspectRatioCheckbox; NSButton *_aspectRatioCheckbox;
NSButton *_analogControlsCheckbox;
NSEventModifierFlags previousModifiers; NSEventModifierFlags previousModifiers;
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton; NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
NSPopUpButton *_preferredJoypadButton; NSPopUpButton *_preferredJoypadButton;
NSPopUpButton *_rumbleModePopupButton;
NSSlider *_temperatureSlider;
NSSlider *_interferenceSlider;
NSSlider *_volumeSlider;
NSButton *_autoUpdatesCheckbox;
NSButton *_OSDCheckbox;
NSButton *_screenshotFilterCheckbox;
NSButton *_joystickMBC7Checkbox;
NSButton *_mouseMBC7Checkbox;
} }
+ (NSArray *)filterList + (NSArray *)filterList
@ -50,12 +63,17 @@
return filters; return filters;
} }
- (NSWindowToolbarStyle)toolbarStyle
{
return NSWindowToolbarStylePreference;
}
- (void)close - (void)close
{ {
joystick_configuration_state = -1; joystick_configuration_state = -1;
[self.configureJoypadButton setEnabled:YES]; [self.configureJoypadButton setEnabled:true];
[self.skipButton setEnabled:NO]; [self.skipButton setEnabled:false];
[self.configureJoypadButton setTitle:@"Configure Joypad"]; [self.configureJoypadButton setTitle:@"Configure Controller"];
[super close]; [super close];
} }
@ -83,11 +101,45 @@
[_colorCorrectionPopupButton selectItemAtIndex:mode]; [_colorCorrectionPopupButton selectItemAtIndex:mode];
} }
- (NSPopUpButton *)colorCorrectionPopupButton - (NSPopUpButton *)colorCorrectionPopupButton
{ {
return _colorCorrectionPopupButton; return _colorCorrectionPopupButton;
} }
- (void)setTemperatureSlider:(NSSlider *)temperatureSlider
{
_temperatureSlider = temperatureSlider;
[temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256];
}
- (NSSlider *)temperatureSlider
{
return _temperatureSlider;
}
- (void)setInterferenceSlider:(NSSlider *)interferenceSlider
{
_interferenceSlider = interferenceSlider;
[interferenceSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"] * 256];
}
- (NSSlider *)interferenceSlider
{
return _interferenceSlider;
}
- (void)setVolumeSlider:(NSSlider *)volumeSlider
{
_volumeSlider = volumeSlider;
[volumeSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] * 256];
}
- (NSSlider *)volumeSlider
{
return _volumeSlider;
}
- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton - (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton
{ {
_frameBlendingModePopupButton = frameBlendingModePopupButton; _frameBlendingModePopupButton = frameBlendingModePopupButton;
@ -103,8 +155,14 @@
- (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton - (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton
{ {
_colorPalettePopupButton = colorPalettePopupButton; _colorPalettePopupButton = colorPalettePopupButton;
[self updatePalettesMenu];
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]; NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"];
[_colorPalettePopupButton selectItemAtIndex:mode]; if (mode >= 0) {
[_colorPalettePopupButton selectItemWithTag:mode];
}
else {
[_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""];
}
} }
- (NSPopUpButton *)colorPalettePopupButton - (NSPopUpButton *)colorPalettePopupButton
@ -124,6 +182,18 @@
return _displayBorderPopupButton; return _displayBorderPopupButton;
} }
- (void)setRumbleModePopupButton:(NSPopUpButton *)rumbleModePopupButton
{
_rumbleModePopupButton = rumbleModePopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"];
[_rumbleModePopupButton selectItemWithTag:mode];
}
- (NSPopUpButton *)rumbleModePopupButton
{
return _rumbleModePopupButton;
}
- (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton - (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton
{ {
_rewindPopupButton = rewindPopupButton; _rewindPopupButton = rewindPopupButton;
@ -136,6 +206,18 @@
return _rewindPopupButton; return _rewindPopupButton;
} }
- (NSPopUpButton *)rtcPopupButton
{
return _rtcPopupButton;
}
- (void)setRtcPopupButton:(NSPopUpButton *)rtcPopupButton
{
_rtcPopupButton = rtcPopupButton;
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"];
[_rtcPopupButton selectItemAtIndex:mode];
}
- (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton - (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton
{ {
_highpassFilterPopupButton = highpassFilterPopupButton; _highpassFilterPopupButton = highpassFilterPopupButton;
@ -150,6 +232,20 @@
return GBGameBoyButtonCount; 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 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{ {
if ([tableColumn.identifier isEqualToString:@"keyName"]) { if ([tableColumn.identifier isEqualToString:@"keyName"]) {
@ -162,6 +258,12 @@
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)]; NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
if (key) { 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]]; return [NSString displayStringForKeyCode: [key unsignedIntegerValue]];
} }
@ -174,12 +276,12 @@
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
is_button_being_modified = true; is_button_being_modified = true;
button_being_modified = row; button_being_modified = row;
tableView.enabled = NO; tableView.enabled = false;
self.playerListButton.enabled = NO; self.playerListButton.enabled = false;
[tableView reloadData]; [tableView reloadData];
[self makeFirstResponder:self]; [self makeFirstResponder:self];
}); });
return NO; return false;
} }
-(void)keyDown:(NSEvent *)theEvent -(void)keyDown:(NSEvent *)theEvent
@ -195,8 +297,8 @@
[[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode [[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode
forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)]; forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)];
self.controlsTableView.enabled = YES; self.controlsTableView.enabled = true;
self.playerListButton.enabled = YES; self.playerListButton.enabled = true;
[self.controlsTableView reloadData]; [self.controlsTableView reloadData];
[self makeFirstResponder:self.controlsTableView]; [self makeFirstResponder:self.controlsTableView];
} }
@ -224,6 +326,25 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil];
} }
- (IBAction)changeMBC7JoystickOverride:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
forKey:@"GBMBC7JoystickOverride"];
}
- (IBAction)changeMBC7AllowMouse:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
forKey:@"GBMBC7AllowMouse"];
}
- (IBAction)changeAnalogControls:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
forKey:@"GBAnalogControls"];
}
- (IBAction)changeAspectRatio:(id)sender - (IBAction)changeAspectRatio:(id)sender
{ {
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState
@ -238,6 +359,27 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil];
} }
- (IBAction)lightTemperatureChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBLightTemperature"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil];
}
- (IBAction)interferenceVolumeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBInterferenceVolume"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil];
}
- (IBAction)volumeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
forKey:@"GBVolume"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBVolumeChanged" object:nil];
}
- (IBAction)franeBlendingModeChanged:(id)sender - (IBAction)franeBlendingModeChanged:(id)sender
{ {
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
@ -246,10 +388,51 @@
} }
- (void)updatePalettesMenu
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
NSMenu *menu = _colorPalettePopupButton.menu;
while (menu.itemArray.count != 4) {
[menu removeItemAtIndex:4];
}
[menu addItem:[NSMenuItem separatorItem]];
for (NSString *name in [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]) {
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:name action:nil keyEquivalent:@""];
item.tag = -2;
[menu addItem:item];
}
if (themes) {
[menu addItem:[NSMenuItem separatorItem]];
}
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Custom…" action:nil keyEquivalent:@""];
item.tag = -1;
[menu addItem:item];
}
- (IBAction)colorPaletteChanged:(id)sender - (IBAction)colorPaletteChanged:(id)sender
{ {
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) signed tag = [sender selectedItem].tag;
if (tag == -2) {
[[NSUserDefaults standardUserDefaults] setObject:@(-1)
forKey:@"GBColorPalette"]; forKey:@"GBColorPalette"];
[[NSUserDefaults standardUserDefaults] setObject:[sender selectedItem].title
forKey:@"GBCurrentTheme"];
}
else if (tag == -1) {
[[NSUserDefaults standardUserDefaults] setObject:@(-1)
forKey:@"GBColorPalette"];
[_paletteEditorController awakeFromNib];
[self beginSheet:_paletteEditor completionHandler:^(NSModalResponse returnCode) {
[self updatePalettesMenu];
[_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""];
}];
}
else {
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
forKey:@"GBColorPalette"];
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
} }
@ -260,6 +443,13 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil];
} }
- (IBAction)rumbleModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
forKey:@"GBRumbleMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil];
}
- (IBAction)rewindLengthChanged:(id)sender - (IBAction)rewindLengthChanged:(id)sender
{ {
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag]) [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
@ -267,13 +457,26 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil];
} }
- (IBAction)rtcModeChanged:(id)sender
{
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
forKey:@"GBRTCMode"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRTCModeChanged" object:nil];
}
- (IBAction)changeAutoUpdates:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
forKey:@"GBAutoUpdatesEnabled"];
}
- (IBAction) configureJoypad:(id)sender - (IBAction) configureJoypad:(id)sender
{ {
[self.configureJoypadButton setEnabled:NO]; [self.configureJoypadButton setEnabled:false];
[self.skipButton setEnabled:YES]; [self.skipButton setEnabled:true];
joystick_being_configured = nil; joystick_being_configured = nil;
[self advanceConfigurationStateMachine]; [self advanceConfigurationStateMachine];
last_axis = -1;
} }
- (IBAction) skipButton:(id)sender - (IBAction) skipButton:(id)sender
@ -284,126 +487,137 @@
- (void) advanceConfigurationStateMachine - (void) advanceConfigurationStateMachine
{ {
joystick_configuration_state++; joystick_configuration_state++;
if (joystick_configuration_state < GBButtonCount) { if (joystick_configuration_state == GBUnderclock) {
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]]; [self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :<
} }
else if (joystick_configuration_state == GBButtonCount) { else if (joystick_configuration_state < GBButtonCount) {
[self.configureJoypadButton setTitle:@"Move the Analog Stick"]; [self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
} }
else { else {
joystick_configuration_state = -1; joystick_configuration_state = -1;
[self.configureJoypadButton setEnabled:YES]; [self.configureJoypadButton setEnabled:true];
[self.skipButton setEnabled:NO]; [self.skipButton setEnabled:false];
[self.configureJoypadButton setTitle:@"Configure Joypad"]; [self.configureJoypadButton setTitle:@"Configure Joypad"];
} }
} }
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{ {
if (!state) return; /* Debounce */
if (joypad_wait) return;
joypad_wait = true;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
joypad_wait = false;
});
if (!button.isPressed) return;
if (joystick_configuration_state == -1) return; if (joystick_configuration_state == -1) return;
if (joystick_configuration_state == GBButtonCount) return; if (joystick_configuration_state == GBButtonCount) return;
if (!joystick_being_configured) { if (!joystick_being_configured) {
joystick_being_configured = joystick_name; joystick_being_configured = controller.uniqueID;
} }
else if (![joystick_being_configured isEqualToString:joystick_name]) { else if (![joystick_being_configured isEqualToString:controller.uniqueID]) {
return; return;
} }
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy]; NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy];
if (!all_mappings) { NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy];
all_mappings = [[NSMutableDictionary alloc] init];
if (!instance_mappings) {
instance_mappings = [[NSMutableDictionary alloc] init];
} }
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy]; if (!name_mappings) {
name_mappings = [[NSMutableDictionary alloc] init];
}
if (!mapping) { NSMutableDictionary *mapping = nil;
if (joystick_configuration_state != 0) {
mapping = [instance_mappings[controller.uniqueID] mutableCopy];
}
else {
mapping = [[NSMutableDictionary alloc] init]; mapping = [[NSMutableDictionary alloc] init];
} }
mapping[GBButtonNames[joystick_configuration_state]] = @(button);
all_mappings[joystick_name] = mapping; static const unsigned gb_to_joykit[] = {
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"]; [GBRight] = JOYButtonUsageDPadRight,
[self refreshJoypadMenu:nil]; [GBLeft] = JOYButtonUsageDPadLeft,
[GBUp] = JOYButtonUsageDPadUp,
[GBDown] = JOYButtonUsageDPadDown,
[GBA] = JOYButtonUsageA,
[GBB] = JOYButtonUsageB,
[GBSelect] = JOYButtonUsageSelect,
[GBStart] = JOYButtonUsageStart,
[GBTurbo] = JOYButtonUsageL1,
[GBRewind] = JOYButtonUsageL2,
[GBUnderclock] = JOYButtonUsageR1,
};
if (joystick_configuration_state == GBUnderclock) {
mapping[@"AnalogUnderclock"] = nil;
double max = 0;
for (JOYAxis *axis in controller.axes) {
if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) {
mapping[@"AnalogUnderclock"] = @(axis.uniqueID);
break;
}
}
}
if (joystick_configuration_state == GBTurbo) {
mapping[@"AnalogTurbo"] = nil;
double max = 0;
for (JOYAxis *axis in controller.axes) {
if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) {
max = axis.value;
mapping[@"AnalogTurbo"] = @(axis.uniqueID);
}
}
}
mapping[n2s(button.uniqueID)] = @(gb_to_joykit[joystick_configuration_state]);
instance_mappings[controller.uniqueID] = mapping;
name_mappings[controller.deviceName] = mapping;
[[NSUserDefaults standardUserDefaults] setObject:instance_mappings forKey:@"JoyKitInstanceMapping"];
[[NSUserDefaults standardUserDefaults] setObject:name_mappings forKey:@"JoyKitNameMapping"];
[self advanceConfigurationStateMachine]; [self advanceConfigurationStateMachine];
} }
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value - (NSButton *)joystickMBC7Checkbox
{ {
if (abs(value) < 0x4000) return; return _joystickMBC7Checkbox;
if (joystick_configuration_state != GBButtonCount) return;
if (!joystick_being_configured) {
joystick_being_configured = joystick_name;
}
else if (![joystick_being_configured isEqualToString:joystick_name]) {
return;
} }
if (last_axis == -1) { - (void)setJoystickMBC7Checkbox:(NSButton *)joystickMBC7Checkbox
last_axis = axis;
return;
}
if (axis == last_axis) {
return;
}
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
if (!all_mappings) {
all_mappings = [[NSMutableDictionary alloc] init];
}
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
if (!mapping) {
mapping = [[NSMutableDictionary alloc] init];
}
mapping[@"XAxis"] = @(MIN(axis, last_axis));
mapping[@"YAxis"] = @(MAX(axis, last_axis));
all_mappings[joystick_name] = mapping;
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
[self advanceConfigurationStateMachine];
}
- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state
{ {
/* Hats are always mapped to the D-pad, ignore them on non-Dpad keys and skip the D-pad configuration if used*/ _joystickMBC7Checkbox = joystickMBC7Checkbox;
if (!state) return; [_joystickMBC7Checkbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]];
if (joystick_configuration_state == -1) return;
if (joystick_configuration_state > GBDown) return;
if (!joystick_being_configured) {
joystick_being_configured = joystick_name;
}
else if (![joystick_being_configured isEqualToString:joystick_name]) {
return;
} }
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy]; - (NSButton *)mouseMBC7Checkbox
{
if (!all_mappings) { return _mouseMBC7Checkbox;
all_mappings = [[NSMutableDictionary alloc] init];
} }
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy]; - (void)setMouseMBC7Checkbox:(NSButton *)mouseMBC7Checkbox
{
if (!mapping) { _mouseMBC7Checkbox = mouseMBC7Checkbox;
mapping = [[NSMutableDictionary alloc] init]; [_mouseMBC7Checkbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7AllowMouse"]];
} }
for (joystick_configuration_state = 0;; joystick_configuration_state++) { - (NSButton *)analogControlsCheckbox
[mapping removeObjectForKey:GBButtonNames[joystick_configuration_state]]; {
if (joystick_configuration_state == GBDown) break; return _analogControlsCheckbox;
} }
all_mappings[joystick_name] = mapping; - (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"]; {
[self refreshJoypadMenu:nil]; _analogControlsCheckbox = analogControlsCheckbox;
[self advanceConfigurationStateMachine]; [_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]];
} }
- (NSButton *)aspectRatioCheckbox - (NSButton *)aspectRatioCheckbox
@ -422,18 +636,21 @@
[super awakeFromNib]; [super awakeFromNib];
[self updateBootROMFolderButton]; [self updateBootROMFolderButton];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil];
[JOYController registerListener:self];
joystick_configuration_state = -1;
} }
- (void)dealloc - (void)dealloc
{ {
[JOYController unregisterListener:self];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView]; [[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView];
} }
- (IBAction)selectOtherBootROMFolder:(id)sender - (IBAction)selectOtherBootROMFolder:(id)sender
{ {
NSOpenPanel *panel = [[NSOpenPanel alloc] init]; NSOpenPanel *panel = [[NSOpenPanel alloc] init];
[panel setCanChooseDirectories:YES]; [panel setCanChooseDirectories:true];
[panel setCanChooseFiles:NO]; [panel setCanChooseFiles:false];
[panel setPrompt:@"Select"]; [panel setPrompt:@"Select"];
[panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]]; [panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]];
[panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) { [panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) {
@ -457,12 +674,12 @@
[self.bootROMsFolderItem setTitle:[url lastPathComponent]]; [self.bootROMsFolderItem setTitle:[url lastPathComponent]];
NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]]; NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]];
[icon setSize:NSMakeSize(16, 16)]; [icon setSize:NSMakeSize(16, 16)];
[self.bootROMsFolderItem setHidden:NO]; [self.bootROMsFolderItem setHidden:false];
[self.bootROMsFolderItem setImage:icon]; [self.bootROMsFolderItem setImage:icon];
[self.bootROMsButton selectItemAtIndex:1]; [self.bootROMsButton selectItemAtIndex:1];
} }
else { else {
[self.bootROMsFolderItem setHidden:YES]; [self.bootROMsFolderItem setHidden:true];
[self.bootROMsButton selectItemAtIndex:0]; [self.bootROMsButton selectItemAtIndex:0];
} }
} }
@ -544,21 +761,47 @@
return _preferredJoypadButton; return _preferredJoypadButton;
} }
- (void)controllerConnected:(JOYController *)controller
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self refreshJoypadMenu:nil];
});
}
- (void)controllerDisconnected:(JOYController *)controller
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self refreshJoypadMenu:nil];
});
}
- (IBAction)refreshJoypadMenu:(id)sender - (IBAction)refreshJoypadMenu:(id)sender
{ {
NSArray *joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] allKeys]; bool preferred_is_connected = false;
for (NSString *joypad in joypads) { NSString *player_string = n2s(self.playerListButton.selectedTag);
if ([self.preferredJoypadButton indexOfItemWithTitle:joypad] == -1) { NSString *selected_controller = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"][player_string];
[self.preferredJoypadButton addItemWithTitle:joypad];
[self.preferredJoypadButton removeAllItems];
[self.preferredJoypadButton addItemWithTitle:@"None"];
for (JOYController *controller in [JOYController allControllers]) {
[self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", controller.deviceName, controller.uniqueID]];
self.preferredJoypadButton.lastItem.identifier = controller.uniqueID;
if ([controller.uniqueID isEqualToString:selected_controller]) {
preferred_is_connected = true;
[self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
} }
} }
NSString *player_string = [NSString stringWithFormat: @"%ld", (long)self.playerListButton.selectedTag]; if (!preferred_is_connected && selected_controller) {
NSString *selected_joypad = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"][player_string]; [self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"Unavailable Controller (%@)", selected_controller]];
if (selected_joypad && [self.preferredJoypadButton indexOfItemWithTitle:selected_joypad] != -1) { self.preferredJoypadButton.lastItem.identifier = selected_controller;
[self.preferredJoypadButton selectItemWithTitle:selected_joypad]; [self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
} }
else {
if (!selected_controller) {
[self.preferredJoypadButton selectItemWithTitle:@"None"]; [self.preferredJoypadButton selectItemWithTitle:@"None"];
} }
[self.controlsTableView reloadData]; [self.controlsTableView reloadData];
@ -566,18 +809,70 @@
- (IBAction)changeDefaultJoypad:(id)sender - (IBAction)changeDefaultJoypad:(id)sender
{ {
NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] mutableCopy]; NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy];
if (!default_joypads) { if (!default_joypads) {
default_joypads = [[NSMutableDictionary alloc] init]; default_joypads = [[NSMutableDictionary alloc] init];
} }
NSString *player_string = [NSString stringWithFormat: @"%ld", self.playerListButton.selectedTag]; NSString *player_string = n2s(self.playerListButton.selectedTag);
if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) { if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) {
[default_joypads removeObjectForKey:player_string]; [default_joypads removeObjectForKey:player_string];
} }
else { else {
default_joypads[player_string] = [sender titleOfSelectedItem]; default_joypads[player_string] = [[sender selectedItem] identifier];
} }
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"GBDefaultJoypads"]; [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"];
} }
- (NSButton *)autoUpdatesCheckbox
{
return _autoUpdatesCheckbox;
}
- (void)setAutoUpdatesCheckbox:(NSButton *)autoUpdatesCheckbox
{
_autoUpdatesCheckbox = autoUpdatesCheckbox;
[_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]];
}
- (NSButton *)OSDCheckbox
{
return _OSDCheckbox;
}
- (void)setOSDCheckbox:(NSButton *)OSDCheckbox
{
_OSDCheckbox = OSDCheckbox;
[_OSDCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]];
}
- (IBAction)changeOSDEnabled:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
forKey:@"GBOSDEnabled"];
}
- (IBAction)changeFilterScreenshots:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
forKey:@"GBFilterScreenshots"];
}
- (NSButton *)screenshotFilterCheckbox
{
return _screenshotFilterCheckbox;
}
- (void)setScreenshotFilterCheckbox:(NSButton *)screenshotFilterCheckbox
{
_screenshotFilterCheckbox = screenshotFilterCheckbox;
if (![GBViewMetal isSupported]) {
[_screenshotFilterCheckbox setEnabled:false];
}
else {
[_screenshotFilterCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]];
}
}
@end @end

128
Cocoa/GBS.xib Normal file
View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Document">
<connections>
<outlet property="gbsAuthor" destination="gaD-ZH-Beh" id="2i7-BD-bJ2"/>
<outlet property="gbsCopyright" destination="2dl-dH-E3J" id="LnT-Vb-pN6"/>
<outlet property="gbsNextPrevButton" destination="SRS-M5-VVL" id="YEN-01-wRX"/>
<outlet property="gbsPlayPauseButton" destination="qxJ-pH-d0y" id="qk8-8I-9u5"/>
<outlet property="gbsPlayerView" destination="c22-O7-iKe" id="A1w-e5-EQE"/>
<outlet property="gbsRewindButton" destination="0yD-Sp-Ilo" id="FgR-xd-JW5"/>
<outlet property="gbsTitle" destination="H3v-X3-48q" id="DCl-wL-oy8"/>
<outlet property="gbsTracks" destination="I1T-VS-Vse" id="Vk4-GP-RjB"/>
<outlet property="gbsVisualizer" destination="Q3o-bK-DIN" id="1YC-C5-Je6"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe">
<rect key="frame" x="0.0" y="0.0" width="332" height="221"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="H3v-X3-48q">
<rect key="frame" x="18" y="192" width="296" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Title" id="BwZ-Zj-sP6">
<font key="font" metaFont="systemBold" size="16"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="gaD-ZH-Beh">
<rect key="frame" x="18" y="166" width="296" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Author" id="IgT-r1-T38">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qxJ-pH-d0y">
<rect key="frame" x="61.5" y="127" width="39" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Play" imagePosition="only" alignment="center" alternateImage="Pause" state="on" borderStyle="border" focusRingType="none" inset="2" id="3ZK-br-UrS">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="togglePause:" target="-2" id="AUe-I7-nOK"/>
</connections>
</button>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
<rect key="frame" x="19.5" y="127" width="38" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
</connections>
</button>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
<rect key="frame" x="106" y="127" width="131" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="YJh-dI-A5D">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="Knp-Ok-Pb4"/>
</popUpButtonCell>
<connections>
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
</connections>
</popUpButton>
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL">
<rect key="frame" x="240.5" y="127" width="72" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="cmq-I8-cFL">
<font key="font" metaFont="system"/>
<segments>
<segment toolTip="Previous Track" image="Previous" width="33"/>
<segment toolTip="Next Track" image="Next" width="32" tag="1"/>
</segments>
</segmentedCell>
<connections>
<action selector="gbsNextPrevPushed:" target="-2" id="roN-Iy-tDQ"/>
</connections>
</segmentedControl>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="b9A-cd-ias">
<rect key="frame" x="0.0" y="117" width="332" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
<customView appearanceType="darkAqua" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tRy-Gw-IaG" customClass="GBOptionalVisualEffectView">
<rect key="frame" x="0.0" y="24" width="332" height="95"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q3o-bK-DIN" customClass="GBVisualizerView">
<rect key="frame" x="0.0" y="0.0" width="332" height="95"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
</subviews>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="2dl-dH-E3J">
<rect key="frame" x="18" y="5" width="296" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Copyright" id="nM9-oF-OV9">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="67" y="292.5"/>
</customView>
</objects>
<resources>
<image name="Next" width="16" height="10"/>
<image name="Pause" width="10" height="10"/>
<image name="Play" width="10" height="10"/>
<image name="Previous" width="16" height="10"/>
<image name="Rewind" width="10" height="10"/>
</resources>
</document>

View File

@ -5,12 +5,14 @@
NSColor *_dividerColor; NSColor *_dividerColor;
} }
- (void)setDividerColor:(NSColor *)color { - (void)setDividerColor:(NSColor *)color
{
_dividerColor = color; _dividerColor = color;
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
} }
- (NSColor *)dividerColor { - (NSColor *)dividerColor
{
if (_dividerColor) { if (_dividerColor) {
return _dividerColor; return _dividerColor;
} }
@ -22,7 +24,8 @@
{ {
if (@available(macOS 10.11, *)) { if (@available(macOS 10.11, *)) {
return [super arrangedSubviews]; return [super arrangedSubviews];
} else { }
else {
return [self subviews]; return [self subviews];
} }
} }

View File

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

View File

@ -2,6 +2,7 @@
#import "GBTerminalTextFieldCell.h" #import "GBTerminalTextFieldCell.h"
@interface GBTerminalTextView : NSTextView @interface GBTerminalTextView : NSTextView
@property GB_gameboy_t *gb;
@end @end
@implementation GBTerminalTextFieldCell @implementation GBTerminalTextFieldCell
@ -12,10 +13,12 @@
- (NSTextView *)fieldEditorForView:(NSView *)controlView - (NSTextView *)fieldEditorForView:(NSView *)controlView
{ {
if (field_editor) { if (field_editor) {
field_editor.gb = self.gb;
return field_editor; return field_editor;
} }
field_editor = [[GBTerminalTextView alloc] init]; field_editor = [[GBTerminalTextView alloc] init];
[field_editor setFieldEditor:YES]; [field_editor setFieldEditor:true];
field_editor.gb = self.gb;
return field_editor; return field_editor;
} }
@ -26,6 +29,8 @@
NSMutableOrderedSet *lines; NSMutableOrderedSet *lines;
NSUInteger current_line; NSUInteger current_line;
bool reverse_search_mode; bool reverse_search_mode;
NSRange auto_complete_range;
uintptr_t auto_complete_context;
} }
- (instancetype)init - (instancetype)init
@ -104,7 +109,7 @@
[self updateReverseSearch]; [self updateReverseSearch];
} }
else { else {
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
reverse_search_mode = true; reverse_search_mode = true;
} }
@ -170,10 +175,12 @@
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag -(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
{ {
reverse_search_mode = false; reverse_search_mode = false;
auto_complete_context = 0;
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag]; [super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
} }
- (BOOL)resignFirstResponder { - (BOOL)resignFirstResponder
{
reverse_search_mode = false; reverse_search_mode = false;
return [super resignFirstResponder]; return [super resignFirstResponder];
} }
@ -187,6 +194,38 @@
[attributes setObject:color forKey:NSForegroundColorAttributeName]; [attributes setObject:color forKey:NSForegroundColorAttributeName];
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)]; [[[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 @end

View File

@ -1,6 +1,8 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include <Core/gb.h> #include <Core/gb.h>
#import "GBJoystickListener.h" #import <JoyKit/JoyKit.h>
#import "GBOSDView.h"
@class Document;
typedef enum { typedef enum {
GB_FRAME_BLENDING_MODE_DISABLED, GB_FRAME_BLENDING_MODE_DISABLED,
@ -10,16 +12,20 @@ typedef enum {
GB_FRAME_BLENDING_MODE_ACCURATE_ODD, GB_FRAME_BLENDING_MODE_ACCURATE_ODD,
} GB_frame_blending_mode_t; } GB_frame_blending_mode_t;
@interface GBView<GBJoystickListener> : NSView @interface GBView : NSView<JOYListener>
- (void) flip; - (void) flip;
- (uint32_t *) pixels; - (uint32_t *) pixels;
@property GB_gameboy_t *gb; @property (nonatomic, weak) IBOutlet Document *document;
@property (nonatomic) GB_gameboy_t *gb;
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; @property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @property (nonatomic, getter=isMouseHidingEnabled) bool mouseHidingEnabled;
@property bool isRewinding; @property (nonatomic) bool isRewinding;
@property NSView *internalView; @property (nonatomic, strong) NSView *internalView;
@property (weak) GBOSDView *osdView;
- (void) createInternalView; - (void) createInternalView;
- (uint32_t *)currentBuffer; - (uint32_t *)currentBuffer;
- (uint32_t *)previousBuffer; - (uint32_t *)previousBuffer;
- (void)screenSizeChanged; - (void)screenSizeChanged;
- (void)setRumble: (double)amp;
- (NSImage *)renderToImage;
@end @end

View File

@ -1,25 +1,124 @@
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <Carbon/Carbon.h> #import <Carbon/Carbon.h>
#import "GBView.h" #import "GBView.h"
#import "GBViewGL.h" #import "GBViewGL.h"
#import "GBViewMetal.h" #import "GBViewMetal.h"
#import "GBButtons.h" #import "GBButtons.h"
#import "NSString+StringForKey.h" #import "NSString+StringForKey.h"
#import "Document.h"
#define JOYSTICK_HIGH 0x4000 #define JOYSTICK_HIGH 0x4000
#define JOYSTICK_LOW 0x3800 #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 @implementation GBView
{ {
uint32_t *image_buffers[3]; uint32_t *image_buffers[3];
unsigned char current_buffer; unsigned char current_buffer;
BOOL mouse_hidden; bool mouse_hidden;
NSTrackingArea *tracking_area; NSTrackingArea *tracking_area;
BOOL _mouseHidingEnabled; bool _mouseHidingEnabled;
bool axisActive[2]; bool axisActive[2];
bool underclockKeyDown; bool underclockKeyDown;
double clockMultiplier; double clockMultiplier;
double analogClockMultiplier;
bool analogClockMultiplierValid;
NSEventModifierFlags previousModifiers; NSEventModifierFlags previousModifiers;
JOYController *lastController;
GB_frame_blending_mode_t _frameBlendingMode; GB_frame_blending_mode_t _frameBlendingMode;
bool _turbo;
bool _mouseControlEnabled;
} }
+ (instancetype)alloc + (instancetype)alloc
@ -45,9 +144,11 @@
- (void) _init - (void) _init
{ {
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil];
tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseMoved
owner:self owner:self
userInfo:nil]; userInfo:nil];
[self addTrackingArea:tracking_area]; [self addTrackingArea:tracking_area];
@ -55,6 +156,8 @@
[self createInternalView]; [self createInternalView];
[self addSubview:self.internalView]; [self addSubview:self.internalView];
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[JOYController registerListener:self];
_mouseControlEnabled = true;
} }
- (void)screenSizeChanged - (void)screenSizeChanged
@ -82,7 +185,7 @@
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode - (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
{ {
_frameBlendingMode = frameBlendingMode; _frameBlendingMode = frameBlendingMode;
[self setNeedsDisplay:YES]; [self setNeedsDisplay:true];
} }
@ -111,11 +214,12 @@
[NSCursor unhide]; [NSCursor unhide];
} }
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
[self setRumble:0];
[JOYController unregisterListener:self];
} }
- (instancetype)initWithCoder:(NSCoder *)coder - (instancetype)initWithCoder:(NSCoder *)coder
{ {
if (!(self = [super initWithCoder:coder])) if (!(self = [super initWithCoder:coder])) {
{
return self; return self;
} }
[self _init]; [self _init];
@ -124,8 +228,7 @@
- (instancetype)initWithFrame:(NSRect)frameRect - (instancetype)initWithFrame:(NSRect)frameRect
{ {
if (!(self = [super initWithFrame:frameRect])) if (!(self = [super initWithFrame:frameRect])) {
{
return self; return self;
} }
[self _init]; [self _init];
@ -158,13 +261,45 @@
- (void) flip - (void) flip
{ {
if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
clockMultiplier = 1.0;
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;
}
if (analogClockMultiplier < 2.0 && analogClockMultiplier > 1.0) {
GB_set_turbo_mode(_gb, false, false);
if (self.document.partner) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
}
}
else {
if (underclockKeyDown && clockMultiplier > 0.5) { if (underclockKeyDown && clockMultiplier > 0.5) {
clockMultiplier -= 1.0/16; clockMultiplier -= 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier); GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
} }
if (!underclockKeyDown && clockMultiplier < 1.0) { if (!underclockKeyDown && clockMultiplier < 1.0) {
clockMultiplier += 1.0/16; clockMultiplier += 1.0/16;
GB_set_clock_multiplier(_gb, clockMultiplier); GB_set_clock_multiplier(_gb, clockMultiplier);
if (self.document.partner) {
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
}
}
}
if ((!analogClockMultiplierValid && clockMultiplier > 1) ||
_turbo || (analogClockMultiplierValid && analogClockMultiplier > 1)) {
[self.osdView displayText:@"Fast forwarding..."];
}
else if ((!analogClockMultiplierValid && clockMultiplier < 1) ||
(analogClockMultiplierValid && analogClockMultiplier < 1)) {
[self.osdView displayText:@"Slow motion..."];
} }
current_buffer = (current_buffer + 1) % self.numberOfBuffers; current_buffer = (current_buffer + 1) % self.numberOfBuffers;
} }
@ -176,11 +311,27 @@
-(void)keyDown:(NSEvent *)theEvent -(void)keyDown:(NSEvent *)theEvent
{ {
if ([theEvent type] != NSEventTypeFlagsChanged && theEvent.isARepeat) return;
unsigned short keyCode = theEvent.keyCode; 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; bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
unsigned player_count = GB_get_player_count(_gb); unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) { for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) { for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
@ -190,20 +341,41 @@
handled = true; handled = true;
switch (button) { switch (button) {
case GBTurbo: case GBTurbo:
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, true, false);
}
else {
GB_set_turbo_mode(_gb, true, self.isRewinding); GB_set_turbo_mode(_gb, true, self.isRewinding);
}
_turbo = true;
analogClockMultiplierValid = false;
break; break;
case GBRewind: case GBRewind:
if (!self.document.partner) {
self.isRewinding = true; self.isRewinding = true;
GB_set_turbo_mode(_gb, false, false); GB_set_turbo_mode(_gb, false, false);
_turbo = false;
}
break; break;
case GBUnderclock: case GBUnderclock:
underclockKeyDown = true; underclockKeyDown = true;
analogClockMultiplierValid = false;
break; break;
default: default:
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); GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
}
break; break;
} }
} }
@ -218,10 +390,22 @@
-(void)keyUp:(NSEvent *)theEvent -(void)keyUp:(NSEvent *)theEvent
{ {
unsigned short keyCode = theEvent.keyCode; 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; bool handled = false;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
unsigned player_count = GB_get_player_count(_gb); unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
for (unsigned player = 0; player < player_count; player++) { for (unsigned player = 0; player < player_count; player++) {
for (GBButton button = 0; button < GBButtonCount; button++) { for (GBButton button = 0; button < GBButtonCount; button++) {
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
@ -231,7 +415,14 @@
handled = true; handled = true;
switch (button) { switch (button) {
case GBTurbo: case GBTurbo:
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false); GB_set_turbo_mode(_gb, false, false);
}
_turbo = false;
analogClockMultiplierValid = false;
break; break;
case GBRewind: case GBRewind:
@ -240,10 +431,21 @@
case GBUnderclock: case GBUnderclock:
underclockKeyDown = false; underclockKeyDown = false;
analogClockMultiplierValid = false;
break; break;
default: default:
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); GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
}
break; break;
} }
} }
@ -254,136 +456,201 @@
} }
} }
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state - (void)setRumble:(double)amp
{ {
unsigned player_count = GB_get_player_count(_gb); [lastController setRumbleAmplitude:amp];
}
- (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller
{
if (!_gb) return false;
if (!GB_has_accelerometer(_gb)) return false;
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return true;
for (JOYAxes3D *axes in controller.axes3D) {
if (axes.usage == JOYAxes3DUsageOrientation || axes.usage == JOYAxes3DUsageAcceleration) {
return false;
}
}
return true;
}
- (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis
{
if (!_gb) return;
if (![self.window isMainWindow]) return;
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
if (!mapping) {
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
}
if ((axis.usage == JOYAxisUsageR1 && !mapping) ||
axis.uniqueID == [mapping[@"AnalogUnderclock"] unsignedLongValue]){
analogClockMultiplier = MIN(MAX(1 - axis.value + 0.05, 1.0 / 3), 1.0);
analogClockMultiplierValid = true;
}
else if ((axis.usage == JOYAxisUsageL1 && !mapping) ||
axis.uniqueID == [mapping[@"AnalogTurbo"] unsignedLongValue]){
analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.95, 1.0), 3.0);
analogClockMultiplierValid = true;
}
}
- (void)controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes
{
if (!_gb) return;
if ([self shouldControllerUseJoystickForMotion:controller]) {
if (!self.mouseControlsActive) {
GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y);
}
}
}
- (void)controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes
{
if (!_gb) return;
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return;
if (self.mouseControlsActive) return;
if (axes.usage == JOYAxes3DUsageOrientation) {
for (JOYAxes3D *axes in controller.axes3D) {
// Only use orientation if there's no acceleration axes
if (axes.usage == JOYAxes3DUsageAcceleration) {
return;
}
}
JOYPoint3D point = axes.normalizedValue;
GB_set_accelerometer_values(_gb, point.x, point.z);
}
else if (axes.usage == JOYAxes3DUsageAcceleration) {
JOYPoint3D point = axes.gUnitsValue;
GB_set_accelerometer_values(_gb, point.x, point.z);
}
}
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{
if (!_gb) return;
if (![self.window isMainWindow]) return;
_mouseControlEnabled = false;
if (button.type == JOYButtonTypeAxes2DEmulated && [self shouldControllerUseJoystickForMotion:controller]) return;
unsigned player_count = GB_get_player_count(_gb);
if (self.document.partner) {
player_count = 2;
}
IOPMAssertionID assertionID;
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
UpdateSystemActivity(UsrActivity);
for (unsigned player = 0; player < player_count; player++) { for (unsigned player = 0; player < player_count; player++) {
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"]
objectForKey:[NSString stringWithFormat:@"%u", player]]; objectForKey:n2s(player)];
if (player_count != 1 && // Single player, accpet inputs from all joypads if (player_count != 1 && // Single player, accpet inputs from all joypads
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads !(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
![preferred_joypad isEqualToString:joystick_name]) { ![preferred_joypad isEqualToString:controller.uniqueID]) {
continue; continue;
} }
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name]; dispatch_async(dispatch_get_main_queue(), ^{
[controller setPlayerLEDs:[controller LEDMaskForPlayer:player]];
});
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
if (!mapping) {
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
}
for (GBButton i = 0; i < GBButtonCount; i++) { JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage;
NSNumber *mapped_button = [mapping objectForKey:GBButtonNames[i]]; if (!mapping && usage >= JOYButtonUsageGeneric0) {
if (mapped_button && [mapped_button integerValue] == button) { usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
switch (i) { }
case GBTurbo:
GB_set_turbo_mode(_gb, state, state && self.isRewinding);
break;
case GBRewind: GB_gameboy_t *effectiveGB = _gb;
self.isRewinding = state; unsigned effectivePlayer = player;
if (state) {
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(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(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break;
case JOYButtonUsageSelect:
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) {
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, false, false);
}
else {
GB_set_turbo_mode(_gb, false, false); GB_set_turbo_mode(_gb, false, false);
} }
_turbo = false;
}
break; break;
}
case GBUnderclock: case JOYButtonUsageL1: {
underclockKeyDown = state; if (!analogClockMultiplierValid || analogClockMultiplier == 1.0 || !button.isPressed) {
if (self.document.isSlave) {
GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false);
}
else {
GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding);
}
_turbo = button.isPressed;
}
break; break;
}
case JOYButtonUsageR1: underclockKeyDown = 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: default:
GB_set_key_state_for_player(_gb, (GB_key_t)i, player, state);
break; break;
} }
} }
} }
}
}
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value
{
unsigned player_count = GB_get_player_count(_gb);
UpdateSystemActivity(UsrActivity);
for (unsigned player = 0; player < player_count; player++) {
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"]
objectForKey:[NSString stringWithFormat:@"%u", player]];
if (player_count != 1 && // Single player, accpet inputs from all joypads
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
![preferred_joypad isEqualToString:joystick_name]) {
continue;
}
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name];
NSNumber *x_axis = [mapping objectForKey:@"XAxis"];
NSNumber *y_axis = [mapping objectForKey:@"YAxis"];
if (axis == [x_axis integerValue]) {
if (value > JOYSTICK_HIGH) {
axisActive[0] = true;
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, true);
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false);
}
else if (value < -JOYSTICK_HIGH) {
axisActive[0] = true;
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, true);
}
else if (axisActive[0] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) {
axisActive[0] = false;
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false);
}
}
else if (axis == [y_axis integerValue]) {
if (value > JOYSTICK_HIGH) {
axisActive[1] = true;
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, true);
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false);
}
else if (value < -JOYSTICK_HIGH) {
axisActive[1] = true;
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, true);
}
else if (axisActive[1] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) {
axisActive[1] = false;
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false);
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false);
}
}
}
}
- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state
{
unsigned player_count = GB_get_player_count(_gb);
UpdateSystemActivity(UsrActivity);
for (unsigned player = 0; player < player_count; player++) {
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"]
objectForKey:[NSString stringWithFormat:@"%u", player]];
if (player_count != 1 && // Single player, accpet inputs from all joypads
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
![preferred_joypad isEqualToString:joystick_name]) {
continue;
}
assert(state + 1 < 9);
/* - N NE E SE S SW W NW */
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, (bool []){0, 1, 1, 0, 0, 0, 0, 0, 1}[state + 1]);
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, (bool []){0, 0, 1, 1, 1, 0, 0, 0, 0}[state + 1]);
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, (bool []){0, 0, 0, 0, 1, 1, 1, 0, 0}[state + 1]);
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, (bool []){0, 0, 0, 0, 0, 0, 1, 1, 1}[state + 1]);
}
}
- (BOOL)acceptsFirstResponder - (BOOL)acceptsFirstResponder
{ {
return YES; return true;
}
- (bool)mouseControlsActive
{
return _gb && GB_is_inited(_gb) && GB_has_accelerometer(_gb) &&
_mouseControlEnabled && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7AllowMouse"];
} }
- (void)mouseEntered:(NSEvent *)theEvent - (void)mouseEntered:(NSEvent *)theEvent
{ {
if (!mouse_hidden) { if (!mouse_hidden) {
mouse_hidden = true; mouse_hidden = true;
if (_mouseHidingEnabled) { if (_mouseHidingEnabled &&
!self.mouseControlsActive) {
[NSCursor hide]; [NSCursor hide];
} }
} }
@ -401,7 +668,47 @@
[super mouseExited:theEvent]; [super mouseExited:theEvent];
} }
- (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled - (void)mouseDown:(NSEvent *)event
{
_mouseControlEnabled = true;
if (self.mouseControlsActive) {
if (event.type == NSEventTypeLeftMouseDown) {
GB_set_key_state(_gb, GB_KEY_A, true);
}
}
}
- (void)mouseUp:(NSEvent *)event
{
if (self.mouseControlsActive) {
if (event.type == NSEventTypeLeftMouseUp) {
GB_set_key_state(_gb, GB_KEY_A, false);
}
}
}
- (void)mouseMoved:(NSEvent *)event
{
if (self.mouseControlsActive) {
NSPoint point = [self convertPoint:[event locationInWindow] toView:nil];
point.x /= self.frame.size.width;
point.x *= 2;
point.x -= 1;
point.y /= self.frame.size.height;
point.y *= 2;
point.y -= 1;
if (GB_get_screen_width(_gb) != 160) { // has border
point.x *= 256 / 160.0;
point.y *= 224 / 114.0;
}
GB_set_accelerometer_values(_gb, -point.x, point.y);
}
}
- (void)setMouseHidingEnabled:(bool)mouseHidingEnabled
{ {
if (mouseHidingEnabled == _mouseHidingEnabled) return; if (mouseHidingEnabled == _mouseHidingEnabled) return;
@ -416,7 +723,7 @@
} }
} }
- (BOOL)isMouseHidingEnabled - (bool)isMouseHidingEnabled
{ {
return _mouseHidingEnabled; return _mouseHidingEnabled;
} }
@ -443,4 +750,35 @@
return image_buffers[(current_buffer + 2) % self.numberOfBuffers]; return image_buffers[(current_buffer + 2) % self.numberOfBuffers];
} }
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
if (GB_is_save_state(fileURL.fileSystemRepresentation)) {
return NSDragOperationGeneric;
}
}
return NSDragOperationNone;
}
-(BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
return [_document loadStateFile:fileURL.fileSystemRepresentation noErrorOnNotFound:false];
}
return false;
}
- (NSImage *)renderToImage;
{
/* Not going to support this on OpenGL, OpenGL is too much of a terrible API for me
to bother figuring out how the hell something so trivial can be done. */
return nil;
}
@end @end

View File

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

View File

@ -1,3 +1,4 @@
#import <CoreImage/CoreImage.h>
#import "GBViewMetal.h" #import "GBViewMetal.h"
#pragma clang diagnostic ignored "-Wpartial-availability" #pragma clang diagnostic ignored "-Wpartial-availability"
@ -51,8 +52,9 @@ static const vector_float2 rect[] =
MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())]; MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(device = MTLCreateSystemDefaultDevice())];
view.delegate = self; view.delegate = self;
self.internalView = view; self.internalView = view;
view.paused = YES; view.paused = true;
view.enableSetNeedsDisplay = YES; view.enableSetNeedsDisplay = true;
view.framebufferOnly = false;
vertices = [device newBufferWithBytes:rect vertices = [device newBufferWithBytes:rect
length:sizeof(rect) length:sizeof(rect)
@ -92,7 +94,7 @@ static const vector_float2 rect[] =
withString:scaler_source]; withString:scaler_source];
MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
options.fastMathEnabled = YES; options.fastMathEnabled = true;
id<MTLLibrary> library = [device newLibraryWithSource:shader_source id<MTLLibrary> library = [device newLibraryWithSource:shader_source
options:options options:options
error:&error]; error:&error];
@ -123,7 +125,7 @@ static const vector_float2 rect[] =
command_queue = [device newCommandQueue]; command_queue = [device newCommandQueue];
} }
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size - (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
{ {
output_resolution = (vector_float2){size.width, size.height}; output_resolution = (vector_float2){size.width, size.height};
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@ -131,7 +133,7 @@ static const vector_float2 rect[] =
}); });
} }
- (void)drawInMTKView:(nonnull MTKView *)view - (void)drawInMTKView:(MTKView *)view
{ {
if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
if (!self.gb) return; if (!self.gb) return;
@ -159,8 +161,7 @@ static const vector_float2 rect[] =
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer]; id<MTLCommandBuffer> command_buffer = [command_queue commandBuffer];
if (render_pass_descriptor != nil) if (render_pass_descriptor != nil) {
{
*(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode]; *(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode];
*(vector_float2 *)[output_resolution_buffer contents] = output_resolution; *(vector_float2 *)[output_resolution_buffer contents] = output_resolution;
@ -209,8 +210,23 @@ static const vector_float2 rect[] =
{ {
[super flip]; [super flip];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[(MTKView *)self.internalView setNeedsDisplay:YES]; [(MTKView *)self.internalView setNeedsDisplay:true];
}); });
} }
- (NSImage *)renderToImage
{
CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture]
options:@{
kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB()
}];
ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1),
0, ciImage.extent.size.height)];
CIContext *context = [CIContext context];
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size];
CGImageRelease(cgImage);
return ret;
}
@end @end

6
Cocoa/GBVisualizerView.h Normal file
View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
#include <Core/gb.h>
@interface GBVisualizerView : NSView
- (void)addSample:(GB_sample_t *)sample;
@end

71
Cocoa/GBVisualizerView.m Normal file
View File

@ -0,0 +1,71 @@
#import "GBVisualizerView.h"
#import "GBPaletteEditorController.h"
#include <Core/gb.h>
#define SAMPLE_COUNT 1024
static NSColor *color_to_effect_color(typeof(GB_PALETTE_DMG.colors[0]) color)
{
if (@available(macOS 10.10, *)) {
double tint = MAX(color.r, MAX(color.g, color.b)) + 64;
return [NSColor colorWithRed:color.r / tint
green:color.g / tint
blue:color.b / tint
alpha:tint/(255 + 64)];
}
return [NSColor colorWithRed:color.r / 255.0
green:color.g / 255.0
blue:color.b / 255.0
alpha:1.0];
}
@implementation GBVisualizerView
{
GB_sample_t _samples[SAMPLE_COUNT];
size_t _position;
}
- (void)drawRect:(NSRect)dirtyRect
{
const GB_palette_t *palette = [GBPaletteEditorController userPalette];
NSSize size = self.bounds.size;
[color_to_effect_color(palette->colors[0]) setFill];
NSRectFill(self.bounds);
NSBezierPath *line = [NSBezierPath bezierPath];
[line moveToPoint:NSMakePoint(0, size.height / 2)];
for (unsigned i = 0; i < SAMPLE_COUNT; i++) {
GB_sample_t *sample = _samples + ((i + _position) % SAMPLE_COUNT);
double volume = ((signed)sample->left + (signed)sample->right) / 32768.0;
[line lineToPoint:NSMakePoint(size.width * (i + 0.5) / SAMPLE_COUNT,
(volume + 1) * size.height / 2)];
}
[line lineToPoint:NSMakePoint(size.width, size.height / 2)];
[line setLineWidth:1.0];
[color_to_effect_color(palette->colors[2]) setFill];
[line fill];
[color_to_effect_color(palette->colors[1]) setFill];
NSRectFill(NSMakeRect(0, size.height / 2 - 0.5, size.width, 1));
[color_to_effect_color(palette->colors[3]) setStroke];
[line stroke];
[super drawRect:dirtyRect];
}
- (void)addSample:(GB_sample_t *)sample
{
_samples[_position++] = *sample;
if (_position == SAMPLE_COUNT) {
_position = 0;
}
}
@end

View File

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

View File

@ -48,6 +48,46 @@
<key>NSDocumentClass</key> <key>NSDocumentClass</key>
<string>Document</string> <string>Document</string>
</dict> </dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>isx</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy ISX File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.github.liji32.sameboy.isx</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gbs</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy Sound File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.github.liji32.sameboy.gbs</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
</array> </array>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>SameBoy</string> <string>SameBoy</string>
@ -72,7 +112,7 @@
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>10.9</string> <string>10.9</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright © 2015-2020 Lior Halphon</string> <string>Copyright © 2015-2021 Lior Halphon</string>
<key>NSMainNibFile</key> <key>NSMainNibFile</key>
<string>MainMenu</string> <string>MainMenu</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
@ -117,6 +157,44 @@
</array> </array>
</dict> </dict>
</dict> </dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Game Boy ISX File</string>
<key>UTTypeIconFile</key>
<string>ColorCartridge</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.isx</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>isx</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Game Boy Sound File</string>
<key>UTTypeIconFile</key>
<string>ColorCartridge</string>
<key>UTTypeIdentifier</key>
<string>com.github.liji32.sameboy.gbs</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gbs</string>
</array>
</dict>
</dict>
</array> </array>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>SameBoy needs to access your camera to emulate the Game Boy Camera</string> <string>SameBoy needs to access your camera to emulate the Game Boy Camera</string>

BIN
Cocoa/Joypad~dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -30,7 +30,7 @@
<h1>SameBoy</h1> <h1>SameBoy</h1>
<h2>MIT License</h2> <h2>MIT License</h2>
<h3>Copyright © 2015-2020 Lior Halphon</h3> <h3>Copyright © 2015-2021 Lior Halphon</h3>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy <p>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -12,7 +12,11 @@
</customObject> </customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/> <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"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6"> <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items> <items>
@ -312,12 +316,35 @@
</menu> </menu>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/> <menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/>
<menuItem title="Save Screenshot" keyEquivalent="s" id="0J3-yf-iXs">
<connections>
<action selector="saveScreenshot:" target="-1" id="gJd-ml-J8p"/>
</connections>
</menuItem>
<menuItem title="Save Screenshot As…" alternate="YES" keyEquivalent="s" id="98X-Fp-Uny">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="saveScreenshotAs:" target="-1" id="Cxc-Gx-ql1"/>
</connections>
</menuItem>
<menuItem title="Copy Screenshot" keyEquivalent="S" id="vbX-pB-QC8">
<connections>
<action selector="copyScreenshot:" target="-1" id="XJC-EB-HNl"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="zk7-gf-LXN"/>
<menuItem title="Game Boy" tag="1" id="g7C-LA-VAr"> <menuItem title="Game Boy" tag="1" id="g7C-LA-VAr">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="reset:" target="-1" id="rxG-cz-s1S"/> <action selector="reset:" target="-1" id="rxG-cz-s1S"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Game Boy Pocket/Light" tag="5" id="1bM-CT-hoW">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="reset:" target="-1" id="U7l-BM-kB1"/>
</connections>
</menuItem>
<menuItem title="Super Game Boy" tag="4" id="vc7-yy-ARW"> <menuItem title="Super Game Boy" tag="4" id="vc7-yy-ARW">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
@ -373,18 +400,35 @@
<action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/> <action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Game Link Cable &amp; Infrared" id="V4S-Fo-xJK">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Game Link Cable &amp; Infrared" id="6sJ-Wz-QLj">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PMY-5j-25T"/>
</connections>
</menu>
<connections>
<action selector="nop:" target="Voe-Tx-rLC" id="Bpa-0C-lkN"/>
</connections>
</menuItem>
<menuItem title="Game Boy Printer" id="zHR-Ha-pOR"> <menuItem title="Game Boy Printer" id="zHR-Ha-pOR">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/> <action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Workboy" id="lo9-CX-BJj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="connectWorkboy:" target="-1" id="6vS-bq-wAX"/>
</connections>
</menuItem>
</items> </items>
</menu> </menu>
</menuItem> </menuItem>
<menuItem title="Developer" id="IwX-DJ-dBk"> <menuItem title="Develop" id="IwX-DJ-dBk">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Developer" id="UVb-cc-at0"> <menu key="submenu" title="Develop" id="UVb-cc-at0">
<items> <items>
<menuItem title="Developer Mode" id="Qx6-Tt-zZR"> <menuItem title="Developer Mode" id="Qx6-Tt-zZR">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
@ -412,6 +456,19 @@
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="M6n-8G-LZS"/> <menuItem isSeparatorItem="YES" id="M6n-8G-LZS"/>
<menuItem title="Show Background and Window" state="on" id="yfD-Qd-zoz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleDisplayBackground:" target="-1" id="p5b-1n-SPR"/>
</connections>
</menuItem>
<menuItem title="Show Objects" state="on" id="OWx-a0-vQk">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleDisplayObjects:" target="-1" id="8ie-ey-739"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="afI-BR-65k"/>
<menuItem title="Show Memory" id="UIa-n7-LSa"> <menuItem title="Show Memory" id="UIa-n7-LSa">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>

View File

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

BIN
Cocoa/Next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
Cocoa/Next@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
Cocoa/Pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
Cocoa/Pause@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
Cocoa/Play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
Cocoa/Play@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because it is too large Load Diff

BIN
Cocoa/Previous.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
Cocoa/Previous@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
Cocoa/Rewind.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
Cocoa/Rewind@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
Cocoa/Speaker~dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

139
Cocoa/UpdateWindow.xib Normal file
View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="14868"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AppDelegate">
<connections>
<outlet property="updateChanges" destination="ATt-VM-cb5" id="hJj-Nd-FBv"/>
<outlet property="updateProgressButton" destination="7nO-AA-WmG" id="wTa-9l-cOG"/>
<outlet property="updateProgressLabel" destination="wIm-GX-c6B" id="URp-JG-6wR"/>
<outlet property="updateProgressSpinner" destination="fqq-Nb-THz" id="4vC-m5-ysO"/>
<outlet property="updateProgressWindow" destination="2Gy-QG-FoA" id="RXw-50-DQh"/>
<outlet property="updateWindow" destination="QvC-M9-y7g" id="iwP-kC-tmG"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Update Available" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="360"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xZt-UI-Wl2">
<rect key="frame" x="338" y="13" width="128" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Install Update" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Mav-rY-sJo">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="installUpdate:" target="-2" id="jJc-CY-4vz"/>
</connections>
</button>
<button verticalHuggingPriority="750" id="b3S-YN-iDZ">
<rect key="frame" x="242" y="13" width="96" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Not Now" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="QsJ-cv-hwF">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="performClose:" target="QvC-M9-y7g" id="8qO-ac-k7U"/>
</connections>
</button>
<button verticalHuggingPriority="750" id="uOb-4Q-S8o">
<rect key="frame" x="14" y="13" width="128" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Skip Version" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ai5-6n-dvH">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="skipVersion:" target="-2" id="Jf8-Qe-6X0"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Swh-S6-6XA">
<rect key="frame" x="18" y="313" width="444" height="27"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="A new version of SameBoy is available with the following changes:" id="WsO-pC-VO7">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="9Tu-Q3-l40">
<rect key="frame" x="0.0" y="302" width="480" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="dva-Ay-nnl">
<rect key="frame" x="0.0" y="58" width="480" height="4"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</box>
<webView maintainsBackForwardList="NO" id="ATt-VM-cb5">
<rect key="frame" x="0.0" y="61" width="480" height="243"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<webPreferences key="preferences" defaultFontSize="13" defaultFixedFontSize="13" minimumFontSize="0" plugInsEnabled="NO" javaEnabled="NO" javaScriptEnabled="NO" javaScriptCanOpenWindowsAutomatically="NO" loadsImagesAutomatically="NO" allowsAnimatedImages="NO" allowsAnimatedImageLooping="NO">
<nil key="identifier"/>
</webPreferences>
<connections>
<outlet property="UIDelegate" destination="-2" id="xQ1-eY-1hu"/>
<outlet property="frameLoadDelegate" destination="-2" id="BOf-df-5LR"/>
</connections>
</webView>
</subviews>
</view>
<point key="canvasLocation" x="217" y="267"/>
</window>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="2Gy-QG-FoA">
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="283" y="305" width="512" height="61"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="jjT-Z5-15Q">
<rect key="frame" x="0.0" y="0.0" width="512" height="61"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<progressIndicator wantsLayer="YES" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fqq-Nb-THz">
<rect key="frame" x="20" y="15" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7nO-AA-WmG">
<rect key="frame" x="417" y="13" width="82" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6l6-qX-gsr">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="updateAction:" target="-2" id="geO-Gk-xrs"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wIm-GX-c6B">
<rect key="frame" x="58" y="15" width="359" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Downloading update..." id="qmF-X1-v5B">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<point key="canvasLocation" x="11" y="-203.5"/>
</window>
</objects>
</document>

View File

@ -1,748 +0,0 @@
/*
Joypad support is based on a stripped-down version of SDL's Darwin implementation
of the Joystick API, under the following license:
*/
/*
Simple DirectMedia Layer
Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <AppKit/AppKit.h>
#include <stdint.h>
#include <stdbool.h>
#include <IOKit/hid/IOHIDLib.h>
#include "GBJoystickListener.h"
typedef signed SDL_JoystickID;
typedef struct _SDL_Joystick SDL_Joystick;
typedef struct _SDL_JoystickAxisInfo
{
int16_t initial_value; /* Initial axis state */
int16_t value; /* Current axis state */
int16_t zero; /* Zero point on the axis (-32768 for triggers) */
bool has_initial_value; /* Whether we've seen a value on the axis yet */
bool sent_initial_value; /* Whether we've sent the initial axis value */
} SDL_JoystickAxisInfo;
struct _SDL_Joystick
{
SDL_JoystickID instance_id; /* Device instance, monotonically increasing from 0 */
char *name; /* Joystick name - system dependent */
int naxes; /* Number of axis controls on the joystick */
SDL_JoystickAxisInfo *axes;
int nbuttons; /* Number of buttons on the joystick */
uint8_t *buttons; /* Current button states */
int nhats;
uint8_t *hats;
struct joystick_hwdata *hwdata; /* Driver dependent information */
int ref_count; /* Reference count for multiple opens */
bool is_game_controller;
bool force_recentering; /* SDL_TRUE if this device needs to have its state reset to 0 */
struct _SDL_Joystick *next; /* pointer to next joystick we have allocated */
};
typedef struct {
uint8_t data[16];
} SDL_JoystickGUID;
struct recElement
{
IOHIDElementRef elementRef;
IOHIDElementCookie cookie;
uint32_t usagePage, usage; /* HID usage */
SInt32 min; /* reported min value possible */
SInt32 max; /* reported max value possible */
/* runtime variables used for auto-calibration */
SInt32 minReport; /* min returned value */
SInt32 maxReport; /* max returned value */
struct recElement *pNext; /* next element in list */
};
typedef struct recElement recElement;
struct joystick_hwdata
{
IOHIDDeviceRef deviceRef; /* HIDManager device handle */
io_service_t ffservice; /* Interface for force feedback, 0 = no ff */
char product[256]; /* name of product */
uint32_t usage; /* usage page from IOUSBHID Parser.h which defines general usage */
uint32_t usagePage; /* usage within above page from IOUSBHID Parser.h which defines specific usage */
int axes; /* number of axis (calculated, not reported by device) */
int buttons; /* number of buttons (calculated, not reported by device) */
int hats;
int elements; /* number of total elements (should be total of above) (calculated, not reported by device) */
recElement *firstAxis;
recElement *firstButton;
recElement *firstHat;
bool removed;
int instance_id;
SDL_JoystickGUID guid;
SDL_Joystick joystick;
};
typedef struct joystick_hwdata recDevice;
/* The base object of the HID Manager API */
static IOHIDManagerRef hidman = NULL;
/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
static int s_joystick_instance_id = -1;
#define SDL_JOYSTICK_AXIS_MAX 32767
void SDL_PrivateJoystickAxis(SDL_Joystick * joystick, uint8_t axis, int16_t value)
{
/* Make sure we're not getting garbage or duplicate events */
if (axis >= joystick->naxes) {
return;
}
if (!joystick->axes[axis].has_initial_value) {
joystick->axes[axis].initial_value = value;
joystick->axes[axis].value = value;
joystick->axes[axis].zero = value;
joystick->axes[axis].has_initial_value = true;
}
if (value == joystick->axes[axis].value) {
return;
}
if (!joystick->axes[axis].sent_initial_value) {
/* Make sure we don't send motion until there's real activity on this axis */
const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 controller needed 96 */
if (abs(value - joystick->axes[axis].value) <= MAX_ALLOWED_JITTER) {
return;
}
joystick->axes[axis].sent_initial_value = true;
joystick->axes[axis].value = value; /* Just so we pass the check above */
SDL_PrivateJoystickAxis(joystick, axis, joystick->axes[axis].initial_value);
}
/* Update internal joystick state */
joystick->axes[axis].value = value;
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
while (responder) {
if ([responder respondsToSelector:@selector(joystick:axis:movedTo:)]) {
[responder joystick:@(joystick->name) axis:axis movedTo:value];
break;
}
responder = (typeof(responder)) [responder nextResponder];
}
}
void SDL_PrivateJoystickButton(SDL_Joystick *joystick, uint8_t button, uint8_t state)
{
/* Make sure we're not getting garbage or duplicate events */
if (button >= joystick->nbuttons) {
return;
}
if (state == joystick->buttons[button]) {
return;
}
/* Update internal joystick state */
joystick->buttons[button] = state;
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
while (responder) {
if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) {
[responder joystick:@(joystick->name) button:button changedState:state];
break;
}
responder = (typeof(responder)) [responder nextResponder];
}
}
void SDL_PrivateJoystickHat(SDL_Joystick *joystick, uint8_t hat, uint8_t state)
{
/* Make sure we're not getting garbage or duplicate events */
if (hat >= joystick->nhats) {
return;
}
if (state == joystick->hats[hat]) {
return;
}
/* Update internal joystick state */
joystick->hats[hat] = state;
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
while (responder) {
if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) {
[responder joystick:@(joystick->name) hat:hat changedState:state];
break;
}
responder = (typeof(responder)) [responder nextResponder];
}
}
static void
FreeElementList(recElement *pElement)
{
while (pElement) {
recElement *pElementNext = pElement->pNext;
free(pElement);
pElement = pElementNext;
}
}
static recDevice *
FreeDevice(recDevice *removeDevice)
{
recDevice *pDeviceNext = NULL;
if (removeDevice) {
if (removeDevice->deviceRef) {
IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
removeDevice->deviceRef = NULL;
}
/* free element lists */
FreeElementList(removeDevice->firstAxis);
FreeElementList(removeDevice->firstButton);
FreeElementList(removeDevice->firstHat);
free(removeDevice);
}
return pDeviceNext;
}
static SInt32
GetHIDElementState(recDevice *pDevice, recElement *pElement)
{
SInt32 value = 0;
if (pDevice && pElement) {
IOHIDValueRef valueRef;
if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
/* record min and max for auto calibration */
if (value < pElement->minReport) {
pElement->minReport = value;
}
if (value > pElement->maxReport) {
pElement->maxReport = value;
}
}
}
return value;
}
static SInt32
GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
{
const float deviceScale = max - min;
const float readScale = pElement->maxReport - pElement->minReport;
const SInt32 value = GetHIDElementState(pDevice, pElement);
if (readScale == 0) {
return value; /* no scaling at all */
}
return ((value - pElement->minReport) * deviceScale / readScale) + min;
}
static void
JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
{
recDevice *device = (recDevice *) ctx;
device->removed = true;
device->deviceRef = NULL; // deviceRef was invalidated due to the remove
FreeDevice(device);
}
static void AddHIDElement(const void *value, void *parameter);
/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
static void
AddHIDElements(CFArrayRef array, recDevice *pDevice)
{
const CFRange range = { 0, CFArrayGetCount(array) };
CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
}
static bool
ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
while (listitem) {
if (listitem->cookie == cookie) {
return true;
}
listitem = listitem->pNext;
}
return false;
}
/* See if we care about this HID element, and if so, note it in our recDevice. */
static void
AddHIDElement(const void *value, void *parameter)
{
recDevice *pDevice = (recDevice *) parameter;
IOHIDElementRef refElement = (IOHIDElementRef) value;
const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
const uint32_t usage = IOHIDElementGetUsage(refElement);
recElement *element = NULL;
recElement **headElement = NULL;
/* look at types of interest */
switch (IOHIDElementGetType(refElement)) {
case kIOHIDElementTypeInput_Misc:
case kIOHIDElementTypeInput_Button:
case kIOHIDElementTypeInput_Axis: {
switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_X:
case kHIDUsage_GD_Y:
case kHIDUsage_GD_Z:
case kHIDUsage_GD_Rx:
case kHIDUsage_GD_Ry:
case kHIDUsage_GD_Rz:
case kHIDUsage_GD_Slider:
case kHIDUsage_GD_Dial:
case kHIDUsage_GD_Wheel:
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->axes++;
headElement = &(pDevice->firstAxis);
}
}
break;
case kHIDUsage_GD_Hatswitch:
if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->hats++;
headElement = &(pDevice->firstHat);
}
}
break;
case kHIDUsage_GD_DPadUp:
case kHIDUsage_GD_DPadDown:
case kHIDUsage_GD_DPadRight:
case kHIDUsage_GD_DPadLeft:
case kHIDUsage_GD_Start:
case kHIDUsage_GD_Select:
case kHIDUsage_GD_SystemMainMenu:
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->buttons++;
headElement = &(pDevice->firstButton);
}
}
break;
}
break;
case kHIDPage_Simulation:
switch (usage) {
case kHIDUsage_Sim_Rudder:
case kHIDUsage_Sim_Throttle:
case kHIDUsage_Sim_Accelerator:
case kHIDUsage_Sim_Brake:
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->axes++;
headElement = &(pDevice->firstAxis);
}
}
break;
default:
break;
}
break;
case kHIDPage_Button:
case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
element = (recElement *) calloc(1, sizeof (recElement));
if (element) {
pDevice->buttons++;
headElement = &(pDevice->firstButton);
}
}
break;
default:
break;
}
}
break;
case kIOHIDElementTypeCollection: {
CFArrayRef array = IOHIDElementGetChildren(refElement);
if (array) {
AddHIDElements(array, pDevice);
}
}
break;
default:
break;
}
if (element && headElement) { /* add to list */
recElement *elementPrevious = NULL;
recElement *elementCurrent = *headElement;
while (elementCurrent && usage >= elementCurrent->usage) {
elementPrevious = elementCurrent;
elementCurrent = elementCurrent->pNext;
}
if (elementPrevious) {
elementPrevious->pNext = element;
} else {
*headElement = element;
}
element->elementRef = refElement;
element->usagePage = usagePage;
element->usage = usage;
element->pNext = elementCurrent;
element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
element->cookie = IOHIDElementGetCookie(refElement);
pDevice->elements++;
}
}
}
static bool
GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
{
const uint16_t BUS_USB = 0x03;
const uint16_t BUS_BLUETOOTH = 0x05;
int32_t vendor = 0;
int32_t product = 0;
int32_t version = 0;
CFTypeRef refCF = NULL;
CFArrayRef array = NULL;
uint16_t *guid16 = (uint16_t *)pDevice->guid.data;
/* get usage page and usage */
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
}
if (pDevice->usagePage != kHIDPage_GenericDesktop) {
return false; /* Filter device list to non-keyboard/mouse stuff */
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
}
if ((pDevice->usage != kHIDUsage_GD_Joystick &&
pDevice->usage != kHIDUsage_GD_GamePad &&
pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
return false; /* Filter device list to non-keyboard/mouse stuff */
}
pDevice->deviceRef = hidDevice;
/* get device name */
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
if (!refCF) {
/* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
}
if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
}
memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
if (vendor && product) {
*guid16++ = BUS_USB;
*guid16++ = 0;
*guid16++ = vendor;
*guid16++ = 0;
*guid16++ = product;
*guid16++ = 0;
*guid16++ = version;
*guid16++ = 0;
} else {
*guid16++ = BUS_BLUETOOTH;
*guid16++ = 0;
strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
}
array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
if (array) {
AddHIDElements(array, pDevice);
CFRelease(array);
}
return true;
}
void
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
{
recDevice *device = joystick->hwdata;
recElement *element;
SInt32 value;
int i;
if (!device) {
return;
}
if (device->removed) { /* device was unplugged; ignore it. */
if (joystick->hwdata) {
joystick->force_recentering = true;
joystick->hwdata = NULL;
}
return;
}
element = device->firstAxis;
i = 0;
while (element) {
value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
SDL_PrivateJoystickAxis(joystick, i, value);
element = element->pNext;
++i;
}
element = device->firstButton;
i = 0;
while (element) {
value = GetHIDElementState(device, element);
if (value > 1) { /* handle pressure-sensitive buttons */
value = 1;
}
SDL_PrivateJoystickButton(joystick, i, value);
element = element->pNext;
++i;
}
element = device->firstHat;
i = 0;
while (element) {
signed range = (element->max - element->min + 1);
value = GetHIDElementState(device, element) - element->min;
if (range == 4) { /* 4 position hatswitch - scale up value */
value *= 2;
} else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */
value = -1;
}
if ((unsigned)value >= 8) {
value = -1;
}
SDL_PrivateJoystickHat(joystick, i, value);
element = element->pNext;
++i;
}
}
static void JoystickInputCallback(
SDL_Joystick * joystick,
IOReturn result,
void * _Nullable sender,
IOHIDReportType type,
uint32_t reportID,
uint8_t * report,
CFIndex reportLength)
{
SDL_SYS_JoystickUpdate(joystick);
}
static void
JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
{
recDevice *device;
io_service_t ioservice;
if (res != kIOReturnSuccess) {
return;
}
device = (recDevice *) calloc(1, sizeof(recDevice));
if (!device) {
abort();
return;
}
if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
free(device);
return; /* not a device we care about, probably. */
}
SDL_Joystick *joystick = &device->joystick;
joystick->instance_id = device->instance_id;
joystick->hwdata = device;
joystick->name = device->product;
joystick->naxes = device->axes;
joystick->nbuttons = device->buttons;
joystick->nhats = device->hats;
if (joystick->naxes > 0) {
joystick->axes = (SDL_JoystickAxisInfo *) calloc(joystick->naxes, sizeof(SDL_JoystickAxisInfo));
}
if (joystick->nbuttons > 0) {
joystick->buttons = (uint8_t *) calloc(joystick->nbuttons, 1);
}
if (joystick->nhats > 0) {
joystick->hats = (uint8_t *) calloc(joystick->nhats, 1);
}
/* Get notified when this device is disconnected. */
IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
static uint8_t junk[80];
IOHIDDeviceRegisterInputReportCallback(ioHIDDeviceObject, junk, sizeof(junk), (IOHIDReportCallback) JoystickInputCallback, joystick);
IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
/* Allocate an instance ID for this device */
device->instance_id = ++s_joystick_instance_id;
/* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
}
static bool
ConfigHIDManager(CFArrayRef matchingArray)
{
CFRunLoopRef runloop = CFRunLoopGetCurrent();
if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
return false;
}
IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
IOHIDManagerScheduleWithRunLoop(hidman, runloop, kCFRunLoopDefaultMode);
return true; /* good to go. */
}
static CFDictionaryRef
CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
{
CFDictionaryRef retval = NULL;
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
if (pageNumRef && usageNumRef) {
retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
if (pageNumRef) {
CFRelease(pageNumRef);
}
if (usageNumRef) {
CFRelease(usageNumRef);
}
if (!retval) {
*okay = 0;
}
return retval;
}
static bool
CreateHIDManager(void)
{
bool retval = false;
int okay = 1;
const void *vals[] = {
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
};
const size_t numElements = sizeof(vals) / sizeof(vals[0]);
CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
size_t i;
for (i = 0; i < numElements; i++) {
if (vals[i]) {
CFRelease((CFTypeRef) vals[i]);
}
}
if (array) {
hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (hidman != NULL) {
retval = ConfigHIDManager(array);
}
CFRelease(array);
}
return retval;
}
void __attribute__((constructor)) SDL_SYS_JoystickInit(void)
{
if (!CreateHIDManager()) {
fprintf(stderr, "Joystick: Couldn't initialize HID Manager");
}
}

View File

@ -1,5 +1,6 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) { int main(int argc, const char * argv[])
{
return NSApplicationMain(argc, argv); return NSApplicationMain(argc, argv);
} }

1022
Core/apu.c

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include "gb_struct_def.h" #include "defs.h"
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
/* Speed = 1 / Length (in seconds) */ /* Speed = 1 / Length (in seconds) */
@ -46,12 +45,19 @@ enum GB_CHANNELS {
GB_N_CHANNELS GB_N_CHANNELS
}; };
typedef struct
{
bool locked:1;
bool clock:1; // Represents FOSY on channel 4
unsigned padding:6;
} GB_envelope_clock_t;
typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample); typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample);
typedef struct typedef struct
{ {
bool global_enable; bool global_enable;
uint8_t apu_cycles; uint16_t apu_cycles;
uint8_t samples[GB_N_CHANNELS]; uint8_t samples[GB_N_CHANNELS];
bool is_active[GB_N_CHANNELS]; bool is_active[GB_N_CHANNELS];
@ -64,23 +70,24 @@ typedef struct
uint8_t square_sweep_countdown; // In 128Hz uint8_t square_sweep_countdown; // In 128Hz
uint8_t square_sweep_calculate_countdown; // In 2 MHz 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; uint16_t shadow_sweep_sample_length;
bool sweep_enabled; bool unshifted_sweep;
bool sweep_decreasing; bool enable_zombie_calculate_stepping;
uint8_t channel_1_restart_hold;
uint16_t channel1_completed_addend;
struct { struct {
uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks
uint8_t current_volume; // Reloaded from NRX2 uint8_t current_volume; // Reloaded from NRX2
uint8_t volume_countdown; // Reloaded from NRX2 uint8_t volume_countdown; // Reloaded from NRX2
uint8_t current_sample_index; /* For save state compatibility, uint8_t current_sample_index;
highest bit is reused (See NR14/NR24's bool sample_surpressed;
write code)*/
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint16_t sample_length; // From NRX3, NRX4, in APU ticks uint16_t sample_length; // From NRX3, NRX4, in APU ticks
bool length_enabled; // NRX4 bool length_enabled; // NRX4
GB_envelope_clock_t envelope_clock;
} square_channels[2]; } square_channels[2];
struct { struct {
@ -92,10 +99,10 @@ typedef struct
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint8_t current_sample_index; uint8_t current_sample_index;
uint8_t current_sample; // Current sample before shifting. uint8_t current_sample_byte; // Current sample byte.
int8_t wave_form[32];
bool wave_form_just_read; bool wave_form_just_read;
bool pulsed;
uint8_t bugged_read_countdown;
} wave_channel; } wave_channel;
struct { struct {
@ -105,20 +112,25 @@ typedef struct
uint16_t lfsr; uint16_t lfsr;
bool narrow; bool narrow;
uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length) uint8_t counter_countdown; // Counts from 0-7 to 0 to tick counter (Scaled from 512KHz to 2MHz)
uint16_t sample_length; // From NR43, in APU ticks uint16_t counter; // A bit from this 14-bit register ticks LFSR
bool length_enabled; // NR44 bool length_enabled; // NR44
uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of
// 1MHz. This variable keeps track of the alignment. // 1MHz. This variable keeps track of the alignment.
bool current_lfsr_sample;
int8_t delta;
bool countdown_reloaded;
uint8_t dmg_delayed_start;
GB_envelope_clock_t envelope_clock;
} noise_channel; } noise_channel;
#define GB_SKIP_DIV_EVENT_INACTIVE 0 enum {
#define GB_SKIP_DIV_EVENT_SKIPPED 1 GB_SKIP_DIV_EVENT_INACTIVE,
#define GB_SKIP_DIV_EVENT_SKIP 2 GB_SKIP_DIV_EVENT_SKIPPED,
uint8_t skip_div_event; GB_SKIP_DIV_EVENT_SKIP,
bool current_lfsr_sample; } skip_div_event:8;
uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch
} GB_apu_t; } GB_apu_t;
typedef enum { typedef enum {
@ -131,8 +143,7 @@ typedef enum {
typedef struct { typedef struct {
unsigned sample_rate; unsigned sample_rate;
double sample_cycles; // In 8 MHz units unsigned sample_cycles; // Counts by sample_rate until it reaches the clock frequency
double cycles_per_sample;
// Samples are NOT normalized to MAX_CH_AMP * 4 at this stage! // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage!
unsigned cycles_since_render; unsigned cycles_since_render;
@ -147,22 +158,24 @@ typedef struct {
GB_sample_callback_t sample_callback; GB_sample_callback_t sample_callback;
bool rate_set_in_clocks; double interference_volume;
double interference_highpass;
} GB_apu_output_t; } GB_apu_output_t;
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */ void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
void GB_set_interference_volume(GB_gameboy_t *gb, double volume);
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); internal bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); internal void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); internal uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
void GB_apu_div_event(GB_gameboy_t *gb); internal void GB_apu_div_event(GB_gameboy_t *gb);
void GB_apu_init(GB_gameboy_t *gb); internal void GB_apu_div_secondary_event(GB_gameboy_t *gb);
void GB_apu_run(GB_gameboy_t *gb); internal void GB_apu_init(GB_gameboy_t *gb);
void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); internal void GB_apu_run(GB_gameboy_t *gb, bool force);
void GB_borrow_sgb_border(GB_gameboy_t *gb);
#endif #endif
#endif /* apu_h */ #endif /* apu_h */

View File

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

View File

@ -1,7 +1,7 @@
#ifndef camera_h #ifndef camera_h
#define camera_h #define camera_h
#include <stdint.h> #include <stdint.h>
#include "gb_struct_def.h" #include "defs.h"
typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y); typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y);
typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb); typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb);

View File

@ -32,10 +32,11 @@ static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value) void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
{ {
if (!gb->cheat_enabled) return; if (likely(!gb->cheat_enabled)) return;
if (!gb->boot_rom_finished) return; if (likely(gb->cheat_count == 0)) return; // Optimization
if (unlikely(!gb->boot_rom_finished)) return;
const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)]; const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)];
if (hash) { if (unlikely(hash)) {
for (unsigned i = 0; i < hash->size; i++) { for (unsigned i = 0; i < hash->size; i++) {
GB_cheat_t *cheat = hash->cheats[i]; GB_cheat_t *cheat = hash->cheats[i];
if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) { if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) {
@ -69,7 +70,7 @@ void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, u
cheat->enabled = enabled; cheat->enabled = enabled;
strncpy(cheat->description, description, sizeof(cheat->description)); strncpy(cheat->description, description, sizeof(cheat->description));
cheat->description[sizeof(cheat->description) - 1] = 0; 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->cheats[gb->cheat_count - 1] = cheat;
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)]; GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)];
@ -100,7 +101,7 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
gb->cheats = NULL; gb->cheats = NULL;
} }
else { else {
gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(*cheat)); gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(gb->cheats[0]));
} }
break; break;
} }
@ -109,7 +110,7 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
for (unsigned i = 0; i < (*hash)->size; i++) { for (unsigned i = 0; i < (*hash)->size; i++) {
if ((*hash)->cheats[i] == cheat) { if ((*hash)->cheats[i] == cheat) {
(*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
if ((*hash)->size == 0) { if ((*hash)->size == 0) {
free(*hash); free(*hash);
*hash = NULL; *hash = NULL;
@ -133,9 +134,6 @@ bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *descriptio
uint8_t value; uint8_t value;
uint16_t address; uint16_t address;
if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) { if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
if (address > 0x7FFF) {
return false;
}
if (bank >= 0x80) { if (bank >= 0x80) {
bank &= 0xF; bank &= 0xF;
} }
@ -144,7 +142,7 @@ bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *descriptio
} }
} }
/* GameGnie */ /* GameGenie */
{ {
char stripped_cheat[10] = {0,}; char stripped_cheat[10] = {0,};
for (unsigned i = 0; i < 9 && *cheat; i++) { for (unsigned i = 0; i < 9 && *cheat; i++) {
@ -203,7 +201,7 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des
GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
for (unsigned i = 0; i < (*hash)->size; i++) { for (unsigned i = 0; i < (*hash)->size; i++) {
if ((*hash)->cheats[i] == cheat) { if ((*hash)->cheats[i] == cheat) {
(*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
if ((*hash)->size == 0) { if ((*hash)->size == 0) {
free(*hash); free(*hash);
*hash = NULL; *hash = NULL;
@ -225,7 +223,7 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des
} }
else { else {
(*hash)->size++; (*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; (*hash)->cheats[(*hash)->size - 1] = cheat;
} }
} }
@ -253,7 +251,7 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path)
uint32_t struct_size = 0; uint32_t struct_size = 0;
fread(&magic, sizeof(magic), 1, f); fread(&magic, sizeof(magic), 1, f);
fread(&struct_size, sizeof(struct_size), 1, f); fread(&struct_size, sizeof(struct_size), 1, f);
if (magic != CHEAT_MAGIC && magic != __builtin_bswap32(CHEAT_MAGIC)) { if (magic != LE32(CHEAT_MAGIC) && magic != BE32(CHEAT_MAGIC)) {
GB_log(gb, "The file is not a SameBoy cheat database"); GB_log(gb, "The file is not a SameBoy cheat database");
return; return;
} }
@ -270,7 +268,7 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path)
GB_cheat_t cheat; GB_cheat_t cheat;
while (fread(&cheat, sizeof(cheat), 1, f)) { while (fread(&cheat, sizeof(cheat), 1, f)) {
if (magic == __builtin_bswap32(CHEAT_MAGIC)) { if (magic != CHEAT_MAGIC) {
cheat.address = __builtin_bswap16(cheat.address); cheat.address = __builtin_bswap16(cheat.address);
cheat.bank = __builtin_bswap16(cheat.bank); cheat.bank = __builtin_bswap16(cheat.bank);
} }

View File

@ -1,6 +1,6 @@
#ifndef cheats_h #ifndef cheats_h
#define cheats_h #define cheats_h
#include "gb_struct_def.h" #include "defs.h"
#define GB_CHEAT_ANY_BANK 0xFFFF #define GB_CHEAT_ANY_BANK 0xFFFF
@ -17,7 +17,11 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path);
int GB_save_cheats(GB_gameboy_t *gb, const char *path); int GB_save_cheats(GB_gameboy_t *gb, const char *path);
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); #ifdef GB_DISABLE_CHEATS
#define GB_apply_cheat(...)
#else
internal void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value);
#endif
#endif #endif
typedef struct { typedef struct {

View File

@ -131,30 +131,25 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer
symbol = NULL; symbol = NULL;
} }
/* Avoid overflow */
if (symbol && strlen(symbol->name) > 240) {
symbol = NULL;
}
if (!symbol) { if (!symbol) {
sprintf(output, "$%04x", value); snprintf(output, sizeof(output), "$%04x", value);
} }
else if (symbol->addr == value) { else if (symbol->addr == value) {
if (prefer_name) { if (prefer_name) {
sprintf(output, "%s ($%04x)", symbol->name, value); snprintf(output, sizeof(output), "%s ($%04x)", symbol->name, value);
} }
else { else {
sprintf(output, "$%04x (%s)", value, symbol->name); snprintf(output, sizeof(output), "$%04x (%s)", value, symbol->name);
} }
} }
else { else {
if (prefer_name) { if (prefer_name) {
sprintf(output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); snprintf(output, sizeof(output), "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value);
} }
else { else {
sprintf(output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); snprintf(output, sizeof(output), "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr);
} }
} }
return output; return output;
@ -171,30 +166,25 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo
symbol = NULL; symbol = NULL;
} }
/* Avoid overflow */
if (symbol && strlen(symbol->name) > 240) {
symbol = NULL;
}
if (!symbol) { if (!symbol) {
sprintf(output, "$%02x:$%04x", value.bank, value.value); snprintf(output, sizeof(output), "$%02x:$%04x", value.bank, value.value);
} }
else if (symbol->addr == value.value) { else if (symbol->addr == value.value) {
if (prefer_name) { if (prefer_name) {
sprintf(output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); snprintf(output, sizeof(output), "%s ($%02x:$%04x)", symbol->name, value.bank, value.value);
} }
else { else {
sprintf(output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); snprintf(output, sizeof(output), "$%02x:$%04x (%s)", value.bank, value.value, symbol->name);
} }
} }
else { else {
if (prefer_name) { if (prefer_name) {
sprintf(output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); snprintf(output, sizeof(output), "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value);
} }
else { else {
sprintf(output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); snprintf(output, sizeof(output), "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr);
} }
} }
return output; return output;
@ -298,13 +288,15 @@ static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value)
static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);} static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);}
static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);} static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);}
static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);} static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);}
static value_t _div(value_t a, value_t b) { static value_t _div(value_t a, value_t b)
{
if (b.value == 0) { if (b.value == 0) {
return FIX_BANK(0); return FIX_BANK(0);
} }
return FIX_BANK(a.value / b.value); return FIX_BANK(a.value / b.value);
}; };
static value_t mod(value_t a, value_t b) { static value_t mod(value_t a, value_t b)
{
if (b.value == 0) { if (b.value == 0) {
return FIX_BANK(0); return FIX_BANK(0);
} }
@ -380,8 +372,7 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
length--; length--;
} }
if (length == 0) if (length == 0) {
{
GB_log(gb, "Expected expression.\n"); GB_log(gb, "Expected expression.\n");
*error = true; *error = true;
return (lvalue_t){0,}; return (lvalue_t){0,};
@ -437,23 +428,23 @@ static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) {
if (length == 1) { if (length == 1) {
switch (string[0]) { switch (string[0]) {
case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]}; case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->af};
case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]}; case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->af};
case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]}; case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->bc};
case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]}; case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->bc};
case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]}; case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->de};
case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]}; case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->de};
case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]}; case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->hl};
case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]}; case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->hl};
} }
} }
else if (length == 2) { else if (length == 2) {
switch (string[0]) { switch (string[0]) {
case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->af};
case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->bc};
case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->de};
case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->hl};
case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->sp};
case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc};
} }
} }
@ -472,7 +463,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
size_t length, bool *error, size_t length, bool *error,
uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) 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; uint16_t n_watchpoints = gb->n_watchpoints;
gb->n_watchpoints = 0; gb->n_watchpoints = 0;
@ -487,8 +478,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
length--; length--;
} }
if (length == 0) if (length == 0) {
{
GB_log(gb, "Expected expression.\n"); GB_log(gb, "Expected expression.\n");
*error = true; *error = true;
goto exit; goto exit;
@ -616,23 +606,23 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) {
if (length == 1) { if (length == 1) {
switch (string[0]) { switch (string[0]) {
case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit; case 'a': ret = VALUE_16(gb->af >> 8); goto exit;
case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit; case 'f': ret = VALUE_16(gb->af & 0xFF); goto exit;
case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit; case 'b': ret = VALUE_16(gb->bc >> 8); goto exit;
case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit; case 'c': ret = VALUE_16(gb->bc & 0xFF); goto exit;
case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit; case 'd': ret = VALUE_16(gb->de >> 8); goto exit;
case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit; case 'e': ret = VALUE_16(gb->de & 0xFF); goto exit;
case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit; case 'h': ret = VALUE_16(gb->hl >> 8); goto exit;
case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit; case 'l': ret = VALUE_16(gb->hl & 0xFF); goto exit;
} }
} }
else if (length == 2) { else if (length == 2) {
switch (string[0]) { switch (string[0]) {
case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;} case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->af); goto exit;}
case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;} case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->bc); goto exit;}
case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;} case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->de); goto exit;}
case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;} case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->hl); goto exit;}
case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;} case 's': if (string[1] == 'p') {ret = VALUE_16(gb->sp); goto exit;}
case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;}
} }
} }
@ -689,6 +679,7 @@ exit:
struct debugger_command_s; 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 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 { typedef struct debugger_command_s {
const char *command; const char *command;
@ -697,6 +688,8 @@ typedef struct debugger_command_s {
const char *help_string; // Null if should not appear in help const char *help_string; // Null if should not appear in help
const char *arguments_format; // For usage message const char *arguments_format; // For usage message
const char *modifiers_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; } debugger_command_t;
static const char *lstrip(const char *str) static const char *lstrip(const char *str)
@ -818,20 +811,50 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
} }
GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->af, /* AF can't really be an address */
(gb->f & GB_CARRY_FLAG)? 'C' : '-', (gb->f & GB_CARRY_FLAG)? 'C' : '-',
(gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-',
(gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-',
(gb->f & GB_ZERO_FLAG)? 'Z' : '-'); (gb->f & GB_ZERO_FLAG)? 'Z' : '-');
GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); GB_log(gb, "BC = %s\n", value_to_string(gb, gb->bc, false));
GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); GB_log(gb, "DE = %s\n", value_to_string(gb, gb->de, false));
GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); GB_log(gb, "HL = %s\n", value_to_string(gb, gb->hl, false));
GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); GB_log(gb, "SP = %s\n", value_to_string(gb, gb->sp, false));
GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false));
GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled"); GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled");
return true; 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 */ /* Find the index of the closest breakpoint equal or greater to addr */
static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
{ {
@ -856,6 +879,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min; 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) static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
bool is_jump_to = true; bool is_jump_to = true;
@ -1023,6 +1105,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min; 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) static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
if (strlen(lstrip(arguments)) == 0) { if (strlen(lstrip(arguments)) == 0) {
@ -1260,6 +1355,19 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to)
return _should_break(gb, full_addr, 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) static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
if (strlen(lstrip(arguments)) == 0) { if (strlen(lstrip(arguments)) == 0) {
@ -1343,7 +1451,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
while (count) { while (count) {
GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); GB_log(gb, "%02x:%04x: ", addr.bank, addr.value);
for (unsigned i = 0; i < 16 && count; i++) { for (unsigned i = 0; i < 16 && count; i++) {
GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); GB_log(gb, "%02x ", GB_safe_read_memory(gb, addr.value + i));
count--; count--;
} }
addr.value += 16; addr.value += 16;
@ -1356,7 +1464,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
while (count) { while (count) {
GB_log(gb, "%04x: ", addr.value); GB_log(gb, "%04x: ", addr.value);
for (unsigned i = 0; i < 16 && count; i++) { for (unsigned i = 0; i < 16 && count; i++) {
GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); GB_log(gb, "%02x ", GB_safe_read_memory(gb, addr.value + i));
count--; count--;
} }
addr.value += 16; addr.value += 16;
@ -1415,7 +1523,9 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
const GB_cartridge_t *cartridge = gb->cartridge_type; const GB_cartridge_t *cartridge = gb->cartridge_type;
if (cartridge->has_ram) { if (cartridge->has_ram) {
GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); bool has_battery = gb->cartridge_type->has_battery &&
(gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 8));
GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", has_battery? " battery-backed": "", gb->mbc_ram_size);
} }
else { else {
GB_log(gb, "No cartridge RAM\n"); GB_log(gb, "No cartridge RAM\n");
@ -1431,15 +1541,18 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
[GB_MBC2] = "MBC2", [GB_MBC2] = "MBC2",
[GB_MBC3] = "MBC3", [GB_MBC3] = "MBC3",
[GB_MBC5] = "MBC5", [GB_MBC5] = "MBC5",
[GB_HUC1] = "HUC1", [GB_MBC7] = "MBC7",
[GB_HUC3] = "HUC3", [GB_HUC1] = "HUC-1",
[GB_HUC3] = "HUC-3",
}; };
GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]);
} }
GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank);
if (cartridge->has_ram) { if (cartridge->has_ram) {
GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank);
GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); if (gb->cartridge_type->mbc_type != GB_HUC1) {
GB_log(gb, "RAM is currently %s\n", gb->mbc_ram_enable? "enabled" : "disabled");
}
} }
if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) {
GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM");
@ -1455,8 +1568,9 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "No MBC\n"); GB_log(gb, "No MBC\n");
} }
if (cartridge->has_rumble) { if (gb->cartridge_type->has_rumble &&
GB_log(gb, "Cart contains a rumble pak\n"); (gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) {
GB_log(gb, "Cart contains a Rumble Pak\n");
} }
if (cartridge->has_rtc) { if (cartridge->has_rtc) {
@ -1493,8 +1607,12 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
return true; return true;
} }
GB_log(gb, "Ticks: %llu. (Resetting)\n", (unsigned long long)gb->debugger_ticks); GB_log(gb, "T-cycles: %llu\n", (unsigned long long)gb->debugger_ticks);
GB_log(gb, "M-cycles: %llu\n", (unsigned long long)gb->debugger_ticks / 4);
GB_log(gb, "Absolute 8MHz ticks: %llu\n", (unsigned long long)gb->absolute_debugger_ticks);
GB_log(gb, "Tick count reset.\n");
gb->debugger_ticks = 0; gb->debugger_ticks = 0;
gb->absolute_debugger_ticks = 0;
return true; return true;
} }
@ -1521,9 +1639,9 @@ static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const d
} }
} }
GB_log(gb, "Sprites palettes: \n"); GB_log(gb, "Object palettes: \n");
for (unsigned i = 0; i < 32; i++) { for (unsigned i = 0; i < 32; i++) {
GB_log(gb, "%04x ", ((uint16_t *)&gb->sprite_palettes_data)[i]); GB_log(gb, "%04x ", ((uint16_t *)&gb->object_palettes_data)[i]);
if (i % 4 == 3) { if (i % 4 == 3) {
GB_log(gb, "\n"); GB_log(gb, "\n");
} }
@ -1541,7 +1659,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
} }
GB_log(gb, "LCDC:\n"); GB_log(gb, "LCDC:\n");
GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled"); GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled");
GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Sprite priority flags" : "Background and Window"), GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Object priority flags" : "Background and Window"),
(gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled"); (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled");
GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled"); GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled");
GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8"); GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8");
@ -1654,15 +1772,22 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n", GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n",
duty > 3? "" : (const char *[]){"12.5", " 25", " 50", " 75"}[duty], duty > 3? "" : (const char *[]){"12.5", " 25", " 50", " 75"}[duty],
duty > 3? "" : (const char *[]){"_______-", "-______-", "-____---", "_------_"}[duty], duty > 3? "" : (const char *[]){"_______-", "-______-", "-____---", "_------_"}[duty],
gb->apu.square_channels[channel].current_sample_index & 0x7f, gb->apu.square_channels[channel].current_sample_index,
gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); gb->apu.square_channels[channel].sample_surpressed ? " (suppressed)" : "");
if (channel == GB_SQUARE_1) { if (channel == GB_SQUARE_1) {
GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n", GB_log(gb, " Frequency sweep %s and %s\n",
gb->apu.sweep_enabled? "active" : "inactive", ((gb->io_registers[GB_IO_NR10] & 0x7) && (gb->io_registers[GB_IO_NR10] & 0x70))? "active" : "inactive",
gb->apu.sweep_decreasing? "decreasing" : "increasing", (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); 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) { if (gb->apu.square_channels[channel].length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n", GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
@ -1673,8 +1798,9 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "\nCH3:\n"); GB_log(gb, "\nCH3:\n");
GB_log(gb, " Wave:"); GB_log(gb, " Wave:");
for (uint8_t i = 0; i < 32; i++) { for (uint8_t i = 0; i < 16; i++) {
GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]); GB_log(gb, "%s%X", i % 2? "" : " ", gb->io_registers[GB_IO_WAV_START + i] >> 4);
GB_log(gb, "%X", gb->io_registers[GB_IO_WAV_START + i] & 0xF);
} }
GB_log(gb, "\n"); GB_log(gb, "\n");
GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index); GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
@ -1694,10 +1820,10 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "\nCH4:\n"); 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.current_volume,
gb->apu.noise_channel.sample_length * 4 + 3, gb->apu.noise_channel.counter,
gb->apu.noise_channel.sample_countdown); gb->apu.noise_channel.counter_countdown);
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.noise_channel.volume_countdown, gb->apu.noise_channel.volume_countdown,
@ -1721,6 +1847,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
return true; 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) 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]))) { if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
@ -1743,8 +1882,11 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) { for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) {
for (uint8_t i = 0; i < 32; i++) { for (uint8_t i = 0; i < 32; i++) {
if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) { uint8_t sample = i & 1?
GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]); (gb->io_registers[GB_IO_WAV_START + i / 2] & 0xF) :
(gb->io_registers[GB_IO_WAV_START + i / 2] >> 4);
if ((sample & mask) == cur_val) {
GB_log(gb, "%X", sample);
} }
else { else {
GB_log(gb, "%c", i % 4 == 2 ? '-' : ' '); GB_log(gb, "%c", i % 4 == 2 ? '-' : ' ');
@ -1756,6 +1898,29 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
return true; 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_no_bess(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); static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command);
#define HELP_NEWLINE "\n " #define HELP_NEWLINE "\n "
@ -1766,9 +1931,10 @@ static const debugger_command_t commands[] = {
{"next", 1, next, "Run the next instruction, skipping over function calls"}, {"next", 1, next, "Run the next instruction, skipping over function calls"},
{"step", 1, step, "Run the next instruction, stepping into function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"},
{"finish", 1, finish, "Run until the current function returns"}, {"finish", 1, finish, "Run until the current function returns"},
{"undo", 1, undo, "Reverts the last command"},
{"backtrace", 2, backtrace, "Displays the current call stack"}, {"backtrace", 2, backtrace, "Displays the current call stack"},
{"bt", 2, }, /* Alias */ {"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 {"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
"used"}, "used"},
{"registers", 1, registers, "Print values of processor registers and other important registers"}, {"registers", 1, registers, "Print values of processor registers and other important registers"},
@ -1777,29 +1943,33 @@ static const debugger_command_t commands[] = {
{"apu", 3, apu, "Displays information about the current state of the audio chip"}, {"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 {"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 "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"}, {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
{"palettes", 3, palettes, "Displays the current CGB palettes"}, {"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 {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
"Can also modify the condition of existing breakpoints." 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 "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
"jumping to the target.", "jumping to the target.",
"<expression>[ if <condition expression>]", "j"}, "<expression>[ if <condition expression>]", "j",
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"}, .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 {"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 "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
"Default watchpoint type is write-only.", "Default watchpoint type is write-only.",
"<expression>[ if <condition expression>]", "(r|w|rw)"}, "<expression>[ if <condition expression>]", "(r|w|rw)",
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"}, .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"}, {"list", 1, list, "List all set breakpoints and watchpoints"},
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE {"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 "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).", "decimal (d), hexadecimal (x), octal (o) or binary (b).",
"<expression>", "format"}, "<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
{"eval", 2, }, /* Alias */ {"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 */ {"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>]"}, {"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
@ -1874,7 +2044,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
gb->debug_stopped = true; gb->debug_stopped = true;
} }
else { else {
gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP]; gb->sp_for_call_depth[gb->debug_call_depth] = gb->sp;
gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc; gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc;
} }
} }
@ -1882,7 +2052,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) {
while (gb->backtrace_size) { while (gb->backtrace_size) {
if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) { if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->sp) {
gb->backtrace_size--; gb->backtrace_size--;
} }
else { else {
@ -1890,7 +2060,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
} }
} }
gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP]; gb->backtrace_sps[gb->backtrace_size] = gb->sp;
gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr);
gb->backtrace_returns[gb->backtrace_size].addr = call_addr; gb->backtrace_returns[gb->backtrace_size].addr = call_addr;
gb->backtrace_size++; gb->backtrace_size++;
@ -1911,9 +2081,9 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
gb->debug_stopped = true; gb->debug_stopped = true;
} }
else { else {
if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { if (gb->sp != gb->sp_for_call_depth[gb->debug_call_depth]) {
GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true)); GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true));
GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->sp,
gb->sp_for_call_depth[gb->debug_call_depth]); gb->sp_for_call_depth[gb->debug_call_depth]);
gb->debug_stopped = true; gb->debug_stopped = true;
} }
@ -1921,7 +2091,7 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
} }
while (gb->backtrace_size) { while (gb->backtrace_size) {
if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) { if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->sp) {
gb->backtrace_size--; gb->backtrace_size--;
} }
else { else {
@ -2027,6 +2197,9 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
return true; return true;
} }
GB_display_sync(gb);
GB_apu_run(gb, true);
char *command_string = input; char *command_string = input;
char *arguments = strchr(input, ' '); char *arguments = strchr(input, ' ');
if (arguments) { if (arguments) {
@ -2047,7 +2220,30 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
const debugger_command_t *command = find_command(command_string); const debugger_command_t *command = find_command(command_string);
if (command) { if (command) {
return command->implementation(gb, arguments, modifiers, command); uint8_t *old_state = malloc(GB_get_save_state_size_no_bess(gb));
GB_save_state_to_buffer_no_bess(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_no_bess(gb));
GB_save_state_to_buffer_no_bess(gb, new_state);
if (memcmp(new_state, old_state, GB_get_save_state_size_no_bess(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 { else {
GB_log(gb, "%s: no such command.\n", command_string); GB_log(gb, "%s: no such command.\n", command_string);
@ -2055,6 +2251,62 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
} }
} }
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 { typedef enum {
JUMP_TO_NONE, JUMP_TO_NONE,
JUMP_TO_BREAK, JUMP_TO_BREAK,
@ -2067,6 +2319,11 @@ void GB_debugger_run(GB_gameboy_t *gb)
{ {
if (gb->debug_disable) return; if (gb->debug_disable) return;
if (!gb->undo_state) {
gb->undo_state = malloc(GB_get_save_state_size_no_bess(gb));
GB_save_state_to_buffer_no_bess(gb, gb->undo_state);
}
char *input = NULL; char *input = NULL;
if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) { if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) {
gb->debug_stopped = true; gb->debug_stopped = true;
@ -2112,9 +2369,9 @@ next_command:
} }
else if (jump_to_result == JUMP_TO_NONTRIVIAL) { else if (jump_to_result == JUMP_TO_NONTRIVIAL) {
if (!gb->nontrivial_jump_state) { if (!gb->nontrivial_jump_state) {
gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb)); gb->nontrivial_jump_state = malloc(GB_get_save_state_size_no_bess(gb));
} }
GB_save_state_to_buffer(gb, gb->nontrivial_jump_state); GB_save_state_to_buffer_no_bess(gb, gb->nontrivial_jump_state);
gb->non_trivial_jump_breakpoint_occured = false; gb->non_trivial_jump_breakpoint_occured = false;
should_delete_state = false; should_delete_state = false;
} }
@ -2160,6 +2417,19 @@ void GB_debugger_handle_async_commands(GB_gameboy_t *gb)
} }
} }
void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol)
{
bank &= 0x1FF;
if (!gb->bank_symbols[bank]) {
gb->bank_symbols[bank] = GB_map_alloc();
}
GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol);
if (allocated_symbol) {
GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol);
}
}
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
{ {
FILE *f = fopen(path, "r"); FILE *f = fopen(path, "r");
@ -2182,14 +2452,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
char symbol[length]; char symbol[length];
if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) { if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) {
bank &= 0x1FF; GB_debugger_add_symbol(gb, bank, address, symbol);
if (!gb->bank_symbols[bank]) {
gb->bank_symbols[bank] = GB_map_alloc();
}
GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol);
if (allocated_symbol) {
GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol);
}
} }
} }
free(line); free(line);
@ -2282,7 +2545,7 @@ static bool is_in_trivial_memory(uint16_t addr)
return false; return false;
} }
typedef uint16_t GB_opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode); typedef uint16_t opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode);
uint16_t trivial_1(GB_gameboy_t *gb, uint8_t opcode) uint16_t trivial_1(GB_gameboy_t *gb, uint8_t opcode)
{ {
@ -2308,13 +2571,13 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode)
{ {
switch ((opcode >> 3) & 0x3) { switch ((opcode >> 3) & 0x3) {
case 0: case 0:
return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); return !(gb->af & GB_ZERO_FLAG);
case 1: case 1:
return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); return (gb->af & GB_ZERO_FLAG);
case 2: case 2:
return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); return !(gb->af & GB_CARRY_FLAG);
case 3: case 3:
return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); return (gb->af & GB_CARRY_FLAG);
} }
return false; return false;
@ -2331,8 +2594,8 @@ static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode)
static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode) static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode)
{ {
return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | return GB_read_memory(gb, gb->sp) |
(GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); (GB_read_memory(gb, gb->sp + 1) << 8);
} }
@ -2372,7 +2635,7 @@ static uint16_t jp_hl(GB_gameboy_t *gb, uint8_t opcode)
return gb->hl; return gb->hl;
} }
static GB_opcode_address_getter_t *opcodes[256] = { static opcode_address_getter_t *opcodes[256] = {
/* X0 X1 X2 X3 X4 X5 X6 X7 */ /* X0 X1 X2 X3 X4 X5 X6 X7 */
/* X8 X9 Xa Xb Xc Xd Xe Xf */ /* X8 X9 Xa Xb Xc Xd Xe Xf */
trivial_1, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 0X */ trivial_1, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 0X */
@ -2414,7 +2677,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE;
if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) ||
!is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { !is_in_trivial_memory(gb->sp) || !is_in_trivial_memory(gb->sp + 1)) {
return JUMP_TO_NONTRIVIAL; return JUMP_TO_NONTRIVIAL;
} }
@ -2450,7 +2713,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
return JUMP_TO_NONE; return JUMP_TO_NONE;
} }
GB_opcode_address_getter_t *getter = opcodes[opcode]; opcode_address_getter_t *getter = opcodes[opcode];
if (!getter) { if (!getter) {
gb->n_watchpoints = n_watchpoints; gb->n_watchpoints = n_watchpoints;
return JUMP_TO_NONE; return JUMP_TO_NONE;

View File

@ -2,27 +2,30 @@
#define debugger_h #define debugger_h
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "gb_struct_def.h" #include "defs.h"
#include "symbol_hash.h" #include "symbol_hash.h"
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
#ifdef DISABLE_DEBUGGER #ifdef GB_DISABLE_DEBUGGER
#define GB_debugger_run(gb) (void)0 #define GB_debugger_run(gb) (void)0
#define GB_debugger_handle_async_commands(gb) (void)0 #define GB_debugger_handle_async_commands(gb) (void)0
#define GB_debugger_ret_hook(gb) (void)0 #define GB_debugger_ret_hook(gb) (void)0
#define GB_debugger_call_hook(gb, addr) (void)addr #define GB_debugger_call_hook(gb, addr) (void)addr
#define GB_debugger_test_write_watchpoint(gb, addr, value) ((void)addr, (void)value) #define GB_debugger_test_write_watchpoint(gb, addr, value) ((void)addr, (void)value)
#define GB_debugger_test_read_watchpoint(gb, addr) (void)addr #define GB_debugger_test_read_watchpoint(gb, addr) (void)addr
#define GB_debugger_add_symbol(gb, bank, address, symbol) ((void)bank, (void)address, (void)symbol)
#else #else
void GB_debugger_run(GB_gameboy_t *gb); internal void GB_debugger_run(GB_gameboy_t *gb);
void GB_debugger_handle_async_commands(GB_gameboy_t *gb); internal void GB_debugger_handle_async_commands(GB_gameboy_t *gb);
void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr); internal void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr);
void GB_debugger_ret_hook(GB_gameboy_t *gb); internal void GB_debugger_ret_hook(GB_gameboy_t *gb);
void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); internal void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); internal void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr);
const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); internal const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr);
#endif /* DISABLE_DEBUGGER */ internal void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol);
#endif /* GB_DISABLE_DEBUGGER */
#endif #endif
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
@ -31,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G
void void
#endif #endif
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */ GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path); 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); const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);

46
Core/defs.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef defs_h
#define defs_h
#ifdef GB_INTERNAL
// "Keyword" definitions
#define likely(x) __builtin_expect((bool)(x), 1)
#define unlikely(x) __builtin_expect((bool)(x), 0)
#define internal __attribute__((visibility("internal")))
#if __clang__
#define unrolled _Pragma("unroll")
#elif __GNUC__ >= 8
#define unrolled _Pragma("GCC unroll 8")
#else
#define unrolled
#endif
#define unreachable() __builtin_unreachable();
#define nodefault default: unreachable()
#ifdef GB_BIG_ENDIAN
#define LE16(x) __builtin_bswap16(x)
#define LE32(x) __builtin_bswap32(x)
#define LE64(x) __builtin_bswap64(x)
#define BE16(x) (x)
#define BE32(x) (x)
#define BE64(x) (x)
#else
#define LE16(x) (x)
#define LE32(x) (x)
#define LE64(x) (x)
#define BE16(x) __builtin_bswap16(x)
#define BE32(x) __builtin_bswap32(x)
#define BE64(x) __builtin_bswap64(x)
#endif
#endif
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; })
#endif
struct GB_gameboy_s;
typedef struct GB_gameboy_s GB_gameboy_t;
#endif

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,14 @@
#include <stdint.h> #include <stdint.h>
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); internal void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force);
void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); internal void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
void GB_STAT_update(GB_gameboy_t *gb); internal void GB_STAT_update(GB_gameboy_t *gb);
void GB_lcd_off(GB_gameboy_t *gb); internal void GB_lcd_off(GB_gameboy_t *gb);
internal void GB_display_vblank(GB_gameboy_t *gb);
#define GB_display_sync(gb) GB_display_run(gb, 0, true)
enum { enum {
GB_OBJECT_PRIORITY_UNDEFINED, // For save state compatibility
GB_OBJECT_PRIORITY_X, GB_OBJECT_PRIORITY_X,
GB_OBJECT_PRIORITY_INDEX, GB_OBJECT_PRIORITY_INDEX,
}; };
@ -51,12 +52,21 @@ typedef enum {
GB_COLOR_CORRECTION_EMULATE_HARDWARE, GB_COLOR_CORRECTION_EMULATE_HARDWARE,
GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS,
GB_COLOR_CORRECTION_REDUCE_CONTRAST, GB_COLOR_CORRECTION_REDUCE_CONTRAST,
GB_COLOR_CORRECTION_LOW_CONTRAST,
} GB_color_correction_mode_t; } GB_color_correction_mode_t;
void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index);
void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type);
uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *object_height);
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border);
void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode);
void GB_set_light_temperature(GB_gameboy_t *gb, double temperature);
bool GB_is_odd_frame(GB_gameboy_t *gb); bool GB_is_odd_frame(GB_gameboy_t *gb);
void GB_set_object_rendering_disabled(GB_gameboy_t *gb, bool disabled);
void GB_set_background_rendering_disabled(GB_gameboy_t *gb, bool disabled);
bool GB_is_object_rendering_disabled(GB_gameboy_t *gb);
bool GB_is_background_rendering_disabled(GB_gameboy_t *gb);
#endif /* display_h */ #endif /* display_h */

940
Core/gb.c

File diff suppressed because it is too large Load Diff

296
Core/gb.h
View File

@ -3,9 +3,10 @@
#define typeof __typeof__ #define typeof __typeof__
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdalign.h>
#include <time.h> #include <time.h>
#include "gb_struct_def.h" #include "defs.h"
#include "save_state.h" #include "save_state.h"
#include "apu.h" #include "apu.h"
@ -22,26 +23,18 @@
#include "symbol_hash.h" #include "symbol_hash.h"
#include "sgb.h" #include "sgb.h"
#include "cheats.h" #include "cheats.h"
#include "rumble.h"
#include "workboy.h"
#include "random.h"
#define GB_STRUCT_VERSION 13 #define GB_STRUCT_VERSION 14
#define GB_MODEL_FAMILY_MASK 0xF00 #define GB_MODEL_FAMILY_MASK 0xF00
#define GB_MODEL_DMG_FAMILY 0x000 #define GB_MODEL_DMG_FAMILY 0x000
#define GB_MODEL_MGB_FAMILY 0x100 #define GB_MODEL_MGB_FAMILY 0x100
#define GB_MODEL_CGB_FAMILY 0x200 #define GB_MODEL_CGB_FAMILY 0x200
#define GB_MODEL_PAL_BIT 0x1000 #define GB_MODEL_PAL_BIT 0x40
#define GB_MODEL_NO_SFC_BIT 0x2000 #define GB_MODEL_NO_SFC_BIT 0x80
#ifdef GB_INTERNAL
#if __clang__
#define UNROLL _Pragma("unroll")
#elif __GNUC__
#define UNROLL _Pragma("GCC unroll 8")
#else
#define UNROLL
#endif
#endif
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define GB_BIG_ENDIAN #define GB_BIG_ENDIAN
@ -51,8 +44,9 @@
#error Unable to detect endianess #error Unable to detect endianess
#endif #endif
typedef struct { typedef struct {
struct { struct GB_color_s {
uint8_t r, g, b; uint8_t r, g, b;
} colors[5]; } colors[5];
} GB_palette_t; } GB_palette_t;
@ -70,9 +64,24 @@ typedef union {
uint8_t days; uint8_t days;
uint8_t high; uint8_t high;
}; };
struct {
uint8_t seconds;
uint8_t minutes;
uint8_t hours:5;
uint8_t weekday:3;
uint8_t weeks;
} tpp1;
uint8_t data[5]; uint8_t data[5];
} GB_rtc_time_t; } GB_rtc_time_t;
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 enum { typedef enum {
// GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_0 = 0x000,
// GB_MODEL_DMG_A = 0x001, // GB_MODEL_DMG_A = 0x001,
@ -84,14 +93,14 @@ typedef enum {
GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT,
GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC, GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC,
GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT, GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT,
// GB_MODEL_MGB = 0x100, GB_MODEL_MGB = 0x100,
GB_MODEL_SGB2 = 0x101, GB_MODEL_SGB2 = 0x101,
GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT,
// GB_MODEL_CGB_0 = 0x200, GB_MODEL_CGB_0 = 0x200,
// GB_MODEL_CGB_A = 0x201, GB_MODEL_CGB_A = 0x201,
// GB_MODEL_CGB_B = 0x202, GB_MODEL_CGB_B = 0x202,
GB_MODEL_CGB_C = 0x203, GB_MODEL_CGB_C = 0x203,
// GB_MODEL_CGB_D = 0x204, GB_MODEL_CGB_D = 0x204,
GB_MODEL_CGB_E = 0x205, GB_MODEL_CGB_E = 0x205,
GB_MODEL_AGB = 0x206, GB_MODEL_AGB = 0x206,
} GB_model_t; } GB_model_t;
@ -102,6 +111,7 @@ enum {
GB_REGISTER_DE, GB_REGISTER_DE,
GB_REGISTER_HL, GB_REGISTER_HL,
GB_REGISTER_SP, GB_REGISTER_SP,
GB_REGISTER_PC,
GB_REGISTERS_16_BIT /* Count */ GB_REGISTERS_16_BIT /* Count */
}; };
@ -119,8 +129,6 @@ typedef enum {
GB_BORDER_ALWAYS, GB_BORDER_ALWAYS,
} GB_border_mode_t; } GB_border_mode_t;
#define GB_MAX_IR_QUEUE 256
enum { enum {
/* Joypad and Serial */ /* Joypad and Serial */
GB_IO_JOYP = 0x00, // Joypad (R/W) GB_IO_JOYP = 0x00, // Joypad (R/W)
@ -182,10 +190,7 @@ enum {
GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only
GB_IO_WY = 0x4a, // Window Y Position (R/W) GB_IO_WY = 0x4a, // Window Y Position (R/W)
GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W) GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W)
// Has some undocumented compatibility flags written at boot. // Controls DMG mode and PGB mode
// Unfortunately it is not readable or writable after boot has finished, so research of this
// register is quite limited. The value written to this register, however, can be controlled
// in some cases.
GB_IO_KEY0 = 0x4c, GB_IO_KEY0 = 0x4c,
/* General CGB features */ /* General CGB features */
@ -211,20 +216,19 @@ enum {
/* CGB Paletts */ /* CGB Paletts */
GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index
GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data
GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index GB_IO_OBPI = 0x6a, // CGB Mode Only - Object Palette Index
GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data GB_IO_OBPD = 0x6b, // CGB Mode Only - Object Palette Data
GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based) GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based)
/* Missing */ /* Missing */
GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank
GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write) GB_IO_PSWX = 0x72, // X position of the palette switching window
GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write) GB_IO_PSWY = 0x73, // Y position of the palette switching window
GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only GB_IO_PSW = 0x74, // Key combo to trigger the palette switching window
GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write) GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write)
GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes GB_IO_PCM12 = 0x76, // Channels 1 and 2 amplitudes
GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes GB_IO_PCM34 = 0x77, // Channels 3 and 4 amplitudes
GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only
}; };
typedef enum { typedef enum {
@ -235,12 +239,12 @@ typedef enum {
} GB_log_attributes; } GB_log_attributes;
typedef enum { typedef enum {
GB_BOOT_ROM_DMG0, GB_BOOT_ROM_DMG_0,
GB_BOOT_ROM_DMG, GB_BOOT_ROM_DMG,
GB_BOOT_ROM_MGB, GB_BOOT_ROM_MGB,
GB_BOOT_ROM_SGB, GB_BOOT_ROM_SGB,
GB_BOOT_ROM_SGB2, GB_BOOT_ROM_SGB2,
GB_BOOT_ROM_CGB0, GB_BOOT_ROM_CGB_0,
GB_BOOT_ROM_CGB, GB_BOOT_ROM_CGB,
GB_BOOT_ROM_AGB, GB_BOOT_ROM_AGB,
} GB_boot_rom_t; } GB_boot_rom_t;
@ -251,7 +255,6 @@ typedef enum {
#define SGB_NTSC_FREQUENCY (21477272 / 5) #define SGB_NTSC_FREQUENCY (21477272 / 5)
#define SGB_PAL_FREQUENCY (21281370 / 5) #define SGB_PAL_FREQUENCY (21281370 / 5)
#define DIV_CYCLES (0x100) #define DIV_CYCLES (0x100)
#define INTERNAL_DIV_CYCLES (0x40000)
#if !defined(MIN) #if !defined(MIN)
#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) #define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
@ -266,8 +269,8 @@ 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 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 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 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, bool rumble_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 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); typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb); typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb);
@ -277,10 +280,8 @@ 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_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 void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type);
typedef struct { typedef void (*GB_execution_callback_t)(GB_gameboy_t *gb, uint16_t address, uint8_t opcode);
bool state; typedef void (*GB_lcd_line_callback_t)(GB_gameboy_t *gb, uint8_t line);
uint64_t delay;
} GB_ir_queue_item_t;
struct GB_breakpoint_s; struct GB_breakpoint_s;
struct GB_watchpoint_s; struct GB_watchpoint_s;
@ -288,8 +289,8 @@ struct GB_watchpoint_s;
typedef struct { typedef struct {
uint8_t pixel; // Color, 0-3 uint8_t pixel; // Color, 0-3
uint8_t palette; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) uint8_t palette; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG)
uint8_t priority; // Sprite priority 0 in DMG, OAM index in CGB uint8_t priority; // Object priority 0 in DMG, OAM index in CGB
bool bg_priority; // For sprite FIFO the BG priority bit. For the BG FIFO  the CGB attributes priority bit bool bg_priority; // For object FIFO the BG priority bit. For the BG FIFO  the CGB attributes priority bit
} GB_fifo_item_t; } GB_fifo_item_t;
#define GB_FIFO_LENGTH 16 #define GB_FIFO_LENGTH 16
@ -299,6 +300,55 @@ typedef struct {
uint8_t write_end; uint8_t write_end;
} GB_fifo_t; } GB_fifo_t;
typedef struct {
uint32_t magic;
uint8_t track_count;
uint8_t first_track;
uint16_t load_address;
uint16_t init_address;
uint16_t play_address;
uint16_t sp;
uint8_t TMA;
uint8_t TAC;
char title[32];
char author[32];
char copyright[32];
} GB_gbs_header_t;
typedef struct {
uint8_t track_count;
uint8_t first_track;
char title[33];
char author[33];
char copyright[33];
} GB_gbs_info_t;
/* Duplicated so it can remain anonymous in GB_gameboy_t */
typedef union {
uint16_t registers[GB_REGISTERS_16_BIT];
struct {
uint16_t af,
bc,
de,
hl,
sp,
pc;
};
struct {
#ifdef GB_BIG_ENDIAN
uint8_t a, f,
b, c,
d, e,
h, l;
#else
uint8_t f, a,
c, b,
e, d,
l, h;
#endif
};
} GB_registers_t;
/* When state saving, each section is dumped independently of other sections. /* When state saving, each section is dumped independently of other sections.
This allows adding data to the end of the section without worrying about future compatibility. This allows adding data to the end of the section without worrying about future compatibility.
Some other changes might be "safe" as well. Some other changes might be "safe" as well.
@ -322,7 +372,6 @@ struct GB_gameboy_internal_s {
GB_SECTION(core_state, GB_SECTION(core_state,
/* Registers */ /* Registers */
uint16_t pc;
union { union {
uint16_t registers[GB_REGISTERS_16_BIT]; uint16_t registers[GB_REGISTERS_16_BIT];
struct { struct {
@ -330,7 +379,8 @@ struct GB_gameboy_internal_s {
bc, bc,
de, de,
hl, hl,
sp; sp,
pc;
}; };
struct { struct {
#ifdef GB_BIG_ENDIAN #ifdef GB_BIG_ENDIAN
@ -345,7 +395,6 @@ struct GB_gameboy_internal_s {
l, h; l, h;
#endif #endif
}; };
}; };
uint8_t ime; uint8_t ime;
uint8_t interrupt_enable; uint8_t interrupt_enable;
@ -367,6 +416,11 @@ struct GB_gameboy_internal_s {
GB_printer_t printer; GB_printer_t printer;
uint8_t extra_oam[0xff00 - 0xfea0]; uint8_t extra_oam[0xff00 - 0xfea0];
uint32_t ram_size; // Different between CGB and DMG uint32_t ram_size; // Different between CGB and DMG
GB_workboy_t workboy;
int32_t ir_sensor;
bool effective_ir_input;
uint16_t address_bus;
); );
/* DMA and HDMA */ /* DMA and HDMA */
@ -382,6 +436,8 @@ struct GB_gameboy_internal_s {
uint16_t dma_current_src; uint16_t dma_current_src;
int16_t dma_cycles; int16_t dma_cycles;
bool is_dma_restarting; bool is_dma_restarting;
uint8_t dma_and_pattern;
bool dma_skip_write;
uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */ uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */
bool hdma_starting; bool hdma_starting;
); );
@ -406,6 +462,7 @@ struct GB_gameboy_internal_s {
struct { struct {
uint8_t rom_bank:8; uint8_t rom_bank:8;
uint8_t ram_bank:3; uint8_t ram_bank:3;
bool rtc_mapped:1;
} mbc3; } mbc3;
struct { struct {
@ -414,21 +471,54 @@ struct GB_gameboy_internal_s {
uint8_t ram_bank:4; uint8_t ram_bank:4;
} mbc5; } mbc5;
struct {
uint8_t rom_bank;
uint16_t x_latch;
uint16_t y_latch;
bool latch_ready:1;
bool eeprom_do:1;
bool eeprom_di:1;
bool eeprom_clk:1;
bool eeprom_cs:1;
uint16_t eeprom_command:11;
uint16_t read_bits;
uint8_t bits_countdown:5;
bool secondary_ram_enable:1;
bool eeprom_write_enabled:1;
} mbc7;
struct { struct {
uint8_t bank_low:6; uint8_t bank_low:6;
uint8_t bank_high:3; uint8_t bank_high:3;
uint8_t mode:1; bool mode:1;
bool ir_mode:1;
} huc1; } huc1;
struct { struct {
uint8_t rom_bank; uint8_t rom_bank:7;
uint8_t ram_bank; uint8_t padding:1;
uint8_t ram_bank:4;
uint8_t mode;
uint8_t access_index;
uint16_t minutes, days;
uint16_t alarm_minutes, alarm_days;
bool alarm_enabled;
uint8_t read;
uint8_t access_flags;
} huc3; } huc3;
struct {
uint16_t rom_bank;
uint8_t ram_bank;
uint8_t mode;
} tpp1;
}; };
uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
bool camera_registers_mapped; bool camera_registers_mapped;
uint8_t camera_registers[0x36]; uint8_t camera_registers[0x36];
bool rumble_state; uint8_t rumble_strength;
bool cart_ir;
); );
@ -448,6 +538,14 @@ struct GB_gameboy_internal_s {
uint16_t serial_length; uint16_t serial_length;
uint8_t double_speed_alignment; uint8_t double_speed_alignment;
uint8_t serial_count; uint8_t serial_count;
int32_t speed_switch_halt_countdown;
uint8_t speed_switch_countdown; // To compensate for the lack of pipeline emulation
uint8_t speed_switch_freeze; // Solely for realigning the PPU, should be removed when the odd modes are implemented
/* For timing of the vblank callback */
uint32_t cycles_since_vblank_callback;
bool lcd_disabled_outside_of_vblank;
int32_t allowed_pending_cycles;
uint16_t mode3_batching_length;
); );
/* APU */ /* APU */
@ -459,16 +557,17 @@ struct GB_gameboy_internal_s {
GB_SECTION(rtc, GB_SECTION(rtc,
GB_rtc_time_t rtc_real, rtc_latched; GB_rtc_time_t rtc_real, rtc_latched;
uint64_t last_rtc_second; uint64_t last_rtc_second;
bool rtc_latch; uint32_t rtc_cycles;
uint8_t tpp1_mr4;
); );
/* Video Display */ /* Video Display */
GB_SECTION(video, GB_SECTION(video,
uint32_t vram_size; // Different between CGB and DMG uint32_t vram_size; // Different between CGB and DMG
uint8_t cgb_vram_bank; bool cgb_vram_bank;
uint8_t oam[0xA0]; uint8_t oam[0xA0];
uint8_t background_palettes_data[0x40]; uint8_t background_palettes_data[0x40];
uint8_t sprite_palettes_data[0x40]; uint8_t object_palettes_data[0x40];
uint8_t position_in_line; uint8_t position_in_line;
bool stat_interrupt_line; bool stat_interrupt_line;
uint8_t effective_scx; uint8_t effective_scx;
@ -493,7 +592,6 @@ struct GB_gameboy_internal_s {
uint8_t current_line; uint8_t current_line;
uint16_t ly_for_comparison; uint16_t ly_for_comparison;
GB_fifo_t bg_fifo, oam_fifo; GB_fifo_t bg_fifo, oam_fifo;
uint8_t fetcher_x;
uint8_t fetcher_y; uint8_t fetcher_y;
uint16_t cycles_for_line; uint16_t cycles_for_line;
uint8_t current_tile; uint8_t current_tile;
@ -508,12 +606,11 @@ struct GB_gameboy_internal_s {
uint8_t n_visible_objs; uint8_t n_visible_objs;
uint8_t oam_search_index; uint8_t oam_search_index;
uint8_t accessed_oam_row; uint8_t accessed_oam_row;
uint8_t extra_penalty_for_sprite_at_0; uint8_t extra_penalty_for_object_at_0;
uint8_t mode_for_interrupt; uint8_t mode_for_interrupt;
bool lyc_interrupt_line; bool lyc_interrupt_line;
bool cgb_palettes_blocked; bool cgb_palettes_blocked;
uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases.
uint32_t cycles_in_stop_mode;
uint8_t object_priority; uint8_t object_priority;
bool oam_ppu_blocked; bool oam_ppu_blocked;
bool vram_ppu_blocked; bool vram_ppu_blocked;
@ -525,6 +622,10 @@ struct GB_gameboy_internal_s {
uint8_t window_tile_x; 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. uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases.
bool is_odd_frame; 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 */ /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
@ -550,18 +651,28 @@ struct GB_gameboy_internal_s {
/* I/O */ /* I/O */
uint32_t *screen; uint32_t *screen;
uint32_t background_palettes_rgb[0x20]; uint32_t background_palettes_rgb[0x20];
uint32_t sprite_palettes_rgb[0x20]; uint32_t object_palettes_rgb[0x20];
const GB_palette_t *dmg_palette; const GB_palette_t *dmg_palette;
GB_color_correction_mode_t color_correction_mode; GB_color_correction_mode_t color_correction_mode;
double light_temperature;
bool keys[4][GB_KEY_MAX]; bool keys[4][GB_KEY_MAX];
double accelerometer_x, accelerometer_y;
GB_border_mode_t border_mode; GB_border_mode_t border_mode;
GB_sgb_border_t borrowed_border; GB_sgb_border_t borrowed_border;
bool tried_loading_sgb_border; bool tried_loading_sgb_border;
bool has_sgb_border; bool has_sgb_border;
bool objects_disabled;
bool background_disabled;
bool joyp_accessed;
bool illegal_inputs_allowed;
/* Timing */ /* Timing */
uint64_t last_sync; uint64_t last_sync;
uint64_t cycles_since_last_sync; // In 8MHz units uint64_t cycles_since_last_sync; // In 8MHz units
GB_rtc_mode_t rtc_mode;
uint32_t rtc_second_length;
uint32_t clock_rate;
uint32_t unmultiplied_clock_rate;
/* Audio */ /* Audio */
GB_apu_output_t apu_output; GB_apu_output_t apu_output;
@ -585,13 +696,13 @@ struct GB_gameboy_internal_s {
GB_icd_vreset_callback_t icd_hreset_callback; GB_icd_vreset_callback_t icd_hreset_callback;
GB_icd_vreset_callback_t icd_vreset_callback; GB_icd_vreset_callback_t icd_vreset_callback;
GB_read_memory_callback_t read_memory_callback; GB_read_memory_callback_t read_memory_callback;
GB_write_memory_callback_t write_memory_callback;
GB_boot_rom_load_callback_t boot_rom_load_callback; GB_boot_rom_load_callback_t boot_rom_load_callback;
GB_print_image_callback_t printer_callback;
/* IR */ GB_workboy_set_time_callback workboy_set_time_callback;
uint64_t cycles_since_ir_change; // In 8MHz units GB_workboy_get_time_callback workboy_get_time_callback;
uint64_t cycles_since_input_ir_change; // In 8MHz units GB_execution_callback_t execution_callback;
GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE]; GB_lcd_line_callback_t lcd_line_callback;
size_t ir_queue_length;
/*** Debugger ***/ /*** Debugger ***/
volatile bool debug_stopped, debug_disable; volatile bool debug_stopped, debug_disable;
@ -600,7 +711,7 @@ struct GB_gameboy_internal_s {
/* Breakpoints */ /* Breakpoints */
uint16_t n_breakpoints; uint16_t n_breakpoints;
struct GB_breakpoint_s *breakpoints; struct GB_breakpoint_s *breakpoints;
bool has_jump_to_breakpoints; bool has_jump_to_breakpoints, has_software_breakpoints;
void *nontrivial_jump_state; void *nontrivial_jump_state;
bool non_trivial_jump_breakpoint_occured; bool non_trivial_jump_breakpoint_occured;
@ -628,6 +739,11 @@ struct GB_gameboy_internal_s {
/* Ticks command */ /* Ticks command */
uint64_t debugger_ticks; uint64_t debugger_ticks;
uint64_t absolute_debugger_ticks;
/* Undo */
uint8_t *undo_state;
const char *undo_label;
/* Rewind */ /* Rewind */
#define GB_REWIND_FRAMES_PER_KEY 255 #define GB_REWIND_FRAMES_PER_KEY 255
@ -660,15 +776,22 @@ struct GB_gameboy_internal_s {
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
double clock_multiplier; double clock_multiplier;
GB_rumble_mode_t rumble_mode;
uint32_t rumble_on_cycles;
uint32_t rumble_off_cycles;
/* Temporary state */ /* Temporary state */
bool wx_just_changed; bool wx_just_changed;
bool tile_sel_glitch;
bool disable_oam_corruption; // For safe memory reads
GB_gbs_header_t gbs_header;
); );
}; };
#ifndef GB_INTERNAL #ifndef GB_INTERNAL
struct GB_gameboy_s { struct GB_gameboy_s {
char __internal[sizeof(struct GB_gameboy_internal_s)]; alignas(struct GB_gameboy_internal_s) uint8_t __internal[sizeof(struct GB_gameboy_internal_s)];
}; };
#endif #endif
@ -681,7 +804,8 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
void GB_init(GB_gameboy_t *gb, GB_model_t model); void GB_init(GB_gameboy_t *gb, GB_model_t model);
bool GB_is_inited(GB_gameboy_t *gb); bool GB_is_inited(GB_gameboy_t *gb);
bool GB_is_cgb(GB_gameboy_t *gb); bool GB_is_cgb(const GB_gameboy_t *gb);
bool GB_is_cgb_in_cgb_mode(GB_gameboy_t *gb);
bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2
bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd
GB_model_t GB_get_model(GB_gameboy_t *gb); GB_model_t GB_get_model(GB_gameboy_t *gb);
@ -711,16 +835,19 @@ typedef enum {
/* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank /* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank
is returned at *bank, even if only a portion of the memory is banked. */ is returned at *bank, even if only a portion of the memory is banked. */
void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank); void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank);
GB_registers_t *GB_get_registers(GB_gameboy_t *gb);
void *GB_get_user_data(GB_gameboy_t *gb); void *GB_get_user_data(GB_gameboy_t *gb);
void GB_set_user_data(GB_gameboy_t *gb, void *data); void GB_set_user_data(GB_gameboy_t *gb, void *data);
int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); int GB_load_boot_rom(GB_gameboy_t *gb, const char *path);
void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size);
int GB_load_rom(GB_gameboy_t *gb, const char *path); int GB_load_rom(GB_gameboy_t *gb, const char *path);
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size);
int GB_load_isx(GB_gameboy_t *gb, const char *path);
int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size, GB_gbs_info_t *info);
int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info);
void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track);
int GB_save_battery_size(GB_gameboy_t *gb); int GB_save_battery_size(GB_gameboy_t *gb);
int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size); int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size);
@ -739,7 +866,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_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_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_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); void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
@ -752,7 +878,11 @@ void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_ca
/* Called when a new boot ROM is needed. The callback should call GB_load_boot_rom or GB_load_boot_rom_from_buffer */ /* Called when a new boot ROM is needed. The callback should call GB_load_boot_rom or GB_load_boot_rom_from_buffer */
void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback); void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback);
void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback);
void GB_set_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback);
void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette);
const GB_palette_t *GB_get_palette(GB_gameboy_t *gb);
/* These APIs are used when using internal clock */ /* These APIs are used when using internal clock */
void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback); void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback);
@ -764,15 +894,23 @@ void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data);
void GB_disconnect_serial(GB_gameboy_t *gb); 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 cartridges motion controls */
bool GB_has_accelerometer(GB_gameboy_t *gb);
// In units of g (gravity's acceleration).
// Values within ±4 recommended
void GB_set_accelerometer_values(GB_gameboy_t *gb, double x, double y);
/* For integration with SFC/SNES emulators */ /* 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_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_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_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); 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); uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
#endif uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb);
void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier);
unsigned GB_get_screen_width(GB_gameboy_t *gb); unsigned GB_get_screen_width(GB_gameboy_t *gb);
@ -780,4 +918,14 @@ unsigned GB_get_screen_height(GB_gameboy_t *gb);
double GB_get_usual_frame_rate(GB_gameboy_t *gb); double GB_get_usual_frame_rate(GB_gameboy_t *gb);
unsigned GB_get_player_count(GB_gameboy_t *gb); unsigned GB_get_player_count(GB_gameboy_t *gb);
/* Handy ROM info APIs */
// `title` must be at least 17 bytes in size
void GB_get_rom_title(GB_gameboy_t *gb, char *title);
uint32_t GB_get_rom_crc32(GB_gameboy_t *gb);
#ifdef GB_INTERNAL
internal void GB_borrow_sgb_border(GB_gameboy_t *gb);
internal void GB_update_clock_rate(GB_gameboy_t *gb);
#endif
#endif /* GB_h */ #endif /* GB_h */

View File

@ -1,5 +0,0 @@
#ifndef gb_struct_def_h
#define gb_struct_def_h
struct GB_gameboy_s;
typedef struct GB_gameboy_s GB_gameboy_t;
#endif

View File

@ -0,0 +1,477 @@
static const uint16_t palette[] = {
0x0000, 0x0000, 0x0011, 0x001A, 0x39CE, 0x6B5A, 0x739C, 0x5265,
0x3DC5, 0x2924, 0x18A4, 0x20E6, 0x2D49, 0x1484, 0x5694, 0x20EC,
};
static const uint16_t tilemap[] = {
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x0010, 0x0011, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012,
0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012,
0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012,
0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x4011, 0x4010, 0x000F,
0x000F, 0x0013, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014,
0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014,
0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014,
0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x4013, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0016, 0x0017, 0x0017,
0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017,
0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017,
0x0017, 0x0017, 0x4016, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0019, 0x001A, 0x4019, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x8019, 0x001B, 0xC019, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F,
0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x001C, 0x001D, 0x001D,
0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D,
0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D,
0x001D, 0x001D, 0x401C, 0x0014, 0x0014, 0x0014, 0x001E, 0x000F,
0x000F, 0x0015, 0x0014, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023,
0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B,
0x002C, 0x002D, 0x002E, 0x002F, 0x0014, 0x0014, 0x0014, 0x0014,
0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0030, 0x0031, 0x000F,
0x000F, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040,
0x0041, 0x0042, 0x0043, 0x0044, 0x0014, 0x0014, 0x0014, 0x0014,
0x0014, 0x0014, 0x0014, 0x0014, 0x0045, 0x0046, 0x000F, 0x000F,
0x000F, 0x0047, 0x0048, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049,
0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049,
0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049,
0x0049, 0x0049, 0x004A, 0x004B, 0x004C, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F,
};
const uint8_t tiles[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x3D,
0x42, 0x7F, 0x81, 0xFF, 0x01, 0xFD, 0x01,
0xFD, 0x01, 0xFF, 0x03, 0xFF, 0x03, 0xFF,
0xFF, 0xBC, 0x7F, 0xFD, 0xFE, 0xFD, 0xFE,
0xFD, 0xFE, 0xFD, 0xFE, 0xFF, 0xFC, 0xFF,
0xFC, 0xFF, 0x00, 0xBF, 0x41, 0xFE, 0xC0,
0xBF, 0xC1, 0xFF, 0x81, 0x7D, 0x03, 0x7F,
0x01, 0x7F, 0x01, 0xFF, 0xFF, 0x3E, 0xFF,
0xBE, 0x7F, 0xBF, 0x7E, 0xFF, 0x7E, 0x7D,
0xFE, 0x7D, 0xFE, 0x7D, 0xFE, 0xFF, 0x00,
0xFF, 0x00, 0xBF, 0x83, 0xBF, 0x87, 0xFC,
0x8D, 0xED, 0x8E, 0xDB, 0xF8, 0xBF, 0xD8,
0xFF, 0xFF, 0x3E, 0xFF, 0xBB, 0x7C, 0xB7,
0x78, 0xAC, 0x73, 0xAD, 0x73, 0x9B, 0x67,
0x9B, 0x67, 0xFF, 0x00, 0xB7, 0x08, 0xFF,
0xF8, 0x3F, 0x38, 0xFF, 0x08, 0xFE, 0x01,
0x87, 0x00, 0xFB, 0x78, 0xFF, 0xFF, 0x07,
0xFF, 0xFB, 0x07, 0x3B, 0xC7, 0xE7, 0xFF,
0xFE, 0xFF, 0x82, 0xFF, 0xFA, 0x87, 0xFF,
0x00, 0xFE, 0x81, 0x5F, 0x40, 0xDE, 0xC0,
0xFE, 0xC0, 0xE0, 0xDE, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x1E, 0xFF, 0x5E, 0xBF,
0xDE, 0x3F, 0xDE, 0x3F, 0xC0, 0x3F, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xCF, 0x30,
0xDF, 0xEF, 0xFF, 0xCF, 0xFF, 0xC1, 0xBD,
0x81, 0xBD, 0x81, 0xFF, 0x83, 0xFF, 0xFF,
0x00, 0xFF, 0xCF, 0x30, 0xEF, 0x30, 0xFD,
0x3E, 0xBD, 0x7E, 0xBD, 0x7E, 0xBF, 0x7C,
0xFF, 0x00, 0xFF, 0x08, 0xF7, 0xF0, 0xFF,
0xF0, 0xBF, 0xC0, 0xFF, 0x80, 0x7F, 0x00,
0x7F, 0x00, 0xFF, 0xFF, 0x07, 0xFF, 0xF7,
0x0F, 0xF7, 0x0F, 0xBF, 0x7F, 0xFF, 0x7F,
0x7F, 0xFF, 0x7F, 0xFF, 0xFB, 0x07, 0xFF,
0x03, 0xFF, 0x03, 0xFB, 0x03, 0xFB, 0x03,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB,
0xFC, 0xFB, 0xFC, 0xFB, 0xFC, 0xFB, 0xFC,
0xFB, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFD, 0x01, 0xFD, 0x81, 0xFF, 0x0B,
0xF7, 0xF3, 0xFB, 0xF7, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0x7D, 0xFE, 0x7D, 0xFE,
0x07, 0xFC, 0xF7, 0x0C, 0xF3, 0x0C, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x98,
0x7B, 0x38, 0x7C, 0x1D, 0xFF, 0x0F, 0xFB,
0x0B, 0xFD, 0x03, 0xFF, 0x00, 0xFF, 0x00,
0xDB, 0x67, 0x5B, 0xE7, 0x7C, 0xE3, 0x6F,
0xF0, 0x73, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x9E, 0x18, 0xFE, 0x3C, 0x5A,
0xDC, 0xFF, 0xF9, 0xED, 0xE3, 0xBF, 0xC0,
0xFF, 0x00, 0xFF, 0x00, 0x9A, 0xE7, 0xDA,
0xE7, 0x1A, 0xE7, 0xFF, 0x06, 0xE5, 0x1E,
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3,
0xFD, 0xBF, 0x81, 0xBF, 0x81, 0xBD, 0x81,
0xFD, 0x81, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xC1, 0x3E, 0xBD, 0x7E, 0xBD, 0x7E,
0xBD, 0x7E, 0xBD, 0x7E, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xBB, 0xC7,
0xFF, 0x83, 0xFF, 0x83, 0x7B, 0x03, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x7C,
0xBB, 0x7C, 0xFB, 0x7C, 0xFB, 0x7C, 0x7B,
0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F,
0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF9, 0x00,
0xF7, 0x00, 0xEE, 0x00, 0xDD, 0x04, 0xDF,
0x04, 0xBF, 0x08, 0xFF, 0x00, 0xFF, 0x01,
0xFF, 0x07, 0xFF, 0x0F, 0xFF, 0x1F, 0xFB,
0x3F, 0xFB, 0x3F, 0xF7, 0x7F, 0x80, 0x00,
0x7F, 0x00, 0xFF, 0x00, 0x80, 0x00, 0x7F,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF,
0x08, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10,
0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF,
0x10, 0xF7, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F,
0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF,
0x7F, 0xEF, 0x7F, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF,
0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10,
0xFF, 0x10, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF,
0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F,
0xEF, 0x7F, 0xEF, 0x7F, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFE, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
0xFE, 0xFE, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01,
0xFF, 0x01, 0xFF, 0x01, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, 0x7E,
0xBD, 0xFF, 0x66, 0xFF, 0x7E, 0xFF, 0x7E,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xC3, 0xFF, 0x81, 0xC3, 0x18, 0x81, 0x00,
0x00, 0x00, 0x00, 0x7E, 0xFF, 0x3C, 0xFF,
0x00, 0x7E, 0x81, 0xBD, 0xC3, 0x42, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, 0xFF,
0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x08, 0xFD, 0x12, 0xFD, 0x12,
0xFD, 0x12, 0xFD, 0x12, 0xFB, 0x24, 0xFB,
0x24, 0xFB, 0x24, 0xF7, 0xFE, 0xEF, 0xFC,
0xEF, 0xFC, 0xEF, 0xFC, 0xEF, 0xFC, 0xDF,
0xF8, 0xDF, 0xF8, 0xDF, 0xF8, 0xFF, 0x00,
0xF0, 0x1E, 0xC0, 0x3F, 0x8D, 0x72, 0x0E,
0xF3, 0x8F, 0xF0, 0x01, 0xFC, 0xA0, 0x1E,
0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xE0, 0xFF,
0xC0, 0x7C, 0x9F, 0x7F, 0x9F, 0x7F, 0x87,
0xFF, 0xC0, 0xFF, 0x00, 0xFC, 0x00, 0x78,
0x87, 0x78, 0x87, 0xF0, 0x07, 0xF8, 0x07,
0xE2, 0x0F, 0xE2, 0x1C, 0xFF, 0xFF, 0xFF,
0xFE, 0xFB, 0xFC, 0x7F, 0xFC, 0xFF, 0xF8,
0xFF, 0xF2, 0xFD, 0xF3, 0xFF, 0xE6, 0xFF,
0x00, 0x7C, 0x02, 0xFC, 0x01, 0x3C, 0xC3,
0x3C, 0x83, 0x3E, 0xC1, 0x3C, 0xC3, 0x18,
0xC7, 0xFF, 0xFF, 0xFD, 0xFE, 0xFF, 0x7C,
0xBF, 0x7E, 0xFF, 0x3E, 0xFF, 0x7C, 0xFF,
0x3C, 0xFB, 0x3C, 0xFF, 0x00, 0x1E, 0x01,
0x1E, 0xE1, 0x1E, 0xE3, 0x1C, 0xF3, 0x0C,
0xE7, 0x08, 0xF7, 0x19, 0x6F, 0xFF, 0xFF,
0xFE, 0x3F, 0xFF, 0x3F, 0xFD, 0x1E, 0xEF,
0x1E, 0xFB, 0x8C, 0xFF, 0xDD, 0xF6, 0x49,
0xFF, 0x00, 0x18, 0x14, 0x08, 0xE3, 0x08,
0xE3, 0x18, 0xF7, 0x1D, 0xE2, 0x18, 0xE7,
0xB8, 0x47, 0xFF, 0xFF, 0xEB, 0x1C, 0xFF,
0x0C, 0xFF, 0x18, 0xEF, 0x1C, 0xFF, 0x98,
0xFF, 0x98, 0xFF, 0x18, 0xFF, 0x00, 0x06,
0x05, 0x02, 0xF8, 0x02, 0xF9, 0xFE, 0x01,
0xFF, 0x00, 0x06, 0xF9, 0x04, 0xF3, 0xFF,
0xFF, 0xFA, 0x07, 0xFF, 0x02, 0xFF, 0x07,
0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x0E, 0xFD,
0x06, 0xFF, 0x00, 0x03, 0x00, 0x01, 0xFF,
0x30, 0xCF, 0x70, 0x8F, 0x30, 0xC6, 0x21,
0xDC, 0x01, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F,
0xFE, 0x01, 0xFF, 0x01, 0xF7, 0x39, 0xFF,
0x79, 0xFF, 0x01, 0xFF, 0x03, 0xFF, 0x00,
0xF8, 0x01, 0xF0, 0x1F, 0xE1, 0x3E, 0x83,
0x78, 0x87, 0x30, 0xCF, 0x30, 0x8F, 0x70,
0xFF, 0xFF, 0xFF, 0xFD, 0xEF, 0xF8, 0xDF,
0xE0, 0xBF, 0xC3, 0xFF, 0x87, 0xFF, 0x8F,
0xFF, 0x9F, 0xFF, 0x00, 0x3C, 0xA0, 0x0C,
0xE1, 0x07, 0xF0, 0x86, 0x38, 0xC7, 0x3C,
0xC3, 0x18, 0xC7, 0x3C, 0xFF, 0xFF, 0xDF,
0xBE, 0xFF, 0x0C, 0xFF, 0x06, 0xFF, 0x87,
0xFB, 0xE7, 0xFF, 0xC7, 0xFB, 0xE7, 0xFF,
0x00, 0x7C, 0x40, 0x78, 0x83, 0x39, 0xEE,
0x19, 0xE7, 0x81, 0x7C, 0x03, 0x78, 0xC7,
0x38, 0xFF, 0xFF, 0xBF, 0x7E, 0xFF, 0x38,
0xD7, 0x38, 0xFE, 0x31, 0xFF, 0x13, 0xFF,
0x83, 0xFF, 0x8F, 0xFF, 0x00, 0x3C, 0x43,
0x7C, 0x83, 0xFC, 0x03, 0xFC, 0x03, 0xFC,
0x03, 0xFD, 0x02, 0xFC, 0x03, 0xFF, 0xFF,
0xBF, 0x7C, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF,
0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD,
0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x04,
0xA3, 0xFD, 0xB6, 0x8C, 0x3A, 0x8B, 0x7C,
0x99, 0x62, 0xFF, 0xFF, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0xF8, 0x4B, 0xFC, 0xF7, 0xCC,
0xF3, 0xCD, 0xFF, 0xCB, 0xFF, 0x00, 0x00,
0xFF, 0x00, 0xFF, 0x90, 0x3F, 0xD8, 0x46,
0x09, 0xF6, 0x0D, 0xF1, 0x12, 0xF4, 0xFF,
0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xCF, 0x78,
0xBF, 0xD9, 0x7F, 0x9F, 0xFE, 0x9B, 0xEF,
0x9B, 0xFF, 0x00, 0x00, 0xFC, 0x02, 0xFC,
0x46, 0x59, 0x13, 0xAC, 0x82, 0x68, 0x07,
0xFC, 0x14, 0xE8, 0xFF, 0xFF, 0xFF, 0x03,
0xFF, 0x02, 0xBF, 0x73, 0x5F, 0xB6, 0xFF,
0x23, 0xFB, 0x07, 0xFF, 0x26, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x03, 0x9F, 0x21,
0x55, 0x48, 0xB7, 0x8F, 0x60, 0x80, 0x6F,
0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFC,
0x27, 0xBA, 0xCD, 0x7F, 0x9D, 0xFF, 0x8F,
0xF7, 0xD8, 0xFF, 0x00, 0x00, 0xFF, 0x0C,
0xF3, 0x18, 0xF3, 0xD8, 0x2F, 0x90, 0x67,
0xB0, 0x4F, 0x10, 0xEF, 0xFF, 0xFF, 0xFF,
0x08, 0xFF, 0x18, 0xEF, 0xBC, 0xF7, 0xBC,
0xFF, 0xD0, 0xFF, 0xD8, 0xFF, 0x30, 0xFF,
0x00, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F,
0x80, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F,
0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF,
0x7F, 0xFF, 0x7F, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF,
0x02, 0xFF, 0x04, 0xFF, 0x08, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF,
0xF7, 0x48, 0xF7, 0x48, 0xEF, 0x90, 0xEF,
0x90, 0xDF, 0x20, 0xBF, 0x40, 0xBF, 0x40,
0x7F, 0x80, 0xBF, 0xF0, 0xBF, 0xF0, 0x7F,
0xE0, 0x7F, 0xE0, 0xFF, 0xC0, 0xFF, 0x80,
0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x10, 0xFF,
0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10,
0xFF, 0x10, 0xFF, 0x10, 0xBF, 0x48, 0xEF,
0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F,
0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xF7,
0x3F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFE, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F,
0xF8, 0x07, 0x00, 0x57, 0x01, 0xFF, 0x05,
0xF8, 0x87, 0x48, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0xF8, 0xFF, 0xFC, 0xEF, 0x99, 0xFE,
0x01, 0xFF, 0x83, 0xB7, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xC0, 0x3F, 0x80, 0x7F, 0x80,
0x7F, 0x0F, 0x70, 0x8F, 0x70, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xE7, 0xBF,
0xC0, 0xFF, 0xCF, 0xFF, 0x9F, 0xEF, 0x1F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18,
0xE3, 0x18, 0xE3, 0x98, 0x77, 0x08, 0x67,
0x1D, 0xE2, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x3C, 0xFF, 0x18, 0xEF, 0x1C,
0xFF, 0x0C, 0x7F, 0x88, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x01, 0x3E, 0xC2, 0xFF,
0xC2, 0x3C, 0xE2, 0x18, 0xC6, 0x39, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x8B,
0x3C, 0xC3, 0xFF, 0xC7, 0xFF, 0xC6, 0xFF,
0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x18, 0xEF, 0x10, 0xE6, 0x10, 0xC6, 0x30,
0xEF, 0x30, 0xCF, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xF7, 0x39, 0xFF, 0x39, 0xFF,
0x10, 0xDF, 0x38, 0xFF, 0x38, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x09, 0xFC,
0x01, 0x0C, 0x0B, 0x04, 0xFB, 0x06, 0xF1,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF7,
0x0E, 0xFF, 0xFC, 0xF7, 0x0E, 0xFF, 0x0E,
0xFF, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xCE, 0x70, 0xCF, 0x70, 0xFE,
0x01, 0xFE, 0x0B, 0xF0, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x69, 0xB7, 0x79,
0x8F, 0x79, 0xFF, 0x03, 0xFF, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x70,
0x87, 0x78, 0x88, 0x72, 0x80, 0x7F, 0xC0,
0x3F, 0xFC, 0x05, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x9F, 0xF7, 0x8F, 0xFD, 0xC7, 0xBF,
0xC0, 0xDF, 0xF0, 0xFA, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xE7, 0x18, 0x87, 0x38, 0x17,
0xE8, 0x1F, 0xF0, 0x3F, 0xC0, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xFF,
0x8F, 0xF7, 0x8F, 0xEF, 0x1F, 0xFF, 0x7F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87,
0x78, 0x8F, 0x70, 0xDF, 0x20, 0x8F, 0x70,
0x8F, 0x70, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xF7, 0xCF, 0xFF, 0xCF, 0xFF, 0x8F,
0xFF, 0x9F, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xFD, 0x03,
0xFD, 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFC,
0xFE, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x9F, 0x30, 0xA0, 0x9E, 0x00, 0x7F, 0x00,
0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xEF, 0xF9, 0x6F, 0xF8, 0xFF,
0x00, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0xCF, 0xE1,
0x1E, 0x40, 0xBF, 0x00, 0xFF, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7C,
0xDB, 0xFF, 0xF3, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x3D, 0x82, 0x86, 0x79, 0x80, 0x7F,
0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0xA6, 0xBF, 0xEC,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x88, 0x27,
0xD6, 0xA0, 0x00, 0xFF, 0x00, 0xFF, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFE, 0xDF, 0x7F, 0xCE, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x18, 0x47, 0x10, 0xC7, 0x00,
0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xFF,
0x18, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F,
0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F,
0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF,
0x02, 0xFF, 0x0C, 0xFF, 0x10, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFF, 0xFD, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF,
0xFE, 0x11, 0xFD, 0x22, 0xFB, 0x44, 0xF7,
0x88, 0xEF, 0x10, 0xDF, 0x20, 0xBF, 0x40,
0x7F, 0x80, 0xEF, 0xFE, 0xDF, 0xFC, 0xBF,
0xF8, 0x7F, 0xF0, 0xFF, 0xE0, 0xFF, 0xC0,
0xFF, 0x80, 0xFF, 0x00, 0xBF, 0x48, 0xDF,
0x24, 0xDF, 0x22, 0xEF, 0x11, 0xF7, 0x08,
0xF9, 0x06, 0xFE, 0x01, 0xFF, 0x00, 0xF7,
0x3F, 0xFB, 0x1F, 0xFD, 0x1F, 0xFE, 0x0F,
0xFF, 0x07, 0xFF, 0x01, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x80, 0xFF, 0x7F, 0xFF, 0x00, 0x7F,
0x80, 0x80, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x7F, 0xFF, 0x80, 0xFF, 0xFF,
0xFF, 0xFF, 0x7F, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x03, 0xFF, 0xFC, 0xFF, 0x00,
0xFC, 0x03, 0x03, 0xFC, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x03, 0xFF, 0x1C, 0xFF, 0xE0,
0xFC, 0x03, 0xE3, 0x1C, 0x1F, 0xE0, 0xFF,
0x00, 0xFF, 0xFF, 0xFC, 0xFF, 0xE3, 0xFF,
0x1F, 0xFF, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF,
0x00, 0xFF, 0x00, 0xFC, 0xE3, 0xF3, 0x0C,
0xEF, 0x10, 0x1F, 0xE0, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0xFC,
0xFF, 0xF0, 0xFF, 0xE0, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
};

View File

@ -12,7 +12,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
gb->io_registers[GB_IO_JOYP] &= 0xF0; gb->io_registers[GB_IO_JOYP] &= 0xF0;
uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0; uint8_t current_player = gb->sgb? gb->sgb->current_player : 0;
switch (key_selection) { switch (key_selection) {
case 3: case 3:
if (gb->sgb && gb->sgb->player_count > 1) { if (gb->sgb && gb->sgb->player_count > 1) {
@ -30,12 +30,14 @@ void GB_update_joyp(GB_gameboy_t *gb)
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i; gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i;
} }
/* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */
if (likely(!gb->illegal_inputs_allowed)) {
if (!(gb->io_registers[GB_IO_JOYP] & 1)) { if (!(gb->io_registers[GB_IO_JOYP] & 1)) {
gb->io_registers[GB_IO_JOYP] |= 2; gb->io_registers[GB_IO_JOYP] |= 2;
} }
if (!(gb->io_registers[GB_IO_JOYP] & 4)) { if (!(gb->io_registers[GB_IO_JOYP] & 4)) {
gb->io_registers[GB_IO_JOYP] |= 8; gb->io_registers[GB_IO_JOYP] |= 8;
} }
}
break; break;
case 1: case 1:
@ -51,8 +53,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
} }
break; break;
default: nodefault;
break;
} }
/* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */ /* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */
@ -90,3 +91,48 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play
gb->keys[player][index] = pressed; gb->keys[player][index] = pressed;
GB_update_joyp(gb); GB_update_joyp(gb);
} }
void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask)
{
memset(gb->keys, 0, sizeof(gb->keys));
bool *key = &gb->keys[0][0];
while (mask) {
if (mask & 1) {
*key = true;
}
mask >>= 1;
key++;
}
GB_update_joyp(gb);
}
void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned player)
{
memset(gb->keys[player], 0, sizeof(gb->keys[player]));
bool *key = gb->keys[player];
while (mask) {
if (mask & 1) {
*key = true;
}
mask >>= 1;
key++;
}
GB_update_joyp(gb);
}
bool GB_get_joyp_accessed(GB_gameboy_t *gb)
{
return gb->joyp_accessed;
}
void GB_clear_joyp_accessed(GB_gameboy_t *gb)
{
gb->joyp_accessed = false;
}
void GB_set_allow_illegal_inputs(GB_gameboy_t *gb, bool allow)
{
gb->illegal_inputs_allowed = allow;
}

View File

@ -1,6 +1,6 @@
#ifndef joypad_h #ifndef joypad_h
#define joypad_h #define joypad_h
#include "gb_struct_def.h" #include "defs.h"
#include <stdbool.h> #include <stdbool.h>
typedef enum { typedef enum {
@ -15,11 +15,31 @@ typedef enum {
GB_KEY_MAX GB_KEY_MAX
} GB_key_t; } GB_key_t;
typedef enum {
GB_KEY_RIGHT_MASK = 1 << GB_KEY_RIGHT,
GB_KEY_LEFT_MASK = 1 << GB_KEY_LEFT,
GB_KEY_UP_MASK = 1 << GB_KEY_UP,
GB_KEY_DOWN_MASK = 1 << GB_KEY_DOWN,
GB_KEY_A_MASK = 1 << GB_KEY_A,
GB_KEY_B_MASK = 1 << GB_KEY_B,
GB_KEY_SELECT_MASK = 1 << GB_KEY_SELECT,
GB_KEY_START_MASK = 1 << GB_KEY_START,
} GB_key_mask_t;
// For example, for player 2's (0-based; logical player 3) A button, use GB_MASK_FOR_PLAYER(GB_KEY_A_MASK, 2)
#define GB_MASK_FOR_PLAYER(mask, player) ((x) << (player * 8))
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed); void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed);
void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed); void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed);
void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask);
void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned player);
void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value);
bool GB_get_joyp_accessed(GB_gameboy_t *gb);
void GB_clear_joyp_accessed(GB_gameboy_t *gb);
void GB_set_allow_illegal_inputs(GB_gameboy_t *gb, bool allow);
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
void GB_update_joyp(GB_gameboy_t *gb); internal void GB_update_joyp(GB_gameboy_t *gb);
#endif #endif
#endif /* joypad_h */ #endif /* joypad_h */

View File

@ -34,11 +34,13 @@ const GB_cartridge_t GB_cart_defs[256] = {
{ GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE
{ GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM
{ GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
[0x22] =
{ GB_MBC7 , GB_STANDARD_MBC, true, true, false, false}, // 22h MBC7+ACCEL+EEPROM
[0xFC] = [0xFC] =
{ GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported)
{ GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only) { GB_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3
{ GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings) { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY
}; };
void GB_update_mbc_mappings(GB_gameboy_t *gb) void GB_update_mbc_mappings(GB_gameboy_t *gb)
@ -75,6 +77,7 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
gb->mbc_rom_bank++; gb->mbc_rom_bank++;
} }
break; break;
nodefault;
} }
break; break;
case GB_MBC2: case GB_MBC2:
@ -88,7 +91,6 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
gb->mbc_ram_bank = gb->mbc3.ram_bank; gb->mbc_ram_bank = gb->mbc3.ram_bank;
if (!gb->is_mbc30) { if (!gb->is_mbc30) {
gb->mbc_rom_bank &= 0x7F; gb->mbc_rom_bank &= 0x7F;
gb->mbc_ram_bank &= 0x3;
} }
if (gb->mbc_rom_bank == 0) { if (gb->mbc_rom_bank == 0) {
gb->mbc_rom_bank = 1; gb->mbc_rom_bank = 1;
@ -98,6 +100,9 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8);
gb->mbc_ram_bank = gb->mbc5.ram_bank; gb->mbc_ram_bank = gb->mbc5.ram_bank;
break; break;
case GB_MBC7:
gb->mbc_rom_bank = gb->mbc7.rom_bank;
break;
case GB_HUC1: case GB_HUC1:
if (gb->huc1.mode == 0) { if (gb->huc1.mode == 0) {
gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6); gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6);
@ -112,12 +117,25 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
gb->mbc_rom_bank = gb->huc3.rom_bank; gb->mbc_rom_bank = gb->huc3.rom_bank;
gb->mbc_ram_bank = gb->huc3.ram_bank; gb->mbc_ram_bank = gb->huc3.ram_bank;
break; break;
case GB_TPP1:
gb->mbc_rom_bank = gb->tpp1.rom_bank;
gb->mbc_ram_bank = gb->tpp1.ram_bank;
gb->mbc_ram_enable = (gb->tpp1.mode == 2) || (gb->tpp1.mode == 3);
break;
nodefault;
} }
} }
void GB_configure_cart(GB_gameboy_t *gb) void GB_configure_cart(GB_gameboy_t *gb)
{ {
gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]];
if (gb->rom[0x147] == 0xbc &&
gb->rom[0x149] == 0xc1 &&
gb->rom[0x14a] == 0x65) {
static const GB_cartridge_t tpp1 = {GB_TPP1, GB_STANDARD_MBC, true, true, true, true};
gb->cartridge_type = &tpp1;
gb->tpp1.rom_bank = 1;
}
if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) {
GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n"); GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n");
@ -127,17 +145,34 @@ void GB_configure_cart(GB_gameboy_t *gb)
GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]); GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]);
} }
if (gb->mbc_ram) {
free(gb->mbc_ram);
gb->mbc_ram = NULL;
gb->mbc_ram_size = 0;
}
if (gb->cartridge_type->has_ram) { if (gb->cartridge_type->has_ram) {
if (gb->cartridge_type->mbc_type == GB_MBC2) { if (gb->cartridge_type->mbc_type == GB_MBC2) {
gb->mbc_ram_size = 0x200; gb->mbc_ram_size = 0x200;
} }
else if (gb->cartridge_type->mbc_type == GB_MBC7) {
gb->mbc_ram_size = 0x100;
}
else if (gb->cartridge_type->mbc_type == GB_TPP1) {
if (gb->rom[0x152] >= 1 && gb->rom[0x152] <= 9) {
gb->mbc_ram_size = 0x2000 << (gb->rom[0x152] - 1);
}
}
else { else {
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
} }
gb->mbc_ram = malloc(gb->mbc_ram_size);
/* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */ 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 cartridge types? */
memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);
} }
@ -162,4 +197,12 @@ void GB_configure_cart(GB_gameboy_t *gb)
if (gb->cartridge_type->mbc_type == GB_MBC5) { if (gb->cartridge_type->mbc_type == GB_MBC5) {
gb->mbc5.rom_bank_low = 1; gb->mbc5.rom_bank_low = 1;
} }
/* Initial MBC7 state */
if (gb->cartridge_type->mbc_type == GB_MBC7) {
gb->mbc7.x_latch = gb->mbc7.y_latch = 0x8000;
gb->mbc7.latch_ready = true;
gb->mbc7.read_bits = -1;
gb->mbc7.eeprom_do = true;
}
} }

Some files were not shown because too many files have changed in this diff Show More