Compare commits

...

167 Commits

Author SHA1 Message Date
Maximilian Mader e3c8f1c1d4
Add an optional OpenAL audio driver for the SDL frontend
To compile the OpenAL driver specify `ENABLE_OPENAL=1` when
invoking `make`.
2022-07-09 21:47:26 +02:00
Lior Halphon 56deb4b92e Update version to 0.15.1 2022-07-08 20:10:11 +03:00
Lior Halphon eceb2e4830 Fix SDL/tester release builds on ARM64 Macs, strip and codesign Quick Look 2022-07-08 20:08:28 +03:00
Lior Halphon 0dbfaef4cf Query the system sample rate on Windows and use it, rather than hardcode 96KHz 2022-07-08 19:51:10 +03:00
Lior Halphon 88f5b22bf6 Allow setting a non-default audio driver, fixes #466 2022-07-08 17:51:42 +03:00
Lior Halphon d9b8e829a5 Fix crash on Windows 8.x 2022-07-08 14:17:28 +03:00
Lior Halphon 856a2b0ebe Makefile fixes, explicitly codesign the app bundle for releases 2022-07-04 00:13:05 +03:00
Lior Halphon 004c20d8e2 Handle GateKeeper and write permissions in the auto updater 2022-07-03 22:23:35 +03:00
Lior Halphon 3c4bfd2a1b Update version to 0.15 2022-07-02 22:41:17 +03:00
Lior Halphon faccdd3e9b Correct the Windows audio dialog rate, add a default file name 2022-07-02 22:41:07 +03:00
Lior Halphon e466c3c5b1 Ehh oops 2022-07-02 22:40:37 +03:00
Lior Halphon 7071032288 Improved MMM01 emulation, fixes #447 2022-07-02 21:03:26 +03:00
Lior Halphon 887a8104f5 Refresh icon if launched via a software update 2022-07-02 21:00:40 +03:00
Lior Halphon a773297b3a Add CGB revision selection to the SDL frontend 2022-07-02 18:11:55 +03:00
Lior Halphon 63a858d767 Fix GBS player's appearance on Big Sur and newer 2022-07-01 22:46:03 +03:00
Lior Halphon 1065a40d8f Add rX as symbol aliases to IO_X 2022-07-01 17:40:47 +03:00
Lior Halphon f2429e1c25 Fix IB oopsies 2022-07-01 16:24:54 +03:00
Lior Halphon 96d127e160 Remove the SLD command, reorder debugger commands 2022-07-01 16:14:52 +03:00
Lior Halphon 1b38e8c932 Prevent audio chuckling when an instance is debug-stopped 2022-06-25 20:09:23 +03:00
Lior Halphon 52a4c09855 More accurate PPU/OAM-DMA conflicts, artifacts in "It Came from Planet Zilog" should match hardware now 2022-06-25 20:08:59 +03:00
Lior Halphon 6a24598266 Grammar 2022-06-25 15:55:42 +03:00
Lior Halphon aaf9a76b67 The vblank callback now reports the vblank type 2022-06-25 01:59:51 +03:00
Lior Halphon 58df8144ec Fix audio artifacts in the SGB jingle 2022-06-25 01:44:50 +03:00
Lior Halphon 16913f925b Oops 2022-06-24 14:24:24 +03:00
Lior Halphon 9a765820cc Runtime audio driver fallback 2022-06-24 14:18:53 +03:00
Lior Halphon fd6b734fd0 Add XAudio2.7 as an compile-time audio driver for vanilla Windows 7 2022-06-24 14:18:30 +03:00
Lior Halphon 9ae2c9fd54 Replace SDL2 audio with XAudio2 on Windows 2022-06-23 01:13:59 +03:00
Lior Halphon 8f8b7f6b33 Minor adjustment to 32x32@2x 2022-06-18 23:10:48 +03:00
Lior Halphon ec4c1948f5 New macOS icon (again) 2022-06-18 22:36:08 +03:00
Lior Halphon 22f8ab6509 Last fix was wrong, this is correct 2022-06-17 18:17:29 +03:00
Lior Halphon 517f455486 Theorized HDMA behavior proven wrong 2022-06-17 15:58:37 +03:00
Lior Halphon 9b5dc9eca7 Refine TILE_SEL glitch, fixes #445 2022-06-12 21:43:04 +03:00
Lior Halphon b932f6699e Fix libretro build 2022-06-12 21:42:26 +03:00
Lior Halphon 20e9b1c655 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2022-06-12 21:29:03 +03:00
Lior Halphon 3fbeb61c09 Fix MMM01 RAM size detection (Closes #446) 2022-06-12 21:28:58 +03:00
Lior Halphon ab4fa3a478
Merge pull request #457 from radimerry/libretro-core-options
[Libretro] Sync core options
2022-06-12 21:07:56 +03:00
Lior Halphon 4d90504688 HuC-1 mode is not a thing, fixes Robopon Japanese prototype 2022-06-11 20:57:02 +03:00
Lior Halphon d41c188cfd More accurate Camera MBC emulation 2022-06-11 14:44:06 +03:00
Lior Halphon 24796acccf MBC7 mouse control in SDL 2022-06-10 23:51:06 +03:00
Lior Halphon 979d32faed Group keyboard and joypad controls into a submenu 2022-06-10 14:55:13 +03:00
Lior Halphon 197a475fab SDL Joypad controls for MBC7 games 2022-06-10 14:37:28 +03:00
radimerry 426d3d3a37
[Libretro] Sync core options 2022-06-10 04:12:15 +00:00
Lior Halphon 4f91b19a94 Added MBC7 BESS support, documented BESS TPP1 2022-06-05 14:09:33 +03:00
Lior Halphon abf6e5632c Disable pkg-config on macOS 2022-05-23 22:16:06 +03:00
Lior Halphon eb60dbce0d There you go, kouhai. Closes #455 2022-05-23 22:13:59 +03:00
Lior Halphon 12891c641b
Merge pull request #453 from remind-me-later/master
fix rlc_r zero flag
2022-05-21 22:15:32 +03:00
Lior Halphon 6bd7b96ed5 Include some default editable color themes 2022-05-21 21:27:02 +03:00
Lior Halphon 95f5eeb40b Add audio recording to SDL 2022-05-21 18:18:34 +03:00
Ricardo Maurizio Paul c79e67b8cc
fix rlc_r zero flag 2022-05-21 14:45:16 +02:00
Lior Halphon cdfcc4ca2d Audio recording in the Cocoa frontend 2022-05-21 14:33:03 +03:00
Lior Halphon 6055092249 Add audio recording APIs 2022-05-21 02:06:10 +03:00
Lior Halphon 5cc845d715 Fix scrolling not updating in the VRAM viewer 2022-05-19 23:43:31 +03:00
Lior Halphon 706135113c Fix AGB APU regression 2022-05-19 20:59:48 +03:00
Lior Halphon 8c86cff486 Fix MBC5's initial state 2022-05-14 01:33:45 +03:00
Lior Halphon bb836662dd More accurate emulation of Hblank skip, emulation of Mode 2 skip 2022-05-14 01:14:41 +03:00
Lior Halphon 87fdf91e0c Better debugger output for scrolling adjustment 2022-05-13 00:58:21 +03:00
Lior Halphon f866284b49 Fix the 16x16 cartridge icon 2022-05-11 03:35:17 +03:00
Lior Halphon 4521bb4767 Fixed and accurate emulation of object at x=0 timings 2022-05-11 02:15:56 +03:00
Lior Halphon a68f749c3a Initial emulation of "SCX banging" to prolong mode 3 2022-05-09 17:15:54 +03:00
Lior Halphon cb73e0b91a Oops, I broke the window 2022-05-09 00:43:49 +03:00
Lior Halphon 6337e3e43a Remove unused 2022-05-07 20:24:28 +03:00
Lior Halphon ac29b4391e Refactor FIFOs 2022-05-07 19:27:25 +03:00
Lior Halphon 69a5ed3396 Fix several potential regressions 2022-05-04 19:30:29 +03:00
Lior Halphon 36e2896ec7 Fix a regression while emulating object size change during fetch 2022-05-04 00:23:55 +03:00
Lior Halphon bef1529bb2
Merge pull request #443 from OFFTKP/issue-442
Fix random segmentation fault (fixes #442 fixes #425)
2022-04-29 01:11:30 +03:00
Lior Halphon 851d44869f
No reason for this to be a do...while 2022-04-29 01:11:11 +03:00
Lior Halphon 18126994ff Downgrade hardware.inc (rgbds is old in Ubuntu's repos, used by GitHub Actions), move definitions to hardware.inc's bottom 2022-04-18 20:03:51 +03:00
Lior Halphon 51cf4c638c
Merge pull request #450 from remind-me-later/bootrom_reg_names
use friendly names for HW registers instead of magic numbers
2022-04-18 19:47:09 +03:00
Ricardo Maurizio Paul de21e8d628
use friendly names for HW registers instead of magic numbers 2022-04-18 18:09:16 +02:00
Lior Halphon bfdab8f246 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2022-04-18 01:36:32 +03:00
Lior Halphon b2edcc9543 Improve serial accuracy 2022-04-17 23:41:05 +03:00
Lior Halphon 2a034d4ebe
Merge pull request #444 from realnc/fix-audio-perf
Libretro: Batch upload audio to frontend after video upload
2022-04-15 00:57:12 +03:00
Lior Halphon 339de0db96
Merge pull request #449 from sredna/comdlg_optim
Improve Windows open dialog handling
2022-04-15 00:55:28 +03:00
Anders 9c271a637d
Improve Windows open dialog handling
- Correctly handle OleUninitialize
- Don't leak LPITEMIDLIST if SHGetPathFromIDListW fails
- Use correct nMaxFile size
- Use string alloc helper function
- Hide read-only checkbox
2022-04-14 19:43:45 +02:00
Lior Halphon 019f262531 Fix RetroAchievements support (Closes #448) 2022-04-10 11:24:42 +03:00
Nikos Chantziaras 9e8f918b27
Libretro: Batch upload audio to frontend after video upload
Uploading audio at the end of retro_run() with
retro_audio_sample_batch_t instead of using retro_audio_sample_t to
submit samples as they are generated has peformance benefits when using
low audio latency settings. Frontends that implement audio sync and DRC
(like RetroArch) will block for a long time when submitting audio. This
can cause video frame presentation to be delayed too long. Low audio
latency settings make it worse, as DRC and audio sync is performed more
often, creating a bottleneck.

Simply accumulating all generated audio samples and uploading them at
the end with retro_audio_sample_batch_t improves things condiderably.
2022-04-02 08:51:39 +03:00
Lior Halphon b31bd58642 Consistent wording 2022-04-02 00:57:22 +03:00
offtkp dc16104cfd fixes #442 2022-03-25 02:19:46 +02:00
Lior Halphon 79945c8c18 XIB fixes for Mavericks 2022-03-19 01:25:08 +02:00
Lior Halphon 9c7bed97d5 I hope this actually works 2022-03-15 00:37:05 +02:00
Lior Halphon 86a1977034 Visual updates for Big Sur and newer 2022-03-13 01:14:29 +02:00
Lior Halphon 9fe965bcc2 Fix skipped square tick when writing to NR13/23, fixes Telefang start screen audio on CGB-D and E 2022-03-12 15:38:05 +02:00
Lior Halphon b5e271386a Add an optional argument to the APU command 2022-03-12 15:08:15 +02:00
Lior Halphon ef15c9b160 More square zombie step quirks 2022-03-12 14:32:16 +02:00
Lior Halphon d713ba85c7 Fix selected GBA model 2022-03-12 00:50:19 +02:00
Lior Halphon ab109da683 Fix several square channel zombie stepping quirks 2022-03-12 00:49:43 +02:00
Lior Halphon 5e119548e9 GDMA during mode 3 writes to both banks, list AGB_E as a future model 2022-03-09 00:32:50 +02:00
Lior Halphon 0925b06555 Add support for Mani carts 2022-03-06 15:54:29 +02:00
Lior Halphon 965e623637 Fix several BESS compatibility issues 2022-03-06 15:53:33 +02:00
Lior Halphon 7350843cca Fix incorrect INFO block on MMM01 BESS states 2022-03-05 23:05:00 +02:00
Lior Halphon c78a003712 MMM01 support 2022-03-05 21:20:42 +02:00
Lior Halphon a621803e82 More compact memory usage for symbol maps, removes the 0x200 bank limit 2022-02-28 23:30:31 +02:00
Lior Halphon 777013e998 Fix the 16x16 icon 2022-02-26 13:07:09 +02:00
Lior Halphon 2c635c7a87 Add Cartridge Instances – allow multiple saves without multiple ROM copies 2022-02-26 01:47:47 +02:00
Lior Halphon 641f26e13e Fix serial close behavior 2022-02-25 14:29:21 +02:00
Lior Halphon 8073e3d39e Visual refresh, update copyright year, crush PNGs 2022-02-24 00:38:27 +02:00
Lior Halphon 4d74719d56 One day I'll just report that bug to Clang 2022-02-23 20:40:10 +02:00
Lior Halphon f52152b2c9 Fix the Preserve Waveforms setting 2022-02-23 20:34:50 +02:00
Lior Halphon 586459bb74 Make it hex editing more reasonable while the core is running 2022-02-20 19:05:49 +02:00
Lior Halphon 7c8b9cf05a Less hacky, less buggy 2022-02-20 18:59:38 +02:00
Lior Halphon a48f251039 New and faster palette viewer 2022-02-20 14:23:49 +02:00
Lior Halphon 9a2e8e1acf Speed things even more by cachine 2022-02-20 03:18:29 +02:00
Lior Halphon f02bb2f0e6 New and faster OAM viewer 2022-02-20 01:59:42 +02:00
Lior Halphon 3c6a46830d Make GBImageView not slow 2022-02-19 22:13:07 +02:00
Lior Halphon 4c6bc91ded Add missing register 2022-02-14 22:59:05 +02:00
Lior Halphon a4209b47d0 Be consistent with hex casing 2022-02-13 16:58:44 +02:00
Lior Halphon efe31cefc9 More accurate DMA write conflicts 2022-02-13 12:56:16 +02:00
Lior Halphon c730ba767b Not sure where I've got that 0 from 2022-02-07 00:21:45 +02:00
Lior Halphon f8a105e8d0 Stop whining GCC 2022-02-06 14:42:53 +02:00
Lior Halphon 97c758ba75 More accurate internal bus behavior 2022-02-06 13:02:15 +02:00
Lior Halphon 4e27558ac2 Mode 2 OAM open bus behavior 2022-02-05 18:50:33 +02:00
Lior Halphon 850e7bb78c Fixed obscured object indication in the OAM viewer API 2022-02-05 17:44:10 +02:00
Lior Halphon b5eea012cc STAT reads 0 during mode 2 if DMA is active 2022-02-05 17:43:48 +02:00
Lior Halphon 6a8db89ae5 Emulate PPU OAM reads while both DMA and GDMA are active 2022-02-05 14:52:09 +02:00
Lior Halphon 1c6ecc2e14 GDMA/DMA Timing fix 2022-02-05 14:08:56 +02:00
Lior Halphon e7236deb11 Fixes to OAM viewer 2022-02-05 13:54:30 +02:00
Lior Halphon ba5416ee5b Correct comment 2022-02-04 19:07:47 +02:00
Lior Halphon 320aff1d1e DMA doesn't block GDMA 2022-02-04 13:58:44 +02:00
Lior Halphon 864f0927be Timing confirmed and improved 2022-02-04 13:55:06 +02:00
Lior Halphon 7c5704621a Don't batch during stop mode 2022-02-03 22:34:14 +02:00
Lior Halphon 37ca174f37 OAM DMA/GDMA conflicts 2022-02-03 21:00:37 +02:00
Lior Halphon 76b881c2e1 More accurate HDMA during halt 2022-02-02 23:01:38 +02:00
Lior Halphon 967fdadd7c More accurate IR modeling, fixes Gen 2 Pokémon mystery gifts 2022-02-01 23:23:24 +02:00
Lior Halphon 1a41957b3c LCDOff behavior, basic halt/stop behavior 2022-01-31 01:02:31 +02:00
Lior Halphon ad1f019893 Actually fix overflow behavior 2022-01-30 23:11:29 +02:00
Lior Halphon 941afee3ba Fix overflow behavior 2022-01-30 21:33:33 +02:00
Lior Halphon dbb14d7040 Minor edge case for HDMA instant start 2022-01-30 20:52:34 +02:00
Lior Halphon 44ee6dc73f HDMA timing 2022-01-30 20:09:58 +02:00
Lior Halphon a7f7530eed Preparation for future AGB-0 and B support 2022-01-30 18:11:35 +02:00
Lior Halphon 4bebd2bc33 Correct comment 2022-01-30 17:33:33 +02:00
Lior Halphon 3a2d028efa GDMA/PPU conflicts 2022-01-30 14:38:58 +02:00
Lior Halphon 9e3ad31df1 Fix HDMA usage in SameBoot 2022-01-29 02:12:27 +02:00
Lior Halphon 26656de44f Improvements to GDMA 2022-01-28 23:56:26 +02:00
Lior Halphon 81e2ec08e0 Oops 2022-01-23 22:07:15 +02:00
Lior Halphon aa5a279116 Halt during DMA with objects 2022-01-23 21:05:29 +02:00
Lior Halphon 0ab7bf7749 VRAM conflicts during halt/stop on a CGB 2022-01-22 22:52:34 +02:00
Lior Halphon 196aaaa7ed Conflicts don't happen in the first 6 T-cycles 2022-01-22 16:56:32 +02:00
Lior Halphon 8676a7c7bc Typo 2022-01-22 16:36:22 +02:00
Lior Halphon f810a2cd60 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2022-01-22 01:11:53 +02:00
Lior Halphon 582a5588ba Undo is stopped only 2022-01-22 01:11:50 +02:00
Lior Halphon 56b14c67aa Fixed AGB audio regression 2022-01-22 01:11:23 +02:00
Lior Halphon 95153af1d6
Merge pull request #431 from max-m/sdl-fix-palette-load
[SDL] Fix loading of the configured DMG palette
2022-01-20 20:43:40 +02:00
Maximilian Mader 13e0b90b47
[SDL] Fix loading of the configured DMG palette
There are four options for the DMG color palette:
- Greyscale
- Lime (Game Boy)
- Olive (Pocket)
- Teal (Light)

When loading the configuration from the `prefs.bin`
the configured `dmg_palette` gets normalized by applying
`mod 3` to it, limiting the possible values to [ 0, 1, 2 ],
thus selecting the “Teal” color palette will be saved as such,
but the next time the config gets loaded it gets reset to Greyscale.
2022-01-20 19:15:08 +01:00
Lior Halphon ee03b1e433 Initial halt/stop during DMA support 2022-01-19 01:24:40 +02:00
Lior Halphon cce36f1754 DMA debugger command 2022-01-18 23:42:22 +02:00
Lior Halphon e903333c7e Add TODO 2022-01-17 22:56:13 +02:00
Lior Halphon ab75858c86 DMA/PPU VRAM conflicts on the CGB/AGB 2022-01-17 22:07:24 +02:00
Lior Halphon b45761146f VRAM DMA during mode 3 2022-01-16 13:50:59 +02:00
Lior Halphon 3133687e68 Potential logic bug fixes 2022-01-16 00:50:08 +02:00
Lior Halphon dbe9035c55 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2022-01-16 00:16:12 +02:00
Lior Halphon 5088bd0959 Redundant OR 2022-01-16 00:16:06 +02:00
Lior Halphon e922b3fc4a
Merge pull request #430 from orbea/pedantic
sgb.h: Silence -Wgnu-anonymous-struct and -Wnested-anon-type warnings
2022-01-15 01:29:49 +02:00
orbea 7c9ab0fd46 sgb.h: Use GB_INTERNAL to hide internal struct data
Silences warnings in downstream projects that use a different language standard
when including gb.h.

sgb.h:14:9: warning: anonymous structs are a GNU extension [-Wgnu-anonymous-struct]
        struct {
        ^
sgb.h:14:9: warning: anonymous types declared in an anonymous union are an extension [-Wnested-anon-types]
        struct {
        ^
2022-01-14 15:26:15 -08:00
Lior Halphon eaeeb49612 Minor stylistic changes 2022-01-14 22:26:23 +02:00
Lior Halphon b92dd51101
Merge pull request #429 from orbea/pedantic
Silence more -pedantic warnings
2022-01-14 22:23:30 +02:00
orbea adfc329cdf gb.h: Silence -Wembedded-directive warning with -pedantic
gb.h:731:2: warning: embedding a directive within macro arguments has undefined behavior [-Wembedded-directive]
\#define GB_REWIND_FRAMES_PER_KEY 255
 ^
2022-01-14 11:40:55 -08:00
orbea 5cf71b406e gb.h: Silence -Wembedded-directive warnings with -pedantic
gb.h:400:2: warning: embedding a directive within macro arguments has undefined behavior [-Wembedded-directive]
\#ifdef GB_BIG_ENDIAN
 ^
gb.h:410:2: warning: embedding a directive within macro arguments has undefined behavior [-Wembedded-directive]
\#endif
 ^
2022-01-14 11:40:55 -08:00
Lior Halphon d92148b461 Merge branch 'master' of https://github.com/LIJI32/SameBoy 2022-01-14 17:09:49 +02:00
Lior Halphon ffa53eda20 DMA during mode 3 emulation 2022-01-14 17:09:39 +02:00
Lior Halphon 4ce8e77796 More accurate OAM access timings 2022-01-14 15:07:50 +02:00
Lior Halphon 20cbc896a1
Merge pull request #428 from orbea/pedantic
gb.h: Silence -pedantic warnings
2022-01-14 13:39:48 +02:00
orbea fefb81ab65 gb.h: Silence -pedantic warnings
Silences warnings such as the following when including gb.h as a dependency.

gb.h:385:6: warning: extra ';' inside a struct [-Wextra-semi]
    );
     ^
2022-01-13 20:12:58 -08:00
Lior Halphon ec012cf9f8 is_dma_restarting hack no longer needed 2022-01-12 14:12:55 +02:00
Lior Halphon c4a14ac4db Simplify DMA code, fix DMA read timing 2022-01-12 00:26:18 +02:00
Lior Halphon b1187919d3 Fixed a bug with the joy_accessed API 2022-01-09 16:43:32 +02:00
Lior Halphon 2c71ca789f Fixed a regression caused by a recent optimization 2022-01-09 15:36:01 +02:00
Lior Halphon 8df572f92e Comment fix 2022-01-09 15:30:33 +02:00
155 changed files with 6949 additions and 2047 deletions

42
BESS.md
View File

@ -176,6 +176,48 @@ The length of this block is 0x11 bytes long and it follows the following structu
| 0x0E | Scheduled alarm time days (16-bit) |
| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) |
#### TPP1 block
The TPP1 block uses the `'TPP1'` identifier, and is an optional block that is used while emulating a TPP1 cartridge to store RTC information. This block can be omitted if the ROM header does not specify the inclusion of a RTC.
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 | The current RTC data (4 bytes) |
| 0x0C | The latched RTC data (4 bytes) |
| 0x10 | The value of the MR4 register (8-bits) |
#### MBC7 block
The MBC7 block uses the `'MBC7'` identifier, and is an optional block that is used while emulating an MBC7 cartridge to store the EEPROM communication state and motion control state.
The length of this block is 0xA bytes long and it follows the following structure:
| Offset | Content |
|--------|-------------------------------------------------------|
| 0x00 | Flags (8-bits) |
| 0x01 | Argument bits left (8-bits) |
| 0x02 | Current EEPROM command (16-bits) |
| 0x04 | Pending bits to read (16-bits) |
| 0x06 | Latched gyro X value (16-bits) |
| 0x08 | Latched gyro Y value (16-bits) |
The meaning of the individual bits in flags are:
* Bit 0: Latch ready; set after writing `0x55` to `0xAX0X` and reset after writing `0xAA` to `0xAX1X`
* Bit 1: EEPROM DO line
* Bit 2: EEPROM DI line
* Bit 3: EEPROM CLK line
* Bit 4: EEPROM CS line
* Bit 5: EEPROM write enable; set after an `EWEN` command, reset after an `EWDS` command
* Bits 6-7: Unused.
The current EEPROM command field has bits pushed to its LSB first, padded with zeros. For example, if the ROM clocked a single `1` bit, this field should contain `0b1`; if the ROM later clocks a `0` bit, this field should contain `0b10`.
If the currently transmitted command has an argument, the "Argument bits left" field should contain the number argument bits remaining. Otherwise, it should contain 0.
The "Pending bits to read" field contains the pending bits waiting to be shifted into the DO signal, MSB first, padded with ones.
#### 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 477 B

View File

@ -1,5 +1,7 @@
; SameBoy CGB bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
INCLUDE "hardware.inc"
SECTION "BootCode", ROM0[$0]
Start:
; Init stack pointer
@ -7,13 +9,6 @@ Start:
; Clear memory VRAM
call ClearMemoryPage8000
ld a, 2
ld c, $70
ld [c], a
; Clear RAM Bank 2 (Like the original boot ROM)
ld h, $D0
call ClearMemoryPage
ld [c], a
; Clear OAM
ld h, $fe
@ -39,18 +34,19 @@ ENDC
; Clear title checksum
ldh [TitleChecksum], a
; Init Audio
ld a, $80
ldh [$26], a
ldh [$11], a
ldh [rNR52], a
ldh [rNR11], a
ld a, $f3
ldh [$12], a
ldh [$25], a
ldh [rNR12], a
ldh [rNR51], a
ld a, $77
ldh [$24], a
ldh [rNR50], a
; Init BG palette
ld a, $fc
ldh [$47], a
ldh [rBGP], a
; Load logo from ROM.
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
@ -72,14 +68,14 @@ ENDC
; Clear the second VRAM bank
ld a, 1
ldh [$4F], a
ldh [rVBK], a
call ClearMemoryPage8000
call LoadTileset
ld b, 3
IF DEF(FAST)
xor a
ldh [$4F], a
ldh [rVBK], a
ELSE
; Load Tilemap
ld hl, $98C2
@ -129,11 +125,11 @@ ELSE
push af
; Switch to second VRAM Bank
ld a, 1
ldh [$4F], a
ldh [rVBK], a
ld [hl], 8
; Switch to back first VRAM Bank
xor a
ldh [$4F], a
ldh [rVBK], a
pop af
ldi [hl], a
ret
@ -187,7 +183,7 @@ ENDC
; Turn on LCD
ld a, $91
ldh [$40], a
ldh [rLCDC], a
IF !DEF(FAST)
call DoIntroAnimation
@ -220,12 +216,15 @@ ENDC
IF DEF(AGB)
ld b, 1
ENDC
jr BootGame
; Will be filled with NOPs
HDMAData:
db $D0, $00, $98, $A0, $12
db $D0, $00, $80, $00, $40
SECTION "BootGame", ROM0[$fe]
BootGame:
ldh [$50], a
ldh [rBANK], a ; unmap boot ROM
SECTION "MoreStuff", ROM0[$200]
; Game Palettes Data
@ -610,9 +609,9 @@ WaitBFrames:
ret
PlaySound:
ldh [$13], a
ldh [rNR13], a
ld a, $87
ldh [$14], a
ldh [rNR14], a
ret
ClearMemoryPage8000:
@ -763,7 +762,7 @@ ReadTrademarkSymbol:
DoIntroAnimation:
; Animate the intro
ld a, 1
ldh [$4F], a
ldh [rVBK], a
ld d, 26
.animationLoop
ld b, 2
@ -835,8 +834,6 @@ IF !DEF(FAST)
res 2, b
.redNotMaxed
; add de, bc
; ld [hli], de
ld a, e
add c
ld [hli], a
@ -854,12 +851,19 @@ IF !DEF(FAST)
dec b
jr nz, .fadeLoop
ENDC
ld a, 1
ld a, 2
ldh [rSVBK], a
; Clear RAM Bank 2 (Like the original boot ROM)
ld hl, $D000
call ClearMemoryPage
inc a
call ClearVRAMViaHDMA
call _ClearVRAMViaHDMA
call ClearVRAMViaHDMA ; A = $40, so it's bank 0
ld a, $ff
ldh [$00], a
xor a
ldh [rSVBK], a
cpl
ldh [rJOYP], a
; Final values for CGB mode
ld d, a
@ -871,7 +875,7 @@ ENDC
call z, EmulateDMG
bit 7, a
ldh [$4C], a
ldh [rKEY0], a ; write CGB compatibility byte, CGB mode
ldh a, [TitleChecksum]
ld b, a
@ -901,7 +905,7 @@ ENDC
.emulateDMGForCGBGame
call EmulateDMG
ldh [$4C], a
ldh [rKEY0], a ; write $04, DMG emulation mode
ld a, $1
ret
@ -915,7 +919,7 @@ GetKeyComboPalette:
EmulateDMG:
ld a, 1
ldh [$6C], a ; DMG Emulation
ldh [rOPRI], a ; DMG Emulation sprite priority
call GetPaletteIndex
bit 7, a
call nz, LoadDMGTilemap
@ -1052,7 +1056,7 @@ LoadPalettesFromHRAM:
LoadBGPalettes:
ld e, 0
ld c, $68
ld c, LOW(rBGPI)
LoadPalettes:
ld a, $80
@ -1067,9 +1071,10 @@ LoadPalettes:
ret
ClearVRAMViaHDMA:
ldh [$4F], a
ldh [rVBK], a
ld hl, HDMAData
_ClearVRAMViaHDMA:
call WaitFrame ; Wait for vblank
ld c, $51
ld b, 5
.loop
@ -1083,8 +1088,8 @@ _ClearVRAMViaHDMA:
; clobbers AF and HL
GetInputPaletteIndex:
ld a, $20 ; Select directions
ldh [$00], a
ldh a, [$00]
ldh [rJOYP], a
ldh a, [rJOYP]
cpl
and $F
ret z ; No direction keys pressed, no palette
@ -1098,8 +1103,8 @@ GetInputPaletteIndex:
; c = 1: Right, 2: Left, 3: Up, 4: Down
ld a, $10 ; Select buttons
ldh [$00], a
ldh a, [$00]
ldh [rJOYP], a
ldh a, [rJOYP]
cpl
rla
rla
@ -1223,10 +1228,6 @@ LoadDMGTilemap:
pop af
ret
HDMAData:
db $88, $00, $98, $A0, $12
db $88, $00, $80, $00, $40
BootEnd:
IF BootEnd > $900
FAIL "BootROM overflowed: {BootEnd}"

View File

@ -1,5 +1,7 @@
; SameBoy DMG bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
INCLUDE "hardware.inc"
SECTION "BootCode", ROM0[$0]
Start:
; Init stack pointer
@ -15,17 +17,17 @@ Start:
; Init Audio
ld a, $80
ldh [$26], a
ldh [$11], a
ldh [rNR52], a
ldh [rNR11], a
ld a, $f3
ldh [$12], a
ldh [$25], a
ldh [rNR12], a
ldh [rNR51], a
ld a, $77
ldh [$24], a
ldh [rNR50], a
; Init BG palette
ld a, $54
ldh [$47], a
ldh [rBGP], a
; Load logo from ROM.
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
@ -70,11 +72,11 @@ Start:
.tilemapDone
ld a, 30
ldh [$ff42], a
ldh [rSCY], a
; Turn on LCD
ld a, $91
ldh [$40], a
ldh [rLCDC], a
ld d, (-119) & $FF
ld c, 15
@ -84,7 +86,7 @@ Start:
ld a, d
sra a
sra a
ldh [$ff42], a
ldh [rSCY], a
ld a, d
add c
ld d, a
@ -92,12 +94,12 @@ Start:
cp 8
jr nz, .noPaletteChange
ld a, $A8
ldh [$47], a
ldh [rBGP], a
.noPaletteChange
dec c
jr nz, .animate
ld a, $fc
ldh [$47], a
ldh [rBGP], a
; Play first sound
ld a, $83
@ -167,9 +169,9 @@ WaitBFrames:
ret
PlaySound:
ldh [$13], a
ldh [rNR13], a
ld a, $87
ldh [$14], a
ldh [rNR14], a
ret
@ -178,4 +180,4 @@ db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
SECTION "BootGame", ROM0[$fe]
BootGame:
ldh [$50], a
ldh [rBANK], a ; unmap boot ROM

919
BootROMs/hardware.inc Normal file
View File

@ -0,0 +1,919 @@
;*
;* Gameboy Hardware definitions
;*
;* Based on Jones' hardware.inc
;* And based on Carsten Sorensen's ideas.
;*
;* Rev 1.1 - 15-Jul-97 : Added define check
;* Rev 1.2 - 18-Jul-97 : Added revision check macro
;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05
;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes
;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines
;* : and Nintendo Logo
;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC
;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1
;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC
;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format
;* Rev 2.0 - : Added GBC registers
;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines
;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates
;* Rev 2.3 - : Fixed incorrect _HRAM equate
;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND)
;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND)
;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND)
;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm)
;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (Álvaro Cuesta)
;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants
;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object)
; If all of these are already defined, don't do it again.
IF !DEF(HARDWARE_INC)
HARDWARE_INC = 1
rev_Check_hardware_inc : MACRO
;NOTE: REVISION NUMBER CHANGES MUST BE ADDED
;TO SECOND PARAMETER IN FOLLOWING LINE.
IF \1 > 3.0 ;PUT REVISION NUMBER HERE
WARN "Version \1 or later of 'hardware.inc' is required."
ENDC
ENDM
_VRAM EQU $8000 ; $8000->$9FFF
_VRAM8000 EQU _VRAM
_VRAM8800 EQU _VRAM+$800
_VRAM9000 EQU _VRAM+$1000
_SCRN0 EQU $9800 ; $9800->$9BFF
_SCRN1 EQU $9C00 ; $9C00->$9FFF
_SRAM EQU $A000 ; $A000->$BFFF
_RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF
_RAMBANK EQU $D000 ; $D000->$DFFF
_OAMRAM EQU $FE00 ; $FE00->$FE9F
_IO EQU $FF00 ; $FF00->$FF7F,$FFFF
_AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F
_HRAM EQU $FF80 ; $FF80->$FFFE
; *** MBC5 Equates ***
rRAMG EQU $0000 ; $0000->$1fff
rROMB0 EQU $2000 ; $2000->$2fff
rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present.
rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present)
;***************************************************************************
;*
;* Custom registers
;*
;***************************************************************************
; --
; -- P1 ($FF00)
; -- Register for reading joy pad info. (R/W)
; --
rP1 EQU $FF00
P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons
P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad
P1F_3 EQU %00001000 ; P13 in port
P1F_2 EQU %00000100 ; P12 in port
P1F_1 EQU %00000010 ; P11 in port
P1F_0 EQU %00000001 ; P10 in port
P1F_GET_DPAD EQU P1F_5
P1F_GET_BTN EQU P1F_4
P1F_GET_NONE EQU P1F_4 | P1F_5
; --
; -- SB ($FF01)
; -- Serial Transfer Data (R/W)
; --
rSB EQU $FF01
; --
; -- SC ($FF02)
; -- Serial I/O Control (R/W)
; --
rSC EQU $FF02
; --
; -- DIV ($FF04)
; -- Divider register (R/W)
; --
rDIV EQU $FF04
; --
; -- TIMA ($FF05)
; -- Timer counter (R/W)
; --
rTIMA EQU $FF05
; --
; -- TMA ($FF06)
; -- Timer modulo (R/W)
; --
rTMA EQU $FF06
; --
; -- TAC ($FF07)
; -- Timer control (R/W)
; --
rTAC EQU $FF07
TACF_START EQU %00000100
TACF_STOP EQU %00000000
TACF_4KHZ EQU %00000000
TACF_16KHZ EQU %00000011
TACF_65KHZ EQU %00000010
TACF_262KHZ EQU %00000001
; --
; -- IF ($FF0F)
; -- Interrupt Flag (R/W)
; --
rIF EQU $FF0F
; --
; -- AUD1SWEEP/NR10 ($FF10)
; -- Sweep register (R/W)
; --
; -- Bit 6-4 - Sweep Time
; -- Bit 3 - Sweep Increase/Decrease
; -- 0: Addition (frequency increases???)
; -- 1: Subtraction (frequency increases???)
; -- Bit 2-0 - Number of sweep shift (# 0-7)
; -- Sweep Time: (n*7.8ms)
; --
rNR10 EQU $FF10
rAUD1SWEEP EQU rNR10
AUD1SWEEP_UP EQU %00000000
AUD1SWEEP_DOWN EQU %00001000
; --
; -- AUD1LEN/NR11 ($FF11)
; -- Sound length/Wave pattern duty (R/W)
; --
; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%)
; -- Bit 5-0 - Sound length data (# 0-63)
; --
rNR11 EQU $FF11
rAUD1LEN EQU rNR11
; --
; -- AUD1ENV/NR12 ($FF12)
; -- Envelope (R/W)
; --
; -- Bit 7-4 - Initial value of envelope
; -- Bit 3 - Envelope UP/DOWN
; -- 0: Decrease
; -- 1: Range of increase
; -- Bit 2-0 - Number of envelope sweep (# 0-7)
; --
rNR12 EQU $FF12
rAUD1ENV EQU rNR12
; --
; -- AUD1LOW/NR13 ($FF13)
; -- Frequency low byte (W)
; --
rNR13 EQU $FF13
rAUD1LOW EQU rNR13
; --
; -- AUD1HIGH/NR14 ($FF14)
; -- Frequency high byte (W)
; --
; -- Bit 7 - Initial (when set, sound restarts)
; -- Bit 6 - Counter/consecutive selection
; -- Bit 2-0 - Frequency's higher 3 bits
; --
rNR14 EQU $FF14
rAUD1HIGH EQU rNR14
; --
; -- AUD2LEN/NR21 ($FF16)
; -- Sound Length; Wave Pattern Duty (R/W)
; --
; -- see AUD1LEN for info
; --
rNR21 EQU $FF16
rAUD2LEN EQU rNR21
; --
; -- AUD2ENV/NR22 ($FF17)
; -- Envelope (R/W)
; --
; -- see AUD1ENV for info
; --
rNR22 EQU $FF17
rAUD2ENV EQU rNR22
; --
; -- AUD2LOW/NR23 ($FF18)
; -- Frequency low byte (W)
; --
rNR23 EQU $FF18
rAUD2LOW EQU rNR23
; --
; -- AUD2HIGH/NR24 ($FF19)
; -- Frequency high byte (W)
; --
; -- see AUD1HIGH for info
; --
rNR24 EQU $FF19
rAUD2HIGH EQU rNR24
; --
; -- AUD3ENA/NR30 ($FF1A)
; -- Sound on/off (R/W)
; --
; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF)
; --
rNR30 EQU $FF1A
rAUD3ENA EQU rNR30
; --
; -- AUD3LEN/NR31 ($FF1B)
; -- Sound length (R/W)
; --
; -- Bit 7-0 - Sound length
; --
rNR31 EQU $FF1B
rAUD3LEN EQU rNR31
; --
; -- AUD3LEVEL/NR32 ($FF1C)
; -- Select output level
; --
; -- Bit 6-5 - Select output level
; -- 00: 0/1 (mute)
; -- 01: 1/1
; -- 10: 1/2
; -- 11: 1/4
; --
rNR32 EQU $FF1C
rAUD3LEVEL EQU rNR32
; --
; -- AUD3LOW/NR33 ($FF1D)
; -- Frequency low byte (W)
; --
; -- see AUD1LOW for info
; --
rNR33 EQU $FF1D
rAUD3LOW EQU rNR33
; --
; -- AUD3HIGH/NR34 ($FF1E)
; -- Frequency high byte (W)
; --
; -- see AUD1HIGH for info
; --
rNR34 EQU $FF1E
rAUD3HIGH EQU rNR34
; --
; -- AUD4LEN/NR41 ($FF20)
; -- Sound length (R/W)
; --
; -- Bit 5-0 - Sound length data (# 0-63)
; --
rNR41 EQU $FF20
rAUD4LEN EQU rNR41
; --
; -- AUD4ENV/NR42 ($FF21)
; -- Envelope (R/W)
; --
; -- see AUD1ENV for info
; --
rNR42 EQU $FF21
rAUD4ENV EQU rNR42
; --
; -- AUD4POLY/NR43 ($FF22)
; -- Polynomial counter (R/W)
; --
; -- Bit 7-4 - Selection of the shift clock frequency of the (scf)
; -- polynomial counter (0000-1101)
; -- freq=drf*1/2^scf (not sure)
; -- Bit 3 - Selection of the polynomial counter's step
; -- 0: 15 steps
; -- 1: 7 steps
; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf)
; -- 000: f/4 001: f/8 010: f/16 011: f/24
; -- 100: f/32 101: f/40 110: f/48 111: f/56 (f=4.194304 Mhz)
; --
rNR43 EQU $FF22
rAUD4POLY EQU rNR43
; --
; -- AUD4GO/NR44 ($FF23)
; --
; -- Bit 7 - Inital
; -- Bit 6 - Counter/consecutive selection
; --
rNR44 EQU $FF23
rAUD4GO EQU rNR44
; --
; -- AUDVOL/NR50 ($FF24)
; -- Channel control / ON-OFF / Volume (R/W)
; --
; -- Bit 7 - Vin->SO2 ON/OFF (Vin??)
; -- Bit 6-4 - SO2 output level (volume) (# 0-7)
; -- Bit 3 - Vin->SO1 ON/OFF (Vin??)
; -- Bit 2-0 - SO1 output level (volume) (# 0-7)
; --
rNR50 EQU $FF24
rAUDVOL EQU rNR50
AUDVOL_VIN_LEFT EQU %10000000 ; SO2
AUDVOL_VIN_RIGHT EQU %00001000 ; SO1
; --
; -- AUDTERM/NR51 ($FF25)
; -- Selection of Sound output terminal (R/W)
; --
; -- Bit 7 - Output sound 4 to SO2 terminal
; -- Bit 6 - Output sound 3 to SO2 terminal
; -- Bit 5 - Output sound 2 to SO2 terminal
; -- Bit 4 - Output sound 1 to SO2 terminal
; -- Bit 3 - Output sound 4 to SO1 terminal
; -- Bit 2 - Output sound 3 to SO1 terminal
; -- Bit 1 - Output sound 2 to SO1 terminal
; -- Bit 0 - Output sound 0 to SO1 terminal
; --
rNR51 EQU $FF25
rAUDTERM EQU rNR51
; SO2
AUDTERM_4_LEFT EQU %10000000
AUDTERM_3_LEFT EQU %01000000
AUDTERM_2_LEFT EQU %00100000
AUDTERM_1_LEFT EQU %00010000
; SO1
AUDTERM_4_RIGHT EQU %00001000
AUDTERM_3_RIGHT EQU %00000100
AUDTERM_2_RIGHT EQU %00000010
AUDTERM_1_RIGHT EQU %00000001
; --
; -- AUDENA/NR52 ($FF26)
; -- Sound on/off (R/W)
; --
; -- Bit 7 - All sound on/off (sets all audio regs to 0!)
; -- Bit 3 - Sound 4 ON flag (read only)
; -- Bit 2 - Sound 3 ON flag (read only)
; -- Bit 1 - Sound 2 ON flag (read only)
; -- Bit 0 - Sound 1 ON flag (read only)
; --
rNR52 EQU $FF26
rAUDENA EQU rNR52
AUDENA_ON EQU %10000000
AUDENA_OFF EQU %00000000 ; sets all audio regs to 0!
; --
; -- LCDC ($FF40)
; -- LCD Control (R/W)
; --
rLCDC EQU $FF40
LCDCF_OFF EQU %00000000 ; LCD Control Operation
LCDCF_ON EQU %10000000 ; LCD Control Operation
LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select
LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select
LCDCF_WINOFF EQU %00000000 ; Window Display
LCDCF_WINON EQU %00100000 ; Window Display
LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select
LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select
LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select
LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select
LCDCF_OBJ8 EQU %00000000 ; OBJ Construction
LCDCF_OBJ16 EQU %00000100 ; OBJ Construction
LCDCF_OBJOFF EQU %00000000 ; OBJ Display
LCDCF_OBJON EQU %00000010 ; OBJ Display
LCDCF_BGOFF EQU %00000000 ; BG Display
LCDCF_BGON EQU %00000001 ; BG Display
; "Window Character Data Select" follows BG
; --
; -- STAT ($FF41)
; -- LCDC Status (R/W)
; --
rSTAT EQU $FF41
STATF_LYC EQU %01000000 ; LYC=LY Coincidence (Selectable)
STATF_MODE10 EQU %00100000 ; Mode 10
STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank)
STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank)
STATF_LYCF EQU %00000100 ; Coincidence Flag
STATF_HBL EQU %00000000 ; H-Blank
STATF_VBL EQU %00000001 ; V-Blank
STATF_OAM EQU %00000010 ; OAM-RAM is used by system
STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system
STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe
; --
; -- SCY ($FF42)
; -- Scroll Y (R/W)
; --
rSCY EQU $FF42
; --
; -- SCY ($FF43)
; -- Scroll X (R/W)
; --
rSCX EQU $FF43
; --
; -- LY ($FF44)
; -- LCDC Y-Coordinate (R)
; --
; -- Values range from 0->153. 144->153 is the VBlank period.
; --
rLY EQU $FF44
; --
; -- LYC ($FF45)
; -- LY Compare (R/W)
; --
; -- When LY==LYC, STATF_LYCF will be set in STAT
; --
rLYC EQU $FF45
; --
; -- DMA ($FF46)
; -- DMA Transfer and Start Address (W)
; --
rDMA EQU $FF46
; --
; -- BGP ($FF47)
; -- BG Palette Data (W)
; --
; -- Bit 7-6 - Intensity for %11
; -- Bit 5-4 - Intensity for %10
; -- Bit 3-2 - Intensity for %01
; -- Bit 1-0 - Intensity for %00
; --
rBGP EQU $FF47
; --
; -- OBP0 ($FF48)
; -- Object Palette 0 Data (W)
; --
; -- See BGP for info
; --
rOBP0 EQU $FF48
; --
; -- OBP1 ($FF49)
; -- Object Palette 1 Data (W)
; --
; -- See BGP for info
; --
rOBP1 EQU $FF49
; --
; -- WY ($FF4A)
; -- Window Y Position (R/W)
; --
; -- 0 <= WY <= 143
; -- When WY = 0, the window is displayed from the top edge of the LCD screen.
; --
rWY EQU $FF4A
; --
; -- WX ($FF4B)
; -- Window X Position (R/W)
; --
; -- 7 <= WX <= 166
; -- When WX = 7, the window is displayed from the left edge of the LCD screen.
; -- Values of 0-6 and 166 are unreliable due to hardware bugs.
; --
rWX EQU $FF4B
; --
; -- SPEED ($FF4D)
; -- Select CPU Speed (R/W)
; --
rKEY1 EQU $FF4D
rSPD EQU rKEY1
KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R)
KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W)
; --
; -- VBK ($FF4F)
; -- Select Video RAM Bank (R/W)
; --
; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1)
; --
rVBK EQU $FF4F
; --
; -- HDMA1 ($FF51)
; -- High byte for Horizontal Blanking/General Purpose DMA source address (W)
; -- CGB Mode Only
; --
rHDMA1 EQU $FF51
; --
; -- HDMA2 ($FF52)
; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W)
; -- CGB Mode Only
; --
rHDMA2 EQU $FF52
; --
; -- HDMA3 ($FF53)
; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W)
; -- CGB Mode Only
; --
rHDMA3 EQU $FF53
; --
; -- HDMA4 ($FF54)
; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W)
; -- CGB Mode Only
; --
rHDMA4 EQU $FF54
; --
; -- HDMA5 ($FF55)
; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W)
; -- CGB Mode Only
; --
rHDMA5 EQU $FF55
HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W)
HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W)
; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete
HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R)
; --
; -- RP ($FF56)
; -- Infrared Communications Port (R/W)
; --
rRP EQU $FF56
; --
; -- BCPS ($FF68)
; -- Background Color Palette Specification (R/W)
; --
rBCPS EQU $FF68
BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing)
; --
; -- BCPD ($FF69)
; -- Background Color Palette Data (R/W)
; --
rBCPD EQU $FF69
; --
; -- OCPS ($FF6A)
; -- Object Color Palette Specification (R/W)
; --
rOCPS EQU $FF6A
OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing)
; --
; -- OCPD ($FF6B)
; -- Object Color Palette Data (R/W)
; --
rOCPD EQU $FF6B
; --
; -- SMBK/SVBK ($FF70)
; -- Select Main RAM Bank (R/W)
; --
; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7)
; --
rSVBK EQU $FF70
rSMBK EQU rSVBK
; --
; -- PCM12 ($FF76)
; -- Sound channel 1&2 PCM amplitude (R)
; --
; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude
; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude
; --
rPCM12 EQU $FF76
; --
; -- PCM34 ($FF77)
; -- Sound channel 3&4 PCM amplitude (R)
; --
; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude
; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude
; --
rPCM34 EQU $FF77
; --
; -- IE ($FFFF)
; -- Interrupt Enable (R/W)
; --
rIE EQU $FFFF
IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13
IEF_SERIAL EQU %00001000 ; Serial I/O transfer end
IEF_TIMER EQU %00000100 ; Timer Overflow
IEF_LCDC EQU %00000010 ; LCDC (see STAT)
IEF_VBLANK EQU %00000001 ; V-Blank
;***************************************************************************
;*
;* Flags common to multiple sound channels
;*
;***************************************************************************
; --
; -- Square wave duty cycle
; --
; -- Can be used with AUD1LEN and AUD2LEN
; -- See AUD1LEN for more info
; --
AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5%
AUDLEN_DUTY_25 EQU %01000000 ; 25%
AUDLEN_DUTY_50 EQU %10000000 ; 50%
AUDLEN_DUTY_75 EQU %11000000 ; 75%
; --
; -- Audio envelope flags
; --
; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV
; -- See AUD1ENV for more info
; --
AUDENV_UP EQU %00001000
AUDENV_DOWN EQU %00000000
; --
; -- Audio trigger flags
; --
; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH
; -- See AUD1HIGH for more info
; --
AUDHIGH_RESTART EQU %10000000
AUDHIGH_LENGTH_ON EQU %01000000
AUDHIGH_LENGTH_OFF EQU %00000000
;***************************************************************************
;*
;* CPU values on bootup (a=type, b=qualifier)
;*
;***************************************************************************
BOOTUP_A_DMG EQU $01 ; Dot Matrix Game
BOOTUP_A_CGB EQU $11 ; Color GameBoy
BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy)
; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or
; other system running in GBC mode
BOOTUP_B_CGB EQU %00000000
BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP
;***************************************************************************
;*
;* Cart related
;*
;***************************************************************************
; $0143 Color GameBoy compatibility code
CART_COMPATIBLE_DMG EQU $00
CART_COMPATIBLE_DMG_GBC EQU $80
CART_COMPATIBLE_GBC EQU $C0
; $0146 GameBoy/Super GameBoy indicator
CART_INDICATOR_GB EQU $00
CART_INDICATOR_SGB EQU $03
; $0147 Cartridge type
CART_ROM EQU $00
CART_ROM_MBC1 EQU $01
CART_ROM_MBC1_RAM EQU $02
CART_ROM_MBC1_RAM_BAT EQU $03
CART_ROM_MBC2 EQU $05
CART_ROM_MBC2_BAT EQU $06
CART_ROM_RAM EQU $08
CART_ROM_RAM_BAT EQU $09
CART_ROM_MMM01 EQU $0B
CART_ROM_MMM01_RAM EQU $0C
CART_ROM_MMM01_RAM_BAT EQU $0D
CART_ROM_MBC3_BAT_RTC EQU $0F
CART_ROM_MBC3_RAM_BAT_RTC EQU $10
CART_ROM_MBC3 EQU $11
CART_ROM_MBC3_RAM EQU $12
CART_ROM_MBC3_RAM_BAT EQU $13
CART_ROM_MBC5 EQU $19
CART_ROM_MBC5_BAT EQU $1A
CART_ROM_MBC5_RAM_BAT EQU $1B
CART_ROM_MBC5_RUMBLE EQU $1C
CART_ROM_MBC5_RAM_RUMBLE EQU $1D
CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E
CART_ROM_MBC7_RAM_BAT_GYRO EQU $22
CART_ROM_POCKET_CAMERA EQU $FC
CART_ROM_BANDAI_TAMA5 EQU $FD
CART_ROM_HUDSON_HUC3 EQU $FE
CART_ROM_HUDSON_HUC1 EQU $FF
; $0148 ROM size
; these are kilobytes
CART_ROM_32K EQU $00 ; 2 banks
CART_ROM_64K EQU $01 ; 4 banks
CART_ROM_128K EQU $02 ; 8 banks
CART_ROM_256K EQU $03 ; 16 banks
CART_ROM_512K EQU $04 ; 32 banks
CART_ROM_1024K EQU $05 ; 64 banks
CART_ROM_2048K EQU $06 ; 128 banks
CART_ROM_4096K EQU $07 ; 256 banks
CART_ROM_8192K EQU $08 ; 512 banks
CART_ROM_1152K EQU $52 ; 72 banks
CART_ROM_1280K EQU $53 ; 80 banks
CART_ROM_1536K EQU $54 ; 96 banks
; $0149 SRAM size
; these are kilobytes
CART_SRAM_NONE EQU 0
CART_SRAM_2K EQU 1 ; 1 incomplete bank
CART_SRAM_8K EQU 2 ; 1 bank
CART_SRAM_32K EQU 3 ; 4 banks
CART_SRAM_128K EQU 4 ; 16 banks
CART_SRAM_ENABLE EQU $0A
CART_SRAM_DISABLE EQU $00
; $014A Destination code
CART_DEST_JAPANESE EQU $00
CART_DEST_NON_JAPANESE EQU $01
;***************************************************************************
;*
;* Keypad related
;*
;***************************************************************************
PADF_DOWN EQU $80
PADF_UP EQU $40
PADF_LEFT EQU $20
PADF_RIGHT EQU $10
PADF_START EQU $08
PADF_SELECT EQU $04
PADF_B EQU $02
PADF_A EQU $01
PADB_DOWN EQU $7
PADB_UP EQU $6
PADB_LEFT EQU $5
PADB_RIGHT EQU $4
PADB_START EQU $3
PADB_SELECT EQU $2
PADB_B EQU $1
PADB_A EQU $0
;***************************************************************************
;*
;* Screen related
;*
;***************************************************************************
SCRN_X EQU 160 ; Width of screen in pixels
SCRN_Y EQU 144 ; Height of screen in pixels
SCRN_X_B EQU 20 ; Width of screen in bytes
SCRN_Y_B EQU 18 ; Height of screen in bytes
SCRN_VX EQU 256 ; Virtual width of screen in pixels
SCRN_VY EQU 256 ; Virtual height of screen in pixels
SCRN_VX_B EQU 32 ; Virtual width of screen in bytes
SCRN_VY_B EQU 32 ; Virtual height of screen in bytes
;***************************************************************************
;*
;* OAM related
;*
;***************************************************************************
; OAM attributes
; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS)
RSRESET
OAMA_Y RB 1 ; y pos
OAMA_X RB 1 ; x pos
OAMA_TILEID RB 1 ; tile id
OAMA_FLAGS RB 1 ; flags (see below)
sizeof_OAM_ATTRS RB 0
OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM
; flags
OAMF_PRI EQU %10000000 ; Priority
OAMF_YFLIP EQU %01000000 ; Y flip
OAMF_XFLIP EQU %00100000 ; X flip
OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG)
OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG)
OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC)
OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC)
OAMF_PALMASK EQU %00000111 ; Palette (GBC)
OAMB_PRI EQU 7 ; Priority
OAMB_YFLIP EQU 6 ; Y flip
OAMB_XFLIP EQU 5 ; X flip
OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG)
OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC)
;*
;* Nintendo scrolling logo
;* (Code won't work on a real GameBoy)
;* (if next lines are altered.)
NINTENDO_LOGO : MACRO
DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D
DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E
ENDM
; SameBoy additions
rKEY0 EQU $FF4C
rBANK EQU $FF50
rOPRI EQU $FF6C
rJOYP EQU rP1
rBGPI EQU rBCPS
rBGPD EQU rBCPD
rOBPI EQU rOCPS
rOBPD EQU rOCPD
ENDC ;HARDWARE_INC

View File

@ -1,5 +1,7 @@
; SameBoy SGB bootstrap ROM
; Todo: use friendly names for HW registers instead of magic numbers
INCLUDE "hardware.inc"
SECTION "BootCode", ROM0[$0]
Start:
; Init stack pointer
@ -15,17 +17,17 @@ Start:
; Init Audio
ld a, $80
ldh [$26], a
ldh [$11], a
ldh [rNR52], a
ldh [rNR11], a
ld a, $f3
ldh [$12], a
ldh [$25], a
ldh [rNR12], a
ldh [rNR51], a
ld a, $77
ldh [$24], a
ldh [rNR50], a
; Init BG palette to white
ld a, $0
ldh [$47], a
ldh [rBGP], a
; Load logo from ROM.
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
@ -71,10 +73,10 @@ Start:
; Turn on LCD
ld a, $91
ldh [$40], a
ldh [rLCDC], a
ld a, $f1 ; Packet magic, increases by 2 for every packet
ldh [$80], a
ldh [_HRAM], a
ld hl, $104 ; Header start
xor a
@ -86,7 +88,7 @@ Start:
ld a, $30
ld [c], a
ldh a, [$80]
ldh a, [_HRAM]
call SendByte
push hl
ld b, $e
@ -117,9 +119,9 @@ Start:
ld [c], a
; Update command
ldh a, [$80]
ldh a, [_HRAM]
add 2
ldh [$80], a
ldh [_HRAM], a
ld a, $58
cp l
@ -135,7 +137,7 @@ Start:
; Init BG palette
ld a, $fc
ldh [$47], a
ldh [rBGP], a
; Set registers to match the original SGB boot
IF DEF(SGB2)
@ -210,4 +212,4 @@ db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
SECTION "BootGame", ROM0[$fe]
BootGame:
ldh [$50], a
ldh [rBANK], a

View File

@ -5,6 +5,7 @@
#import <Carbon/Carbon.h>
#import <JoyKit/JoyKit.h>
#import <WebKit/WebKit.h>
#import <mach-o/dyld.h>
#define UPDATE_SERVER "https://sameboy.github.io"
@ -31,10 +32,14 @@ static uint32_t color_to_int(NSColor *color)
UPDATE_FAILED,
} _updateState;
NSString *_downloadDirectory;
AuthorizationRef _auth;
}
- (void) applicationDidFinishLaunching:(NSNotification *)notification
{
// Refresh icon if launched via a software update
[NSApplication sharedApplication].applicationIconImage = [NSImage imageNamed:@"AppIcon"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
for (unsigned i = 0; i < GBButtonCount; i++) {
if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) {
@ -71,6 +76,68 @@ static uint32_t color_to_int(NSColor *color)
@"GBMBC7JoystickOverride": @NO,
@"GBMBC7AllowMouse": @YES,
// Default themes
@"GBThemes": @{
@"Desert": @{
@"BrightnessBias": @0.0,
@"Colors": @[@0xff302f3e, @0xff576674, @0xff839ba4, @0xffb1d0d2, @0xffb7d7d8],
@"DisabledLCDColor": @YES,
@"HueBias": @0.10087773904382469,
@"HueBiasStrength": @0.062142056772908363,
@"Manual": @NO,
},
@"Evening": @{
@"BrightnessBias": @-0.10168700106441975,
@"Colors": @[@0xff362601, @0xff695518, @0xff899853, @0xffa6e4ae, @0xffa9eebb],
@"DisabledLCDColor": @YES,
@"HueBias": @0.60027079191058874,
@"HueBiasStrength": @0.33816297305747867,
@"Manual": @NO,
},
@"Fog": @{
@"BrightnessBias": @0.0,
@"Colors": @[@0xff373c34, @0xff737256, @0xff9da386, @0xffc3d2bf, @0xffc7d8c6],
@"DisabledLCDColor": @YES,
@"HueBias": @0.55750435756972117,
@"HueBiasStrength": @0.18424738545816732,
@"Manual": @NO,
},
@"Magic Eggplant": @{
@"BrightnessBias": @0.0,
@"Colors": @[@0xff3c2136, @0xff942e84, @0xffc7699d, @0xfff1e4b0, @0xfff6f9b2],
@"DisabledLCDColor": @YES,
@"HueBias": @0.87717878486055778,
@"HueBiasStrength": @0.65018052788844627,
@"Manual": @NO,
},
@"Radioactive Pea": @{
@"BrightnessBias": @-0.48079556772908372,
@"Colors": @[@0xff215200, @0xff1f7306, @0xff169e34, @0xff03ceb8, @0xff00d4d1],
@"DisabledLCDColor": @YES,
@"HueBias": @0.3795131972111554,
@"HueBiasStrength": @0.34337649402390436,
@"Manual": @NO,
},
@"Seaweed": @{
@"BrightnessBias": @-0.28532744023904377,
@"Colors": @[@0xff3f0015, @0xff426532, @0xff58a778, @0xff95e0df, @0xffa0e7ee],
@"DisabledLCDColor": @YES,
@"HueBias": @0.2694067480079681,
@"HueBiasStrength": @0.51565612549800799,
@"Manual": @NO,
},
@"Twilight": @{
@"BrightnessBias": @-0.091789093625498031,
@"Colors": @[@0xff3f0015, @0xff461286, @0xff6254bd, @0xff97d3e9, @0xffa0e7ee],
@"DisabledLCDColor": @YES,
@"HueBias": @0.0,
@"HueBiasStrength": @0.49710532868525897,
@"Manual": @NO,
},
},
@"NSToolbarItemForcesStandardSize": @YES, // Forces Monterey to resepect toolbar item sizes
}];
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
@ -154,6 +221,10 @@ static uint32_t color_to_int(NSColor *color)
#ifndef UPDATE_SUPPORT
[_preferencesWindow.toolbar removeItemAtIndex:4];
#endif
if (@available(macOS 11.0, *)) {
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:0];
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:_preferencesWindow.toolbar.items.count];
}
}
[_preferencesWindow makeKeyAndOrderFront:self];
}
@ -297,8 +368,55 @@ static uint32_t color_to_int(NSColor *color)
[self.updateWindow performClose:sender];
}
- (bool)executePath:(NSString *)path withArguments:(NSArray <NSString *> *)arguments
{
if (!_auth) {
NSTask *task = [[NSTask alloc] init];
task.launchPath = path;
task.arguments = arguments;
[task launch];
[task waitUntilExit];
return task.terminationStatus == 0 && task.terminationReason == NSTaskTerminationReasonExit;
}
char *argv[arguments.count + 1];
argv[arguments.count] = NULL;
for (unsigned i = 0; i < arguments.count; i++) {
argv[i] = (char *)arguments[i].UTF8String;
}
return AuthorizationExecuteWithPrivileges(_auth, path.UTF8String, kAuthorizationFlagDefaults, argv, NULL) == errAuthorizationSuccess;
}
- (void)deauthorize
{
if (_auth) {
AuthorizationFree(_auth, kAuthorizationFlagDefaults);
_auth = nil;
}
}
- (IBAction)installUpdate:(id)sender
{
bool needsAuthorization = false;
if ([self executePath:@"/usr/sbin/spctl" withArguments:@[@"--status"]]) { // Succeeds when GateKeeper is on
// GateKeeper is on, we need to --add ourselves as root, else we might get a GateKeeper crash
needsAuthorization = true;
}
else if (access(_dyld_get_image_name(0), W_OK)) {
// We don't have write access, so we need authorization to update as root
needsAuthorization = true;
}
if (needsAuthorization && !_auth) {
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed, &_auth);
// Make sure we can modify the bundle
if (![self executePath:@"/usr/sbin/chown" withArguments:@[@"-R", [NSString stringWithFormat:@"%d:%d", getuid(), getgid()], [NSBundle mainBundle].bundlePath]]) {
[self deauthorize];
return;
}
}
[self.updateProgressSpinner startAnimation:nil];
self.updateProgressButton.title = @"Cancel";
self.updateProgressButton.enabled = true;
@ -317,8 +435,8 @@ static uint32_t color_to_int(NSColor *color)
appropriateForURL:[[NSBundle mainBundle] bundleURL]
create:true
error:nil] path];
NSTask *unzipTask;
if (!_downloadDirectory) {
[self deauthorize];
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Failed to extract update.";
@ -330,12 +448,14 @@ static uint32_t color_to_int(NSColor *color)
return;
}
NSTask *unzipTask;
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) {
[self deauthorize];
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
@ -380,6 +500,7 @@ static uint32_t color_to_int(NSColor *color)
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtPath:contentsPath toPath:contentsTempPath error:&error];
if (error) {
[self deauthorize];
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
_downloadDirectory = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
@ -394,6 +515,7 @@ static uint32_t color_to_int(NSColor *color)
}
[[NSFileManager defaultManager] moveItemAtPath:updateContentsPath toPath:contentsPath error:&error];
if (error) {
[self deauthorize];
[[NSFileManager defaultManager] moveItemAtPath:contentsTempPath toPath:contentsPath error:nil];
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
_downloadDirectory = nil;
@ -440,6 +562,14 @@ static uint32_t color_to_int(NSColor *color)
}
}
- (void)orderFrontAboutPanel:(id)sender
{
// NSAboutPanelOptionApplicationIcon is not available prior to 10.13, but the key is still there and working.
[[NSApplication sharedApplication] orderFrontStandardAboutPanelWithOptions:@{
@"ApplicationIcon": [NSImage imageNamed:@"Icon"]
}];
}
- (void)dealloc
{
if (_downloadDirectory) {

Binary file not shown.

View File

@ -0,0 +1,51 @@
<?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>
<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="audioFormatButton" destination="knX-AW-zt5" id="fKt-eI-H0y"/>
<outlet property="audioRecordingAccessoryView" destination="c22-O7-iKe" id="XD8-Gi-qOC"/>
</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="354" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Atq-RE-328">
<rect key="frame" x="18" y="10" width="56" height="16"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Format:" id="dso-NS-JlD">
<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>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="knX-AW-zt5">
<rect key="frame" x="81" y="4" width="256" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Apple AIFF" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="M3Z-UN-VKZ" id="tLM-Di-Dy3">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<menu key="menu" id="gqn-SL-AA5">
<items>
<menuItem title="Apple AIFF" state="on" tag="1" id="M3Z-UN-VKZ"/>
<menuItem title="RIFF WAVE" tag="2" id="zA0-Np-4XD"/>
<menuItem title="Raw PCM (Stereo 96KHz, 16-bit LE)" id="r9J-4k-XH5"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="audioFormatChanged:" target="-2" id="I1k-d9-afp"/>
</connections>
</popUpButton>
</subviews>
<point key="canvasLocation" x="75" y="19"/>
</customView>
</objects>
</document>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 36 KiB

BIN
Cocoa/CPU~solid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
Cocoa/CPU~solid@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

BIN
Cocoa/CPU~solid~dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 870 B

BIN
Cocoa/Display~solid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

BIN
Cocoa/Display~solid@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

View File

@ -6,8 +6,10 @@
#include "GBOSDView.h"
@class GBCheatWindowController;
@class GBPaletteView;
@class GBObjectView;
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSSplitViewDelegate>
@property (nonatomic, readonly) GB_gameboy_t *gb;
@property (nonatomic, strong) IBOutlet GBView *view;
@property (nonatomic, strong) IBOutlet NSTextView *consoleOutput;
@ -29,8 +31,8 @@
@property (nonatomic, strong) IBOutlet NSTabView *vramTabView;
@property (nonatomic, strong) IBOutlet NSPanel *vramWindow;
@property (nonatomic, strong) IBOutlet NSTextField *vramStatusLabel;
@property (nonatomic, strong) IBOutlet NSTableView *paletteTableView;
@property (nonatomic, strong) IBOutlet NSTableView *objectsTableView;
@property (nonatomic, strong) IBOutlet GBPaletteView *paletteView;
@property (nonatomic, strong) IBOutlet GBObjectView *objectView;
@property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow;
@property (nonatomic, strong) IBOutlet NSImageView *feedImageView;
@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput;
@ -51,7 +53,13 @@
@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton;
@property (strong) IBOutlet GBVisualizerView *gbsVisualizer;
@property (strong) IBOutlet GBOSDView *osdView;
@property (readonly) GB_oam_info_t *oamInfo;
@property uint8_t oamCount;
@property uint8_t oamHeight;
@property (strong) IBOutlet NSView *audioRecordingAccessoryView;
@property (strong) IBOutlet NSPopUpButton *audioFormatButton;
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale;
-(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
-(void) performAtomicBlock: (void (^)())block;

View File

@ -1,16 +1,42 @@
#include <AVFoundation/AVFoundation.h>
#include <CoreAudio/CoreAudio.h>
#include <Core/gb.h>
#include "GBAudioClient.h"
#include "Document.h"
#include "AppDelegate.h"
#include "HexFiend/HexFiend.h"
#include "GBMemoryByteArray.h"
#include "GBWarningPopover.h"
#include "GBCheatWindowController.h"
#include "GBTerminalTextFieldCell.h"
#include "BigSurToolbar.h"
#import <AVFoundation/AVFoundation.h>
#import <CoreAudio/CoreAudio.h>
#import <Core/gb.h>
#import "GBAudioClient.h"
#import "Document.h"
#import "AppDelegate.h"
#import "HexFiend/HexFiend.h"
#import "GBMemoryByteArray.h"
#import "GBWarningPopover.h"
#import "GBCheatWindowController.h"
#import "GBTerminalTextFieldCell.h"
#import "BigSurToolbar.h"
#import "GBPaletteEditorController.h"
#import "GBObjectView.h"
#import "GBPaletteView.h"
@implementation NSString (relativePath)
- (NSString *)pathRelativeToDirectory:(NSString *)directory
{
NSMutableArray<NSString *> *baseComponents = [[directory pathComponents] mutableCopy];
NSMutableArray<NSString *> *selfComponents = [[self pathComponents] mutableCopy];
while (baseComponents.count) {
if (![baseComponents.firstObject isEqualToString:selfComponents.firstObject]) {
break;
}
[baseComponents removeObjectAtIndex:0];
[selfComponents removeObjectAtIndex:0];
}
while (baseComponents.count) {
[baseComponents removeObjectAtIndex:0];
[selfComponents insertObject:@".." atIndex:0];
}
return [selfComponents componentsJoinedByString:@"/"];
}
@end
#define GB_MODEL_PAL_BIT_OLD 0x1000
@ -46,10 +72,7 @@ enum model {
AVCaptureConnection *cameraConnection;
AVCaptureStillImageOutput *cameraOutput;
GB_oam_info_t oamInfo[40];
uint16_t oamCount;
uint8_t oamHeight;
bool oamUpdating;
GB_oam_info_t _oamInfo[40];
NSMutableData *currentPrinterImageData;
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory;
@ -78,6 +101,9 @@ enum model {
Document *slave;
signed linkOffset;
bool linkCableBit;
NSSavePanel *_audioSavePanel;
bool _isRecordingAudio;
}
@property GBAudioClient *audioClient;
@ -105,7 +131,7 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
[self loadBootROM: type];
}
static void vblank(GB_gameboy_t *gb)
static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
{
Document *self = (__bridge Document *)GB_get_user_data(gb);
[self vblank];
@ -454,7 +480,6 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (void) run
{
assert(!master);
running = true;
[self preRun];
if (slave) {
[slave preRun];
@ -501,8 +526,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
[_audioClient stop];
_audioClient = nil;
self.view.mouseHidingEnabled = false;
GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]);
GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]);
GB_save_battery(&gb, self.savPath.UTF8String);
GB_save_cheats(&gb, self.chtPath.UTF8String);
unsigned time_to_alarm = GB_time_to_alarm(&gb);
if (time_to_alarm) {
@ -533,6 +558,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
return;
}
if (running) return;
running = true;
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}
@ -907,7 +933,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
for (NSView *view in [_mainWindow.contentView.subviews copy]) {
[view removeFromSuperview];
}
[[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
if (@available(macOS 11, *)) {
[[NSBundle mainBundle] loadNibNamed:@"GBS11" owner:self topLevelObjects:nil];
}
else {
[[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
}
[_mainWindow setContentSize:self.gbsPlayerView.bounds.size];
_mainWindow.styleMask &= ~NSWindowStyleMaskResizable;
dispatch_async(dispatch_get_main_queue(), ^{ // Cocoa is weird, no clue why it's needed
@ -944,28 +975,107 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
}
}
- (bool)isCartContainer
{
return [self.fileName.pathExtension.lowercaseString isEqualToString:@"gbcart"];
}
- (NSString *)savPath
{
if (self.isCartContainer) {
return [self.fileName stringByAppendingPathComponent:@"battery.sav"];
}
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path;
}
- (NSString *)chtPath
{
if (self.isCartContainer) {
return [self.fileName stringByAppendingPathComponent:@"cheats.cht"];
}
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path;
}
- (NSString *)saveStatePath:(unsigned)index
{
if (self.isCartContainer) {
return [self.fileName stringByAppendingPathComponent:[NSString stringWithFormat:@"state.s%u", index]];
}
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%u", index]].path;
}
- (NSString *)romPath
{
NSString *fileName = self.fileName;
if (self.isCartContainer) {
NSArray *paths = [[NSString stringWithContentsOfFile:[fileName stringByAppendingPathComponent:@"rom.gbl"]
encoding:NSUTF8StringEncoding
error:nil] componentsSeparatedByString:@"\n"];
fileName = nil;
bool needsRebuild = false;
for (NSString *path in paths) {
NSURL *url = [NSURL URLWithString:path relativeToURL:self.fileURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
if (fileName && ![fileName isEqualToString:url.path]) {
needsRebuild = true;
break;
}
fileName = url.path;
}
else {
needsRebuild = true;
}
}
if (fileName && needsRebuild) {
[[NSString stringWithFormat:@"%@\n%@\n%@",
[fileName pathRelativeToDirectory:self.fileName],
fileName,
[[NSURL fileURLWithPath:fileName].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]]
writeToFile:[self.fileName stringByAppendingPathComponent:@"rom.gbl"]
atomically:false
encoding:NSUTF8StringEncoding
error:nil];
}
}
return fileName;
}
- (int) loadROM
{
__block int ret = 0;
NSString *fileName = self.romPath;
if (!fileName) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Could not locate the ROM referenced by this Game Boy Cartridge"];
[alert setAlertStyle:NSAlertStyleCritical];
[alert runModal];
return 1;
}
NSString *rom_warnings = [self captureOutputForBlock:^{
GB_debugger_clear_symbols(&gb);
if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"isx"]) {
ret = GB_load_isx(&gb, self.fileURL.path.UTF8String);
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String);
if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"isx"]) {
ret = GB_load_isx(&gb, fileName.UTF8String);
if (!self.isCartContainer) {
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String);
}
}
else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) {
else if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"gbs"]) {
__block GB_gbs_info_t info;
ret = GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info);
ret = GB_load_gbs(&gb, fileName.UTF8String, &info);
[self prepareGBSInterface:&info];
}
else {
ret = GB_load_rom(&gb, [self.fileURL.path UTF8String]);
ret = GB_load_rom(&gb, [fileName UTF8String]);
}
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path.UTF8String);
GB_load_cheats(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path.UTF8String);
GB_load_battery(&gb, self.savPath.UTF8String);
GB_load_cheats(&gb, self.chtPath.UTF8String);
[self.cheatWindowController cheatsUpdated];
GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]);
GB_debugger_load_symbol_file(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sym"].path.UTF8String);
GB_debugger_load_symbol_file(&gb, [[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"].UTF8String);
}];
if (ret) {
NSAlert *alert = [[NSAlert alloc] init];
@ -1032,7 +1142,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
return !GB_debugger_is_stopped(&gb);
}
else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) {
[(NSMenuItem*)anItem setState:anItem.tag == current_model];
[(NSMenuItem *)anItem setState:anItem.tag == current_model];
}
else if ([anItem action] == @selector(interrupt:)) {
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) {
@ -1040,26 +1150,29 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
}
}
else if ([anItem action] == @selector(disconnectAllAccessories:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryNone];
[(NSMenuItem *)anItem setState:accessory == GBAccessoryNone];
}
else if ([anItem action] == @selector(connectPrinter:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter];
[(NSMenuItem *)anItem setState:accessory == GBAccessoryPrinter];
}
else if ([anItem action] == @selector(connectWorkboy:)) {
[(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy];
[(NSMenuItem *)anItem setState:accessory == GBAccessoryWorkboy];
}
else if ([anItem action] == @selector(connectLinkCable:)) {
[(NSMenuItem*)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
[(NSMenuItem *)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
[(NSMenuItem *)anItem representedObject] == slave];
}
else if ([anItem action] == @selector(toggleCheats:)) {
[(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)];
[(NSMenuItem *)anItem setState:GB_cheats_enabled(&gb)];
}
else if ([anItem action] == @selector(toggleDisplayBackground:)) {
[(NSMenuItem*)anItem setState:!GB_is_background_rendering_disabled(&gb)];
[(NSMenuItem *)anItem setState:!GB_is_background_rendering_disabled(&gb)];
}
else if ([anItem action] == @selector(toggleDisplayObjects:)) {
[(NSMenuItem*)anItem setState:!GB_is_object_rendering_disabled(&gb)];
[(NSMenuItem *)anItem setState:!GB_is_object_rendering_disabled(&gb)];
}
else if ([anItem action] == @selector(toggleAudioRecording:)) {
[(NSMenuItem *)anItem setTitle:_isRecordingAudio? @"Stop Audio Recording" : @"Start Audio Recording…"];
}
return [super validateUserInterfaceItem:anItem];
@ -1128,9 +1241,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
[self.consoleWindow orderFront:nil];
}
pending_console_output = nil;
}
}
[console_output_lock unlock];
}
- (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes
@ -1255,6 +1367,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (char *) getDebuggerInput
{
bool isPlaying = _audioClient.isPlaying;
if (isPlaying) {
[_audioClient stop];
}
[audioLock lock];
[audioLock signal];
[audioLock unlock];
@ -1273,6 +1389,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
[self.debuggerSideView setString:@""];
}
});
if (isPlaying) {
[_audioClient start];
}
if ((id) input == [NSNull null]) {
return NULL;
}
@ -1297,7 +1416,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
{
bool __block success = false;
[self performAtomicBlock:^{
success = GB_save_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0;
success = GB_save_state(&gb, [self saveStatePath:[sender tag]].UTF8String) == 0;
}];
if (!success) {
@ -1335,8 +1454,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
- (IBAction)loadState:(id)sender
{
int ret = [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:true];
if (ret == ENOENT) {
int ret = [self loadStateFile:[self saveStatePath:[sender tag]].UTF8String noErrorOnNotFound:true];
if (ret == ENOENT && !self.isCartContainer) {
[self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false];
}
}
@ -1390,28 +1509,21 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale
{
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef iref = CGImageCreate(width,
height,
8,
32,
4 * width,
colorSpaceRef,
bitmapInfo,
provider,
NULL,
true,
renderingIntent);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpaceRef);
NSImage *ret = [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(width * scale, height * scale)];
CGImageRelease(iref);
NSImage *ret = [[NSImage alloc] initWithSize:NSMakeSize(width * scale, height * scale)];
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:3
hasAlpha:false
isPlanar:false
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:0
bytesPerRow:4 * width
bitsPerPixel:32];
memcpy(rep.bitmapData, data.bytes, data.length);
[ret addRepresentation:rep];
return ret;
}
@ -1475,13 +1587,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
case 2:
/* OAM */
{
oamCount = GB_get_oam_info(&gb, oamInfo, &oamHeight);
_oamCount = GB_get_oam_info(&gb, _oamInfo, &_oamHeight);
dispatch_async(dispatch_get_main_queue(), ^{
if (!oamUpdating) {
oamUpdating = true;
[self.objectsTableView reloadData];
oamUpdating = false;
}
[self.objectView reloadData:self];
});
}
break;
@ -1490,7 +1598,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
/* Palettes */
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.paletteTableView reloadData];
[self.paletteView reloadData:self];
});
}
break;
@ -1757,14 +1865,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
window_rect.origin.y += window_rect.size.height;
switch ([sender selectedSegment]) {
case 0:
case 2:
window_rect.size.height = 384 + height_diff + 48;
break;
case 1:
case 2:
window_rect.size.height = 512 + height_diff + 48;
break;
case 3:
window_rect.size.height = 20 * 16 + height_diff + 34;
window_rect.size.height = 24 * 16 + height_diff;
break;
default:
@ -1796,7 +1904,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
uint8_t *vram = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL);
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && lcdc & 0x08)) {
map_base = 0x1c00;
map_base = 0x1C00;
}
if (tileset_type == GB_TILESET_AUTO) {
@ -1837,79 +1945,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
}
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
- (GB_oam_info_t *)oamInfo
{
if (tableView == self.paletteTableView) {
return 16; /* 8 BG palettes, 8 OBJ palettes*/
}
else if (tableView == self.objectsTableView) {
return oamCount;
}
return 0;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (tableView == self.paletteTableView) {
if (columnIndex == 0) {
return [NSString stringWithFormat:@"%s %u", row >= 8 ? "Object" : "Background", (unsigned)(row & 7)];
}
uint8_t *palette_data = GB_get_direct_access(&gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, NULL, NULL);
uint16_t index = columnIndex - 1 + (row & 7) * 4;
return @((palette_data[(index << 1) + 1] << 8) | palette_data[(index << 1)]);
}
else if (tableView == self.objectsTableView) {
switch (columnIndex) {
case 0:
return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image
length:64 * 4 * 2
freeWhenDone:false]
width:8
height:oamHeight
scale:16.0/oamHeight];
case 1:
return @((unsigned)oamInfo[row].x - 8);
case 2:
return @((unsigned)oamInfo[row].y - 16);
case 3:
return [NSString stringWithFormat:@"$%02x", oamInfo[row].tile];
case 4:
return [NSString stringWithFormat:@"$%04x", 0x8000 + oamInfo[row].tile * 0x10];
case 5:
return [NSString stringWithFormat:@"$%04x", oamInfo[row].oam_addr];
case 6:
if (GB_is_cgb(&gb)) {
return [NSString stringWithFormat:@"%c%c%c%d%d",
oamInfo[row].flags & 0x80? 'P' : '-',
oamInfo[row].flags & 0x40? 'Y' : '-',
oamInfo[row].flags & 0x20? 'X' : '-',
oamInfo[row].flags & 0x08? 1 : 0,
oamInfo[row].flags & 0x07];
}
return [NSString stringWithFormat:@"%c%c%c%d",
oamInfo[row].flags & 0x80? 'P' : '-',
oamInfo[row].flags & 0x40? 'Y' : '-',
oamInfo[row].flags & 0x20? 'X' : '-',
oamInfo[row].flags & 0x10? 1 : 0];
case 7:
return oamInfo[row].obscured_by_line_limit? @"Dropped: Too many objects in line": @"";
}
}
return nil;
}
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
{
return tableView == self.objectsTableView;
}
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
return false;
return _oamInfo;
}
- (IBAction)showVRAMViewer:(id)sender
@ -2148,6 +2186,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
bool wasRunning = self->running;
Document *partner = master ?: slave;
if (partner) {
wasRunning |= partner->running;
[self stop];
partner->master = nil;
partner->slave = nil;
@ -2342,4 +2381,95 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
GB_set_object_rendering_disabled(&gb, !GB_is_object_rendering_disabled(&gb));
}
- (IBAction)newCartridgeInstance:(id)sender
{
bool shouldResume = running;
[self stop];
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel setAllowedFileTypes:@[@"gbcart"]];
[savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[savePanel orderOut:self];
NSString *romPath = self.romPath;
[[NSFileManager defaultManager] trashItemAtURL:savePanel.URL resultingItemURL:nil error:nil];
[[NSFileManager defaultManager] createDirectoryAtURL:savePanel.URL withIntermediateDirectories:false attributes:nil error:nil];
[[NSString stringWithFormat:@"%@\n%@\n%@",
[romPath pathRelativeToDirectory:savePanel.URL.path],
romPath,
[[NSURL fileURLWithPath:romPath].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]
] writeToURL:[savePanel.URL URLByAppendingPathComponent:@"rom.gbl"] atomically:false encoding:NSUTF8StringEncoding error:nil];
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:savePanel.URL display:true completionHandler:nil];
}
if (shouldResume) {
[self start];
}
}];
}
- (IBAction)toggleAudioRecording:(id)sender
{
bool shouldResume = running;
[self stop];
if (_isRecordingAudio) {
_isRecordingAudio = false;
int error = GB_stop_audio_recording(&gb);
if (error) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:[NSString stringWithFormat:@"Could not finalize recording: %s", strerror(error)]];
[alert setAlertStyle:NSAlertStyleCritical];
[alert runModal];
}
else {
[self.osdView displayText:@"Audio recording ended"];
}
if (shouldResume) {
[self start];
}
return;
}
_audioSavePanel = [NSSavePanel savePanel];
if (!self.audioRecordingAccessoryView) {
[[NSBundle mainBundle] loadNibNamed:@"AudioRecordingAccessoryView" owner:self topLevelObjects:nil];
}
_audioSavePanel.accessoryView = self.audioRecordingAccessoryView;
[self audioFormatChanged:self.audioFormatButton];
[_audioSavePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[_audioSavePanel orderOut:self];
int error = GB_start_audio_recording(&gb, _audioSavePanel.URL.fileSystemRepresentation, self.audioFormatButton.selectedTag);
if (error) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:[NSString stringWithFormat:@"Could not start recording: %s", strerror(error)]];
[alert setAlertStyle:NSAlertStyleCritical];
[alert runModal];
}
else {
[self.osdView displayText:@"Audio recording started"];
_isRecordingAudio = true;
}
}
if (shouldResume) {
[self start];
}
_audioSavePanel = nil;
}];
}
- (IBAction)audioFormatChanged:(NSPopUpButton *)sender
{
switch ((GB_audio_format_t)sender.selectedTag) {
case GB_AUDIO_FORMAT_RAW:
_audioSavePanel.allowedFileTypes = @[@"raw", @"pcm"];
break;
case GB_AUDIO_FORMAT_AIFF:
_audioSavePanel.allowedFileTypes = @[@"aiff", @"aif", @"aifc"];
break;
case GB_AUDIO_FORMAT_WAV:
_audioSavePanel.allowedFileTypes = @[@"wav"];
break;
}
}
@end

View File

@ -25,9 +25,9 @@
<outlet property="memoryBankItem" destination="bWC-FW-IYP" id="Lf2-dh-z32"/>
<outlet property="memoryView" destination="8hr-8o-3rN" id="fF0-rh-8ND"/>
<outlet property="memoryWindow" destination="mRm-dL-mCj" id="VPR-lu-vtI"/>
<outlet property="objectsTableView" destination="TOc-XJ-w9w" id="O4R-4Z-9hU"/>
<outlet property="objectView" destination="fIM-GT-QXJ" id="jzs-q8-Z2U"/>
<outlet property="osdView" destination="MX4-l2-7NE" id="Am7-fq-uvu"/>
<outlet property="paletteTableView" destination="gfC-d3-dmq" id="fTC-eL-Qg3"/>
<outlet property="paletteView" destination="ZuP-AU-0pA" id="ef6-27-Bci"/>
<outlet property="printerFeedWindow" destination="NdE-0B-WCf" id="yVK-cS-NOJ"/>
<outlet property="tilemapImageView" destination="LlK-tV-bjv" id="nSY-Xd-BjZ"/>
<outlet property="tilemapMapButton" destination="YIJ-Qc-SIZ" id="BB7-Gg-7XP"/>
@ -341,17 +341,17 @@
</textFieldCell>
</textField>
<tabView fixedFrame="YES" drawsBackground="NO" type="noTabsNoBorder" initialItem="pXb-od-Wb1" translatesAutoresizingMaskIntoConstraints="NO" id="AZz-Mh-rPA">
<rect key="frame" x="0.0" y="24" width="512" height="408"/>
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Tileset" identifier="1" id="pXb-od-Wb1">
<view key="view" ambiguous="YES" id="lCG-Gt-XMF">
<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="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QcQ-ex-36R" customClass="GBImageView">
<rect key="frame" x="0.0" y="0.0" width="512" height="384"/>
<rect key="frame" x="0.0" y="24" width="512" height="384"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" id="pXc-O8-jg5"/>
<connections>
@ -360,7 +360,7 @@
</connections>
</imageView>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TLv-xS-X5K">
<rect key="frame" x="4" y="388" width="128" height="17"/>
<rect key="frame" x="4" y="412" width="128" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="None" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="G8p-CH-PlV" id="1jI-s4-4YY">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -392,7 +392,7 @@
</connections>
</popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fL6-2S-Rgd">
<rect key="frame" x="452" y="388" width="56" height="17"/>
<rect key="frame" x="452" y="412" width="56" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundRect" title="Grid" bezelStyle="roundedRect" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="pDn-9a-Se6">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
@ -407,11 +407,11 @@
</tabViewItem>
<tabViewItem label="Tilemap" identifier="2" id="kaY-Wy-Yt1">
<view key="view" id="c6j-cM-dDx">
<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="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DhM-Em-hj7">
<rect key="frame" x="385" y="388" width="63" height="17"/>
<rect key="frame" x="385" y="412" width="63" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundRect" title="Scrolling" bezelStyle="roundedRect" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="P2E-5t-BN9">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
@ -422,7 +422,7 @@
</connections>
</button>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LlK-tV-bjv" customClass="GBImageView">
<rect key="frame" x="0.0" y="0.0" width="512" height="384"/>
<rect key="frame" x="0.0" y="24" width="512" height="384"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" id="RvP-El-ILj"/>
<connections>
@ -431,7 +431,7 @@
</connections>
</imageView>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="loB-0k-Qff">
<rect key="frame" x="4" y="388" width="128" height="17"/>
<rect key="frame" x="4" y="412" width="128" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Palettes" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="oUH-Sa-Ec1" id="Eij-Cp-URa">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -464,7 +464,7 @@
</connections>
</popUpButton>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YIJ-Qc-SIZ">
<rect key="frame" x="135" y="388" width="96" height="17"/>
<rect key="frame" x="135" y="412" width="96" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Tilemap" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="XRF-Vj-3gs" id="3W1-Db-wDn">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -482,7 +482,7 @@
</connections>
</popUpButton>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k4c-Vg-MBu">
<rect key="frame" x="235" y="388" width="96" height="17"/>
<rect key="frame" x="235" y="412" width="96" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Tileset" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="CRe-dX-rzY" id="h53-sb-Odg">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -504,250 +504,45 @@
</tabViewItem>
<tabViewItem label="Objects" identifier="" id="a08-eg-Maw">
<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="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="krD-gH-o5I">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vhq-K4-baH">
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="3VT-AA-xVT">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<clipView key="contentView" ambiguous="YES" id="JYu-CM-49p">
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" headerView="of1-KC-dXC" id="TOc-XJ-w9w">
<rect key="frame" x="0.0" y="0.0" width="512" height="391"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="32" minWidth="32" maxWidth="1000" id="hRp-Kh-nWC">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<imageCell key="dataCell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" alignment="left" id="Jhk-KW-Hoc" customClass="GBImageCell"/>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="28" minWidth="28" maxWidth="1000" id="Vrl-in-npm">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="X">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" id="czf-Bn-nZN">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="28" minWidth="28" maxWidth="1000" id="636-Td-Zcm">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Y">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="zh6-hI-Ss8">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="28" minWidth="28" maxWidth="1000" id="vMj-ya-pGG">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Tile">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="0s8-eF-rgd">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="68" minWidth="40" maxWidth="1000" id="U5B-eh-aer">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Tile Addr.">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="PtW-wF-c0o">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="68" minWidth="40" maxWidth="1000" id="LSg-Sc-Sdr">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="OAM Addr.">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="a7k-83-iPE">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="65" minWidth="40" maxWidth="1000" id="S9B-Hc-6Ee">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Attributes">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="sJa-oU-QZp">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="168" minWidth="40" maxWidth="1000" id="8fQ-EC-E5K">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" selectable="YES" editable="YES" id="YSL-O0-ZwU">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="-2" id="ypl-SU-g5n"/>
<outlet property="delegate" destination="-2" id="HzE-pT-cX5"/>
</connections>
</tableView>
<view ambiguous="YES" id="fIM-GT-QXJ" customClass="GBObjectView">
<rect key="frame" x="0.0" y="0.0" width="512" height="706"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
</subviews>
<nil key="backgroundColor"/>
<edgeInsets key="contentInsets" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="n53-qA-NpY">
<rect key="frame" x="0.0" y="392" width="512" height="16"/>
<edgeInsets key="contentInsets" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="zUB-xg-cXq">
<rect key="frame" x="-100" y="-100" width="512" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="mqp-NY-g8d">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="jMe-gO-ERw">
<rect key="frame" x="496" y="0.0" width="16" height="432"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="of1-KC-dXC">
<rect key="frame" x="0.0" y="0.0" width="512" height="17"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
</subviews>
</view>
</tabViewItem>
<tabViewItem label="Palettes" identifier="" id="vLb-Nh-UYE">
<view key="view" id="qIg-ci-WTA">
<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="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="iSs-Ow-wwb">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZuP-AU-0pA" customClass="GBPaletteView">
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" id="bP9-su-zQw">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" id="gfC-d3-dmq">
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" dashed="YES"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="125" minWidth="40" maxWidth="1000" id="Aje-FQ-fUw">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" title="Text Cell" id="Ygb-xo-X8v">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="93" minWidth="40" maxWidth="1000" id="4EN-0j-lda">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" title="Text Cell" id="3a2-A4-XQW" customClass="GBColorCell">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="syl-os-nSf">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" alignment="left" title="Text Cell" id="3ql-bn-AxP" customClass="GBColorCell">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="Qw3-u2-c1s">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" title="Text Cell" id="9at-mH-PWc" customClass="GBColorCell">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="gTl-gN-qLn">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" alignment="left" title="Text Cell" id="dY8-Zu-d4t" customClass="GBColorCell">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="-2" id="F2f-nK-QTN"/>
<outlet property="delegate" destination="-2" id="7hn-MM-DDD"/>
</connections>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="OS3-sw-bjv">
<rect key="frame" x="-100" y="-100" width="510" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="4HA-2m-8TZ">
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</customView>
</subviews>
</view>
</tabViewItem>
@ -840,7 +635,7 @@
<rect key="frame" x="16" y="174" width="154" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="To value:" id="Ycx-oE-aA4">
<font key="font" usesAppearanceFont="YES"/>
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@ -971,7 +766,7 @@
<rect key="frame" x="16" y="204" width="152" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Change byte at address:" id="xwa-TF-eY1">
<font key="font" usesAppearanceFont="YES"/>
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@ -986,7 +781,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" headerView="pvX-uJ-qK5" id="tA3-8T-bxb">
<rect key="frame" x="0.0" y="0.0" width="398" height="249"/>
<rect key="frame" x="0.0" y="0.0" width="398" height="257"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -998,7 +793,7 @@
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<buttonCell key="dataCell" type="bevel" bezelStyle="regularSquare" image="NSStopProgressFreestandingTemplate" imagePosition="only" inset="2" id="5xh-hN-jHH">
<buttonCell key="dataCell" type="inline" bezelStyle="inline" image="NSStopProgressFreestandingTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="5xh-hN-jHH">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
@ -1051,7 +846,7 @@
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="3Hg-LL-VqH">
<rect key="frame" x="1" y="258" width="398" height="16"/>
<rect key="frame" x="1" y="119" width="223" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="zET-KH-qF4">
@ -1059,7 +854,7 @@
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="pvX-uJ-qK5">
<rect key="frame" x="0.0" y="0.0" width="398" height="25"/>
<rect key="frame" x="0.0" y="0.0" width="398" height="17"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>

View File

@ -1,5 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBColorCell : NSTextFieldCell
@end

View File

@ -1,49 +0,0 @@
#import "GBColorCell.h"
static inline double scale_channel(uint8_t x)
{
x &= 0x1f;
return x / 31.0;
}
@implementation GBColorCell
{
NSInteger _integerValue;
}
- (void)setObjectValue:(id)objectValue
{
_integerValue = [objectValue integerValue];
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
{
return _integerValue;
}
- (int)intValue
{
return (int)_integerValue;
}
- (NSColor *) backgroundColor
{
/* Todo: color correction */
uint16_t color = self.integerValue;
return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0];
}
- (BOOL)drawsBackground
{
return true;
}
@end

View File

@ -1,5 +0,0 @@
#import <Cocoa/Cocoa.h>
@interface GBImageCell : NSImageCell
@end

View File

@ -1,10 +0,0 @@
#import "GBImageCell.h"
@implementation GBImageCell
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
[super drawWithFrame:cellFrame inView:controlView];
}
@end

View File

@ -10,18 +10,18 @@
}
@end
@implementation GBImageView
{
NSTrackingArea *trackingArea;
}
@interface GBGridView : NSView
@end
@implementation GBGridView
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
[super drawRect:dirtyRect];
CGFloat y_ratio = self.frame.size.height / self.image.size.height;
CGFloat x_ratio = self.frame.size.width / self.image.size.width;
for (GBImageViewGridConfiguration *conf in self.verticalGrids) {
GBImageView *parent = (GBImageView *)self.superview;
CGFloat y_ratio = parent.frame.size.height / parent.image.size.height;
CGFloat x_ratio = parent.frame.size.width / parent.image.size.width;
for (GBImageViewGridConfiguration *conf in parent.verticalGrids) {
[conf.color set];
for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) {
NSBezierPath *line = [NSBezierPath bezierPath];
@ -32,7 +32,7 @@
}
}
for (GBImageViewGridConfiguration *conf in self.horizontalGrids) {
for (GBImageViewGridConfiguration *conf in parent.horizontalGrids) {
[conf.color set];
for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) {
NSBezierPath *line = [NSBezierPath bezierPath];
@ -43,11 +43,11 @@
}
}
if (self.displayScrollRect) {
if (parent.displayScrollRect) {
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite];
for (unsigned x = 0; x < 2; x++) {
for (unsigned y = 0; y < 2; y++) {
NSRect rect = self.scrollRect;
NSRect rect = parent.scrollRect;
rect.origin.x *= x_ratio;
rect.origin.y *= y_ratio;
rect.size.width *= x_ratio;
@ -56,7 +56,7 @@
rect.origin.x -= self.frame.size.width * x;
rect.origin.y += self.frame.size.height * y;
NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect];
[path appendBezierPath:subpath];
@ -72,36 +72,62 @@
[path stroke];
}
}
@end
@implementation GBImageView
{
NSTrackingArea *_trackingArea;
GBGridView *_gridView;
NSRect _scrollRect;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
self.wantsLayer = true;
_gridView = [[GBGridView alloc] initWithFrame:self.bounds];
_gridView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[self addSubview:_gridView];
return self;
}
-(void)viewWillDraw
{
[super viewWillDraw];
for (CALayer *layer in self.layer.sublayers) {
layer.magnificationFilter = kCAFilterNearest;
}
}
- (void)setHorizontalGrids:(NSArray *)horizontalGrids
{
self->_horizontalGrids = horizontalGrids;
[self setNeedsDisplay];
[_gridView setNeedsDisplay:true];
}
- (void)setVerticalGrids:(NSArray *)verticalGrids
{
self->_verticalGrids = verticalGrids;
[self setNeedsDisplay];
[_gridView setNeedsDisplay:true];
}
- (void)setDisplayScrollRect:(bool)displayScrollRect
{
self->_displayScrollRect = displayScrollRect;
[self setNeedsDisplay];
[_gridView setNeedsDisplay:true];
}
- (void)updateTrackingAreas
{
if (trackingArea != nil) {
[self removeTrackingArea:trackingArea];
if (_trackingArea != nil) {
[self removeTrackingArea:_trackingArea];
}
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
_trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved
owner:self
userInfo:nil];
[self addTrackingArea:trackingArea];
[self addTrackingArea:_trackingArea];
}
- (void)mouseExited:(NSEvent *)theEvent
@ -124,4 +150,17 @@
}
}
- (void)setScrollRect:(NSRect)scrollRect
{
if (memcmp(&scrollRect, &_scrollRect, sizeof(scrollRect)) != 0) {
_scrollRect = scrollRect;
[_gridView setNeedsDisplay:true];
}
}
- (NSRect)scrollRect
{
return _scrollRect;
}
@end

6
Cocoa/GBObjectView.h Normal file
View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
#import "Document.h"
@interface GBObjectView : NSView
- (void)reloadData:(Document *)document;
@end

124
Cocoa/GBObjectView.m Normal file
View File

@ -0,0 +1,124 @@
#import "GBObjectView.h"
@interface GBObjectViewItem : NSObject
@property IBOutlet NSView *view;
@property IBOutlet NSImageView *image;
@property IBOutlet NSTextField *oamAddress;
@property IBOutlet NSTextField *position;
@property IBOutlet NSTextField *attributes;
@property IBOutlet NSTextField *tile;
@property IBOutlet NSTextField *tileAddress;
@property IBOutlet NSImageView *warningIcon;
@property IBOutlet NSBox *verticalLine;
@end
@implementation GBObjectViewItem
{
@public
uint32_t _lastImageData[128];
uint8_t _lastHeight;
}
@end
@implementation GBObjectView
{
NSMutableArray<GBObjectViewItem *> *_items;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
_items = [NSMutableArray array];
CGFloat height = self.frame.size.height;
for (unsigned i = 0; i < 40; i++) {
GBObjectViewItem *item = [[GBObjectViewItem alloc] init];
[_items addObject:item];
[[NSBundle mainBundle] loadNibNamed:@"GBObjectViewItem" owner:item topLevelObjects:nil];
item.view.hidden = true;
[self addSubview:item.view];
[item.view setFrameOrigin:NSMakePoint((i % 4) * 120, height - (i / 4 * 68) - 68)];
item.oamAddress.toolTip = @"OAM address";
item.position.toolTip = @"Position";
item.attributes.toolTip = @"Attributes";
item.tile.toolTip = @"Tile index";
item.tileAddress.toolTip = @"Tile address";
item.warningIcon.toolTip = @"Dropped: too many objects in line";
if ((i % 4) == 3) {
[item.verticalLine removeFromSuperview];
}
item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin;
}
return self;
}
- (void)reloadData:(Document *)document
{
GB_oam_info_t *info = document.oamInfo;
uint8_t length = document.oamCount;
bool cgb = GB_is_cgb(document.gb);
uint8_t height = document.oamHeight;
for (unsigned i = 0; i < 40; i++) {
GBObjectViewItem *item = _items[i];
if (i >= length) {
item.view.hidden = true;
}
else {
item.view.hidden = false;
item.oamAddress.stringValue = [NSString stringWithFormat:@"$%04X", info[i].oam_addr];
item.position.stringValue = [NSString stringWithFormat:@"(%d, %d)",
((signed)(unsigned)info[i].x) - 8,
((signed)(unsigned)info[i].y) - 16];
item.tile.stringValue = [NSString stringWithFormat:@"$%02X", info[i].tile];
item.tileAddress.stringValue = [NSString stringWithFormat:@"$%04X", 0x8000 + info[i].tile * 0x10];
item.warningIcon.hidden = !info[i].obscured_by_line_limit;
if (cgb) {
item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d%d",
info[i].flags & 0x80? 'P' : '-',
info[i].flags & 0x40? 'Y' : '-',
info[i].flags & 0x20? 'X' : '-',
info[i].flags & 0x08? 1 : 0,
info[i].flags & 0x07];
}
else {
item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d",
info[i].flags & 0x80? 'P' : '-',
info[i].flags & 0x40? 'Y' : '-',
info[i].flags & 0x20? 'X' : '-',
info[i].flags & 0x10? 1 : 0];
}
size_t imageSize = 8 * 4 * height;
if (height == item->_lastHeight && memcmp(item->_lastImageData, info[i].image, imageSize) == 0) {
continue;
}
memcpy(item->_lastImageData, info[i].image, imageSize);
item->_lastHeight = height;
item.image.image = [Document imageFromData:[NSData dataWithBytesNoCopy:info[i].image
length:64 * 4 * 2
freeWhenDone:false]
width:8
height:height
scale:32.0 / height];
}
}
NSRect frame = self.frame;
CGFloat newHeight = MAX(68 * ((length + 3) / 4), self.superview.frame.size.height);
frame.origin.y -= newHeight - frame.size.height;
frame.size.height = newHeight;
self.frame = frame;
}
- (void)drawRect:(NSRect)dirtyRect
{
if (@available(macOS 10.14, *)) {
[[NSColor alternatingContentBackgroundColors].lastObject setFill];
}
else {
[[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill];
}
NSRect frame = self.frame;
for (unsigned i = 1; i <= 5; i++) {
NSRectFill(NSMakeRect(0, frame.size.height - i * 68 * 2, frame.size.width, 68));
}
}
@end

View File

@ -0,0 +1,94 @@
<?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="GBObjectViewItem">
<connections>
<outlet property="attributes" destination="AVH-lh-aVu" id="F5l-vc-9YB"/>
<outlet property="image" destination="GWF-Vk-eLx" id="cQa-lM-SqU"/>
<outlet property="oamAddress" destination="HiB-x7-HCb" id="LLX-HU-t00"/>
<outlet property="position" destination="bxJ-ig-rox" id="woq-X2-lCK"/>
<outlet property="tile" destination="Kco-1J-bKc" id="C8u-jH-bCh"/>
<outlet property="tileAddress" destination="ptE-vl-9HD" id="9K3-Oq-1vs"/>
<outlet property="verticalLine" destination="Q91-eq-oeo" id="6kc-qh-cFx"/>
<outlet property="view" destination="c22-O7-iKe" id="50f-xf-9Gg"/>
<outlet property="warningIcon" destination="dQV-cO-218" id="Rxi-Wq-Upl"/>
</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="120" height="68"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kco-1J-bKc">
<rect key="frame" x="44" y="4" width="58" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="left" title="$32" id="Aps-81-wR7">
<font key="font" size="11" name="Menlo-Regular"/>
<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" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ptE-vl-9HD">
<rect key="frame" x="0.0" y="4" width="44" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="center" title="$8320" id="t2G-E2-vt5">
<font key="font" size="11" name="Menlo-Regular"/>
<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" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bxJ-ig-rox">
<rect key="frame" x="44" y="36" width="76" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="(32,16)" id="oac-yZ-h47">
<font key="font" size="11" name="Menlo-Regular"/>
<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" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AVH-lh-aVu">
<rect key="frame" x="44" y="20" width="76" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="---04" id="tJX-6t-5Kx">
<font key="font" size="11" name="Menlo-Regular"/>
<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" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="HiB-x7-HCb">
<rect key="frame" x="44" y="52" width="76" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="left" title="$FE32" id="ysm-jq-PKy">
<font key="font" size="11" name="Menlo-Bold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="GWF-Vk-eLx" customClass="GBImageView">
<rect key="frame" x="2" y="24" width="40" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" id="AJz-KH-eeo"/>
</imageView>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dQV-cO-218">
<rect key="frame" x="100" y="4" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="TQB-Vp-9Jm"/>
</imageView>
<box horizontalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Q91-eq-oeo">
<rect key="frame" x="116" y="4" width="5" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
</subviews>
<point key="canvasLocation" x="132" y="149"/>
</customView>
</objects>
<resources>
<image name="NSCaution" width="32" height="32"/>
</resources>
</document>

6
Cocoa/GBPaletteView.h Normal file
View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
#import "Document.h"
@interface GBPaletteView : NSView
- (void)reloadData:(Document *)document;
@end

91
Cocoa/GBPaletteView.m Normal file
View File

@ -0,0 +1,91 @@
#import "GBPaletteView.h"
@interface GBPaletteViewItem : NSObject
@property IBOutlet NSView *view;
@property (strong) IBOutlet NSTextField *label;
@property (strong) IBOutlet NSTextField *color0;
@property (strong) IBOutlet NSTextField *color1;
@property (strong) IBOutlet NSTextField *color2;
@property (strong) IBOutlet NSTextField *color3;
@end
@implementation GBPaletteViewItem
@end
@implementation GBPaletteView
{
NSMutableArray<NSTextField *> *_colors;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
_colors = [NSMutableArray array];
CGFloat height = self.frame.size.height;
for (unsigned i = 0; i < 16; i++) {
GBPaletteViewItem *item = [[GBPaletteViewItem alloc] init];
[[NSBundle mainBundle] loadNibNamed:@"GBPaletteViewRow" owner:item topLevelObjects:nil];
[self addSubview:item.view];
[item.view setFrameOrigin:NSMakePoint(0, height - (i * 24) - 24)];
item.label.stringValue = [NSString stringWithFormat:@"%@ %d", i < 8? @"Background" : @"Object", i % 8];
item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin;
[_colors addObject:item.color0];
[_colors addObject:item.color1];
[_colors addObject:item.color2];
[_colors addObject:item.color3];
}
return self;
}
- (void)reloadData:(Document *)document
{
GB_gameboy_t *gb = document.gb;
uint8_t *bg = GB_get_direct_access(gb, GB_DIRECT_ACCESS_BGP, NULL, NULL);
uint8_t *obj = GB_get_direct_access(gb, GB_DIRECT_ACCESS_OBP, NULL, NULL);
for (unsigned i = 0; i < 4 * 8 * 2; i++) {
uint8_t index = i % (4 * 8);
uint8_t *palette = i >= 4 * 8 ? obj : bg;
uint16_t color = (palette[(index << 1) + 1] << 8) | palette[(index << 1)];
uint32_t nativeColor = GB_convert_rgb15(gb, color, false);
uint8_t r = color & 0x1F,
g = (color >> 5) & 0x1F,
b = (color >> 10) & 0x1F;
NSTextField *field = _colors[i];
field.stringValue = [NSString stringWithFormat:@"$%04X", color];
field.textColor = r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor];
field.toolTip = [NSString stringWithFormat:@"Red: %d, Green: %d, Blue: %d", r, g, b];
field.backgroundColor = [NSColor colorWithRed:(nativeColor & 0xFF) / 255.0
green:((nativeColor >> 8) & 0xFF) / 255.0
blue:((nativeColor >> 16) & 0xFF) / 255.0
alpha:1.0];
}
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRect frame = self.frame;
if (@available(macOS 10.14, *)) {
[[NSColor alternatingContentBackgroundColors].lastObject setFill];
}
else {
[[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill];
}
for (unsigned i = 1; i <= 8; i++) {
NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2, frame.size.width, 24));
}
if (@available(macOS 10.14, *)) {
[[NSColor alternatingContentBackgroundColors].firstObject setFill];
}
else {
[[NSColor controlBackgroundColor] setFill];
}
for (unsigned i = 0; i < 8; i++) {
NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2 - 24, frame.size.width, 24));
}
}
@end

View File

@ -0,0 +1,74 @@
<?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="GBPaletteViewItem">
<connections>
<outlet property="color0" destination="ypt-t4-Mf3" id="Bam-dX-hHk"/>
<outlet property="color1" destination="KkX-Z8-Sqi" id="uCl-UT-PWu"/>
<outlet property="color2" destination="jDk-Ej-4yI" id="Xjs-el-m3s"/>
<outlet property="color3" destination="7PI-YE-fTk" id="7J8-aH-LEO"/>
<outlet property="label" destination="NOK-yI-LKh" id="AK3-g5-H7a"/>
<outlet property="view" destination="c22-O7-iKe" id="DPx-8k-YQB"/>
</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="512" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ypt-t4-Mf3">
<rect key="frame" x="131" y="0.0" width="96" height="24"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="sKu-Uy-2LG">
<font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KkX-Z8-Sqi">
<rect key="frame" x="226" y="0.0" width="96" height="24"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="9LH-TF-W1L">
<font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jDk-Ej-4yI">
<rect key="frame" x="321" y="0.0" width="96" height="24"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="arE-i5-zCC">
<font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7PI-YE-fTk">
<rect key="frame" x="416" y="0.0" width="96" height="24"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="ZbU-nE-FsE">
<font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NOK-yI-LKh">
<rect key="frame" x="4" y="0.0" width="121" height="20"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Background 0" id="qM4-cY-SDE">
<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>
<point key="canvasLocation" x="139" y="31"/>
</customView>
</objects>
</document>

View File

@ -65,7 +65,7 @@
- (NSWindowToolbarStyle)toolbarStyle
{
return NSWindowToolbarStylePreference;
return NSWindowToolbarStyleExpanded;
}
- (void)close

54
Cocoa/GBS.xib Normal file → Executable file
View File

@ -44,7 +44,7 @@
</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"/>
<rect key="frame" x="63.5" y="128" width="38" 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"/>
@ -54,37 +54,14 @@
<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"/>
<rect key="frame" x="244.5" y="128" width="68" 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"/>
<segment toolTip="Previous Track" image="Previous" width="31"/>
<segment toolTip="Next Track" image="Next" width="30" tag="1"/>
</segments>
</segmentedCell>
<connections>
@ -114,6 +91,29 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
<rect key="frame" x="20.5" y="128" 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="107" y="127" width="131" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="center" 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>
</subviews>
<point key="canvasLocation" x="67" y="292.5"/>
</customView>

128
Cocoa/GBS11.xib Executable 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="57" y="124" width="50" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" bezelStyle="rounded" 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>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
<rect key="frame" x="105" y="127" width="141" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="center" 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="247" y="129" width="68" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="momentary" id="cmq-I8-cFL">
<font key="font" metaFont="system"/>
<segments>
<segment toolTip="Previous Track" image="Previous" width="31"/>
<segment toolTip="Next Track" image="Next" width="30" 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>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
<rect key="frame" x="13" y="124" width="50" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" bezelStyle="rounded" 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>
</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>

BIN
Cocoa/Icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
Cocoa/Icon@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -88,6 +88,24 @@
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gbcart</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ColorCartridge</string>
<key>CFBundleTypeName</key>
<string>Game Boy Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array/>
<key>LSTypeIsPackage</key>
<integer>1</integer>
<key>NSDocumentClass</key>
<string>Document</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>SameBoy</string>
@ -112,7 +130,7 @@
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015-2021 Lior Halphon</string>
<string>Copyright © 2015-2022 Lior Halphon</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
Cocoa/Joypad~solid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

View File

@ -30,7 +30,7 @@
<h1>SameBoy</h1>
<h2>MIT License</h2>
<h3>Copyright © 2015-2021 Lior Halphon</h3>
<h3>Copyright © 2015-2022 Lior Halphon</h3>
<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

View File

@ -27,7 +27,7 @@
<menuItem title="About SameBoy" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
<action selector="orderFrontAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
@ -91,6 +91,12 @@
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="New Cartridge Instance…" keyEquivalent="n" id="Vld-be-NZu">
<connections>
<action selector="newCartridgeInstance:" target="-1" id="GJc-xU-ZEr"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="vQH-Yd-TH4"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
@ -364,9 +370,14 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="DPb-Sh-5tg"/>
<menuItem title="Mute Sound" keyEquivalent="m" id="1UK-8n-QPP">
<menuItem title="Start Audio Recording…" keyEquivalent="A" id="1UK-8n-QPP">
<connections>
<action selector="mute:" target="-1" id="YE5-mi-Yzd"/>
<action selector="toggleAudioRecording:" target="-1" id="YE5-mi-Yzd"/>
</connections>
</menuItem>
<menuItem title="Mute Sound" keyEquivalent="m" id="zo0-Rh-wTu">
<connections>
<action selector="mute:" target="-1" id="eK3-ea-ExJ"/>
</connections>
</menuItem>
</items>

View File

@ -11,6 +11,13 @@ static NSImage * (*imageNamed)(Class self, SEL _cmd, NSString *name);
+ (NSImage *)imageNamedWithDark:(NSImageName)name
{
if (@available(macOS 11.0, *)) {
if (![name containsString:@"~solid"]) {
NSImage *solid = [self imageNamed:[name stringByAppendingString:@"~solid"]];
[solid setTemplate:true];
if (solid) return solid;
}
}
NSImage *light = imageNamed(self, _cmd, name);
if (@available(macOS 10.14, *)) {
NSImage *dark = imageNamed(self, _cmd, [name stringByAppendingString:@"~dark"]);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 221 B

View File

@ -52,7 +52,7 @@
<action selector="switchPreferencesTab:" target="-2" id="Tio-D7-PaA"/>
</connections>
</toolbarItem>
<toolbarItem implicitItemIdentifier="EB7AB5BC-57B0-4639-A037-E28226866C80" label="Updates" paletteLabel="Updates" tag="4" image="AppIcon" autovalidates="NO" selectable="YES" id="lMV-68-ntz">
<toolbarItem implicitItemIdentifier="EB7AB5BC-57B0-4639-A037-E28226866C80" label="Updates" paletteLabel="Updates" tag="4" image="Updates" autovalidates="NO" selectable="YES" id="lMV-68-ntz">
<connections>
<action selector="switchPreferencesTab:" target="-2" id="bfU-hc-FnN"/>
</connections>
@ -222,7 +222,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
<rect key="frame" x="30" y="165" width="262" height="22"/>
<rect key="frame" x="30" y="165" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -251,7 +251,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Iwr-eI-SD1">
<rect key="frame" x="30" y="114" width="262" height="22"/>
<rect key="frame" x="30" y="114" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Greyscale" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Ajr-5r-iIk" id="rEU-jh-m3j">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -281,7 +281,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="R9D-FV-bpd">
<rect key="frame" x="30" y="60" width="262" height="25"/>
<rect key="frame" x="30" y="60" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="heL-AV-0az" id="DY9-2D-h1L">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -326,11 +326,11 @@
<point key="canvasLocation" x="-501" y="236.5"/>
</customView>
<customView id="ymk-46-SX7">
<rect key="frame" x="0.0" y="0.0" width="320" height="375"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="427"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB">
<rect key="frame" x="18" y="283" width="284" height="17"/>
<rect key="frame" x="18" y="335" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding duration:" id="JaO-5h-ugl">
<font key="font" metaFont="system"/>
@ -339,7 +339,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o3Z-34-FJk">
<rect key="frame" x="18" y="228" width="280" height="17"/>
<rect key="frame" x="18" y="280" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Real Time Clock emulation:" id="Qoi-ub-YtI">
<font key="font" metaFont="system"/>
@ -348,7 +348,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MI2-ql-f6M">
<rect key="frame" x="18" y="338" width="284" height="17"/>
<rect key="frame" x="18" y="390" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Boot ROMs location:" id="nj0-Cb-gEA">
<font key="font" metaFont="system"/>
@ -357,7 +357,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wg8-hJ-df9">
<rect key="frame" x="18" y="160" width="284" height="17"/>
<rect key="frame" x="18" y="212" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy revision:" id="GIA-ep-SBi">
<font key="font" metaFont="system"/>
@ -366,7 +366,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LFw-Uk-cPR">
<rect key="frame" x="30" y="127" width="262" height="26"/>
<rect key="frame" x="30" y="179" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="DMG-CPU B" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="aXT-sE-m5Z" id="FuX-Hc-uO7">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -385,7 +385,7 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MAq-1X-Gpo">
<rect key="frame" x="16" y="50" width="284" height="17"/>
<rect key="frame" x="16" y="102" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Color revision:" id="edD-t7-vwk">
<font key="font" metaFont="system"/>
@ -394,19 +394,19 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dlD-sk-SHO">
<rect key="frame" x="28" y="17" width="262" height="26"/>
<rect key="frame" x="28" y="69" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="CPU-CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz">
<popUpButtonCell key="cell" type="push" title="CPU CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" autoenablesItems="NO" id="bbF-hB-Hv7">
<items>
<menuItem title="CPU-CGB 0 (Experimental)" tag="512" id="2Uk-u3-6Gw"/>
<menuItem title="CPU-CGB A (Experimental)" tag="513" id="axv-yk-RWM"/>
<menuItem title="CPU-CGB B (Experimental)" tag="514" id="NtJ-oo-IM2"/>
<menuItem title="CPU-CGB C (Experimental)" tag="515" id="9YL-u8-12z"/>
<menuItem title="CPU-CGB D" tag="516" id="c76-oF-fkU"/>
<menuItem title="CPU-CGB E" state="on" tag="517" id="3lF-1Q-2SS"/>
<menuItem title="CPU CGB 0 (Experimental)" tag="512" id="2Uk-u3-6Gw"/>
<menuItem title="CPU CGB A (Experimental)" tag="513" id="axv-yk-RWM"/>
<menuItem title="CPU CGB B (Experimental)" tag="514" id="NtJ-oo-IM2"/>
<menuItem title="CPU CGB C (Experimental)" tag="515" id="9YL-u8-12z"/>
<menuItem title="CPU CGB D" tag="516" id="c76-oF-fkU"/>
<menuItem title="CPU CGB E" state="on" tag="517" id="3lF-1Q-2SS"/>
</items>
</menu>
</popUpButtonCell>
@ -415,7 +415,7 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tAa-0A-0fP">
<rect key="frame" x="18" y="105" width="284" height="17"/>
<rect key="frame" x="18" y="157" width="284" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Super Game Boy model:" id="d0g-rk-FK0">
<font key="font" metaFont="system"/>
@ -424,11 +424,11 @@
</textFieldCell>
</textField>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="mdm-eW-ia1">
<rect key="frame" x="12" y="183" width="296" height="5"/>
<rect key="frame" x="12" y="235" width="296" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dza-T7-RkX">
<rect key="frame" x="28" y="72" width="262" height="26"/>
<rect key="frame" x="28" y="124" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Super Game Boy (NTSC)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="x5A-7f-ef9" id="2Mt-ci-bB0">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -446,7 +446,7 @@
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tFf-H1-XUL">
<rect key="frame" x="30" y="199" width="262" height="22"/>
<rect key="frame" x="30" y="251" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Sync to system clock" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="arp-Qi-Xix" id="uRs-Ag-Sbw">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -465,7 +465,7 @@
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wC4-aJ-mhQ">
<rect key="frame" x="30" y="305" width="262" height="26"/>
<rect key="frame" x="30" y="357" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Use built-in boot ROMs" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tnm-SR-ZEm" id="T3Y-Ln-Onl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -489,7 +489,7 @@
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR">
<rect key="frame" x="30" y="250" width="262" height="26"/>
<rect key="frame" x="30" y="302" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="lxQ-4n-kEv" id="lvb-QF-0Ht">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -512,8 +512,33 @@
<action selector="rewindLengthChanged:" target="QvC-M9-y7g" id="5NQ-1T-RNc"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5r5-qY-b8h">
<rect key="frame" x="18" y="47" width="280" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Advance revision:" id="R5q-dJ-NvD">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Dl-S6-O7c">
<rect key="frame" x="28" y="17" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="CPU AGB A (GBA, GB Player)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="519" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="8bk-wP-Cbr" id="EhC-I2-5Th">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" autoenablesItems="NO" id="b2b-jo-SrI">
<items>
<menuItem title="CPU AGB 0 (Early GBA)" tag="518" enabled="NO" id="75z-Yy-XaY"/>
<menuItem title="CPU AGB A (GBA, GB Player)" state="on" tag="519" id="8bk-wP-Cbr"/>
<menuItem title="CPU AGB B (GBA SP)" tag="520" enabled="NO" id="jIE-v4-768"/>
<menuItem title="CPU AGB E (Late GBA SP, GB Player)" tag="521" enabled="NO" id="w7s-kh-HaZ"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
</subviews>
<point key="canvasLocation" x="-501" y="667.5"/>
<point key="canvasLocation" x="-501" y="693.5"/>
</customView>
<customView id="Zn1-Y5-RbR">
<rect key="frame" x="0.0" y="0.0" width="320" height="201"/>
@ -765,7 +790,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ogs-xG-b4b">
<rect key="frame" x="30" y="58" width="262" height="22"/>
<rect key="frame" x="30" y="58" width="262" height="26"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="jki-7x-bnM" id="o9b-MH-8kd">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -854,7 +879,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="5Al-aC-tq8">
<rect key="frame" x="1" y="1" width="158" height="316"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" id="ZVn-bk-duk">
<rect key="frame" x="0.0" y="0.0" width="158" height="316"/>
@ -1088,12 +1113,12 @@
</customObject>
</objects>
<resources>
<image name="AppIcon" width="128" height="128"/>
<image name="CPU" width="32" height="32"/>
<image name="Display" width="32" height="32"/>
<image name="Joypad" width="32" height="32"/>
<image name="NSAddTemplate" width="11" height="11"/>
<image name="NSRemoveTemplate" width="11" height="11"/>
<image name="Speaker" width="32" height="32"/>
<image name="Updates" width="32" height="32"/>
</resources>
</document>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
Cocoa/Speaker~solid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
Cocoa/Updates.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
Cocoa/Updates@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
Cocoa/Updates~solid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

BIN
Cocoa/Updates~solid@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

View File

@ -2,6 +2,7 @@
#include <math.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "gb.h"
static const uint8_t duties[] = {
@ -21,7 +22,7 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
{
if (gb->model >= GB_MODEL_AGB) {
if (gb->model > GB_MODEL_CGB_E) {
/* On the AGB, mixing is done digitally, so there are no per-channel
DACs. Instead, all channels are summed digital regardless of
whatever the DAC state would be on a CGB or earlier model. */
@ -68,14 +69,8 @@ static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index)
static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset)
{
if (gb->model >= GB_MODEL_AGB && index == GB_WAVE) {
/* For some reason, channel 3 is inverted on the AGB */
value ^= 0xF;
}
if (value == 0 && gb->apu.samples[index] == 0) return;
if (gb->model >= GB_MODEL_AGB) {
if (gb->model > GB_MODEL_CGB_E) {
/* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different.
A channel that is not connected to a terminal is idenitcal to a connected channel
playing PCM sample 0. */
@ -85,21 +80,26 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1;
unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
if (index == GB_WAVE) {
/* For some reason, channel 3 is inverted on the AGB */
value ^= 0xF;
}
GB_sample_t output;
uint8_t bias = agb_bias_for_channel(gb, index);
if (gb->io_registers[GB_IO_NR51] & (1 << index)) {
output.right = (0xf - value * 2 + bias) * right_volume;
output.right = (0xF - value * 2 + bias) * right_volume;
}
else {
output.right = 0xf * right_volume;
output.right = 0xF * right_volume;
}
if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
output.left = (0xf - value * 2 + bias) * left_volume;
output.left = (0xF - value * 2 + bias) * left_volume;
}
else {
output.left = 0xf * left_volume;
output.left = 0xF * left_volume;
}
if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) {
@ -111,6 +111,8 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
return;
}
if (value == 0 && gb->apu.samples[index] == 0) return;
if (!GB_apu_is_DAC_enabled(gb, index)) {
value = gb->apu.samples[index];
}
@ -127,7 +129,7 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
}
GB_sample_t output = {(0xf - value * 2) * left_volume, (0xf - value * 2) * right_volume};
GB_sample_t output = {(0xF - value * 2) * left_volume, (0xF - value * 2) * right_volume};
if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) {
refresh_channel(gb, index, cycles_offset);
gb->apu_output.current_sample[index] = output;
@ -145,7 +147,7 @@ static signed interference(GB_gameboy_t *gb)
/* These aren't scientifically measured, but based on ear based on several recordings */
signed ret = 0;
if (gb->halted) {
if (gb->model != GB_MODEL_AGB) {
if (gb->model <= GB_MODEL_CGB_E) {
ret -= MAX_CH_AMP / 5;
}
else {
@ -154,7 +156,7 @@ static signed interference(GB_gameboy_t *gb)
}
if (gb->io_registers[GB_IO_LCDC] & 0x80) {
ret += MAX_CH_AMP / 7;
if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model != GB_MODEL_AGB) {
if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model <= GB_MODEL_CGB_E) {
ret += MAX_CH_AMP / 14;
}
else if ((gb->io_registers[GB_IO_STAT] & 3) == 1) {
@ -166,7 +168,7 @@ static signed interference(GB_gameboy_t *gb)
ret += MAX_CH_AMP / 10;
}
if (GB_is_cgb(gb) && gb->model < GB_MODEL_AGB && (gb->io_registers[GB_IO_RP] & 1)) {
if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_E && (gb->io_registers[GB_IO_RP] & 1)) {
ret += MAX_CH_AMP / 10;
}
@ -186,7 +188,7 @@ static void render(GB_gameboy_t *gb)
unrolled for (unsigned i = 0; i < GB_N_CHANNELS; i++) {
double multiplier = CH_STEP;
if (gb->model < GB_MODEL_AGB) {
if (gb->model <= GB_MODEL_CGB_E) {
if (!GB_apu_is_DAC_enabled(gb, i)) {
gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate;
if (gb->apu_output.dac_discharge[i] < 0) {
@ -223,6 +225,8 @@ static void render(GB_gameboy_t *gb)
gb->apu_output.last_update[i] = 0;
}
gb->apu_output.cycles_since_render = 0;
if (gb->sgb && gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) return;
GB_sample_t filtered_output = gb->apu_output.highpass_mode?
(GB_sample_t) {output.left - gb->apu_output.highpass_diff.left,
@ -243,18 +247,14 @@ static void render(GB_gameboy_t *gb)
unsigned left_volume = 0;
unsigned right_volume = 0;
unrolled for (unsigned i = GB_N_CHANNELS; i--;) {
if (gb->apu.is_active[i]) {
if (GB_apu_is_DAC_enabled(gb, i)) {
if (mask & 1) {
left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF;
left_volume += ((gb->io_registers[GB_IO_NR50] & 7) + 1) * CH_STEP * 0xF;
}
if (mask & 0x10) {
right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF;
right_volume += (((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1) * CH_STEP * 0xF;
}
}
else {
left_volume += gb->apu_output.current_sample[i].left * CH_STEP;
right_volume += gb->apu_output.current_sample[i].right * CH_STEP;
}
mask >>= 1;
}
gb->apu_output.highpass_diff = (GB_double_sample_t)
@ -279,11 +279,29 @@ static void render(GB_gameboy_t *gb)
}
assert(gb->apu_output.sample_callback);
gb->apu_output.sample_callback(gb, &filtered_output);
if (unlikely(gb->apu_output.output_file)) {
#ifdef GB_BIG_ENDIAN
if (gb->apu_output.output_format == GB_AUDIO_FORMAT_WAV) {
filtered_output.left = LE16(filtered_output.left);
filtered_output.right = LE16(filtered_output.right);
}
#endif
if (fwrite(&filtered_output, sizeof(filtered_output), 1, gb->apu_output.output_file) != 1) {
fclose(gb->apu_output.output_file);
gb->apu_output.output_file = NULL;
gb->apu_output.output_error = errno;
}
}
}
static void update_square_sample(GB_gameboy_t *gb, unsigned index)
{
if (gb->apu.square_channels[index].sample_surpressed) return;
if (gb->apu.square_channels[index].sample_surpressed) {
if (gb->model > GB_MODEL_CGB_E) {
update_sample(gb, index, gb->apu.samples[index], 0);
}
return;
}
uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
update_sample(gb, index,
@ -528,7 +546,7 @@ void GB_apu_div_event(GB_gameboy_t *gb)
if (gb->apu.wave_channel.length_enabled) {
if (gb->apu.wave_channel.pulse_length) {
if (!--gb->apu.wave_channel.pulse_length) {
if (gb->apu.is_active[GB_WAVE] && gb->model == GB_MODEL_AGB) {
if (gb->apu.is_active[GB_WAVE] && gb->model > GB_MODEL_CGB_E) {
if (gb->apu.wave_channel.sample_countdown == 0) {
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (((gb->apu.wave_channel.current_sample_index + 1) & 0xF) >> 1)];
@ -607,7 +625,7 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
(gb->apu.apu_cycles > 0x1000) ||
(gb->apu_output.sample_cycles >= clock_rate) ||
(gb->apu.square_sweep_calculate_countdown || gb->apu.channel_1_restart_hold) ||
(gb->model < GB_MODEL_AGB && (gb->apu.wave_channel.bugged_read_countdown || (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed)))) {
(gb->model <= GB_MODEL_CGB_E && (gb->apu.wave_channel.bugged_read_countdown || (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed)))) {
force = true;
}
if (!force) {
@ -690,6 +708,14 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
unrolled for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) {
if (gb->apu.is_active[i]) {
uint16_t cycles_left = cycles;
if (unlikely(gb->apu.square_channels[i].delay)) {
if (gb->apu.square_channels[i].delay < cycles_left) {
gb->apu.square_channels[i].delay = 0;
}
else {
gb->apu.square_channels[i].delay -= cycles_left;
}
}
while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) {
cycles_left -= gb->apu.square_channels[i].sample_countdown + 1;
gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1;
@ -699,7 +725,7 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
if (cycles_left == 0 && gb->apu.samples[i] == 0) {
gb->apu.pcm_mask[0] &= i == GB_SQUARE_1? 0xF0 : 0x0F;
}
gb->apu.square_channels[i].did_tick = true;
update_square_sample(gb, i);
}
if (cycles_left) {
@ -726,7 +752,7 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
gb->apu.wave_channel.wave_form_just_read = false;
}
}
else if (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed && gb->model < GB_MODEL_AGB) {
else if (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed && gb->model <= GB_MODEL_CGB_E) {
uint16_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) {
cycles_left -= gb->apu.wave_channel.sample_countdown + 1;
@ -807,6 +833,8 @@ void GB_apu_init(GB_gameboy_t *gb)
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP;
gb->apu.div_divider = 1;
}
gb->apu.square_channels[GB_SQUARE_1].sample_countdown = -1;
gb->apu.square_channels[GB_SQUARE_2].sample_countdown = -1;
}
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
@ -844,7 +872,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) {
return 0xFF;
}
if (gb->model == GB_MODEL_AGB) {
if (gb->model > GB_MODEL_CGB_E) {
return 0xFF;
}
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
@ -959,7 +987,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb)
effective_counter |= 0x10;
}
break;
case GB_MODEL_AGB:
case GB_MODEL_AGB_A:
/* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple
pattern like the other revisions. */
/* For the most part, AGS seems to do:
@ -987,7 +1015,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) {
if ((!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) || gb->model == GB_MODEL_AGB) {
if ((!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) || gb->model > GB_MODEL_CGB_E) {
return;
}
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
@ -1002,7 +1030,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
/* These registers affect the output of all 4 channels (but not the output of the PCM registers).*/
/* We call update_samples with the current value so the APU output is updated with the new outputs */
for (unsigned i = GB_N_CHANNELS; i--;) {
update_sample(gb, i, gb->apu.samples[i], 0);
int8_t sample = gb->apu.samples[i];
gb->apu.samples[i] = 0x10; // Invalidate to force update
update_sample(gb, i, sample, 0);
}
break;
case GB_IO_NR52: {
@ -1051,9 +1081,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR11:
case GB_IO_NR21: {
unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1;
gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f));
gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3F));
if (!gb->apu.global_enable) {
value &= 0x3f;
value &= 0x3F;
}
break;
}
@ -1080,6 +1110,19 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR13:
case GB_IO_NR23: {
unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1;
if (gb->apu.is_active[index]) {
/* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on
double speed. */
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D || gb->apu.square_channels[index].sample_countdown & 1) {
if (gb->apu.square_channels[index].did_tick &&
gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
gb->apu.square_channels[index].current_sample_index--;
gb->apu.square_channels[index].current_sample_index &= 7;
gb->apu.square_channels[index].sample_surpressed = false;
}
}
}
gb->apu.square_channels[index].sample_length &= ~0xFF;
gb->apu.square_channels[index].sample_length |= value & 0xFF;
break;
@ -1096,7 +1139,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
/* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on
double speed. */
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D || gb->apu.square_channels[index].sample_countdown & 1) {
if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
if (gb->apu.square_channels[index].did_tick &&
gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
gb->apu.square_channels[index].current_sample_index--;
gb->apu.square_channels[index].current_sample_index &= 7;
gb->apu.square_channels[index].sample_surpressed = false;
@ -1112,16 +1156,27 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
turning the APU off. */
gb->apu.square_channels[index].envelope_clock.locked = false;
gb->apu.square_channels[index].envelope_clock.clock = false;
gb->apu.square_channels[index].did_tick = false;
bool force_unsurpressed = false;
if (!gb->apu.is_active[index]) {
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div;
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) {
if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - gb->apu.square_channels[index].delay) / 2) & 0x400)) {
gb->apu.square_channels[index].current_sample_index++;
gb->apu.square_channels[index].current_sample_index &= 0x7;
force_unsurpressed = true;
}
}
gb->apu.square_channels[index].delay = 6 - gb->apu.lf_div;
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay;
if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) {
gb->apu.square_channels[index].sample_countdown += 2;
gb->apu.square_channels[index].delay += 2;
}
}
else {
unsigned extra_delay = 0;
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) {
if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1) / 2) & 0x400)) {
if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1 - gb->apu.square_channels[index].delay) / 2) & 0x400)) {
gb->apu.square_channels[index].current_sample_index++;
gb->apu.square_channels[index].current_sample_index &= 0x7;
gb->apu.square_channels[index].sample_surpressed = false;
@ -1134,9 +1189,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
}
}
/* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div + extra_delay;
gb->apu.square_channels[index].delay = 4 - gb->apu.lf_div + extra_delay;
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay;
if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) {
gb->apu.square_channels[index].sample_countdown += 2;
gb->apu.square_channels[index].delay += 2;
}
}
gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4;
@ -1152,7 +1209,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) {
gb->apu.is_active[index] = true;
update_sample(gb, index, 0, 0);
gb->apu.square_channels[index].sample_surpressed = true;
gb->apu.square_channels[index].sample_surpressed = true && !force_unsurpressed;
}
if (gb->apu.square_channels[index].pulse_length == 0) {
gb->apu.square_channels[index].pulse_length = 0x40;
@ -1219,7 +1276,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.wave_channel.pulsed = false;
if (gb->apu.is_active[GB_WAVE]) {
// Todo: I assume this happens on pre-CGB models; test this with an audible test
if (gb->apu.wave_channel.sample_countdown == 0 && gb->model < GB_MODEL_AGB) {
if (gb->apu.wave_channel.sample_countdown == 0 && gb->model <= GB_MODEL_CGB_E) {
gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (gb->pc & 0xF)];
}
else if (gb->apu.wave_channel.wave_form_just_read && gb->model <= GB_MODEL_CGB_C) {
@ -1315,7 +1372,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
/* Noise Channel */
case GB_IO_NR41: {
gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f));
gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3F));
break;
}
@ -1498,6 +1555,11 @@ void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample)
gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample);
}
unsigned GB_get_sample_rate(GB_gameboy_t *gb)
{
return gb->apu_output.sample_rate;
}
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback)
{
gb->apu_output.sample_callback = callback;
@ -1512,3 +1574,179 @@ void GB_set_interference_volume(GB_gameboy_t *gb, double volume)
{
gb->apu_output.interference_volume = volume;
}
typedef struct __attribute__((packed)) {
uint32_t format_chunk; // = BE32('FORM')
uint32_t size; // = BE32(file size - 8)
uint32_t format; // = BE32('AIFC')
uint32_t fver_chunk; // = BE32('FVER')
uint32_t fver_size; // = BE32(4)
uint32_t fver;
uint32_t comm_chunk; // = BE32('COMM')
uint32_t comm_size; // = BE32(0x18)
uint16_t channels; // = BE16(2)
uint32_t samples_per_channel; // = BE32(total number of samples / 2)
uint16_t bit_depth; // = BE16(16)
uint16_t frequency_exponent;
uint64_t frequency_significand;
uint32_t compression_type; // = 'NONE' (BE) or 'twos' (LE)
uint16_t compression_name; // = 0
uint32_t ssnd_chunk; // = BE32('SSND')
uint32_t ssnd_size; // = BE32(length of samples - 8)
uint32_t ssnd_offset; // = 0
uint32_t ssnd_block; // = 0
} aiff_header_t;
typedef struct __attribute__((packed)) {
uint32_t marker; // = BE32('RIFF')
uint32_t size; // = LE32(file size - 8)
uint32_t type; // = BE32('WAVE')
uint32_t fmt_chunk; // = BE32('fmt ')
uint32_t fmt_size; // = LE16(16)
uint16_t format; // = LE16(1)
uint16_t channels; // = LE16(2)
uint32_t sample_rate; // = LE32(sample_rate)
uint32_t byte_rate; // = LE32(sample_rate * 4)
uint16_t frame_size; // = LE32(4)
uint16_t bit_depth; // = LE16(16)
uint32_t data_chunk; // = BE32('data')
uint32_t data_size; // = LE32(length of samples)
} wav_header_t;
int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format)
{
if (gb->apu_output.sample_rate == 0) {
return EINVAL;
}
if (gb->apu_output.output_file) {
GB_stop_audio_recording(gb);
}
gb->apu_output.output_file = fopen(path, "wb");
if (!gb->apu_output.output_file) return errno;
gb->apu_output.output_format = format;
switch (format) {
case GB_AUDIO_FORMAT_RAW:
return 0;
case GB_AUDIO_FORMAT_AIFF: {
aiff_header_t header = {0,};
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
fclose(gb->apu_output.output_file);
gb->apu_output.output_file = NULL;
return errno;
}
return 0;
}
case GB_AUDIO_FORMAT_WAV: {
wav_header_t header = {0,};
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
fclose(gb->apu_output.output_file);
gb->apu_output.output_file = NULL;
return errno;
}
return 0;
}
default:
fclose(gb->apu_output.output_file);
gb->apu_output.output_file = NULL;
return EINVAL;
}
}
int GB_stop_audio_recording(GB_gameboy_t *gb)
{
if (!gb->apu_output.output_file) {
int ret = gb->apu_output.output_error ?: -1;
gb->apu_output.output_error = 0;
return ret;
}
gb->apu_output.output_error = 0;
switch (gb->apu_output.output_format) {
case GB_AUDIO_FORMAT_RAW:
break;
case GB_AUDIO_FORMAT_AIFF: {
size_t file_size = ftell(gb->apu_output.output_file);
size_t frames = (file_size - sizeof(aiff_header_t)) / sizeof(GB_sample_t);
aiff_header_t header = {
.format_chunk = BE32('FORM'),
.size = BE32(file_size - 8),
.format = BE32('AIFC'),
.fver_chunk = BE32('FVER'),
.fver_size = BE32(4),
.fver = BE32(0xA2805140),
.comm_chunk = BE32('COMM'),
.comm_size = BE32(0x18),
.channels = BE16(2),
.samples_per_channel = BE32(frames),
.bit_depth = BE16(16),
#ifdef GB_BIG_ENDIAN
.compression_type = 'NONE',
#else
.compression_type = 'twos',
#endif
.compression_name = 0,
.ssnd_chunk = BE32('SSND'),
.ssnd_size = BE32(frames * sizeof(GB_sample_t) - 8),
.ssnd_offset = 0,
.ssnd_block = 0,
};
uint64_t significand = gb->apu_output.sample_rate;
uint16_t exponent = 0x403E;
while ((int64_t)significand > 0) {
significand <<= 1;
exponent--;
}
header.frequency_exponent = BE16(exponent);
header.frequency_significand = BE64(significand);
fseek(gb->apu_output.output_file, 0, SEEK_SET);
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
gb->apu_output.output_error = errno;
}
break;
}
case GB_AUDIO_FORMAT_WAV: {
size_t file_size = ftell(gb->apu_output.output_file);
size_t frames = (file_size - sizeof(wav_header_t)) / sizeof(GB_sample_t);
wav_header_t header = {
.marker = BE32('RIFF'),
.size = LE32(file_size - 8),
.type = BE32('WAVE'),
.fmt_chunk = BE32('fmt '),
.fmt_size = LE16(16),
.format = LE16(1),
.channels = LE16(2),
.sample_rate = LE32(gb->apu_output.sample_rate),
.byte_rate = LE32(gb->apu_output.sample_rate * 4),
.frame_size = LE32(4),
.bit_depth = LE16(16),
.data_chunk = BE32('data'),
.data_size = LE32(frames * sizeof(GB_sample_t)),
};
fseek(gb->apu_output.output_file, 0, SEEK_SET);
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
gb->apu_output.output_error = errno;
}
break;
}
}
fclose(gb->apu_output.output_file);
gb->apu_output.output_file = NULL;
int ret = gb->apu_output.output_error;
gb->apu_output.output_error = 0;
return ret;
}

View File

@ -3,6 +3,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include "defs.h"
#ifdef GB_INTERNAL
@ -88,6 +89,8 @@ typedef struct
uint16_t sample_length; // From NRX3, NRX4, in APU ticks
bool length_enabled; // NRX4
GB_envelope_clock_t envelope_clock;
uint8_t delay; // Hack for CGB D/E phantom step due to how sample_countdown is implemented in SameBoy
bool did_tick;
} square_channels[2];
struct {
@ -140,6 +143,12 @@ typedef enum {
GB_HIGHPASS_MAX
} GB_highpass_mode_t;
typedef enum {
GB_AUDIO_FORMAT_RAW, // Native endian
GB_AUDIO_FORMAT_AIFF, // Native endian
GB_AUDIO_FORMAT_WAV,
} GB_audio_format_t;
typedef struct {
unsigned sample_rate;
@ -160,14 +169,20 @@ typedef struct {
double interference_volume;
double interference_highpass;
FILE *output_file;
GB_audio_format_t output_format;
int output_error;
} GB_apu_output_t;
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
unsigned GB_get_sample_rate(GB_gameboy_t *gb);
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
void GB_set_interference_volume(GB_gameboy_t *gb, double volume);
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format);
int GB_stop_audio_recording(GB_gameboy_t *gb);
#ifdef GB_INTERNAL
internal bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
internal void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);

View File

@ -155,12 +155,20 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer
return output;
}
static GB_symbol_map_t *get_symbol_map(GB_gameboy_t *gb, uint16_t bank)
{
if (bank >= gb->n_symbol_maps) {
return NULL;
}
return gb->bank_symbols[bank];
}
static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name)
{
if (!value.has_bank) return value_to_string(gb, value.value, prefer_name);
static __thread char output[256];
const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value);
const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, value.bank), value.value);
if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) {
symbol = NULL;
@ -786,22 +794,6 @@ static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
return false;
}
static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
print_usage(gb, command);
return true;
}
gb->debug_stopped = false;
gb->stack_leak_detection = true;
gb->debug_call_depth = 0;
return false;
}
static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
@ -911,13 +903,14 @@ static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_
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) {
GB_symbol_map_t *map = get_symbol_map(gb, context->bank);
if (map == NULL ||
context->symbol >= map->n_symbols) {
context->bank++;
context->symbol = 0;
continue;
}
const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name;
const char *candidate = map->symbols[context->symbol++].name;
if (memcmp(symbol_prefix, candidate, length) == 0) {
return strdup(candidate + length);
}
@ -1537,16 +1530,22 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
}
else {
static const char *const mapper_names[] = {
[GB_MBC1] = "MBC1",
[GB_MBC2] = "MBC2",
[GB_MBC3] = "MBC3",
[GB_MBC5] = "MBC5",
[GB_MBC7] = "MBC7",
[GB_HUC1] = "HUC-1",
[GB_HUC3] = "HUC-3",
[GB_MBC1] = "MBC1",
[GB_MBC2] = "MBC2",
[GB_MBC3] = "MBC3",
[GB_MBC5] = "MBC5",
[GB_MBC7] = "MBC7",
[GB_MMM01] = "MMM01",
[GB_HUC1] = "HUC-1",
[GB_HUC3] = "HUC-3",
[GB_CAMERA] = "MAC-GBD",
};
GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]);
}
if (cartridge->mbc_type == GB_MMM01 || cartridge->mbc_type == GB_MBC1) {
GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank);
}
GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank);
if (cartridge->has_ram) {
GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank);
@ -1650,6 +1649,29 @@ static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const d
return true;
}
static bool dma(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_is_dma_active(gb)) {
GB_log(gb, "DMA is inactive\n");
return true;
}
if (gb->dma_current_dest == 0xFF) {
GB_log(gb, "DMA warming up\n"); // Shouldn't actually happen, as it only lasts 2 T-cycles
return true;
}
GB_log(gb, "Next DMA write: [$FE%02X] = [$%04X]\n", gb->dma_current_dest, gb->dma_current_src);
return true;
}
static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
@ -1691,8 +1713,13 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "Glitched line 0 OAM mode (%d cycles to next event)\n", -gb->display_cycles / 2);
}
else if (gb->mode_for_interrupt == 3) {
signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line;
GB_log(gb, "Rendering pixel (%d/160)\n", pixel);
if (((uint8_t)(gb->position_in_line + 16) < 8)) {
GB_log(gb, "Adjusting for scrolling (%d/%d)\n", gb->position_in_line & 7, gb->io_registers[GB_IO_SCX] & 7);
}
else {
signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line;
GB_log(gb, "Rendering pixel (%d/160)\n", pixel);
}
}
else {
GB_log(gb, "Sleeping (%d cycles to next event)\n", -gb->display_cycles / 2);
@ -1708,54 +1735,60 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
static bool apu(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;
}
GB_log(gb, "Current state: ");
if (!gb->apu.global_enable) {
GB_log(gb, "Disabled\n");
}
else {
GB_log(gb, "Enabled\n");
for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) {
GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1,
gb->apu.is_active[channel] ? "active " : "inactive",
GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive",
gb->apu.samples[channel]);
const char *stripped = lstrip(arguments);
if (strlen(stripped)) {
if (stripped[0] != 0 && (stripped[0] < '1' || stripped[0] > '5')) {
print_usage(gb, command);
return true;
}
}
GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07);
if (gb->io_registers[GB_IO_NR51] & 0x0f) {
for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if (gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
if (stripped[0] == 0 || stripped[0] == '5') {
GB_log(gb, "Current state: ");
if (!gb->apu.global_enable) {
GB_log(gb, "Disabled\n");
}
else {
GB_log(gb, "Enabled\n");
for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) {
GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1,
gb->apu.is_active[channel] ? "active " : "inactive",
GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive",
gb->apu.samples[channel]);
}
}
}
else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4);
if (gb->io_registers[GB_IO_NR51] & 0xf0) {
for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if (gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07);
if (gb->io_registers[GB_IO_NR51] & 0x0F) {
for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if (gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
}
}
}
else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4);
if (gb->io_registers[GB_IO_NR51] & 0xF0) {
for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
if (gb->io_registers[GB_IO_NR51] & mask) {
GB_log(gb, " CH%u", channel + 1);
}
}
}
else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
}
else {
GB_log(gb, " no channels");
}
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) {
if (stripped[0] != 0 && stripped[0] != ('1') + channel) continue;
GB_log(gb, "\nCH%u:\n", channel + 1);
GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.square_channels[channel].current_volume,
@ -1795,50 +1828,53 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
}
}
if (stripped[0] == 0 || stripped[0] == '3') {
GB_log(gb, "\nCH3:\n");
GB_log(gb, " Wave:");
for (uint8_t i = 0; i < 16; 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, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
GB_log(gb, "\nCH3:\n");
GB_log(gb, " Wave:");
for (uint8_t i = 0; i < 16; 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, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
GB_log(gb, " Volume %s (right-shifted %u times)\n",
gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift],
gb->apu.wave_channel.shift);
GB_log(gb, " Volume %s (right-shifted %u times)\n",
gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift],
gb->apu.wave_channel.shift);
GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.wave_channel.sample_length ^ 0x7FF,
gb->apu.wave_channel.sample_countdown);
GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n",
gb->apu.wave_channel.sample_length ^ 0x7ff,
gb->apu.wave_channel.sample_countdown);
if (gb->apu.wave_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.wave_channel.pulse_length);
if (gb->apu.wave_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.wave_channel.pulse_length);
}
}
GB_log(gb, "\nCH4:\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.counter,
gb->apu.noise_channel.counter_countdown);
if (stripped[0] == 0 || stripped[0] == '4') {
GB_log(gb, "\nCH4:\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.counter,
gb->apu.noise_channel.counter_countdown);
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.noise_channel.volume_countdown,
gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de",
gb->io_registers[GB_IO_NR42] & 7);
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
gb->apu.noise_channel.volume_countdown,
gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de",
gb->io_registers[GB_IO_NR42] & 7);
GB_log(gb, " LFSR in %u-step mode, current value ",
gb->apu.noise_channel.narrow? 7 : 15);
for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) {
GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " ");
}
GB_log(gb, " LFSR in %u-step mode, current value ",
gb->apu.noise_channel.narrow? 7 : 15);
for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) {
GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " ");
}
if (gb->apu.noise_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.noise_channel.pulse_length);
if (gb->apu.noise_channel.length_enabled) {
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
gb->apu.noise_channel.pulse_length);
}
}
@ -1878,9 +1914,9 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
break;
}
}
mask = (0xf << (shift_amount - 1)) & 0xf;
mask = (0xF << (shift_amount - 1)) & 0xF;
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++) {
uint8_t sample = i & 1?
(gb->io_registers[GB_IO_WAV_START + i / 2] & 0xF) :
@ -1901,6 +1937,8 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
print_usage(gb, command);
return true;
@ -1931,46 +1969,45 @@ static const debugger_command_t commands[] = {
{"next", 1, next, "Run the next instruction, skipping over function calls"},
{"step", 1, step, "Run the next instruction, stepping into function calls"},
{"finish", 1, finish, "Run until the current function returns"},
{"undo", 1, undo, "Reverts the last command"},
{"backtrace", 2, backtrace, "Displays the current call stack"},
{"bt", 2, }, /* Alias */
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"},
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
"used"},
{"undo", 1, undo, "Revert the last command"},
{"registers", 1, registers, "Print values of processor registers and other important registers"},
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
{"mbc", 3, }, /* Alias */
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
{"palettes", 3, palettes, "Displays the current CGB palettes"},
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer},
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
"jumping to the target.",
"<expression>[ if <condition expression>]", "j",
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
"Default watchpoint type is write-only.",
"<expression>[ if <condition expression>]", "(r|w|rw)",
.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"},
{"backtrace", 2, backtrace, "Display the current call stack"},
{"bt", 2, }, /* Alias */
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
{"eval", 2, }, /* Alias */
{"examine", 2, examine, "Examine values at address", "<expression>", "count", .argument_completer = symbol_completer},
{"x", 1, }, /* Alias */
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count", .argument_completer = symbol_completer},
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
"jumping to the target.",
"<expression>[ if <condition expression>]", "j",
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
"Default watchpoint type is write-only.",
"<expression>[ if <condition expression>]", "(r|w|rw)",
.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},
{"softbreak", 2, softbreak, "Enable or disable software breakpoints", "(on|off)", .argument_completer = on_off_completer},
{"list", 1, list, "List all set breakpoints and watchpoints"},
{"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
"used"},
{"cartridge", 2, mbc, "Display information about the MBC and cartridge"},
{"mbc", 3, }, /* Alias */
{"apu", 3, apu, "Display information about the current state of the audio processing unit", "[channel (1-4, 5 for NR5x)]"},
{"wave", 3, wave, "Print a visual representation of the wave RAM." HELP_NEWLINE
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
{"lcd", 3, lcd, "Display information about the current state of the LCD controller"},
{"palettes", 3, palettes, "Display the current CGB palettes"},
{"dma", 3, dma, "Display the current OAM DMA status"},
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
{NULL,}, /* Null terminator */
@ -2038,19 +2075,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
{
/* Called just after the CPU calls a function/enters an interrupt/etc... */
if (gb->stack_leak_detection) {
if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) {
GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n");
gb->debug_stopped = true;
}
else {
gb->sp_for_call_depth[gb->debug_call_depth] = gb->sp;
gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc;
}
}
if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) {
while (gb->backtrace_size) {
if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->sp) {
gb->backtrace_size--;
@ -2075,21 +2100,6 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
gb->debug_call_depth--;
if (gb->stack_leak_detection) {
if (gb->debug_call_depth < 0) {
GB_log(gb, "Function finished without a stack leak.\n");
gb->debug_stopped = true;
}
else {
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, "SP is $%04x, should be $%04x.\n", gb->sp,
gb->sp_for_call_depth[gb->debug_call_depth]);
gb->debug_stopped = true;
}
}
}
while (gb->backtrace_size) {
if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->sp) {
gb->backtrace_size--;
@ -2390,7 +2400,6 @@ next_command:
if (gb->debug_stopped && !gb->debug_disable) {
gb->debug_next_command = false;
gb->debug_fin_command = false;
gb->stack_leak_detection = false;
input = gb->input_callback(gb);
if (input == NULL) {
@ -2419,7 +2428,12 @@ 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 (bank >= gb->n_symbol_maps) {
gb->bank_symbols = realloc(gb->bank_symbols, (bank + 1) * sizeof(*gb->bank_symbols));
while (bank >= gb->n_symbol_maps) {
gb->bank_symbols[gb->n_symbol_maps++] = NULL;
}
}
if (!gb->bank_symbols[bank]) {
gb->bank_symbols[bank] = GB_map_alloc();
@ -2461,7 +2475,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
void GB_debugger_clear_symbols(GB_gameboy_t *gb)
{
for (unsigned i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) {
for (unsigned i = gb->n_symbol_maps; i--;) {
if (gb->bank_symbols[i]) {
GB_map_free(gb->bank_symbols[i]);
gb->bank_symbols[i] = 0;
@ -2474,15 +2488,20 @@ void GB_debugger_clear_symbols(GB_gameboy_t *gb)
gb->reversed_symbol_map.buckets[i] = next;
}
}
gb->n_symbol_maps = 0;
if (gb->bank_symbols) {
free(gb->bank_symbols);
gb->bank_symbols = NULL;
}
}
const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr)
{
uint16_t bank = bank_for_addr(gb, addr);
const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr);
const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, bank), addr);
if (symbol) return symbol;
if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */
if (bank != 0) return GB_map_find_symbol(get_symbol_map(gb, 0), addr); /* Maybe the symbol incorrectly uses bank 0? */
return NULL;
}

View File

@ -9,27 +9,31 @@
static inline unsigned fifo_size(GB_fifo_t *fifo)
{
return (fifo->write_end - fifo->read_end) & (GB_FIFO_LENGTH - 1);
return fifo->size;
}
static void fifo_clear(GB_fifo_t *fifo)
{
fifo->read_end = fifo->write_end = 0;
fifo->read_end = fifo->size = 0;
}
static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo)
{
assert(fifo->size);
GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end];
fifo->read_end++;
fifo->read_end &= (GB_FIFO_LENGTH - 1);
fifo->size--;
return ret;
}
static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x)
{
assert(fifo->size == 0);
fifo->size = 8;
if (!flip_x) {
unrolled for (unsigned i = 8; i--;) {
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
unrolled for (unsigned i = 0; i < 8; i++) {
fifo->fifo[i] = (GB_fifo_item_t) {
(lower >> 7) | ((upper >> 7) << 1),
palette,
0,
@ -37,14 +41,11 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint
};
lower <<= 1;
upper <<= 1;
fifo->write_end++;
fifo->write_end &= (GB_FIFO_LENGTH - 1);
}
}
else {
unrolled for (unsigned i = 8; i--;) {
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
unrolled for (unsigned i = 0; i < 8; i++) {
fifo->fifo[i] = (GB_fifo_item_t) {
(lower & 1) | ((upper & 1) << 1),
palette,
0,
@ -52,19 +53,15 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint
};
lower >>= 1;
upper >>= 1;
fifo->write_end++;
fifo->write_end &= (GB_FIFO_LENGTH - 1);
}
}
}
static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x)
{
while (fifo_size(fifo) < 8) {
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,};
fifo->write_end++;
fifo->write_end &= (GB_FIFO_LENGTH - 1);
while (fifo->size < GB_FIFO_LENGTH) {
fifo->fifo[(fifo->read_end + fifo->size) & (GB_FIFO_LENGTH - 1)] = (GB_fifo_item_t) {0,};
fifo->size++;
}
uint8_t flip_xor = flip_x? 0: 0x7;
@ -109,7 +106,7 @@ typedef struct __attribute__((packed)) {
uint8_t flags;
} object_t;
void GB_display_vblank(GB_gameboy_t *gb)
void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
{
gb->vblank_just_occured = true;
gb->cycles_since_vblank_callback = 0;
@ -159,18 +156,18 @@ void GB_display_vblank(GB_gameboy_t *gb)
GB_borrow_sgb_border(gb);
uint32_t border_colors[16 * 4];
if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) {
if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_E) {
uint16_t colors[] = {
0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648,
0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C,
0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964,
};
unsigned index = gb->rom? gb->rom[0x14e] % 5 : 0;
unsigned index = gb->rom? gb->rom[0x14E] % 5 : 0;
if (gb->model == GB_MODEL_CGB_0) {
index = 1; // CGB 0 was only available in Indigo!
index = 1; // CGB 0 was only available in indigo!
}
else if (gb->model == GB_MODEL_CGB_A) {
index = 0; // CGB 0 was only available in Indigo!
index = 0; // CGB A was only available in red!
}
gb->borrowed_border.palette[0] = LE16(colors[index]);
gb->borrowed_border.palette[10] = LE16(colors[5 + index]);
@ -214,7 +211,7 @@ void GB_display_vblank(GB_gameboy_t *gb)
GB_handle_rumble(gb);
if (gb->vblank_callback) {
gb->vblank_callback(gb);
gb->vblank_callback(gb, type);
}
GB_timing_sync(gb);
}
@ -277,7 +274,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
b = scale_channel_with_curve_sgb(b);
}
else {
bool agb = gb->model == GB_MODEL_AGB;
bool agb = gb->model > GB_MODEL_CGB_E;
r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b);
@ -331,10 +328,10 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
uint8_t old_min = MIN(r, MIN(g, b));
uint8_t new_min = MIN(new_r, MIN(new_g, new_b));
if (new_min != 0xff) {
new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min);
new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min);
new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);
if (new_min != 0xFF) {
new_r = 0xFF - (0xFF - new_r) * (0xFF - old_min) / (0xFF - new_min);
new_g = 0xFF - (0xFF - new_g) * (0xFF - old_min) / (0xFF - new_min);
new_b = 0xFF - (0xFF - new_b) * (0xFF - old_min) / (0xFF - new_min);
}
}
r = new_r;
@ -397,6 +394,9 @@ void GB_set_light_temperature(GB_gameboy_t *gb, double temperature)
void GB_STAT_update(GB_gameboy_t *gb)
{
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return;
if (GB_is_dma_active(gb) && (gb->io_registers[GB_IO_STAT] & 3) == 2) {
gb->io_registers[GB_IO_STAT] &= ~3;
}
bool previous_interrupt_line = gb->stat_interrupt_line;
/* Set LY=LYC bit */
@ -433,20 +433,19 @@ void GB_STAT_update(GB_gameboy_t *gb)
void GB_lcd_off(GB_gameboy_t *gb)
{
gb->cycles_for_line = 0;
gb->display_state = 0;
gb->display_cycles = 0;
/* When the LCD is disabled, state is constant */
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3)) {
gb->hdma_on = true;
}
/* When the LCD is off, LY is 0 and STAT mode is 0. */
gb->io_registers[GB_IO_LY] = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
if (gb->hdma_on_hblank) {
gb->hdma_on_hblank = false;
gb->hdma_on = false;
/* Todo: is this correct? */
gb->hdma_steps_left = 0xff;
}
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
@ -465,32 +464,60 @@ void GB_lcd_off(GB_gameboy_t *gb)
}
}
static inline uint8_t oam_read(GB_gameboy_t *gb, uint8_t addr)
{
if (unlikely(gb->oam_ppu_blocked)) {
return 0xFF;
}
if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0)) { // TODO: what happens in the last and first M cycles?
if (gb->hdma_in_progress) {
return GB_read_oam(gb, (gb->hdma_current_src & ~1) | (addr & 1));
}
if (gb->dma_current_dest != 0xA0) {
return gb->oam[(gb->dma_current_dest & ~1) | (addr & 1)];
}
}
return gb->oam[addr];
}
static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
{
if (likely(!GB_is_dma_active(gb) || gb->halted || gb->stopped)) {
gb->mode2_y_bus = oam_read(gb, index * 4);
gb->mode2_x_bus = oam_read(gb, index * 4 + 1);
}
if (gb->n_visible_objs == 10) return;
/* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */
if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) {
if (unlikely(GB_is_dma_active(gb) && (gb->halted || gb->stopped))) {
if (gb->model < GB_MODEL_CGB_E) {
return;
}
/* CGB-0 to CGB-D: Halted DMA blocks Mode 2;
Pre-CGB: Unit specific behavior, some units read FFs, some units read using
several different corruption pattterns. For simplicity, we emulate
FFs. */
}
if (unlikely(gb->oam_ppu_blocked)) {
return;
}
if (gb->oam_ppu_blocked) {
return;
}
/* This reverse sorts the visible objects by location and priority */
object_t *objects = (object_t *) &gb->oam;
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
signed y = objects[index].y - 16;
signed y = gb->mode2_y_bus - 16;
/* This reverse sorts the visible objects by location and priority */
if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) {
unsigned j = 0;
for (; j < gb->n_visible_objs; j++) {
if (gb->obj_comparators[j] <= objects[index].x) break;
if (gb->objects_x[j] <= gb->mode2_x_bus) break;
}
memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j);
memmove(gb->obj_comparators + j + 1, gb->obj_comparators + j, gb->n_visible_objs - j);
memmove(gb->objects_x + j + 1, gb->objects_x + j, gb->n_visible_objs - j);
memmove(gb->objects_y + j + 1, gb->objects_y + j, gb->n_visible_objs - j);
gb->visible_objs[j] = index;
gb->obj_comparators[j] = objects[index].x;
gb->objects_x[j] = gb->mode2_x_bus;
gb->objects_y[j] = gb->mode2_y_bus;
gb->n_visible_objs++;
}
}
@ -529,22 +556,37 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
const GB_fifo_item_t *oam_fifo_item = NULL;
bool draw_oam = false;
bool bg_enabled = true, bg_priority = false;
// Rendering (including scrolling adjustment) does not occur as long as an object at x=0 is pending
if (gb->n_visible_objs != 0 &&
(gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) &&
gb->objects_x[gb->n_visible_objs - 1] == 0) {
return;
}
if (fifo_size(&gb->bg_fifo)) {
fifo_item = fifo_pop(&gb->bg_fifo);
bg_priority = fifo_item->bg_priority;
if (fifo_size(&gb->oam_fifo)) {
oam_fifo_item = fifo_pop(&gb->oam_fifo);
if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2) && unlikely(!gb->objects_disabled)) {
draw_oam = true;
bg_priority |= oam_fifo_item->bg_priority;
}
if (unlikely(gb->wx_triggered && !fifo_size(&gb->bg_fifo))) return;
fifo_item = fifo_pop(&gb->bg_fifo);
bg_priority = fifo_item->bg_priority;
if (fifo_size(&gb->oam_fifo)) {
oam_fifo_item = fifo_pop(&gb->oam_fifo);
if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2) && unlikely(!gb->objects_disabled)) {
draw_oam = true;
bg_priority |= oam_fifo_item->bg_priority;
}
}
if (!fifo_item) return;
// (gb->position_in_line + 16 < 8) is (gb->position_in_line < -8) in unsigned logic
if (((uint8_t)(gb->position_in_line + 16) < 8)) {
if ((gb->position_in_line & 7) == (gb->io_registers[GB_IO_SCX] & 7)) {
gb->position_in_line = -8;
}
else if (gb->position_in_line == (uint8_t) -9) {
gb->position_in_line = -16;
return;
}
}
/* Drop pixels for scrollings */
if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) {
@ -641,6 +683,23 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
gb->window_is_being_fetched = false;
}
static inline void dma_sync(GB_gameboy_t *gb, unsigned *cycles)
{
if (unlikely(GB_is_dma_active(gb))) {
unsigned offset = *cycles - gb->display_cycles; // Time passed in 8MHz ticks
if (offset) {
*cycles = gb->display_cycles;
if (!gb->cgb_double_speed) {
offset >>= 1; // Convert to T-cycles
}
unsigned old = gb->dma_cycles;
gb->dma_cycles = offset;
GB_dma_run(gb);
gb->dma_cycles = old - offset;
}
}
}
/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have
slightly different timings than CPUs <= C.
@ -651,7 +710,45 @@ static inline uint8_t fetcher_y(GB_gameboy_t *gb)
return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY];
}
static void advance_fetcher_state_machine(GB_gameboy_t *gb)
static inline uint8_t vram_read(GB_gameboy_t *gb, uint16_t addr)
{
if (unlikely(gb->vram_ppu_blocked)) {
return 0xFF;
}
if (unlikely(gb->hdma_in_progress)) {
gb->addr_for_hdma_conflict = addr;
return 0;
}
// TODO: what if both?
else if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0 && (gb->dma_current_src & 0xE000) == 0x8000)) { // TODO: what happens in the last and first M cycles?
// DMAing from VRAM!
/* TODO: AGS has its own, very different pattern, but AGS is not currently a supported model */
/* TODO: Research this when researching odd modes */
/* TODO: probably not 100% on the first few reads during halt/stop modes*/
unsigned offset = 1 - (gb->halted || gb->stopped);
if (GB_is_cgb(gb)) {
if (gb->dma_ppu_vram_conflict) {
addr = (gb->dma_ppu_vram_conflict_addr & 0x1FFF) | (addr & 0x2000);
}
else if (gb->dma_cycles_modulo && !gb->halted && !gb->stopped) {
addr &= 0x2000;
addr |= ((gb->dma_current_src - offset) & 0x1FFF);
}
else {
addr &= 0x2000 | ((gb->dma_current_src - offset) & 0x1FFF);
gb->dma_ppu_vram_conflict_addr = addr;
gb->dma_ppu_vram_conflict = !gb->halted && !gb->stopped;
}
}
else {
addr |= ((gb->dma_current_src - offset) & 0x1FFF);
}
gb->oam[gb->dma_current_dest - offset] = gb->vram[(addr & 0x1FFF) | (gb->cgb_vram_bank? 0x2000 : 0)];
}
return gb->vram[addr];
}
static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles)
{
typedef enum {
GB_FETCHER_GET_TILE,
@ -673,6 +770,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
};
switch (fetcher_state_machine[gb->fetcher_state & 7]) {
case GB_FETCHER_GET_TILE: {
dma_sync(gb, cycles);
uint16_t map = 0x1800;
if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) {
@ -694,6 +792,9 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
if (gb->wx_triggered) {
x = gb->window_tile_x;
}
else if ((uint8_t)(gb->position_in_line + 16) < 8) {
x = gb->io_registers[GB_IO_SCX] >> 3;
}
else {
/* TODO: There is some CGB timing error around here.
Adjusting SCX by 7 or less shouldn't have an effect on a CGB,
@ -705,23 +806,18 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
gb->fetcher_y = y;
}
gb->last_tile_index_address = map + x + y / 8 * 32;
gb->current_tile = gb->vram[gb->last_tile_index_address];
if (gb->vram_ppu_blocked) {
gb->current_tile = 0xFF;
}
gb->current_tile = vram_read(gb, gb->last_tile_index_address);
if (GB_is_cgb(gb)) {
/* The CGB actually accesses both the tile index AND the attributes in the same T-cycle.
This probably means the CGB has a 16-bit data bus for the VRAM. */
gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000];
if (gb->vram_ppu_blocked) {
gb->current_tile_attributes = 0xFF;
}
gb->current_tile_attributes = vram_read(gb, gb->last_tile_index_address + 0x2000);
}
}
gb->fetcher_state++;
break;
case GB_FETCHER_GET_TILE_DATA_LOWER: {
dma_sync(gb, cycles);
bool use_glitched = false;
bool cgb_d_glitch = false;
if (gb->tile_sel_glitch) {
@ -746,29 +842,22 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
}
if (!use_glitched) {
gb->current_tile_data[0] =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[0] = 0xFF;
}
vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2);
}
if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) {
gb->data_for_sel_glitch =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
if (gb->vram_ppu_blocked) {
gb->data_for_sel_glitch = 0xFF;
}
vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2);
}
else if (cgb_d_glitch) {
gb->data_for_sel_glitch = gb->vram[gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2];
if (gb->vram_ppu_blocked) {
gb->data_for_sel_glitch = 0xFF;
}
gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2);
}
}
gb->fetcher_state++;
break;
case GB_FETCHER_GET_TILE_DATA_HIGH: {
dma_sync(gb, cycles);
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
bool use_glitched = false;
@ -796,27 +885,20 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1 - cgb_d_glitch;
if (!use_glitched) {
gb->current_tile_data[1] =
gb->vram[gb->last_tile_data_address];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[1] = 0xFF;
}
vram_read(gb, gb->last_tile_data_address);
}
if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) {
gb->data_for_sel_glitch = gb->vram[gb->last_tile_data_address];
if (gb->vram_ppu_blocked) {
gb->data_for_sel_glitch = 0xFF;
}
gb->data_for_sel_glitch = vram_read(gb, gb->last_tile_data_address);
}
else if (cgb_d_glitch) {
gb->data_for_sel_glitch = gb->vram[gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1];
if (gb->vram_ppu_blocked) {
gb->data_for_sel_glitch = 0xFF;
}
gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1);
}
}
if (gb->wx_triggered) {
gb->window_tile_x++;
gb->window_tile_x &= 0x1f;
gb->window_tile_x &= 0x1F;
}
// fallthrough
@ -842,25 +924,19 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
}
}
static uint16_t get_object_line_address(GB_gameboy_t *gb, const object_t *object)
static uint16_t get_object_line_address(GB_gameboy_t *gb, uint8_t y, uint8_t tile, uint8_t flags)
{
/* TODO: what does the PPU read if DMA is active? */
if (gb->oam_ppu_blocked) {
static const object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF};
object = &blocked;
}
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */
uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7);
uint8_t tile_y = (gb->current_line - y) & (height_16? 0xF : 7);
if (object->flags & 0x40) { /* Flip Y */
if (flags & 0x40) { /* Flip Y */
tile_y ^= height_16? 0xF : 7;
}
/* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */
uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2;
uint16_t line_address = (height_16? tile & 0xFE : tile) * 0x10 + tile_y * 2;
if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */
if (gb->cgb_mode && (flags & 0x8)) { /* Use VRAM bank 2 */
line_address += 0x2000;
}
return line_address;
@ -932,7 +1008,7 @@ static void render_line(GB_gameboy_t *gb)
const object_t *object = &objects[object_index];
gb->n_visible_objs--;
uint16_t line_address = get_object_line_address(gb, object);
uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags);
uint8_t data0 = gb->vram[line_address];
uint8_t data1 = gb->vram[line_address + 1];
if (gb->n_visible_objs == 0) {
@ -1096,7 +1172,7 @@ static void render_line_sgb(GB_gameboy_t *gb)
const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
gb->n_visible_objs--;
uint16_t line_address = get_object_line_address(gb, object);
uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags);
uint8_t data0 = gb->vram[line_address];
uint8_t data1 = gb->vram[line_address + 1];
if (object->flags & 0x20) {
@ -1213,9 +1289,11 @@ object_buffer_pointer++\
static inline uint16_t mode3_batching_length(GB_gameboy_t *gb)
{
if (gb->position_in_line != (uint8_t)-16) return 0;
if (gb->model & GB_MODEL_NO_SFC_BIT) return 0;
if (gb->hdma_on) return 0;
if (gb->dma_steps_left) return 0;
if (gb->stopped) return 0;
if (GB_is_dma_active(gb)) return 0;
if (gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && (gb->io_registers[GB_IO_WX] < 8 || gb->io_registers[GB_IO_WX] == 166)) {
return 0;
}
@ -1236,23 +1314,47 @@ static inline uint16_t mode3_batching_length(GB_gameboy_t *gb)
return 0;
}
static inline uint8_t x_for_object_match(GB_gameboy_t *gb)
{
uint8_t ret = gb->position_in_line + 8;
if (ret > (uint8_t)-16) return 0;
return ret;
}
/*
TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles.
The PPU logic can be greatly simplified if that delay is simply emulated.
*/
void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
{
if (unlikely((gb->io_registers[GB_IO_LCDC] & 0x80) && (signed)(gb->cycles_for_line * 2 + cycles + gb->display_cycles) > LINE_LENGTH * 2)) {
unsigned first_batch = (LINE_LENGTH * 2 - gb->cycles_for_line * 2 + gb->display_cycles);
GB_display_run(gb, first_batch, force);
cycles -= first_batch;
if (gb->display_state == 22) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->mode_for_interrupt = 0;
GB_STAT_update(gb);
}
gb->display_state = 9;
gb->display_cycles = 0;
}
if (unlikely(gb->delayed_glitch_hblank_interrupt && cycles && gb->current_line < LINES)) {
gb->delayed_glitch_hblank_interrupt = false;
gb->mode_for_interrupt = 0;
GB_STAT_update(gb);
gb->mode_for_interrupt = 3;
}
gb->cycles_since_vblank_callback += cycles / 2;
/* The PPU does not advance while in STOP mode on the DMG */
if (gb->stopped && !GB_is_cgb(gb)) {
if (gb->cycles_since_vblank_callback >= LCDC_PERIOD) {
GB_display_vblank(gb);
GB_display_vblank(gb, GB_VBLANK_TYPE_ARTIFICIAL);
}
return;
}
object_t *objects = (object_t *) &gb->oam;
GB_BATCHABLE_STATE_MACHINE(gb, display, cycles, 2, !force) {
GB_STATE(gb, display, 1);
GB_STATE(gb, display, 2);
@ -1262,7 +1364,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
GB_STATE(gb, display, 6);
GB_STATE(gb, display, 7);
GB_STATE(gb, display, 8);
// GB_STATE(gb, display, 9);
GB_STATE(gb, display, 9);
GB_STATE(gb, display, 10);
GB_STATE(gb, display, 11);
GB_STATE(gb, display, 12);
@ -1277,7 +1379,6 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
GB_STATE(gb, display, 22);
GB_STATE(gb, display, 23);
GB_STATE(gb, display, 24);
GB_STATE(gb, display, 25);
GB_STATE(gb, display, 26);
GB_STATE(gb, display, 27);
GB_STATE(gb, display, 28);
@ -1295,6 +1396,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
GB_STATE(gb, display, 40);
GB_STATE(gb, display, 41);
GB_STATE(gb, display, 42);
GB_STATE(gb, display, 43);
}
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
@ -1302,7 +1404,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
if (gb->cycles_since_vblank_callback < LCDC_PERIOD) {
GB_SLEEP(gb, display, 1, LCDC_PERIOD - gb->cycles_since_vblank_callback);
}
GB_display_vblank(gb);
GB_display_vblank(gb, GB_VBLANK_TYPE_LCD_OFF);
gb->cgb_repeated_a_frame = true;
}
return;
@ -1318,6 +1420,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
gb->current_line = 0;
gb->window_y = -1;
gb->wy_triggered = false;
gb->position_in_line = -16;
gb->ly_for_comparison = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
@ -1337,6 +1440,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
GB_SLEEP(gb, display, 34, 2);
gb->n_visible_objs = 0;
gb->orig_n_visible_objs = 0;
gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles.
gb->io_registers[GB_IO_STAT] &= ~3;
@ -1363,7 +1467,47 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
gb->wx_triggered = false;
gb->wx166_glitch = false;
goto mode_3_start;
// Mode 3 abort, state 9
display9: {
// TODO: Timing of things in this scenario is almost completely untested
if (gb->current_line < LINES && !GB_is_sgb(gb) && !gb->disable_rendering) {
GB_log(gb, "The ROM is preventing line %d from fully rendering, this could damage a real device's LCD display.\n", gb->current_line);
uint32_t *dest = NULL;
if (gb->border_mode != GB_BORDER_ALWAYS) {
dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
}
else {
dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
}
uint32_t color = GB_is_cgb(gb)? GB_convert_rgb15(gb, 0x7FFF, false) : gb->background_palettes_rgb[4];
while (gb->lcd_x < 160) {
*(dest++) = color;
gb->lcd_x++;
}
}
gb->n_visible_objs = gb->orig_n_visible_objs;
gb->current_line++;
gb->cycles_for_line = 0;
if (gb->current_line != LINES) {
gb->cycles_for_line = 2;
GB_SLEEP(gb, display, 28, 2);
gb->io_registers[GB_IO_LY] = gb->current_line;
if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) {
gb->delayed_glitch_hblank_interrupt = true;
}
GB_STAT_update(gb);
gb->position_in_line = -15;
goto mode_3_start;
}
else {
if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) {
gb->delayed_glitch_hblank_interrupt = true;
}
gb->position_in_line = -16;
}
}
while (true) {
/* Lines 0 - 143 */
gb->window_y = -1;
@ -1405,8 +1549,9 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
gb->mode_for_interrupt = -1;
GB_STAT_update(gb);
gb->n_visible_objs = 0;
gb->orig_n_visible_objs = 0;
if (!gb->dma_steps_left && !gb->oam_ppu_blocked) {
if (!GB_is_dma_active(gb) && !gb->oam_ppu_blocked) {
GB_BATCHPOINT(gb, display, 5, 80);
}
for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {
@ -1427,7 +1572,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
}
}
gb->cycles_for_line = MODE2_LENGTH + 4;
gb->orig_n_visible_objs = gb->n_visible_objs;
gb->accessed_oam_row = -1;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 3;
@ -1462,11 +1607,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
fifo_clear(&gb->oam_fifo);
/* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */
fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false);
/* Todo: find out actual access time of SCX */
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
gb->lcd_x = 0;
gb->extra_penalty_for_object_at_0 = MIN((gb->io_registers[GB_IO_SCX] & 7), 5);
/* The actual rendering cycle */
gb->fetcher_state = 0;
@ -1555,20 +1696,19 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
/* Handle objects */
/* When the object enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB.
On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */
while (gb->n_visible_objs != 0 &&
(gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) &&
gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) {
gb->objects_x[gb->n_visible_objs - 1] < x_for_object_match(gb)) {
gb->n_visible_objs--;
}
gb->during_object_fetch = true;
while (gb->n_visible_objs != 0 &&
(gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) &&
gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) {
gb->objects_x[gb->n_visible_objs - 1] == x_for_object_match(gb)) {
while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) {
advance_fetcher_state_machine(gb);
advance_fetcher_state_machine(gb, &cycles);
gb->cycles_for_line++;
GB_SLEEP(gb, display, 27, 1);
if (gb->object_fetch_aborted) {
@ -1576,20 +1716,8 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
}
}
/* Todo: Measure if penalty occurs before or after waiting for the fetcher. */
if (gb->extra_penalty_for_object_at_0 != 0) {
if (gb->obj_comparators[gb->n_visible_objs - 1] == 0) {
gb->cycles_for_line += gb->extra_penalty_for_object_at_0;
GB_SLEEP(gb, display, 28, gb->extra_penalty_for_object_at_0);
gb->extra_penalty_for_object_at_0 = 0;
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
}
}
/* TODO: Can this be deleted? { */
advance_fetcher_state_machine(gb);
advance_fetcher_state_machine(gb, &cycles);
gb->cycles_for_line++;
GB_SLEEP(gb, display, 41, 1);
if (gb->object_fetch_aborted) {
@ -1597,18 +1725,28 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
}
/* } */
advance_fetcher_state_machine(gb);
gb->cycles_for_line += 3;
GB_SLEEP(gb, display, 20, 3);
advance_fetcher_state_machine(gb, &cycles);
dma_sync(gb, &cycles);
gb->mode2_y_bus = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 2);
gb->object_flags = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 3);
gb->cycles_for_line += 2;
GB_SLEEP(gb, display, 20, 2);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]);
/* TODO: timing not verified */
dma_sync(gb, &cycles);
gb->object_low_line_address = get_object_line_address(gb,
gb->objects_y[gb->n_visible_objs - 1],
gb->mode2_y_bus,
gb->object_flags);
gb->object_tile_data[0] = vram_read(gb, gb->object_low_line_address);
gb->cycles_for_line++;
GB_SLEEP(gb, display, 39, 1);
gb->cycles_for_line += 2;
GB_SLEEP(gb, display, 39, 2);
if (gb->object_fetch_aborted) {
goto abort_fetching_object;
}
@ -1616,24 +1754,28 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
gb->during_object_fetch = false;
gb->cycles_for_line++;
GB_SLEEP(gb, display, 40, 1);
/* TODO: timing not verified */
dma_sync(gb, &cycles);
gb->object_tile_data[1] = vram_read(gb, get_object_line_address(gb,
gb->objects_y[gb->n_visible_objs - 1],
gb->mode2_y_bus,
gb->object_flags) + 1);
const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
uint16_t line_address = get_object_line_address(gb, object);
uint8_t palette = (object->flags & 0x10) ? 1 : 0;
uint8_t palette = (gb->object_flags & 0x10) ? 1 : 0;
if (gb->cgb_mode) {
palette = object->flags & 0x7;
palette = gb->object_flags & 0x7;
}
fifo_overlay_object_row(&gb->oam_fifo,
gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address],
gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1],
gb->object_tile_data[0],
gb->object_tile_data[1],
palette,
object->flags & 0x80,
gb->object_flags & 0x80,
gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0,
object->flags & 0x20);
gb->object_flags & 0x20);
gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1];
gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address + 1];
gb->n_visible_objs--;
}
@ -1642,14 +1784,15 @@ abort_fetching_object:
gb->during_object_fetch = false;
render_pixel_if_possible(gb);
advance_fetcher_state_machine(gb);
advance_fetcher_state_machine(gb, &cycles);
if (gb->position_in_line == 160) break;
gb->cycles_for_line++;
GB_SLEEP(gb, display, 21, 1);
}
skip_slow_mode_3:
gb->position_in_line = -16;
/* TODO: This seems incorrect (glitches Tesserae), verify further */
/*
if (gb->fetcher_state == 4 || gb->fetcher_state == 5) {
@ -1712,17 +1855,25 @@ skip_slow_mode_3:
GB_SLEEP(gb, display, 33, 2);
gb->cgb_palettes_blocked = !gb->cgb_double_speed;
if (gb->hdma_on_hblank && !gb->halted && !gb->stopped) {
gb->hdma_on = true;
}
gb->cycles_for_line += 2;
GB_SLEEP(gb, display, 36, 2);
gb->cgb_palettes_blocked = false;
gb->cycles_for_line += 8;
GB_SLEEP(gb, display, 25, 8);
if (gb->hdma_on_hblank) {
gb->hdma_starting = true;
if (gb->cycles_for_line > LINE_LENGTH - 2) {
gb->cycles_for_line = 0;
GB_SLEEP(gb, display, 43, LINE_LENGTH - gb->cycles_for_line);
goto display9;
}
{
uint16_t cycles_for_line = gb->cycles_for_line;
gb->cycles_for_line = 0;
GB_SLEEP(gb, display, 11, LINE_LENGTH - cycles_for_line - 2);
}
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2);
/*
TODO: Verify double speed timing
TODO: Timing differs on a DMG
@ -1731,6 +1882,7 @@ skip_slow_mode_3:
(gb->io_registers[GB_IO_WY] == gb->current_line)) {
gb->wy_triggered = true;
}
gb->cycles_for_line = 0;
GB_SLEEP(gb, display, 31, 2);
if (gb->current_line != LINES - 1) {
gb->mode_for_interrupt = 2;
@ -1739,7 +1891,7 @@ skip_slow_mode_3:
// Todo: unverified timing
gb->current_lcd_line++;
if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) {
GB_display_vblank(gb);
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
}
if (gb->icd_hreset_callback) {
@ -1760,6 +1912,10 @@ skip_slow_mode_3:
gb->io_registers[GB_IO_IF] |= 2;
}
GB_SLEEP(gb, display, 12, 2);
if (gb->delayed_glitch_hblank_interrupt) {
gb->delayed_glitch_hblank_interrupt = false;
gb->mode_for_interrupt = 0;
}
gb->ly_for_comparison = gb->current_line;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 24, 1);
@ -1777,13 +1933,13 @@ skip_slow_mode_3:
if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
if (GB_is_cgb(gb)) {
GB_display_vblank(gb);
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED;
}
else {
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
gb->is_odd_frame ^= true;
GB_display_vblank(gb);
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
}
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
@ -1791,7 +1947,7 @@ skip_slow_mode_3:
else {
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
gb->is_odd_frame ^= true;
GB_display_vblank(gb);
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
}
if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) {
gb->cgb_repeated_a_frame = true;
@ -1918,7 +2074,7 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette
}
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) {
map = 0x1c00;
map = 0x1C00;
}
if (tileset_type == GB_TILESET_AUTO) {
@ -1971,9 +2127,9 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *object_h
for (signed y = 0; y < LINES; y++) {
object_t *object = (object_t *) &gb->oam;
uint8_t objects_in_line = 0;
bool obscured = false;
for (uint8_t i = 0; i < 40; i++, object++) {
signed object_y = object->y - 16;
bool obscured = false;
// Is object not in this line?
if (object_y > y || object_y + *object_height <= y) continue;
if (++objects_in_line == 11) obscured = true;

View File

@ -5,12 +5,18 @@
#include <stdbool.h>
#include <stdint.h>
typedef enum {
GB_VBLANK_TYPE_NORMAL_FRAME, // An actual Vblank-triggered frame
GB_VBLANK_TYPE_LCD_OFF, // An artificial frame pushed while the LCD was off
GB_VBLANK_TYPE_ARTIFICIAL, // An artificial frame pushed for some other reason
} GB_vblank_type_t;
#ifdef GB_INTERNAL
internal void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force);
internal void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
internal void GB_STAT_update(GB_gameboy_t *gb);
internal void GB_lcd_off(GB_gameboy_t *gb);
internal void GB_display_vblank(GB_gameboy_t *gb);
internal void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type);
#define GB_display_sync(gb) GB_display_run(gb, 0, true)
enum {

143
Core/gb.c
View File

@ -121,7 +121,7 @@ static void load_default_border(GB_gameboy_t *gb)
}
#endif
if (gb->model == GB_MODEL_AGB) {
if (gb->model > GB_MODEL_CGB_E) {
#include "graphics/agb_border.inc"
LOAD_BORDER();
}
@ -216,6 +216,7 @@ void GB_free(GB_gameboy_t *gb)
GB_remove_cheat(gb, gb->cheats[0]);
}
#endif
GB_stop_audio_recording(gb);
memset(gb, 0, sizeof(*gb));
}
@ -437,12 +438,12 @@ int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size
if (gb->gbs_header.load_address) {
// Generate interrupt handlers
for (unsigned i = 0; i <= (has_interrupts? 0x50 : 0x38); i += 8) {
gb->rom[i] = 0xc3; // jp $XXXX
gb->rom[i] = 0xC3; // jp $XXXX
gb->rom[i + 1] = (LE16(gb->gbs_header.load_address) + i);
gb->rom[i + 2] = (LE16(gb->gbs_header.load_address) + i) >> 8;
}
for (unsigned i = has_interrupts? 0x58 : 0x40; i <= 0x60; i += 8) {
gb->rom[i] = 0xc9; // ret
gb->rom[i] = 0xC9; // ret
}
// Generate entry
@ -703,7 +704,7 @@ error:
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
{
gb->rom_size = (size + 0x3fff) & ~0x3fff;
gb->rom_size = (size + 0x3FFF) & ~0x3FFF;
while (gb->rom_size & (gb->rom_size - 1)) {
gb->rom_size |= gb->rom_size >> 1;
gb->rom_size++;
@ -715,7 +716,7 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz
free(gb->rom);
}
gb->rom = malloc(gb->rom_size);
memset(gb->rom, 0xff, gb->rom_size);
memset(gb->rom, 0xFF, gb->rom_size);
memcpy(gb->rom, buffer, size);
GB_configure_cart(gb);
gb->tried_loading_sgb_border = false;
@ -1128,7 +1129,7 @@ exit:
return;
}
uint8_t GB_run(GB_gameboy_t *gb)
unsigned GB_run(GB_gameboy_t *gb)
{
gb->vblank_just_occured = false;
@ -1219,10 +1220,10 @@ void GB_set_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback)
gb->lcd_line_callback = callback;
}
const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff, 0xff, 0xff}, {0xff, 0xff, 0xff}}};
const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2, 0xe6, 0xa6}}};
const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}};
const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}};
const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xAA, 0xAA, 0xAA}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF}}};
const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xA5, 0x63}, {0xC6, 0xDE, 0x8C}, {0xD2, 0xE6, 0xA6}}};
const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0E}, {0x3A, 0x4C, 0x3A}, {0x81, 0x8D, 0x66}, {0xC2, 0xCE, 0x93}, {0xCF, 0xDA, 0xAC}}};
const GB_palette_t GB_PALETTE_GBL = {{{0x0A, 0x1C, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xB4, 0x95}, {0x7F, 0xE2, 0xC3}, {0x91, 0xEA, 0xD0}}};
static void update_dmg_palette(GB_gameboy_t *gb)
{
@ -1293,26 +1294,38 @@ void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfe
bool GB_serial_get_data_bit(GB_gameboy_t *gb)
{
if (!(gb->io_registers[GB_IO_SC] & 0x80)) {
/* Disabled serial returns 0 bits */
return false;
}
if (gb->io_registers[GB_IO_SC] & 1) {
/* Internal Clock */
GB_log(gb, "Serial read request while using internal clock. \n");
return 0xFF;
return true;
}
return gb->io_registers[GB_IO_SB] & 0x80;
}
void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data)
{
if (!(gb->io_registers[GB_IO_SC] & 0x80)) {
/* Serial disabled */
return;
}
if (gb->io_registers[GB_IO_SC] & 1) {
/* Internal Clock */
GB_log(gb, "Serial write request while using internal clock. \n");
return;
}
gb->io_registers[GB_IO_SB] <<= 1;
gb->io_registers[GB_IO_SB] |= data;
gb->serial_count++;
if (gb->serial_count == 8) {
gb->io_registers[GB_IO_IF] |= 8;
gb->io_registers[GB_IO_SC] &= ~0x80;
gb->serial_count = 0;
}
}
@ -1378,7 +1391,7 @@ static void reset_ram(GB_gameboy_t *gb)
switch (gb->model) {
case GB_MODEL_MGB:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB: /* Unverified */
case GB_MODEL_AGB_A: /* Unverified */
for (unsigned i = 0; i < gb->ram_size; i++) {
gb->ram[i] = GB_random();
}
@ -1442,7 +1455,7 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_D:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
case GB_MODEL_AGB_A:
for (unsigned i = 0; i < sizeof(gb->hram); i++) {
gb->hram[i] = GB_random();
}
@ -1475,8 +1488,8 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_D:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
/* Zero'd out by boot ROM anyway, extra OAM no accessible */
case GB_MODEL_AGB_A:
/* Zero'd out by boot ROM anyway */
break;
case GB_MODEL_DMG_B:
@ -1509,7 +1522,7 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_D:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
case GB_MODEL_AGB_A:
/* Initialized by CGB-A and newer, 0s in CGB-0 */
break;
case GB_MODEL_MGB: {
@ -1589,7 +1602,7 @@ static void request_boot_rom(GB_gameboy_t *gb)
case GB_MODEL_CGB_E:
type = GB_BOOT_ROM_CGB;
break;
case GB_MODEL_AGB:
case GB_MODEL_AGB_A:
type = GB_BOOT_ROM_AGB;
break;
}
@ -1609,7 +1622,8 @@ void GB_reset(GB_gameboy_t *gb)
gb->model = model;
gb->version = GB_STRUCT_VERSION;
gb->mbc_rom_bank = 1;
GB_reset_mbc(gb);
gb->last_rtc_second = time(NULL);
gb->cgb_ram_bank = 1;
gb->io_registers[GB_IO_JOYP] = 0xCF;
@ -1631,16 +1645,15 @@ void GB_reset(GB_gameboy_t *gb)
}
reset_ram(gb);
/* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */
gb->serial_cycles = 0x100-0xF7;
gb->serial_mask = 0x80;
gb->io_registers[GB_IO_SC] = 0x7E;
/* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */
gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF;
gb->accessed_oam_row = -1;
gb->dma_current_dest = 0xA1;
if (GB_is_hle_sgb(gb)) {
if (!gb->sgb) {
gb->sgb = malloc(sizeof(*gb->sgb));
@ -1905,49 +1918,49 @@ void GB_get_rom_title(GB_gameboy_t *gb, char *title)
uint32_t GB_get_rom_crc32(GB_gameboy_t *gb)
{
static const uint32_t table[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
const uint8_t *byte = gb->rom;

207
Core/gb.h
View File

@ -36,6 +36,8 @@
#define GB_MODEL_PAL_BIT 0x40
#define GB_MODEL_NO_SFC_BIT 0x80
#define GB_REWIND_FRAMES_PER_KEY 255
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define GB_BIG_ENDIAN
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@ -44,6 +46,17 @@
#error Unable to detect endianess
#endif
#ifdef GB_BIG_ENDIAN
#define GB_REGISTER_ORDER a, f, \
b, c, \
d, e, \
h, l
#else
#define GB_REGISTER_ORDER f, a, \
c, b, \
e, d, \
l, h
#endif
typedef struct {
struct GB_color_s {
@ -102,7 +115,11 @@ typedef enum {
GB_MODEL_CGB_C = 0x203,
GB_MODEL_CGB_D = 0x204,
GB_MODEL_CGB_E = 0x205,
GB_MODEL_AGB = 0x206,
// GB_MODEL_AGB_0 = 0x206,
GB_MODEL_AGB_A = 0x207,
GB_MODEL_AGB = GB_MODEL_AGB_A,
//GB_MODEL_AGB_B = 0x208
//GB_MODEL_AGB_E = 0x209
} GB_model_t;
enum {
@ -145,7 +162,7 @@ enum {
/* Missing */
GB_IO_IF = 0x0f, // Interrupt Flag (R/W)
GB_IO_IF = 0x0F, // Interrupt Flag (R/W)
/* Sound */
GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W)
@ -158,11 +175,11 @@ enum {
GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W)
GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W)
GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W)
GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W)
GB_IO_NR31 = 0x1b, // Channel 3 Sound Length
GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W)
GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W)
GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W)
GB_IO_NR30 = 0x1A, // Channel 3 Sound on/off (R/W)
GB_IO_NR31 = 0x1B, // Channel 3 Sound Length
GB_IO_NR32 = 0x1C, // Channel 3 Select output level (R/W)
GB_IO_NR33 = 0x1D, // Channel 3 Frequency's lower data (W)
GB_IO_NR34 = 0x1E, // Channel 3 Frequency's higher data (R/W)
/* NR40 does not exist */
GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W)
GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W)
@ -175,7 +192,7 @@ enum {
/* Missing */
GB_IO_WAV_START = 0x30, // Wave pattern start
GB_IO_WAV_END = 0x3f, // Wave pattern end
GB_IO_WAV_END = 0x3F, // Wave pattern end
/* Graphics */
GB_IO_LCDC = 0x40, // LCD Control (R/W)
@ -188,17 +205,17 @@ enum {
GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only
GB_IO_OBP0 = 0x48, // Object Palette 0 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_WX = 0x4b, // Window X Position minus 7 (R/W)
GB_IO_WY = 0x4A, // Window Y Position (R/W)
GB_IO_WX = 0x4B, // Window X Position minus 7 (R/W)
// Controls DMG mode and PGB mode
GB_IO_KEY0 = 0x4c,
GB_IO_KEY0 = 0x4C,
/* General CGB features */
GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch
GB_IO_KEY1 = 0x4D, // CGB Mode Only - Prepare Speed Switch
/* Missing */
GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank
GB_IO_VBK = 0x4F, // CGB Mode Only - VRAM Bank
GB_IO_BANK = 0x50, // Write to disable the BIOS mapping
/* CGB DMA */
@ -213,16 +230,17 @@ enum {
/* Missing */
/* CGB Paletts */
/* CGB Palettes */
GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index
GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data
GB_IO_OBPI = 0x6a, // CGB Mode Only - Object Palette Index
GB_IO_OBPD = 0x6b, // CGB Mode Only - Object Palette Data
GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based)
GB_IO_OBPI = 0x6A, // CGB Mode Only - Object Palette Index
GB_IO_OBPD = 0x6B, // CGB Mode Only - Object Palette Data
GB_IO_OPRI = 0x6C, // Affects object priority (X based or index based)
/* Missing */
GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank
GB_IO_PSM = 0x71, // Palette Selection Mode, controls the PSW and key combo
GB_IO_PSWX = 0x72, // X position of the palette switching window
GB_IO_PSWY = 0x73, // Y position of the palette switching window
GB_IO_PSW = 0x74, // Key combo to trigger the palette switching window
@ -265,7 +283,7 @@ typedef enum {
#endif
#endif
typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb, GB_vblank_type_t type);
typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
@ -293,11 +311,11 @@ typedef struct {
bool bg_priority; // For object FIFO the BG priority bit. For the BG FIFO  the CGB attributes priority bit
} GB_fifo_item_t;
#define GB_FIFO_LENGTH 16
#define GB_FIFO_LENGTH 8
typedef struct {
GB_fifo_item_t fifo[GB_FIFO_LENGTH];
uint8_t read_end;
uint8_t write_end;
uint8_t size;
} GB_fifo_t;
typedef struct {
@ -335,17 +353,7 @@ typedef union {
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
uint8_t GB_REGISTER_ORDER;
};
} GB_registers_t;
@ -368,7 +376,7 @@ struct GB_gameboy_internal_s {
/* The version field makes sure we don't load save state files with a completely different structure.
This happens when struct fields are removed/resized in an backward incompatible manner. */
uint32_t version;
);
)
GB_SECTION(core_state,
/* Registers */
@ -383,17 +391,7 @@ struct GB_gameboy_internal_s {
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
uint8_t GB_REGISTER_ORDER;
};
};
uint8_t ime;
@ -414,37 +412,37 @@ struct GB_gameboy_internal_s {
/* Misc state */
bool infrared_input;
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
GB_workboy_t workboy;
int32_t ir_sensor;
bool effective_ir_input;
uint16_t address_bus;
);
)
/* DMA and HDMA */
GB_SECTION(dma,
bool hdma_on;
bool hdma_on_hblank;
uint8_t hdma_steps_left;
int16_t hdma_cycles; // in 8MHz units
uint16_t hdma_current_src, hdma_current_dest;
uint8_t dma_steps_left;
uint8_t dma_current_dest;
uint8_t last_dma_read;
uint16_t dma_current_src;
int16_t dma_cycles;
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 */
bool hdma_starting;
);
uint16_t dma_cycles;
int8_t dma_cycles_modulo;
bool dma_ppu_vram_conflict;
uint16_t dma_ppu_vram_conflict_addr;
uint8_t hdma_open_bus; /* Required to emulate HDMA reads from Exxx */
bool allow_hdma_on_wake;
)
/* MBC */
GB_SECTION(mbc,
uint16_t mbc_rom_bank;
uint16_t mbc_rom0_bank; /* For multicart mappings . */
uint8_t mbc_ram_bank;
uint32_t mbc_ram_size;
bool mbc_ram_enable;
@ -469,29 +467,45 @@ struct GB_gameboy_internal_s {
uint8_t rom_bank_low;
uint8_t rom_bank_high:1;
uint8_t ram_bank:4;
} mbc5;
} mbc5; // Also used for GB_CAMERA
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 {
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 argument_bits_left:5;
bool secondary_ram_enable:1;
bool eeprom_write_enabled:1;
} mbc7;
struct {
uint8_t rom_bank_low:5;
uint8_t rom_bank_mid:2;
bool mbc1_mode:1;
uint8_t rom_bank_mask:4;
uint8_t rom_bank_high:2;
uint8_t ram_bank_low:2;
uint8_t ram_bank_high:2;
uint8_t ram_bank_mask:2;
bool locked:1;
bool mbc1_mode_disable:1;
bool multiplex_mode:1;
} mmm01;
struct {
uint8_t bank_low:6;
uint8_t bank_high:3;
bool mode:1;
bool ir_mode:1;
bool ir_mode;
} huc1;
struct {
@ -513,20 +527,17 @@ struct GB_gameboy_internal_s {
uint8_t mode;
} tpp1;
};
uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
bool camera_registers_mapped;
uint8_t camera_registers[0x36];
uint8_t rumble_strength;
bool cart_ir;
);
)
/* HRAM and HW Registers */
GB_SECTION(hram,
uint8_t hram[0xFFFF - 0xFF80];
uint8_t io_registers[0x80];
);
)
/* Timing */
GB_SECTION(timing,
@ -534,8 +545,8 @@ struct GB_gameboy_internal_s {
GB_UNIT(div);
uint16_t div_counter;
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
uint16_t serial_cycles;
uint16_t serial_length;
bool serial_master_clock;
uint8_t serial_mask;
uint8_t double_speed_alignment;
uint8_t serial_count;
int32_t speed_switch_halt_countdown;
@ -546,12 +557,12 @@ struct GB_gameboy_internal_s {
bool lcd_disabled_outside_of_vblank;
int32_t allowed_pending_cycles;
uint16_t mode3_batching_length;
);
)
/* APU */
GB_SECTION(apu,
GB_apu_t apu;
);
)
/* RTC */
GB_SECTION(rtc,
@ -559,7 +570,7 @@ struct GB_gameboy_internal_s {
uint64_t last_rtc_second;
uint32_t rtc_cycles;
uint8_t tpp1_mr4;
);
)
/* Video Display */
GB_SECTION(video,
@ -570,7 +581,6 @@ struct GB_gameboy_internal_s {
uint8_t object_palettes_data[0x40];
uint8_t position_in_line;
bool stat_interrupt_line;
uint8_t effective_scx;
uint8_t window_y;
/* The LCDC will skip the first frame it renders after turning it on.
On the CGB, a frame is not skipped if the previous frame was skipped as well.
@ -588,7 +598,6 @@ struct GB_gameboy_internal_s {
bool vram_read_blocked;
bool oam_write_blocked;
bool vram_write_blocked;
bool fifo_insertion_glitch;
uint8_t current_line;
uint16_t ly_for_comparison;
GB_fifo_t bg_fifo, oam_fifo;
@ -602,11 +611,19 @@ struct GB_gameboy_internal_s {
bool wx166_glitch;
bool wx_triggered;
uint8_t visible_objs[10];
uint8_t obj_comparators[10];
uint8_t objects_x[10];
uint8_t objects_y[10];
uint8_t object_tile_data[2];
uint8_t mode2_y_bus;
// They're the same bus
union {
uint8_t mode2_x_bus;
uint8_t object_flags;
};
uint8_t n_visible_objs;
uint8_t orig_n_visible_objs;
uint8_t oam_search_index;
uint8_t accessed_oam_row;
uint8_t extra_penalty_for_object_at_0;
uint8_t mode_for_interrupt;
bool lyc_interrupt_line;
bool cgb_palettes_blocked;
@ -626,7 +643,8 @@ struct GB_gameboy_internal_s {
uint16_t last_tile_index_address;
bool cgb_repeated_a_frame;
uint8_t data_for_sel_glitch;
);
bool delayed_glitch_hblank_interrupt;
)
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
/* This data is reserved on reset and must come last in the struct */
@ -715,8 +733,6 @@ struct GB_gameboy_internal_s {
void *nontrivial_jump_state;
bool non_trivial_jump_breakpoint_occured;
/* SLD (Todo: merge with backtrace) */
bool stack_leak_detection;
signed debug_call_depth;
uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
uint16_t addr_for_call_depth[0x200];
@ -734,7 +750,8 @@ struct GB_gameboy_internal_s {
struct GB_watchpoint_s *watchpoints;
/* Symbol tables */
GB_symbol_map_t *bank_symbols[0x200];
GB_symbol_map_t **bank_symbols;
size_t n_symbol_maps;
GB_reversed_symbol_map_t reversed_symbol_map;
/* Ticks command */
@ -746,7 +763,6 @@ struct GB_gameboy_internal_s {
const char *undo_label;
/* Rewind */
#define GB_REWIND_FRAMES_PER_KEY 255
size_t rewind_buffer_length;
struct {
uint8_t *key_state;
@ -774,7 +790,7 @@ struct GB_gameboy_internal_s {
bool disable_rendering;
uint8_t boot_rom[0x900];
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
unsigned cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
double clock_multiplier;
GB_rumble_mode_t rumble_mode;
uint32_t rumble_on_cycles;
@ -784,9 +800,12 @@ struct GB_gameboy_internal_s {
bool wx_just_changed;
bool tile_sel_glitch;
bool disable_oam_corruption; // For safe memory reads
bool in_dma_read;
bool hdma_in_progress;
uint16_t addr_for_hdma_conflict;
GB_gbs_header_t gbs_header;
);
)
};
#ifndef GB_INTERNAL
@ -814,7 +833,7 @@ void GB_reset(GB_gameboy_t *gb);
void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model);
/* Returns the time passed, in 8MHz ticks. */
uint8_t GB_run(GB_gameboy_t *gb);
unsigned GB_run(GB_gameboy_t *gb);
/* Returns the time passed since the last frame, in nanoseconds */
uint64_t GB_run_frame(GB_gameboy_t *gb);

View File

@ -59,7 +59,10 @@ void GB_update_joyp(GB_gameboy_t *gb)
/* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
/* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */
gb->io_registers[GB_IO_IF] |= 0x10;
if (!(gb->io_registers[GB_IO_IF] & 0x10)) {
gb->joyp_accessed = true;
gb->io_registers[GB_IO_IF] |= 0x10;
}
}
gb->io_registers[GB_IO_JOYP] |= 0xC0;
@ -72,7 +75,10 @@ void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value)
gb->io_registers[GB_IO_JOYP] |= value & 0xF;
if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) {
gb->io_registers[GB_IO_IF] |= 0x10;
if (!(gb->io_registers[GB_IO_IF] & 0x10)) {
gb->joyp_accessed = true;
gb->io_registers[GB_IO_IF] |= 0x10;
}
}
gb->io_registers[GB_IO_JOYP] |= 0xC0;
}

View File

@ -5,42 +5,41 @@
const GB_cartridge_t GB_cart_defs[256] = {
// From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type
/* MBC SUBTYPE RAM BAT. RTC RUMB. */
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY
{ GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1
{ GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM
{ GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY
/* MBC RAM BAT. RTC RUMB. */
{ GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY
{ GB_MBC1 , false, false, false, false}, // 01h MBC1
{ GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM
{ GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY
[5] =
{ GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2
{ GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY
{ GB_MBC2 , true , false, false, false}, // 05h MBC2
{ GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY
[8] =
{ GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM
{ GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY
{ GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM
{ GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY
[0xB] =
/* Todo: Not supported yet */
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY
{ GB_MMM01 , false, false, false, false}, // 0Bh MMM01
{ GB_MMM01 , true , false, false, false}, // 0Ch MMM01+RAM
{ GB_MMM01 , true , true , false, false}, // 0Dh MMM01+RAM+BATTERY
[0xF] =
{ GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY
{ GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY
{ GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3
{ GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM
{ GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY
{ GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY
{ GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY
{ GB_MBC3 , false, false, false, false}, // 11h MBC3
{ GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM
{ GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY
[0x19] =
{ GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5
{ GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM
{ GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY
{ 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 , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
{ GB_MBC5 , false, false, false, false}, // 19h MBC5
{ GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM
{ GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY
{ GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE
{ GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM
{ GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
[0x22] =
{ GB_MBC7 , GB_STANDARD_MBC, true, true, false, false}, // 22h MBC7+ACCEL+EEPROM
{ GB_MBC7 , true, true, false, false}, // 22h MBC7+ACCEL+EEPROM
[0xFC] =
{ 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_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3
{ GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY
{ GB_CAMERA, true , true , false, false}, // FCh POCKET CAMERA
{ GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported)
{ GB_HUC3 , true , true , true, false}, // FEh HuC3
{ GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY
};
void GB_update_mbc_mappings(GB_gameboy_t *gb)
@ -97,22 +96,51 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
}
break;
case GB_MBC5:
case GB_CAMERA:
gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8);
gb->mbc_ram_bank = gb->mbc5.ram_bank;
break;
case GB_MBC7:
gb->mbc_rom_bank = gb->mbc7.rom_bank;
break;
case GB_HUC1:
if (gb->huc1.mode == 0) {
gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6);
gb->mbc_ram_bank = 0;
case GB_MMM01:
if (gb->mmm01.locked) {
if (gb->mmm01.multiplex_mode) {
gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) |
((gb->mmm01.mbc1_mode? 0 : gb->mmm01.ram_bank_low) << 5) |
(gb->mmm01.rom_bank_high << 7);
gb->mbc_rom_bank = gb->mmm01.rom_bank_low |
(gb->mmm01.ram_bank_low << 5) |
(gb->mmm01.rom_bank_high << 7);
gb->mbc_ram_bank = gb->mmm01.rom_bank_mid | (gb->mmm01.ram_bank_high << 2);
}
else {
gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) |
(gb->mmm01.rom_bank_mid << 5) |
(gb->mmm01.rom_bank_high << 7);
gb->mbc_rom_bank = gb->mmm01.rom_bank_low |
(gb->mmm01.rom_bank_mid << 5) |
(gb->mmm01.rom_bank_high << 7);
if (gb->mmm01.mbc1_mode) {
gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2);
}
else {
gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2);
}
}
if (gb->mbc_rom_bank == gb->mbc_rom0_bank) {
gb->mbc_rom_bank++;
}
}
else {
gb->mbc_rom_bank = gb->huc1.bank_low;
gb->mbc_ram_bank = gb->huc1.bank_high;
gb->mbc_rom_bank = -1;
gb->mbc_rom0_bank = -2;
}
break;
case GB_HUC1:
gb->mbc_rom_bank = gb->huc1.bank_low;
gb->mbc_ram_bank = gb->huc1.bank_high;
break;
case GB_HUC3:
gb->mbc_rom_bank = gb->huc3.rom_bank;
gb->mbc_ram_bank = gb->huc3.ram_bank;
@ -129,22 +157,44 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
void GB_configure_cart(GB_gameboy_t *gb)
{
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};
if (gb->cartridge_type->mbc_type == GB_MMM01) {
uint8_t *temp = malloc(0x8000);
memcpy(temp, gb->rom, 0x8000);
memmove(gb->rom, gb->rom + 0x8000, gb->rom_size - 0x8000);
memcpy(gb->rom + gb->rom_size - 0x8000, temp, 0x8000);
free(temp);
}
else {
const GB_cartridge_t *maybe_mmm01_type = &GB_cart_defs[gb->rom[gb->rom_size - 0x8000 + 0x147]];
if (memcmp(gb->rom + 0x104, gb->rom + gb->rom_size - 0x8000 + 0x104, 0x30) == 0) {
if (maybe_mmm01_type->mbc_type == GB_MMM01) {
gb->cartridge_type = maybe_mmm01_type;
}
else if(gb->rom[gb->rom_size - 0x8000 + 0x147] == 0x11) {
GB_log(gb, "ROM header reports MBC3, but it appears to be an MMM01 ROM. Assuming cartridge uses MMM01.");
gb->cartridge_type = &GB_cart_defs[0xB];
}
}
}
if (gb->rom[0x147] == 0xBC &&
gb->rom[0x149] == 0xC1 &&
gb->rom[0x14A] == 0x65) {
static const GB_cartridge_t tpp1 = {GB_TPP1, true, true, true, true};
gb->cartridge_type = &tpp1;
gb->tpp1.rom_bank = 1;
}
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->cartridge_type = &GB_cart_defs[0x11];
if (gb->cartridge_type->mbc_type != GB_MMM01) {
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->cartridge_type = &GB_cart_defs[0x11];
}
else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) {
GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]);
}
}
else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) {
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;
@ -165,7 +215,12 @@ void GB_configure_cart(GB_gameboy_t *gb)
}
else {
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
if (gb->cartridge_type->mbc_type == GB_MMM01) {
gb->mbc_ram_size = ram_sizes[gb->rom[gb->rom_size - 0x8000 + 0x149]];
}
else {
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
}
}
if (gb->mbc_ram_size) {
@ -193,16 +248,28 @@ void GB_configure_cart(GB_gameboy_t *gb)
}
}
/* Set MBC5's bank to 1 correctly */
if (gb->cartridge_type->mbc_type == GB_MBC5) {
gb->mbc5.rom_bank_low = 1;
GB_reset_mbc(gb);
}
void GB_reset_mbc(GB_gameboy_t *gb)
{
if (gb->cartridge_type->mbc_type == GB_MMM01) {
gb->mbc_rom_bank = -1;
gb->mbc_rom0_bank = -2;
gb->mmm01.ram_bank_mask = -1;
}
/* Initial MBC7 state */
if (gb->cartridge_type->mbc_type == GB_MBC7) {
else if (gb->cartridge_type->mbc_type == GB_MBC5 ||
gb->cartridge_type->mbc_type == GB_CAMERA) {
gb->mbc5.rom_bank_low = 1;
gb->mbc_rom_bank = 1;
}
else 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;
}
else {
gb->mbc_rom_bank = 1;
}
}

View File

@ -11,14 +11,12 @@ typedef struct {
GB_MBC3,
GB_MBC5,
GB_MBC7,
GB_MMM01,
GB_HUC1,
GB_HUC3,
GB_TPP1,
} mbc_type;
enum {
GB_STANDARD_MBC,
GB_CAMERA,
} mbc_subtype;
} mbc_type;
bool has_ram;
bool has_battery;
bool has_rtc;
@ -26,9 +24,10 @@ typedef struct {
} GB_cartridge_t;
#ifdef GB_INTERNAL
extern const GB_cartridge_t GB_cart_defs[256];
internal extern const GB_cartridge_t GB_cart_defs[256];
internal void GB_update_mbc_mappings(GB_gameboy_t *gb);
internal void GB_configure_cart(GB_gameboy_t *gb);
internal void GB_reset_mbc(GB_gameboy_t *gb);
#endif
#endif /* MBC_h */

View File

@ -95,7 +95,7 @@ void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address)
if (address >= 0xFE00 && address < 0xFF00) {
GB_display_sync(gb);
if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) {
if (gb->accessed_oam_row != 0xFF && gb->accessed_oam_row >= 8) {
uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row);
base[0] = bitwise_glitch(base[0],
base[-4],
@ -197,7 +197,7 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address)
if (GB_is_cgb(gb)) return;
if (address >= 0xFE00 && address < 0xFF00) {
if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) {
if (gb->accessed_oam_row != 0xFF && gb->accessed_oam_row >= 8) {
if ((gb->accessed_oam_row & 0x18) == 0x10) {
oam_bug_secondary_read_corruption(gb);
}
@ -251,15 +251,16 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address)
static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr)
{
if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xfe00) return false;
if (addr >= 0xfe00) return false;
if (!GB_is_dma_active(gb) || addr >= 0xFE00 || gb->hdma_in_progress) return false;
if (gb->dma_current_dest == 0xFF || gb->dma_current_dest == 0x0) return false; // Warm up
if (addr >= 0xFE00) return false;
if (gb->dma_current_src == addr) return false; // Shortcut for DMA access flow
if (gb->dma_current_src > 0xe000 && (gb->dma_current_src & ~0x2000) == addr) return false;
if (gb->dma_current_src >= 0xE000 && (gb->dma_current_src & ~0x2000) == addr) return false;
if (GB_is_cgb(gb)) {
if (addr >= 0xe000) {
if (addr >= 0xC000) {
return bus_for_addr(gb, gb->dma_current_src) != GB_BUS_VRAM;
}
if (gb->dma_current_src >= 0xe000) {
if (gb->dma_current_src >= 0xE000) {
return bus_for_addr(gb, addr) != GB_BUS_VRAM;
}
}
@ -291,8 +292,18 @@ static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr)
static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr)
{
GB_display_sync(gb);
if (unlikely(gb->vram_read_blocked)) {
if (likely(!GB_is_dma_active(gb))) {
/* Prevent syncing from a DMA read. Batching doesn't happen during DMA anyway. */
GB_display_sync(gb);
}
else {
if ((gb->dma_current_dest & 0xE000) == 0x8000) {
// TODO: verify conflict behavior
return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)];
}
}
if (unlikely(gb->vram_read_blocked && !gb->in_dma_read)) {
return 0xFF;
}
if (unlikely(gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed)) {
@ -372,14 +383,14 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
}
}
else if ((!gb->mbc_ram_enable) &&
gb->cartridge_type->mbc_subtype != GB_CAMERA &&
gb->cartridge_type->mbc_type != GB_CAMERA &&
gb->cartridge_type->mbc_type != GB_HUC1 &&
gb->cartridge_type->mbc_type != GB_HUC3) {
return 0xFF;
}
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
return 0xc0 | gb->effective_ir_input;
return 0xC0 | gb->effective_ir_input;
}
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 &&
@ -403,8 +414,8 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
return 0xFF;
}
if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) {
return GB_camera_read_image(gb, addr - 0xa100);
if (gb->cartridge_type->mbc_type == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xA100 && addr < 0xAF00) {
return GB_camera_read_image(gb, addr - 0xA100);
}
uint8_t effective_bank = gb->mbc_ram_bank;
@ -462,13 +473,45 @@ static inline void sync_ppu_if_needed(GB_gameboy_t *gb, uint8_t register_accesse
}
}
static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr)
{
if (gb->hdma_on) {
return gb->last_opcode_read;
if (addr < 0xA0) {
return gb->oam[addr];
}
switch (gb->model) {
case GB_MODEL_CGB_E:
case GB_MODEL_AGB_A:
return (addr & 0xF0) | (addr >> 4);
case GB_MODEL_CGB_D:
if (addr >= 0xC0) {
addr |= 0xF0;
}
return gb->extra_oam[addr - 0xA0];
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_B:
case GB_MODEL_CGB_A:
case GB_MODEL_CGB_0:
addr &= ~0x18;
return gb->extra_oam[addr - 0xA0];
case GB_MODEL_DMG_B:
case GB_MODEL_MGB:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
return 0;
}
unreachable();
}
static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0xFE00) {
return read_banked_ram(gb, addr);
}
@ -479,12 +522,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
if (!gb->disable_oam_corruption) {
GB_trigger_oam_bug_read(gb, addr);
}
return 0xff;
return 0xFF;
}
if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
if (GB_is_dma_active(gb)) {
/* Todo: Does reading from OAM during DMA causes the OAM bug? */
return 0xff;
return 0xFF;
}
if (gb->oam_read_blocked) {
@ -492,20 +535,20 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
if (addr < 0xFEA0) {
uint16_t *oam = (uint16_t *)gb->oam;
if (gb->accessed_oam_row == 0) {
oam[(addr & 0xf8) >> 1] =
oam[(addr & 0xF8) >> 1] =
oam[0] = bitwise_glitch_read(oam[0],
oam[(addr & 0xf8) >> 1],
oam[(addr & 0xff) >> 1]);
oam[(addr & 0xF8) >> 1],
oam[(addr & 0xFF) >> 1]);
for (unsigned i = 2; i < 8; i++) {
gb->oam[i] = gb->oam[(addr & 0xf8) + i];
gb->oam[i] = gb->oam[(addr & 0xF8) + i];
}
}
else if (gb->accessed_oam_row == 0xa0) {
else if (gb->accessed_oam_row == 0xA0) {
uint8_t target = (addr & 7) | 0x98;
uint16_t a = oam[0x9c >> 1],
uint16_t a = oam[0x9C >> 1],
b = oam[target >> 1],
c = oam[(addr & 0xf8) >> 1];
c = oam[(addr & 0xF8) >> 1];
switch (addr & 7) {
case 0:
case 1:
@ -520,7 +563,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
case 2:
case 3: {
/* Probably instance specific */
c = oam[(addr & 0xfe) >> 1];
c = oam[(addr & 0xFE) >> 1];
// MGB only: oam[target >> 1] = bitwise_glitch_read(a, b, c);
oam[target >> 1] = (a & b) | (a & c) | (b & c);
@ -538,54 +581,15 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
}
for (unsigned i = 0; i < 8; i++) {
gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i];
gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i];
}
}
}
}
return 0xff;
}
if (addr < 0xFEA0) {
return gb->oam[addr & 0xFF];
}
if (gb->oam_read_blocked) {
return 0xFF;
}
switch (gb->model) {
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
return (addr & 0xF0) | ((addr >> 4) & 0xF);
case GB_MODEL_CGB_D:
if (addr > 0xfec0) {
addr |= 0xf0;
}
return gb->extra_oam[addr - 0xfea0];
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_B:
case GB_MODEL_CGB_A:
case GB_MODEL_CGB_0:
addr &= ~0x18;
return gb->extra_oam[addr - 0xfea0];
case GB_MODEL_DMG_B:
case GB_MODEL_MGB:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
break;
}
}
if (addr < 0xFF00) {
return 0;
return GB_read_oam(gb, addr);
}
if (addr < 0xFF80) {
@ -688,7 +692,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
if (gb->model != GB_MODEL_CGB_E) {
ret |= 0x10;
}
if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model != GB_MODEL_AGB) {
if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model <= GB_MODEL_CGB_E) {
ret &= ~2;
}
return ret;
@ -739,21 +743,21 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
GB_debugger_test_read_watchpoint(gb, addr);
}
if (unlikely(is_addr_in_dma_use(gb, addr))) {
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xe000) {
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xE000) {
/* This is cart specific! Everdrive 7X on a CGB-A or 0 behaves differently. */
return 0xFF;
}
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xc000) {
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xC000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
}
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xe000 && addr > 0xc000) {
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xE000 && addr >= 0xC000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
}
else {
addr = gb->dma_current_src;
addr = (gb->dma_current_src - 1);
}
}
uint8_t data = read_map[addr >> 12](gb, addr);
@ -823,7 +827,16 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
value &= 7;
}
gb->mbc5.ram_bank = value;
gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA;
break;
}
break;
case GB_CAMERA:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: gb->mbc5.rom_bank_low = value; break;
case 0x4000: case 0x5000:
gb->mbc5.ram_bank = value;
gb->camera_registers_mapped = (value & 0x10);
break;
}
break;
@ -834,12 +847,46 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case 0x4000: case 0x5000: gb->mbc7.secondary_ram_enable = value == 0x40; break;
}
break;
case GB_MMM01:
switch (addr & 0xF000) {
case 0x0000: case 0x1000:
gb->mbc_ram_enable = (value & 0xF) == 0xA;
if (!gb->mmm01.locked) {
gb->mmm01.ram_bank_mask = value >> 4;
gb->mmm01.locked = value & 0x40;
}
break;
case 0x2000: case 0x3000:
if (!gb->mmm01.locked) {
gb->mmm01.rom_bank_mid = value >> 5;
}
gb->mmm01.rom_bank_low &= (gb->mmm01.rom_bank_mask << 1);
gb->mmm01.rom_bank_low |= ~(gb->mmm01.rom_bank_mask << 1) & value;
break;
case 0x4000: case 0x5000:
gb->mmm01.ram_bank_low = value | ~gb->mmm01.ram_bank_mask;
if (!gb->mmm01.locked) {
gb->mmm01.ram_bank_high = value >> 2;
gb->mmm01.rom_bank_high = value >> 4;
gb->mmm01.mbc1_mode_disable = value & 0x40;
}
break;
case 0x6000: case 0x7000:
if (!gb->mmm01.mbc1_mode_disable) {
gb->mmm01.mbc1_mode = (value & 1);
}
if (!gb->mmm01.locked) {
gb->mmm01.rom_bank_mask = value >> 2;
gb->mmm01.multiplex_mode = value & 0x40;
}
break;
}
break;
case GB_HUC1:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break;
case 0x2000: case 0x3000: gb->huc1.bank_low = value; break;
case 0x4000: case 0x5000: gb->huc1.bank_high = value; break;
case 0x6000: case 0x7000: gb->huc1.mode = value; break;
}
break;
case GB_HUC3:
@ -941,15 +988,15 @@ static bool huc3_write(GB_gameboy_t *gb, uint8_t value)
gb->huc3.days &= ~(0xF << ((gb->huc3.access_index - 3) * 4));
gb->huc3.days |= ((value & 0xF) << ((gb->huc3.access_index - 3) * 4));
}
else if (gb->huc3.access_index >= 0x58 && gb->huc3.access_index <= 0x5a) {
else if (gb->huc3.access_index >= 0x58 && gb->huc3.access_index <= 0x5A) {
gb->huc3.alarm_minutes &= ~(0xF << ((gb->huc3.access_index - 0x58) * 4));
gb->huc3.alarm_minutes |= ((value & 0xF) << ((gb->huc3.access_index - 0x58) * 4));
}
else if (gb->huc3.access_index >= 0x5b && gb->huc3.access_index <= 0x5e) {
gb->huc3.alarm_days &= ~(0xF << ((gb->huc3.access_index - 0x5b) * 4));
gb->huc3.alarm_days |= ((value & 0xF) << ((gb->huc3.access_index - 0x5b) * 4));
else if (gb->huc3.access_index >= 0x5B && gb->huc3.access_index <= 0x5E) {
gb->huc3.alarm_days &= ~(0xF << ((gb->huc3.access_index - 0x5B) * 4));
gb->huc3.alarm_days |= ((value & 0xF) << ((gb->huc3.access_index - 0x5B) * 4));
}
else if (gb->huc3.access_index == 0x5f) {
else if (gb->huc3.access_index == 0x5F) {
gb->huc3.alarm_enabled = value & 1;
}
else {
@ -1024,7 +1071,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->mbc7.eeprom_do = gb->mbc7.read_bits >> 15;
gb->mbc7.read_bits <<= 1;
gb->mbc7.read_bits |= 1;
if (gb->mbc7.bits_countdown == 0) {
if (gb->mbc7.argument_bits_left == 0) {
/* Not transferring extra bits for a command*/
gb->mbc7.eeprom_command <<= 1;
gb->mbc7.eeprom_command |= gb->mbc7.eeprom_di;
@ -1055,7 +1102,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (gb->mbc7.eeprom_write_enabled) {
((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0;
}
gb->mbc7.bits_countdown = 16;
gb->mbc7.argument_bits_left = 16;
// We still need to process this command, don't erase eeprom_command
break;
case 0xC:
@ -1083,7 +1130,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (gb->mbc7.eeprom_write_enabled) {
memset(gb->mbc_ram, 0, gb->mbc_ram_size);
}
gb->mbc7.bits_countdown = 16;
gb->mbc7.argument_bits_left = 16;
// We still need to process this command, don't erase eeprom_command
break;
}
@ -1091,10 +1138,10 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
}
else {
// We're shifting in extra bits for a WRITE/WRAL command
gb->mbc7.bits_countdown--;
gb->mbc7.argument_bits_left--;
gb->mbc7.eeprom_do = true;
if (gb->mbc7.eeprom_di) {
uint16_t bit = LE16(1 << gb->mbc7.bits_countdown);
uint16_t bit = LE16(1 << gb->mbc7.argument_bits_left);
if (gb->mbc7.eeprom_command & 0x100) {
// WRITE
((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit;
@ -1106,7 +1153,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
}
}
}
if (gb->mbc7.bits_countdown == 0) { // We're done
if (gb->mbc7.argument_bits_left == 0) { // We're done
gb->mbc7.eeprom_command = 0;
gb->mbc7.read_bits = (gb->mbc7.eeprom_command & 0x100)? 0xFF : 0x3FFF; // Emulate some time to settle
}
@ -1194,6 +1241,40 @@ static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value;
}
static void write_oam(GB_gameboy_t *gb, uint8_t addr, uint8_t value)
{
if (addr < 0xA0) {
gb->oam[addr] = value;
return;
}
switch (gb->model) {
case GB_MODEL_CGB_D:
if (addr >= 0xC0) {
addr |= 0xF0;
}
gb->extra_oam[addr - 0xA0] = value;
break;
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_B:
case GB_MODEL_CGB_A:
case GB_MODEL_CGB_0:
addr &= ~0x18;
gb->extra_oam[addr - 0xA0] = value;
break;
case GB_MODEL_CGB_E:
case GB_MODEL_AGB_A:
case GB_MODEL_DMG_B:
case GB_MODEL_MGB:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
break;
}
}
static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
if (addr < 0xFE00) {
@ -1209,54 +1290,24 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
}
if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
if (GB_is_dma_active(gb)) {
/* Todo: Does writing to OAM during DMA causes the OAM bug? */
return;
}
if (GB_is_cgb(gb)) {
if (addr < 0xFEA0) {
gb->oam[addr & 0xFF] = value;
return;
}
switch (gb->model) {
case GB_MODEL_CGB_D:
if (addr > 0xfec0) {
addr |= 0xf0;
}
gb->extra_oam[addr - 0xfea0] = value;
break;
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_B:
case GB_MODEL_CGB_A:
case GB_MODEL_CGB_0:
addr &= ~0x18;
gb->extra_oam[addr - 0xfea0] = value;
break;
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
break;
case GB_MODEL_DMG_B:
case GB_MODEL_MGB:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
unreachable();
}
write_oam(gb, addr, value);
return;
}
if (addr < 0xFEA0) {
if (gb->accessed_oam_row == 0xa0) {
if (gb->accessed_oam_row == 0xA0) {
for (unsigned i = 0; i < 8; i++) {
if ((i & 6) != (addr & 6)) {
gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i];
gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i];
}
else {
gb->oam[(addr & 0xf8) + i] = bitwise_glitch(gb->oam[(addr & 0xf8) + i], gb->oam[0x9c], gb->oam[0x98 + i]);
gb->oam[(addr & 0xF8) + i] = bitwise_glitch(gb->oam[(addr & 0xF8) + i], gb->oam[0x9C], gb->oam[0x98 + i]);
}
}
}
@ -1265,13 +1316,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (gb->accessed_oam_row == 0) {
gb->oam[0] = bitwise_glitch(gb->oam[0],
gb->oam[(addr & 0xf8)],
gb->oam[(addr & 0xfe)]);
gb->oam[(addr & 0xF8)],
gb->oam[(addr & 0xFE)]);
gb->oam[1] = bitwise_glitch(gb->oam[1],
gb->oam[(addr & 0xf8) + 1],
gb->oam[(addr & 0xfe) | 1]);
gb->oam[(addr & 0xF8) + 1],
gb->oam[(addr & 0xFE) | 1]);
for (unsigned i = 2; i < 8; i++) {
gb->oam[i] = gb->oam[(addr & 0xf8) + i];
gb->oam[i] = gb->oam[(addr & 0xF8) + i];
}
}
}
@ -1370,24 +1421,17 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case GB_IO_LCDC:
if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
if (value & 0x80) {
// LCD turned on
if (!gb->lcd_disabled_outside_of_vblank &&
(gb->cycles_since_vblank_callback > 10 * 456 || GB_is_sgb(gb))) {
// Trigger a vblank here so we don't exceed LCDC_PERIOD
GB_display_vblank(gb);
}
}
else {
// LCD turned off
if (gb->current_line < 144) {
// ROM might be repeatedly disabling LCDC outside of vblank, avoid callback spam
gb->lcd_disabled_outside_of_vblank = true;
}
// LCD turned on
if (!gb->lcd_disabled_outside_of_vblank &&
(gb->cycles_since_vblank_callback > 10 * 456 || GB_is_sgb(gb))) {
// Trigger a vblank here so we don't exceed LCDC_PERIOD
GB_display_vblank(gb, GB_VBLANK_TYPE_ARTIFICIAL);
}
gb->display_cycles = 0;
gb->display_state = 0;
gb->double_speed_alignment = 0;
gb->cycles_for_line = 0;
if (GB_is_sgb(gb)) {
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
@ -1459,19 +1503,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
case GB_IO_DMA:
if (gb->dma_steps_left) {
/* This is not correct emulation, since we're not really delaying the second DMA.
One write that should have happened in the first DMA will not happen. However,
since that byte will be overwritten by the second DMA before it can actually be
read, it doesn't actually matter. */
gb->is_dma_restarting = true;
}
gb->dma_and_pattern = 0xFF;
gb->dma_cycles = -7;
gb->dma_current_dest = 0;
gb->dma_cycles = 0;
gb->dma_cycles_modulo = 2;
gb->dma_current_dest = 0xFF;
gb->dma_current_src = value << 8;
gb->dma_steps_left = 0xa0;
gb->io_registers[GB_IO_DMA] = value;
GB_STAT_update(gb);
return;
case GB_IO_SVBK:
if (gb->cgb_mode || (GB_is_cgb(gb) && !gb->boot_rom_finished)) {
@ -1531,6 +1568,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->hdma_current_src &= 0xF0;
gb->hdma_current_src |= value << 8;
}
/* Range 0xE*** like 0xF*** and can't overflow (with 0x800 bytes) to anything meaningful */
if (gb->hdma_current_src >= 0xE000) {
gb->hdma_current_src |= 0xF000;
}
return;
case GB_IO_HDMA2:
if (gb->cgb_mode) {
@ -1546,7 +1587,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
case GB_IO_HDMA4:
if (gb->cgb_mode) {
gb->hdma_current_dest &= 0x1F00;
gb->hdma_current_dest &= 0xFF00;
gb->hdma_current_dest |= value & 0xF0;
}
return;
@ -1558,38 +1599,31 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
}
gb->hdma_on = (value & 0x80) == 0;
gb->hdma_on_hblank = (value & 0x80) != 0;
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) {
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->display_state != 7) {
gb->hdma_on = true;
}
gb->io_registers[GB_IO_HDMA5] = value;
gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1;
/* Todo: Verify this. Gambatte's DMA tests require this. */
if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) {
gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4;
}
gb->hdma_cycles = -12;
return;
/* Todo: what happens when starting a transfer during a transfer?
What happens when starting a transfer during external clock?
*/
/* TODO: What happens when starting a transfer during external clock?
TODO: When a cable is connected, the clock of the other side affects "zombie" serial clocking */
case GB_IO_SC:
gb->serial_count = 0;
if (!gb->cgb_mode) {
value |= 2;
}
if (gb->serial_master_clock) {
GB_serial_master_edge(gb);
}
gb->io_registers[GB_IO_SC] = value | (~0x83);
gb->serial_mask = gb->cgb_mode && (value & 2)? 4 : 0x80;
if ((value & 0x80) && (value & 0x1) ) {
gb->serial_length = gb->cgb_mode && (value & 2)? 16 : 512;
gb->serial_count = 0;
/* Todo: This is probably incorrect for CGB's faster clock mode. */
gb->serial_cycles &= 0xFF;
if (gb->serial_transfer_bit_start_callback) {
gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
}
}
else {
gb->serial_length = 0;
}
return;
case GB_IO_RP: {
@ -1656,89 +1690,149 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (unlikely(is_addr_in_dma_use(gb, addr))) {
bool oam_write = addr >= 0xFE00;
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xe000) {
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xE000) {
/* This is cart specific! Everdrive 7X on a CGB-A or 0 behaves differently. */
return;
}
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xc000) {
if (GB_is_cgb(gb) && (gb->dma_current_src < 0xC000 || gb->dma_current_src >= 0xE000) && addr >= 0xC000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
goto write;
}
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xe000 && addr > 0xc000) {
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xE000 && addr >= 0xC000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
}
else {
addr = gb->dma_current_src;
addr = (gb->dma_current_src - 1);
}
if (GB_is_cgb(gb) || addr > 0xc000) {
gb->dma_and_pattern = addr < 0xc000? 0x00 : 0xFF;
if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B) && addr > 0xc000) {
gb->dma_and_pattern = value;
if (GB_is_cgb(gb) || addr >= 0xA000) {
if (addr < 0xA000) {
gb->oam[gb->dma_current_dest - 1] = 0;
}
else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && addr > 0xc000 && !oam_write) {
gb->dma_skip_write = true;
gb->oam[gb->dma_current_dest] = value;
else if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B)) {
gb->oam[gb->dma_current_dest - 1] &= value;
}
if (gb->model < GB_MODEL_CGB_E || addr >= 0xc000) return;
else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && !oam_write) {
gb->oam[gb->dma_current_dest - 1] = value;
}
if (gb->model < GB_MODEL_CGB_E || addr >= 0xA000) return;
}
}
write:
write_map[addr >> 12](gb, addr, value);
}
bool GB_is_dma_active(GB_gameboy_t *gb)
{
return gb->dma_current_dest != 0xA1;
}
void GB_dma_run(GB_gameboy_t *gb)
{
while (unlikely(gb->dma_cycles >= 4 && gb->dma_steps_left)) {
/* Todo: measure this value */
gb->dma_cycles -= 4;
gb->dma_steps_left--;
if (gb->dma_skip_write) {
gb->dma_skip_write = false;
if (gb->dma_current_dest == 0xA1) return;
if (unlikely(gb->halted || gb->stopped)) return;
signed cycles = gb->dma_cycles + gb->dma_cycles_modulo;
gb->in_dma_read = true;
while (unlikely(cycles >= 4)) {
cycles -= 4;
if (gb->dma_current_dest >= 0xA0) {
gb->dma_current_dest++;
if (gb->display_state == 8) {
gb->io_registers[GB_IO_STAT] |= 2;
GB_STAT_update(gb);
}
break;
}
if (unlikely(gb->hdma_in_progress && (gb->hdma_steps_left > 1 || (gb->hdma_current_dest & 0xF) != 0xF))) {
gb->dma_current_dest++;
}
else if (gb->dma_current_src < 0xe000) {
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src) & gb->dma_and_pattern;
else if (gb->dma_current_src < 0xE000) {
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src);
}
else {
if (GB_is_cgb(gb)) {
gb->oam[gb->dma_current_dest++] = gb->dma_and_pattern;
gb->oam[gb->dma_current_dest++] = 0xFF;
}
else {
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000) & gb->dma_and_pattern;
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000);
}
}
gb->dma_and_pattern = 0xFF;
/* dma_current_src must be the correct value during GB_read_memory */
gb->dma_current_src++;
if (!gb->dma_steps_left) {
gb->is_dma_restarting = false;
}
gb->dma_ppu_vram_conflict = false;
}
gb->in_dma_read = false;
gb->dma_cycles_modulo = cycles;
gb->dma_cycles = 0;
}
void GB_hdma_run(GB_gameboy_t *gb)
{
if (likely(!gb->hdma_on)) return;
while (gb->hdma_cycles >= 0x4) {
gb->hdma_cycles -= 0x4;
GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++)));
unsigned cycles = gb->cgb_double_speed? 4 : 2;
/* This is a bit cart, revision and unit specific. TODO: what if PC is in cart RAM? */
if (gb->model < GB_MODEL_CGB_D || gb->pc > 0x8000) {
gb->hdma_open_bus = 0xFF;
}
gb->addr_for_hdma_conflict = 0xFFFF;
uint16_t vram_base = gb->cgb_vram_bank? 0x2000 : 0;
gb->hdma_in_progress = true;
GB_advance_cycles(gb, cycles);
while (gb->hdma_on) {
uint8_t byte = gb->hdma_open_bus;
gb->addr_for_hdma_conflict = 0xFFFF;
if ((gb->hdma_current_dest & 0xf) == 0) {
if (--gb->hdma_steps_left == 0) {
if (gb->hdma_current_src < 0x8000 ||
(gb->hdma_current_src & 0xE000) == 0xC000 ||
(gb->hdma_current_src & 0xE000) == 0xA000) {
byte = GB_read_memory(gb, gb->hdma_current_src);
}
if (unlikely(GB_is_dma_active(gb)) && (gb->dma_cycles_modulo == 2 || gb->cgb_double_speed)) {
write_oam(gb, gb->hdma_current_src, byte);
}
gb->hdma_current_src++;
GB_advance_cycles(gb, cycles);
if (gb->addr_for_hdma_conflict == 0xFFFF /* || (gb->model >= GB_MODEL_AGB_B && gb->cgb_double_speed) */) {
uint16_t addr = (gb->hdma_current_dest++ & 0x1FFF);
gb->vram[vram_base + addr] = byte;
// TODO: vram_write_blocked might not be the correct timing
if (gb->vram_write_blocked /* && gb->model < GB_MODEL_AGB_B */) {
gb->vram[(vram_base ^ 0x2000) + addr] = byte;
}
}
else {
if (gb->model == GB_MODEL_CGB_E || gb->cgb_double_speed) {
/*
These corruptions revision (unit?) specific in single speed. They happen only on my CGB-E.
*/
gb->addr_for_hdma_conflict &= 0x1FFF;
// TODO: there are *some* scenarions in single speed mode where this write doesn't happen. What's the logic?
uint16_t addr = (gb->hdma_current_dest & gb->addr_for_hdma_conflict & 0x1FFF);
gb->vram[vram_base + addr] = byte;
// TODO: vram_write_blocked might not be the correct timing
if (gb->vram_write_blocked /* && gb->model < GB_MODEL_AGB_B */) {
gb->vram[(vram_base ^ 0x2000) + addr] = byte;
}
}
gb->hdma_current_dest++;
}
gb->hdma_open_bus = 0xFF;
if ((gb->hdma_current_dest & 0xF) == 0) {
if (--gb->hdma_steps_left == 0 || gb->hdma_current_dest == 0) {
gb->hdma_on = false;
gb->hdma_on_hblank = false;
gb->hdma_starting = false;
gb->io_registers[GB_IO_HDMA5] &= 0x7F;
break;
}
if (gb->hdma_on_hblank) {
else if (gb->hdma_on_hblank) {
gb->hdma_on = false;
break;
}
}
}
gb->hdma_in_progress = false; // TODO: timing? (affects VRAM reads)
if (!gb->cgb_double_speed) {
GB_advance_cycles(gb, 2);
}
}

View File

@ -13,8 +13,10 @@ uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr); // Without side ef
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
#ifdef GB_INTERNAL
internal void GB_dma_run(GB_gameboy_t *gb);
internal bool GB_is_dma_active(GB_gameboy_t *gb);
internal void GB_hdma_run(GB_gameboy_t *gb);
internal void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address);
internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr);
#endif
#endif /* memory_h */

View File

@ -22,8 +22,8 @@ static void handle_command(GB_gameboy_t *gb)
gb->printer.status = 6; /* Printing */
uint32_t image[gb->printer.image_offset];
uint8_t palette = gb->printer.command_data[2];
uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff),
gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa),
uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF),
gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA),
gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55),
gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)};
for (unsigned i = 0; i < gb->printer.image_offset; i++) {

View File

@ -17,7 +17,7 @@ static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t
while (uncompressed_size) {
if (prev_mode) {
if (*data == *prev && COUNTER != 0xffff) {
if (*data == *prev && COUNTER != 0xFFFF) {
COUNTER++;
data++;
prev++;
@ -35,7 +35,7 @@ static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t
}
}
else {
if (*data != *prev && COUNTER != 0xffff) {
if (*data != *prev && COUNTER != 0xFFFF) {
COUNTER++;
DATA = *data;
data_pos++;

View File

@ -119,6 +119,28 @@ typedef struct __attribute__((packed)){
GB_huc3_rtc_time_t data;
} BESS_HUC3_t;
typedef struct __attribute__((packed)) {
BESS_block_t header;
// Flags
bool latch_ready:1;
bool eeprom_do:1;
bool eeprom_di:1;
bool eeprom_clk:1;
bool eeprom_cs:1;
bool eeprom_write_enabled:1;
uint8_t padding:2;
uint8_t argument_bits_left;
uint16_t eeprom_command;
uint16_t read_bits;
uint16_t x_latch;
uint16_t y_latch;
} BESS_MBC7_t;
typedef struct __attribute__((packed)){
BESS_block_t header;
uint64_t last_rtc_second;
@ -232,8 +254,14 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart)
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + (cart->has_rtc? sizeof(BESS_RTC_t) : 0);
case GB_MBC5:
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t);
case GB_CAMERA:
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t);
case GB_MBC7:
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_MBC7_t);
case GB_MMM01:
return sizeof(BESS_block_t) + 8 * sizeof(BESS_MBC_pair_t);
case GB_HUC1:
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t);
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t);
case GB_HUC3:
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t);
case GB_TPP1:
@ -268,7 +296,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
+ sizeof(BESS_CORE_t)
+ sizeof(BESS_XOAM_t)
+ (gb->sgb? sizeof(BESS_SGB_t) : 0)
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1 block
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1/MBC7 block
+ sizeof(BESS_block_t) // END block
+ sizeof(BESS_footer_t);
}
@ -323,7 +351,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t
case GB_MODEL_CGB_C: return true;
case GB_MODEL_CGB_D: return true;
case GB_MODEL_CGB_E: return true;
case GB_MODEL_AGB: return true;
case GB_MODEL_AGB_A: return true;
}
if ((gb->model & GB_MODEL_FAMILY_MASK) == (save->model & GB_MODEL_FAMILY_MASK)) {
save->model = gb->model;
@ -340,10 +368,8 @@ static void sanitize_state(GB_gameboy_t *gb)
GB_palette_changed(gb, true, i * 2);
}
gb->bg_fifo.read_end &= 0xF;
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF;
gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1;
gb->oam_fifo.read_end &= GB_FIFO_LENGTH - 1;
gb->last_tile_index_address &= 0x1FFF;
gb->window_tile_x &= 0x1F;
@ -372,11 +398,6 @@ static void sanitize_state(GB_gameboy_t *gb)
if (!GB_is_cgb(gb)) {
gb->current_tile_attributes = 0;
}
if ((unsigned)gb->dma_current_dest + (unsigned)gb->dma_steps_left >= 0xa0) {
gb->dma_current_dest = 0;
gb->dma_steps_left = 0;
}
gb->object_low_line_address &= gb->vram_size & ~1;
if (gb->lcd_x > gb->position_in_line) {
@ -411,7 +432,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
{
BESS_block_t mbc_block = {BE32('MBC '), 0};
BESS_MBC_pair_t pairs[4];
BESS_MBC_pair_t pairs[8];
switch (gb->cartridge_type->mbc_type) {
default:
case GB_NO_MBC: return 0;
@ -440,20 +461,41 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
pairs[3] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank};
mbc_block.size = 4 * sizeof(pairs[0]);
break;
case GB_CAMERA:
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0};
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc5.rom_bank_low};
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank};
mbc_block.size = 3 * sizeof(pairs[0]);
break;
case GB_MBC7:
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0};
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc7.rom_bank};
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc7.secondary_ram_enable? 0x40 : 0};
mbc_block.size = 3 * sizeof(pairs[0]);
break;
case GB_MMM01:
pairs[0] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | (gb->mmm01.rom_bank_mid << 5)};
pairs[1] = (BESS_MBC_pair_t){LE16(0x6000), gb->mmm01.mbc1_mode | (gb->mmm01.rom_bank_mask << 2) | (gb->mmm01.multiplex_mode << 6)};
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2) | (gb->mmm01.rom_bank_high << 4) | (gb->mmm01.mbc1_mode_disable << 6)};
pairs[3] = (BESS_MBC_pair_t){LE16(0x0000), (gb->mbc_ram_enable? 0xA : 0x0) | (gb->mmm01.ram_bank_mask << 4) | (gb->mmm01.locked << 6)};
/* For compatibility with emulators that inaccurately emulate MMM01, and also require two writes per register */
pairs[4] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & ~(gb->mmm01.rom_bank_mask << 1))};
pairs[5] = pairs[1];
pairs[6] = pairs[2];
pairs[7] = pairs[3];
mbc_block.size = 8 * sizeof(pairs[0]);
break;
case GB_HUC1:
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc1.ir_mode? 0xE : 0x0};
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->huc1.bank_low};
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc1.bank_high};
pairs[3] = (BESS_MBC_pair_t){LE16(0x6000), gb->huc1.mode};
mbc_block.size = 4 * sizeof(pairs[0]);
mbc_block.size = 3 * sizeof(pairs[0]);
case GB_HUC3:
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc3.mode};
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->huc3.rom_bank};
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc3.ram_bank};
mbc_block.size = 3 * sizeof(pairs[0]);
break;
case GB_TPP1:
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->tpp1.rom_bank};
pairs[1] = (BESS_MBC_pair_t){LE16(0x0001), gb->tpp1.rom_bank >> 8};
@ -476,6 +518,14 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
return 0;
}
static const uint8_t *get_header_bank(GB_gameboy_t *gb)
{
if (gb->cartridge_type->mbc_type == GB_MMM01) {
return gb->rom + gb->rom_size - 0x8000;
}
return gb->rom;
}
static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess)
{
if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error;
@ -545,11 +595,13 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
goto error;
}
if (file->write(file, gb->rom + 0x134, 0x10) != 0x10) {
const uint8_t *bank = get_header_bank(gb);
if (file->write(file, bank + 0x134, 0x10) != 0x10) {
goto error;
}
if (file->write(file, gb->rom + 0x14e, 2) != 2) {
if (file->write(file, bank + 0x14E, 2) != 2) {
goto error;
}
@ -581,7 +633,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
case GB_MODEL_CGB_C: bess_core.full_model = BE32('CCC '); break;
case GB_MODEL_CGB_D: bess_core.full_model = BE32('CCD '); break;
case GB_MODEL_CGB_E: bess_core.full_model = BE32('CCE '); break;
case GB_MODEL_AGB: bess_core.full_model = BE32('CA '); break; // SameBoy doesn't emulate a specific AGB revision yet
case GB_MODEL_AGB_A: bess_core.full_model = BE32('CAA '); break;
}
bess_core.pc = LE16(gb->pc);
@ -685,6 +737,30 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
}
}
if (gb->cartridge_type ->mbc_type == GB_MBC7) {
BESS_MBC7_t bess_mbc7 = {
.latch_ready = gb->mbc7.latch_ready,
.eeprom_do = gb->mbc7.eeprom_do,
.eeprom_di = gb->mbc7.eeprom_di,
.eeprom_clk = gb->mbc7.eeprom_clk,
.eeprom_cs = gb->mbc7.eeprom_cs,
.eeprom_write_enabled = gb->mbc7.eeprom_write_enabled,
.argument_bits_left = gb->mbc7.argument_bits_left,
.eeprom_command = LE16(gb->mbc7.eeprom_command),
.read_bits = LE16(gb->mbc7.read_bits),
.x_latch = LE16(gb->mbc7.x_latch),
.y_latch = LE16(gb->mbc7.y_latch),
};
bess_mbc7.header = (BESS_block_t){BE32('MBC7'), LE32(sizeof(bess_mbc7) - sizeof(bess_mbc7.header))};
if (file->write(file, &bess_mbc7, sizeof(bess_mbc7)) != sizeof(bess_mbc7)) {
goto error;
}
}
bool needs_sgb_padding = false;
if (gb->sgb) {
/* BESS SGB */
@ -970,6 +1046,11 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
// Interrupts
GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]);
/* Required to be compatible with both SameBoy 0.14.x AND BGB */
if (GB_is_cgb(&save) && !save.cgb_mode && save.cgb_ram_bank == 7) {
save.cgb_ram_bank = 1;
}
break;
case BE32('NAME'):
if (LE32(block.size) > sizeof(emulator_name) - 1) {
@ -983,7 +1064,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
BESS_INFO_t bess_info = {0,};
if (LE32(block.size) != sizeof(bess_info) - sizeof(block)) goto parse_error;
if (file->read(file, &bess_info.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
if (memcmp(bess_info.title, gb->rom + 0x134, sizeof(bess_info.title))) {
const uint8_t *bank = get_header_bank(gb);
if (memcmp(bess_info.title, bank + 0x134, sizeof(bess_info.title))) {
char ascii_title[0x11] = {0,};
for (unsigned i = 0; i < 0x10; i++) {
if (bess_info.title[i] < 0x20 || bess_info.title[i] > 0x7E) break;
@ -991,7 +1073,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
}
GB_log(gb, "Save state was made on another ROM: '%s'\n", ascii_title);
}
else if (memcmp(bess_info.checksum, gb->rom + 0x14E, 2)) {
else if (memcmp(bess_info.checksum, bank + 0x14E, 2)) {
GB_log(gb, "Save state was potentially made on another revision of the same ROM.\n");
}
break;
@ -1004,7 +1086,12 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
case BE32('MBC '):
if (!found_core) goto parse_error;
if (LE32(block.size) % 3 != 0) goto parse_error;
if (LE32(block.size) > 0x1000) goto parse_error;
if (LE32(block.size) > 0x1000) goto parse_error;
/* Inject some default writes, as some emulators omit them */
if (gb->cartridge_type->mbc_type == GB_MMM01) {
GB_write_memory(&save, 0x6000, 0x30);
GB_write_memory(&save, 0x4000, 0x70);
}
for (unsigned i = LE32(block.size); i > 0; i -= 3) {
BESS_MBC_pair_t pair;
file->read(file, &pair, sizeof(pair));
@ -1063,6 +1150,29 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i];
}
save.tpp1_mr4 = bess_tpp1.mr4;
break;
case BE32('MBC7'):
if (!found_core) goto parse_error;
BESS_MBC7_t bess_mbc7;
if (LE32(block.size) != sizeof(bess_mbc7) - sizeof(block)) goto parse_error;
if (file->read(file, &bess_mbc7.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
if (gb->cartridge_type->mbc_type != GB_MBC7) break;
save.mbc7.latch_ready = bess_mbc7.latch_ready;
save.mbc7.eeprom_do = bess_mbc7.eeprom_do;
save.mbc7.eeprom_di = bess_mbc7.eeprom_di;
save.mbc7.eeprom_clk = bess_mbc7.eeprom_clk;
save.mbc7.eeprom_cs = bess_mbc7.eeprom_cs;
save.mbc7.eeprom_write_enabled = bess_mbc7.eeprom_write_enabled;
save.mbc7.argument_bits_left = bess_mbc7.argument_bits_left;
save.mbc7.eeprom_command = LE16(bess_mbc7.eeprom_command);
save.mbc7.read_bits = LE16(bess_mbc7.read_bits);
save.mbc7.x_latch = LE16(bess_mbc7.x_latch);
save.mbc7.y_latch = LE16(bess_mbc7.y_latch);
break;
case BE32('SGB '):
if (!found_core) goto parse_error;

View File

@ -10,7 +10,7 @@
as anonymous enums inside unions */
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__
#else
#define GB_SECTION(name, ...) union __attribute__ ((aligned (8))) {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]
#define GB_SECTION(name, ...) union __attribute__ ((aligned (8))) {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0];
#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start))
#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))

View File

@ -165,7 +165,7 @@ static void command_ready(GB_gameboy_t *gb)
return;
}
memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14);
if (gb->sgb->command[0] == 0xfb) {
if (gb->sgb->command[0] == 0xFB) {
if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) {
gb->sgb->disable_commands = true;
for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) {

View File

@ -7,6 +7,7 @@
typedef struct GB_sgb_s GB_sgb_t;
typedef struct {
uint8_t tiles[0x100 * 8 * 4];
#ifdef GB_INTERNAL
union {
struct {
uint16_t map[32 * 32];
@ -14,6 +15,9 @@ typedef struct {
};
uint16_t raw_data[0x440];
};
#else
uint16_t raw_data[0x440];
#endif
} GB_sgb_border_t;
#ifdef GB_INTERNAL

View File

@ -23,6 +23,7 @@ typedef enum {
GB_CONFLICT_WX,
GB_CONFLICT_CGB_LCDC,
GB_CONFLICT_NR10,
GB_CONFLICT_CGB_SCX,
} conflict_t;
/* Todo: How does double speed mode affect these? */
@ -35,9 +36,7 @@ static const conflict_t cgb_conflict_map[0x80] = {
[GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB,
[GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB,
[GB_IO_NR10] = GB_CONFLICT_NR10,
[GB_IO_SCX] = GB_CONFLICT_WRITE_CPU, // TODO: Similar to BGP, there's some time travelling involved
/* Todo: most values not verified, and probably differ between revisions */
[GB_IO_SCX] = GB_CONFLICT_CGB_SCX,
};
/* Todo: verify on an MGB */
@ -145,6 +144,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
/* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */
case GB_CONFLICT_STAT_DMG:
GB_advance_cycles(gb, gb->pending_cycles);
GB_display_sync(gb);
/* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird.
The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite
the timing not making much sense for that.
@ -206,7 +206,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
uint8_t old_value = GB_read_memory(gb, addr);
GB_advance_cycles(gb, gb->pending_cycles - 2);
GB_display_sync(gb);
if (gb->model != GB_MODEL_MGB && gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) {
old_value &= ~2;
}
@ -242,7 +242,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
break;
case GB_CONFLICT_CGB_LCDC:
if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) {
if ((~value & gb->io_registers[GB_IO_LCDC]) & 0x10) {
// Todo: This is difference is because my timing is off in one of the models
if (gb->model > GB_MODEL_CGB_C) {
GB_advance_cycles(gb, gb->pending_cycles);
@ -277,6 +277,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, gb->pending_cycles);
if (gb->model <= GB_MODEL_CGB_C) {
// TODO: Double speed mode? This logic is also a bit weird, it needs more tests
GB_apu_run(gb, true);
if (gb->apu.square_sweep_calculate_countdown > 3 && gb->apu.enable_zombie_calculate_stepping) {
gb->apu.square_sweep_calculate_countdown -= 2;
}
@ -289,6 +290,19 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_write_memory(gb, addr, value);
gb->pending_cycles = 4;
break;
case GB_CONFLICT_CGB_SCX:
if (gb->cgb_double_speed) {
GB_advance_cycles(gb, gb->pending_cycles - 2);
GB_write_memory(gb, addr, value);
gb->pending_cycles = 6;
}
else {
GB_advance_cycles(gb, gb->pending_cycles);
GB_write_memory(gb, addr, value);
gb->pending_cycles = 4;
}
break;
}
gb->address_bus = addr;
}
@ -346,6 +360,7 @@ static void enter_stop_mode(GB_gameboy_t *gb)
gb->div_cycles = -4; // Emulate the CPU-side DIV-reset signal being held
}
gb->stopped = true;
gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3);
gb->oam_ppu_blocked = !gb->oam_read_blocked;
gb->vram_ppu_blocked = !gb->vram_read_blocked;
gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked;
@ -354,6 +369,12 @@ static void enter_stop_mode(GB_gameboy_t *gb)
static void leave_stop_mode(GB_gameboy_t *gb)
{
gb->stopped = false;
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) {
gb->hdma_on = true;
}
// TODO: verify this
gb->dma_cycles = 4;
GB_dma_run(gb);
gb->oam_ppu_blocked = false;
gb->vram_ppu_blocked = false;
gb->cgb_palettes_ppu_blocked = false;
@ -372,6 +393,9 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
bool interrupt_pending = (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F);
// When entering with IF&IE, the 2nd byte of STOP is actually executed
if (!exit_by_joyp) {
if (!immediate_exit) {
GB_dma_run(gb);
}
enter_stop_mode(gb);
}
@ -414,8 +438,10 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
if (immediate_exit) {
leave_stop_mode(gb);
if (!interrupt_pending) {
GB_dma_run(gb);
gb->halted = true;
gb->just_halted = true;
gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3);
}
else {
gb->speed_switch_halt_countdown = 0;
@ -858,7 +884,7 @@ static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \
cycle_write(gb, gb->hl, gb->y); \
}
LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a)
LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a)
LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a)
LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a)
LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a)
@ -1004,7 +1030,6 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode)
gb->pending_cycles = 0;
GB_advance_cycles(gb, 4);
gb->halted = true;
/* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */
if (((gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0)) {
if (gb->ime) {
@ -1016,6 +1041,10 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode)
gb->halt_bug = true;
}
}
else {
gb->halted = true;
gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3);
}
gb->just_halted = true;
}
@ -1357,7 +1386,7 @@ static void rlc_r(GB_gameboy_t *gb, uint8_t opcode)
if (carry) {
gb->af |= GB_CARRY_FLAG;
}
if (!(value << 1)) {
if (value == 0) {
gb->af |= GB_ZERO_FLAG;
}
}
@ -1571,10 +1600,6 @@ static opcode_t *opcodes[256] = {
};
void GB_cpu_run(GB_gameboy_t *gb)
{
if (gb->hdma_on) {
GB_advance_cycles(gb, 4);
return;
}
if (gb->stopped) {
GB_timing_sync(gb);
GB_advance_cycles(gb, 4);
@ -1612,16 +1637,27 @@ void GB_cpu_run(GB_gameboy_t *gb)
/* Wake up from HALT mode without calling interrupt code. */
if (gb->halted && !effective_ime && interrupt_queue) {
gb->halted = false;
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) {
gb->hdma_on = true;
}
gb->dma_cycles = 4;
GB_dma_run(gb);
gb->speed_switch_halt_countdown = 0;
}
/* Call interrupt */
else if (effective_ime && interrupt_queue) {
gb->halted = false;
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) {
gb->hdma_on = true;
}
// TODO: verify the timing!
gb->dma_cycles = 4;
GB_dma_run(gb);
gb->speed_switch_halt_countdown = 0;
uint16_t call_addr = gb->pc;
gb->last_opcode_read = cycle_read(gb, gb->pc++);
cycle_read(gb, gb->pc++);
cycle_oam_bug_pc(gb);
gb->pc--;
GB_trigger_oam_bug(gb, gb->sp); /* Todo: test T-cycle timing */
@ -1660,22 +1696,19 @@ void GB_cpu_run(GB_gameboy_t *gb)
}
/* Run mode */
else if (!gb->halted) {
gb->last_opcode_read = cycle_read(gb, gb->pc++);
uint8_t opcode = gb->hdma_open_bus = cycle_read(gb, gb->pc++);
if (unlikely(gb->hdma_on)) {
GB_hdma_run(gb);
}
if (unlikely(gb->execution_callback)) {
gb->execution_callback(gb, gb->pc - 1, gb->last_opcode_read);
gb->execution_callback(gb, gb->pc - 1, opcode);
}
if (unlikely(gb->halt_bug)) {
gb->pc--;
gb->halt_bug = false;
}
opcodes[gb->last_opcode_read](gb, gb->last_opcode_read);
opcodes[opcode](gb, opcode);
}
flush_pending_cycles(gb);
if (gb->hdma_starting) {
gb->hdma_starting = false;
gb->hdma_on = true;
gb->hdma_cycles = -8;
}
}

View File

@ -519,7 +519,7 @@ static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint8_t addr = GB_read_memory(gb, (*pc)++);
const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr);
if (symbol) {
GB_log(gb, "LDH [%s & $FF], a ; =$%02x\n", symbol, addr);
}
@ -532,7 +532,7 @@ static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint8_t addr = GB_read_memory(gb, (*pc)++);
const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr);
if (symbol) {
GB_log(gb, "LDH a, [%s & $FF] ; =$%02x\n", symbol, addr);
}

View File

@ -44,6 +44,9 @@ const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr)
index--;
}
if (index < map->n_symbols) {
while (index && map->symbols[index].addr == map->symbols[index - 1].addr) {
index--;
}
return &map->symbols[index];
}
return NULL;

View File

@ -108,23 +108,32 @@ void GB_timing_sync(GB_gameboy_t *gb)
#endif
#define IR_DECAY 31500
#define IR_THRESHOLD 19900
#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY
#define IR_WARMUP 19900
#define IR_THRESHOLD 240
#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY + 268
static void ir_run(GB_gameboy_t *gb, uint32_t cycles)
{
if ((gb->model == GB_MODEL_AGB || !gb->cgb_mode) && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) return;
if (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1)) {
/* TODO: the way this thing works makes the CGB IR port behave inaccurately when used together with HUC1/3 IR ports*/
if ((gb->model > GB_MODEL_CGB_E || !gb->cgb_mode) && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) return;
bool is_sensing = (gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 ||
(gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) ||
(gb->cartridge_type->mbc_type == GB_HUC3 && gb->huc3.mode == 0xE);
if (is_sensing && (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1))) {
gb->ir_sensor += cycles;
if (gb->ir_sensor > IR_MAX) {
gb->ir_sensor = IR_MAX;
}
gb->effective_ir_input = gb->ir_sensor >= IR_THRESHOLD && gb->ir_sensor <= IR_THRESHOLD + IR_DECAY;
gb->effective_ir_input = gb->ir_sensor >= IR_WARMUP + IR_THRESHOLD && gb->ir_sensor <= IR_WARMUP + IR_THRESHOLD + IR_DECAY;
}
else {
if (gb->ir_sensor <= cycles) {
gb->ir_sensor = 0;
unsigned target = is_sensing? IR_WARMUP : 0;
if (gb->ir_sensor < target) {
gb->ir_sensor += cycles;
}
else if (gb->ir_sensor <= target + cycles) {
gb->ir_sensor = target;
}
else {
gb->ir_sensor -= cycles;
@ -154,6 +163,42 @@ static void increase_tima(GB_gameboy_t *gb)
}
}
void GB_serial_master_edge(GB_gameboy_t *gb)
{
if (unlikely(gb->printer_callback && (gb->printer.command_state || gb->printer.bits_received))) {
gb->printer.idle_time += 1 << gb->serial_mask;
}
gb->serial_master_clock ^= true;
if (!gb->serial_master_clock && (gb->io_registers[GB_IO_SC] & 0x81) == 0x81) {
gb->serial_count++;
if (gb->serial_count == 8) {
gb->serial_count = 0;
gb->io_registers[GB_IO_SC] &= ~0x80;
gb->io_registers[GB_IO_IF] |= 8;
}
gb->io_registers[GB_IO_SB] <<= 1;
if (gb->serial_transfer_bit_end_callback) {
gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb);
}
else {
gb->io_registers[GB_IO_SB] |= 1;
}
if (gb->serial_count) {
/* Still more bits to send */
if (gb->serial_transfer_bit_start_callback) {
gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
}
}
}
}
void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value)
{
/* TIMA increases when a specific high-bit becomes a low-bit. */
@ -162,6 +207,10 @@ void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value)
increase_tima(gb);
}
if (triggers & gb->serial_mask) {
GB_serial_master_edge(gb);
}
/* TODO: Can switching to double speed mode trigger an event? */
uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000;
if (triggers & apu_bit) {
@ -199,53 +248,6 @@ static void timers_run(GB_gameboy_t *gb, uint8_t cycles)
}
}
static void advance_serial(GB_gameboy_t *gb, uint8_t cycles)
{
if (unlikely(gb->printer_callback && (gb->printer.command_state || gb->printer.bits_received))) {
gb->printer.idle_time += cycles;
}
if (likely(gb->serial_length == 0)) {
gb->serial_cycles += cycles;
return;
}
while (cycles > gb->serial_length) {
advance_serial(gb, gb->serial_length);
cycles -= gb->serial_length;
}
uint16_t previous_serial_cycles = gb->serial_cycles;
gb->serial_cycles += cycles;
if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) {
gb->serial_count++;
if (gb->serial_count == 8) {
gb->serial_length = 0;
gb->serial_count = 0;
gb->io_registers[GB_IO_SC] &= ~0x80;
gb->io_registers[GB_IO_IF] |= 8;
}
gb->io_registers[GB_IO_SB] <<= 1;
if (gb->serial_transfer_bit_end_callback) {
gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb);
}
else {
gb->io_registers[GB_IO_SB] |= 1;
}
if (gb->serial_length) {
/* Still more bits to send */
if (gb->serial_transfer_bit_start_callback) {
gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
}
}
}
return;
}
void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode)
{
if (gb->rtc_mode != mode) {
@ -384,12 +386,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
}
gb->apu.pcm_mask[0] = gb->apu.pcm_mask[1] = 0xFF; // Sort of hacky, but too many cross-component interactions to do it right
// Affected by speed boost
gb->dma_cycles += cycles;
gb->dma_cycles = cycles;
timers_run(gb, cycles);
if (unlikely(!gb->stopped)) {
advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
}
if (unlikely(gb->speed_switch_halt_countdown)) {
gb->speed_switch_halt_countdown -= cycles;
@ -420,7 +419,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
if (likely(gb->io_registers[GB_IO_LCDC] & 0x80)) {
gb->double_speed_alignment += cycles;
}
gb->hdma_cycles += cycles;
gb->apu_output.sample_cycles += cycles * gb->apu_output.sample_rate;
gb->cycles_since_last_sync += cycles;
gb->cycles_since_run += cycles;
@ -428,12 +426,11 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
gb->rumble_on_cycles += gb->rumble_strength & 3;
gb->rumble_off_cycles += (gb->rumble_strength & 3) ^ 3;
if (unlikely(!gb->stopped)) { // TODO: Verify what happens in STOP mode
GB_dma_run(gb);
GB_hdma_run(gb);
}
GB_apu_run(gb, false);
GB_display_run(gb, cycles, false);
if (unlikely(!gb->stopped)) { // TODO: Verify what happens in STOP mode
GB_dma_run(gb);
}
ir_run(gb, cycles);
rtc_run(gb, cycles);
}

View File

@ -19,6 +19,7 @@ internal void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t
internal bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */
internal void GB_timing_sync(GB_gameboy_t *gb);
internal void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value);
internal void GB_serial_master_edge(GB_gameboy_t *gb);
enum {
GB_TIMA_RUNNING = 0,
GB_TIMA_RELOADING = 1,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 28 KiB

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