From 1e9e961e9ce793963c94583a48bc66401e5a7434 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 29 Sep 2020 20:43:47 +0300 Subject: [PATCH 001/365] Create CONTRIBUTING.md --- CONTRIBUTING.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..94627d1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,79 @@ +# SameBoy Coding and Contribution Guidelines + +## Issues + +GitHub Issues are the most effective way to report a bug or request a feature in SameBoy. When reporting a bug, make sure you use the latest stable release, and make sure you mention the SameBoy frontend (Cocoa, SDL, Libretro) and operating system you're using. If you're using Linux/BSD/etc, or you build your own copy of SameBoy for another reason, give as much details as possible on your environment. + +If your bug involves a crash, please attach a crash log or a core dump. If you're using Linux/BSD/etc, or if you're using the Libretro core, please attach the `sameboy` binary (or `libretro_sameboy` library) in that case. + +If your bug is a regression, it'd be extremely helpful if you can report the the first affected version. You get extra credits if you use `git bisect` to point the exact breaking commit. + +If your bug is an emulation bug (Such as a failing test ROM), and you have access to a Game Boy you can test on, please confirm SameBoy is indeed behaving differently from hardware, and report both the emulated model and revision in SameBoy, and the hardware revision you're testing on. + +If your issue is a feature request, demonstrating use cases can help me better prioritize it. + +## Pull Requests + +To allow quicker integration into SameBoy's master branch, contributors are asked to follow SameBoy's style and coding guidelines. Keep in mind that despite the seemingly strict guidelines, all pull requests are welcome – not following the guidelines does not mean your pull request will not be accepted, but it will require manual tweaks from my side for integrating. + +### Languages and Compilers + +SameBoy's core, SDL frontend, Libretro frontend, and automatic tester (Folders `Core`, `SDL` & `OpenDialog`, `libretro`, and `Tester`; respectively) are all written in C11. The Cocoa frontend, SameBoy's fork of Hex Fiend, JoyKit and the Quick Look previewer (Folders `Cocoa`, `HexFiend`, `JoyKit` and `QuickLook`; respectively) are all written in ARC-enabled Objective-C. The SameBoot ROMs (Under `BootROMs`) are written in rgbds-flavor SM83 assembly, with build tools in C11. The shaders (inside `Shaders`) are written in a polyglot GLSL and Metal style, with a few GLSL- and Metal-specific sources. The build system uses standalone Make, in the GNU flavor. Avoid adding new languages (C++, Swift, Python, CMake...) to any of the existing sub-projects. + +SameBoy's main target compiler is Clang, but GCC is also supported when targeting Linux and Libretro. Other compilers (e.g. MSVC) are not supported, and unless there's a good reason, there's no need to go out of your way to add specific support for them. Extensions that are supported by both compilers (Such as `typeof`) may be used if it makes sense. It's OK if you can't test one of these compilers yourself; once you push a commit, the CI bot will let you know if you broke something. + +### Third Party Libraries and Tools + +Avoid adding new required dependencies; run-time and compile-time dependencies alike. Most importantly, avoid linking against GPL licensed libraries (LGPL libraries are fine), so SameBoy can retain its MIT license. + +### Spacing, Indentation and Formatting + +In all files and languages (Other than Makefiles when required), 4 spaces are used for indentation. Unix line endings (`\n`) are used exclusively, even in Windows-specific source files. (`\r` and `\t` shouldn't appear in any source file). Opening braces belong on the same line as their control flow directive, and on their own line when following a function prototype. The `else` keyword always starts on its own line. The `case` keyword is indented relative to its `switch` block, and the code inside a `case` is indented relative to its label. A control flow keyword should have a space between it and the following `(`, commas should follow a space, and operator (except `.` and `->`) should be surrounded by spaces. + +Control flow statements must use `{}`, with the exception of `if` statements that only contain a single `break`, `continue`, or trivial `return` statements. If `{}`s are omitted, the statement must be on the same line as the `if` condition. Functions that do not have any argument must be specified as `(void)`, as mandated by the C standard. The `sizeof` and `typeof` operators should be used as if they're functions (With `()`). `*`, when used to declare pointer types (including functions that return pointers), and when used to dereference a pointer, is attached to the right side (The variable name) – not to the left, and not with spaces on both sides. + +No strict limitations on a line's maximum width, but use your best judgement if you think a statement would benefit from an additional line break. + +Well formatted code example: + +``` +static void my_function(void) +{ + GB_something_t *thing = GB_function(&gb, GB_FLAG_ONE | GB_FLAG_TWO, sizeof(thing)); + if (GB_is_thing(thing)) return; + + switch (*thing) { + case GB_QUACK: + // Something + case GB_DUCK: + // Something else + } +} +``` + +Badly formatted code example: +``` +static void my_function(){ + GB_something_t* thing=GB_function(&gb , GB_FLAG_ONE|GB_FLAG_TWO , sizeof thing); + if( GB_is_thing ( thing ) ) + return; + + switch(* thing) + { + case GB_QUACK: + // Something + case GB_DUCK: + // Something else + } +} +``` + +### Other Coding Conventions + +The primitive types to be used in SameBoy are `unsigned` and `signed` (Without the `int` keyword), the `(u)int*_t` types, `char *` for UTF-8 strings, `double` for non-integer numbers, and `bool` for booleans (Including in Objective-C code, avoid `BOOL`). As long as it's not mandated by a 3rd-party API (e.g. `int` when using file descriptors), avoid using other primitive types. Use `const` whenever possible. + +Most C names should be `lower_case_snake_case`. Constants and macros use `UPPER_CASE_SNAKE_CASE`. Type definitions use a `_t` suffix. Type definitions, as well as non-static (exported) core symbols, should be prefixed with `GB_` (SameBoy's core is intended to be used as a library, so it shouldn't contaminate the global namespace without prefixes). Exported symbols that are only meant to be used by other parts of the core should still get the `GB_` prefix, but their header definition should be inside `#ifdef GB_INTERNAL`. + +For Objective-C naming conventions, use Apple's conventions (Some old Objective-C code mixes these with the C naming convention; new code should use Apple's convention exclusively). The name prefix for SameBoy classes and constants is `GB`. JoyKit's prefix is `JOY`, and Hex Fiend's prefix is `HF`. + +In all languages, prefer long, unambiguous names over short ambiguous ones. From 2a5aed626da3cfe6d8e2adac9116e82a280e93c5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 29 Sep 2020 20:50:14 +0300 Subject: [PATCH 002/365] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 88b8caa..dcffabe 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ Features currently supported only with the Cocoa version: ## Compatibility SameBoy passes all of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), all of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) tests (Some tests require the original boot ROMs), and all of [Wilbert Pol's tests](https://github.com/wilbertpol/mooneye-gb/tree/master/tests/acceptance). SameBoy should work with most games and demos, please [report](https://github.com/LIJI32/SameBoy/issues/new) any broken ROM. The latest results for SameBoy's automatic tester are available [here](https://sameboy.github.io/automation/). +## Contributing +SameBoy is an open-source project licensed under the MIT license, and you're welcome to contribute by creating issues, implementing new features, improving emulation accuracy and fixing existing open issues. You can read the [contribution guidelines](CONTRIBUTING.md) to make sure your contributions are as effective as possible. + ## Compilation SameBoy requires the following tools and libraries to build: * clang From 04e5f1b8cf78313e68aeac07a46f75c8db0cdd09 Mon Sep 17 00:00:00 2001 From: yo Date: Mon, 5 Oct 2020 14:33:36 -0700 Subject: [PATCH 003/365] Updated for Windows clang and SDL2 changes --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index be52287..78c28b6 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ endif ifeq ($(PLATFORM),windows32) _ := $(shell chcp 65001) EXESUFFIX:=.exe -NATIVE_CC = clang -IWindows -Wno-deprecated-declarations +NATIVE_CC = clang -IWindows -Wno-deprecated-declarations --target=i386-pc-windows else EXESUFFIX:= NATIVE_CC := cc @@ -129,8 +129,8 @@ GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl) GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL) endif ifeq ($(PLATFORM),windows32) -CFLAGS += -IWindows -Drandom=rand -LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lSDL2main -Wl,/MANIFESTFILE:NUL +CFLAGS += -IWindows -Drandom=rand --target=i386-pc-windows +LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lshell32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows SDL_LDFLAGS := -lSDL2 GL_LDFLAGS := -lopengl32 else @@ -404,7 +404,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ + $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ --target=i386-pc-windows $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm From 28234da2d29a35f381c3015e0a5eab38ccdf4623 Mon Sep 17 00:00:00 2001 From: yo Date: Mon, 5 Oct 2020 14:34:00 -0700 Subject: [PATCH 004/365] Updated instructions for Windows building --- README.md | 4 ++-- build-faq.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dcffabe..5df1d5c 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,14 @@ SameBoy is an open-source project licensed under the MIT license, and you're wel SameBoy requires the following tools and libraries to build: * clang * make - * Cocoa port: OS X SDK and Xcode command line tools + * Cocoa port: OS X SDK and Xcode command line tools [OSX Only] * SDL port: libsdl2 * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation On Windows, SameBoy also requires: * Visual Studio (For headers, etc.) * [GnuWin](http://gnuwin32.sourceforge.net/) - * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. + * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ] (https://github.com/LIJI32/SameBoy/blob/master/build-faq.md)) To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release`, `CONF=native_release` or `CONF=fat_release` to control optimization, symbols and multi-architectures. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. `fat_release` is exclusive to macOS and builds x86-64 and ARM64 fat binaries; this requires using a recent enough `clang` and macOS SDK using `xcode-select`, or setting them explicitly with `CC=` and `SYSROOT=`, respectively. All other configurations will build to your host architecture. You may set `BOOTROMS_DIR=...` to a directory containing precompiled boot ROM files, otherwise the build system will compile and use SameBoy's own boot ROMs. diff --git a/build-faq.md b/build-faq.md index 2b056dd..481d1b9 100644 --- a/build-faq.md +++ b/build-faq.md @@ -4,4 +4,58 @@ When building on macOS, the build system will make a native Cocoa app by default # Attempting to build the SDL frontend on macOS fails on linking -SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case. \ No newline at end of file +SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case. + +# Windows build process + +For the various tools and libraries, follow the below guide to ensure easy, proper configuration for the build environment: + +#### clang + +This may be installed via a Visual Studio installer packages instead of built from source. + +#### SDL Port + +[libsdl2](https://libsdl.org/download-2.0.php) has two separate files that must be downloaded + 1. The `-x86` Runtime Binary (e.g., `SDL2-2.0.12-win32-x86.zip` (as of writing)) + 2. The Visual C++ Development Library (e.g., `SDL2-devel-2.0.12-VC.zip` (as of writing)) + +For the Runtime Binary, place the extracted `SDL2.dll` into a known folder for later. + +- `C:\SDL2\bin\SDL2.dll` will be used as an example + +For the Visual C++ Development Library, place the extracted files within a known folder for later. + +The following examples will be referenced later: + +- `C:\SDL2\lib\x86\*` +- `C:\SDL2\include\*` + +#### Gnuwin + +Ensure that this is in %PATH%. + +If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. + +### Building + +Within a command prompt in the project directory: + +``` +vcvars32 +set path=%path%;C:\SDL2\bin +set lib=%lib%;C:\SDL2\lib\x86 +set include=%include%;C:\SDL2\include +make +``` +Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `path`, `lib`, and `include` paths are updated appropriately with the SDL2 downloads. + +#### Error -1073741819 + +If encountering an error that appears as follows: + +> make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819 + +Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. + + From 0b5853070aa71da85482c60a49100b309cc7a57d Mon Sep 17 00:00:00 2001 From: yo Date: Mon, 5 Oct 2020 14:37:49 -0700 Subject: [PATCH 005/365] Updated instructions for Windows building --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5df1d5c..1218fc1 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ SameBoy requires the following tools and libraries to build: On Windows, SameBoy also requires: * Visual Studio (For headers, etc.) * [GnuWin](http://gnuwin32.sourceforge.net/) - * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ] (https://github.com/LIJI32/SameBoy/blob/master/build-faq.md)) + * Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ](https://github.com/LIJI32/SameBoy/blob/master/build-faq.md) for more details on Windows compilation) To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release`, `CONF=native_release` or `CONF=fat_release` to control optimization, symbols and multi-architectures. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. `fat_release` is exclusive to macOS and builds x86-64 and ARM64 fat binaries; this requires using a recent enough `clang` and macOS SDK using `xcode-select`, or setting them explicitly with `CC=` and `SYSROOT=`, respectively. All other configurations will build to your host architecture. You may set `BOOTROMS_DIR=...` to a directory containing precompiled boot ROM files, otherwise the build system will compile and use SameBoy's own boot ROMs. From 38afb187cf7891724624f2f5199d163761e1b3a1 Mon Sep 17 00:00:00 2001 From: yo Date: Tue, 6 Oct 2020 23:03:39 -0700 Subject: [PATCH 006/365] Resolving some comments and clarifying some language --- Makefile | 2 +- build-faq.md | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 78c28b6..0881fe9 100644 --- a/Makefile +++ b/Makefile @@ -404,7 +404,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(realpath $(PB12_COMPRESS)) < $< > $@ $(PB12_COMPRESS): BootROMs/pb12.c - $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ --target=i386-pc-windows + $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm diff --git a/build-faq.md b/build-faq.md index 481d1b9..56c59ae 100644 --- a/build-faq.md +++ b/build-faq.md @@ -10,30 +10,22 @@ SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a frame For the various tools and libraries, follow the below guide to ensure easy, proper configuration for the build environment: -#### clang - -This may be installed via a Visual Studio installer packages instead of built from source. - #### SDL Port -[libsdl2](https://libsdl.org/download-2.0.php) has two separate files that must be downloaded - 1. The `-x86` Runtime Binary (e.g., `SDL2-2.0.12-win32-x86.zip` (as of writing)) - 2. The Visual C++ Development Library (e.g., `SDL2-devel-2.0.12-VC.zip` (as of writing)) +For [libSDL2](https://libsdl.org/download-2.0.php), download the Visual C++ Development Library pack. Place the extracted files within a known folder for later. Both the `\x86\` and `\include\` paths will be needed. -For the Runtime Binary, place the extracted `SDL2.dll` into a known folder for later. - -- `C:\SDL2\bin\SDL2.dll` will be used as an example - -For the Visual C++ Development Library, place the extracted files within a known folder for later. - -The following examples will be referenced later: +The following examples will be referenced later: - `C:\SDL2\lib\x86\*` - `C:\SDL2\include\*` -#### Gnuwin +#### rgbds -Ensure that this is in %PATH%. +After downloading [rgbds](https://github.com/bentley/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`. + +#### GnuWin + +Ensure that the `gnuwin32\bin\` directory is included in `%PATH%`. Like rgbds above, this may instead be manually included on the command line before installation: `set path=%path%;C:\path\to\gnuwin32\bin`. If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. @@ -43,12 +35,11 @@ Within a command prompt in the project directory: ``` vcvars32 -set path=%path%;C:\SDL2\bin set lib=%lib%;C:\SDL2\lib\x86 set include=%include%;C:\SDL2\include make ``` -Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `path`, `lib`, and `include` paths are updated appropriately with the SDL2 downloads. +Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `%PATH%` properly includes `rgbds` and `gnuwin32\bin`, and that the `lib` and `include` paths include the appropriate SDL2 directories. #### Error -1073741819 From 64963e1746f0a548d3412e115fd7fba934193226 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Oct 2020 15:57:23 +0300 Subject: [PATCH 007/365] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1218fc1..97cd2e4 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ SameBoy is an open-source project licensed under the MIT license, and you're wel SameBoy requires the following tools and libraries to build: * clang * make - * Cocoa port: OS X SDK and Xcode command line tools [OSX Only] + * macOS Cocoa port: macOS SDK and Xcode (For command line tools and ibtool) * SDL port: libsdl2 * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation From 99ec5b32fc09700b20bade5e976242830770c4e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Oct 2020 16:03:32 +0300 Subject: [PATCH 008/365] Update build-faq.md --- build-faq.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/build-faq.md b/build-faq.md index 56c59ae..9def134 100644 --- a/build-faq.md +++ b/build-faq.md @@ -1,16 +1,19 @@ -# Attempting to build the Cocoa frontend fails with NSInternalInconsistencyException +# macOS Specific Issues +## Attempting to build the Cocoa frontend fails with NSInternalInconsistencyException When building on macOS, the build system will make a native Cocoa app by default. In this case, the build system uses the Xcode `ibtool` command to build user interface files. If this command fails, you can fix this issue by starting Xcode and letting it install components. After this is done, you should be able to close Xcode and build successfully. -# Attempting to build the SDL frontend on macOS fails on linking +## Attempting to build the SDL frontend on macOS fails on linking SameBoy on macOS expects you to have SDL2 installed via Brew, and not as a framework. Older versions expected it to be installed as a framework, but this is no longer the case. -# Windows build process +# Windows Build Process + +## Tools and Libraries Installation For the various tools and libraries, follow the below guide to ensure easy, proper configuration for the build environment: -#### SDL Port +### SDL2 For [libSDL2](https://libsdl.org/download-2.0.php), download the Visual C++ Development Library pack. Place the extracted files within a known folder for later. Both the `\x86\` and `\include\` paths will be needed. @@ -19,17 +22,15 @@ The following examples will be referenced later: - `C:\SDL2\lib\x86\*` - `C:\SDL2\include\*` -#### rgbds +### rgbds After downloading [rgbds](https://github.com/bentley/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`. -#### GnuWin +### GnuWin Ensure that the `gnuwin32\bin\` directory is included in `%PATH%`. Like rgbds above, this may instead be manually included on the command line before installation: `set path=%path%;C:\path\to\gnuwin32\bin`. -If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. - -### Building +## Building Within a command prompt in the project directory: @@ -41,12 +42,16 @@ make ``` Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `%PATH%` properly includes `rgbds` and `gnuwin32\bin`, and that the `lib` and `include` paths include the appropriate SDL2 directories. -#### Error -1073741819 +## Common Errors + +### Error -1073741819 If encountering an error that appears as follows: -> make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819 +``` make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819``` -Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. +Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. This appears to be an issue with GnuWin. +### The system cannot find the file specified (`usr/bin/mkdir`) +If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. This happens because the Git for Windows version of `which` is used instead of the GnuWin one, and it returns a Unix-style path instead of a Windows one. From c35fe8b5179cf8022454714d1848abe6cbe2644d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 9 Oct 2020 16:39:23 +0300 Subject: [PATCH 009/365] Make `gb.h` compatible with C++ again for bsnes integration. Fixed #300 --- Core/save_state.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/save_state.h b/Core/save_state.h index fcb9135..8e5fc4e 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -5,10 +5,16 @@ #define GB_PADDING(type, old_usage) type old_usage##__do_not_use +#ifdef __cplusplus +/* For bsnes integration. C++ code does not need section information, and throws a fit over certain types such + as anonymous enums inside unions */ +#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__ +#else #define GB_SECTION(name, ...) __attribute__ ((aligned (8))) union {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0] #define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) #define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start)) #define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) +#endif #define GB_aligned_double __attribute__ ((aligned (8))) double From faeb1d2e184a5bd4c9cdd023baaf09298eb57b68 Mon Sep 17 00:00:00 2001 From: slash0042 <57612744+slash0042@users.noreply.github.com> Date: Fri, 9 Oct 2020 23:21:20 +0000 Subject: [PATCH 010/365] Add libnx port --- libretro/Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libretro/Makefile b/libretro/Makefile index b327628..00b28cd 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -97,6 +97,15 @@ else ifeq ($(platform), switch) include $(LIBTRANSISTOR_HOME)/libtransistor.mk CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls STATIC_LINKING=1 +# Nintendo Switch (libnx) +else ifeq ($(platform), libnx) + include $(DEVKITPRO)/libnx/switch_rules + TARGET := $(TARGET_NAME)_libretro_$(platform).a + DEFINES += -DSWITCH=1 -D__SWITCH__ -DARM + CFLAGS += $(DEFINES) -fPIE -I$(LIBNX)/include/ -ffunction-sections -fdata-sections -ftls-model=local-exec + CFLAGS += -march=armv8-a -mtune=cortex-a57 -mtp=soft -mcpu=cortex-a57+crc+fp+simd -ffast-math + CXXFLAGS := $(ASFLAGS) $(CFLAGS) + STATIC_LINKING = 1 # Nintendo WiiU else ifeq ($(platform), wiiu) TARGET := $(TARGET_NAME)_libretro_$(platform).a From efe8d6b643c0829739bdb9892e80cbcbd4c27464 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Sat, 10 Oct 2020 01:21:13 +0000 Subject: [PATCH 011/365] Update Makefile --- libretro/Makefile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index 00b28cd..366ec17 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -109,8 +109,8 @@ else ifeq ($(platform), libnx) # Nintendo WiiU else ifeq ($(platform), wiiu) TARGET := $(TARGET_NAME)_libretro_$(platform).a - CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) - AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) + CC ?= $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) + AR ?= $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) CFLAGS += -DGEKKO -DHW_RVL -DWIIU -mwup -mcpu=750 -meabi -mhard-float -D__ppc__ -DMSB_FIRST -I$(DEVKITPRO)/libogc/include CFLAGS += -U__INT32_TYPE__ -U __UINT32_TYPE__ -D__INT32_TYPE__=int STATIC_LINKING = 1 @@ -149,7 +149,7 @@ else ifeq ($(platform), emscripten) fpic := -fPIC SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined else ifeq ($(platform), vita) - TARGET := $(TARGET_NAME)_vita.a + TARGET := $(TARGET_NAME)_libretro_vita.a CC = arm-vita-eabi-gcc AR = arm-vita-eabi-ar CFLAGS += -Wl,-q -O3 -fno-short-enums -fno-optimize-sibling-calls @@ -181,14 +181,14 @@ else ifneq (,$(findstring windows_msvc2017,$(platform))) TargetArchMoniker = $(subst $(WinPartition)_,,$(PlatformSuffix)) - CC = cl.exe - CXX = cl.exe - LD = link.exe + CC ?= cl.exe + CXX ?= cl.exe + LD ?= link.exe reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>nul))) fix_path = $(subst $(SPACE),\ ,$(subst \,/,$1)) - ProgramFiles86w := $(shell cmd /c "echo %PROGRAMFILES(x86)%") + ProgramFiles86w := $(shell cmd //c "echo %PROGRAMFILES(x86)%") ProgramFiles86 := $(shell cygpath "$(ProgramFiles86w)") WindowsSdkDir ?= $(call reg_query,InstallationFolder,HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0) @@ -251,7 +251,7 @@ else ifneq (,$(findstring windows_msvc2017,$(platform))) LDFLAGS += -DLL else - CC = gcc + CC ?= gcc TARGET := $(TARGET_NAME)_libretro.dll SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined endif From 8dc60d0b87d77db1effa7ba1c6003775affc16db Mon Sep 17 00:00:00 2001 From: radius Date: Sat, 10 Oct 2020 03:52:22 +0000 Subject: [PATCH 012/365] update makefile --- libretro/Makefile | 35 +++++++++++++++++++++++++++++++++-- libretro/Makefile.common | 2 ++ libretro/jni/Android.mk | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index 366ec17..c72b1f7 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -51,7 +51,7 @@ ifeq ($(platform), win) INCFLAGS += -I Windows endif -CORE_DIR += .. +CORE_DIR = ../ TARGET_NAME = sameboy LIBM = -lm @@ -90,7 +90,38 @@ else ifeq ($(platform), linux-portable) TARGET := $(TARGET_NAME)_libretro.$(EXT) fpic := -fPIC -nostdlib SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T - LIBM := + LIBM := +# (armv7 a7, hard point, neon based) ### +# NESC, SNESC, C64 mini +else ifeq ($(platform), classic_armv7_a7) + TARGET := $(TARGET_NAME)_libretro.so + fpic := -fPIC + SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined + CFLAGS += -Ofast \ + -flto=4 -fwhole-program -fuse-linker-plugin \ + -fdata-sections -ffunction-sections -Wl,--gc-sections \ + -fno-stack-protector -fno-ident -fomit-frame-pointer \ + -falign-functions=1 -falign-jumps=1 -falign-loops=1 \ + -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-unroll-loops \ + -fmerge-all-constants -fno-math-errno \ + -marm -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard + CXXFLAGS += $(CFLAGS) + CPPFLAGS += $(CFLAGS) + ASFLAGS += $(CFLAGS) + HAVE_NEON = 1 + ARCH = arm + BUILTIN_GPU = neon + USE_DYNAREC = 1 + ifeq ($(shell echo `$(CC) -dumpversion` "< 4.9" | bc -l), 1) + CFLAGS += -march=armv7-a + else + CFLAGS += -march=armv7ve + # If gcc is 5.0 or later + ifeq ($(shell echo `$(CC) -dumpversion` ">= 5" | bc -l), 1) + LDFLAGS += -static-libgcc -static-libstdc++ + endif + endif +####################################### # Nintendo Switch (libtransistor) else ifeq ($(platform), switch) TARGET := $(TARGET_NAME)_libretro_$(platform).a diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 7f7688a..430c03d 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -1,3 +1,5 @@ +VERSION := 0.13.6 + INCFLAGS := -I$(CORE_DIR) SOURCES_C := $(CORE_DIR)/Core/gb.c \ diff --git a/libretro/jni/Android.mk b/libretro/jni/Android.mk index 3b2d74b..e0646b9 100644 --- a/libretro/jni/Android.mk +++ b/libretro/jni/Android.mk @@ -8,7 +8,7 @@ include $(CORE_DIR)/libretro/Makefile.common GENERATED_SOURCES := $(filter %_boot.c,$(SOURCES_C)) -COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -Wno-multichar +COREFLAGS := -DINLINE=inline -D__LIBRETRO__ -DGB_INTERNAL $(INCFLAGS) -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" -Wno-multichar -DANDROID GIT_VERSION := " $(shell git rev-parse --short HEAD || echo unknown)" ifneq ($(GIT_VERSION)," unknown") From cd526d960e9eb8c5074478e1251aabb5d6158b4f Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Wed, 7 Oct 2020 21:59:29 -0500 Subject: [PATCH 013/365] libretro: changing model requires manual game restart --- libretro/libretro.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 24514d4..3f70611 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -207,7 +207,7 @@ static retro_environment_t environ_cb; static const struct retro_variable vars_single[] = { { "sameboy_color_correction_mode", "Color correction; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, { "sameboy_high_pass_filter_mode", "High-pass filter; accurate|remove dc offset|off" }, - { "sameboy_model", "Emulated model; Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, + { "sameboy_model", "Emulated model (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, { "sameboy_border", "Display border; Super Game Boy only|always|never" }, { "sameboy_rumble", "Enable rumble; rumble-enabled games|all games|never" }, { NULL } @@ -219,8 +219,8 @@ static const struct retro_variable vars_dual[] = { /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_model_2", "Emulated model for Game Boy #2; Auto|Game Boy|Game Boy Color|Game Boy Advance" }, + { "sameboy_model_1", "Emulated model for Game Boy #1 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, + { "sameboy_model_2", "Emulated model for Game Boy #2 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; accurate|remove dc offset|off" }, @@ -601,11 +601,7 @@ static void check_variables() new_model = MODEL_AUTO; } - if (new_model != model[0]) { - geometry_updated = true; - model[0] = new_model; - init_for_current_model(0); - } + model[0] = new_model; } var.key = "sameboy_border"; @@ -747,10 +743,7 @@ static void check_variables() new_model = MODEL_AUTO; } - if (model[0] != new_model) { - model[0] = new_model; - init_for_current_model(0); - } + model[0] = new_model; } var.key = "sameboy_model_2"; @@ -776,10 +769,7 @@ static void check_variables() new_model = MODEL_AUTO; } - if (model[1] != new_model) { - model[1] = new_model; - init_for_current_model(1); - } + model[1] = new_model; } var.key = "sameboy_screen_layout"; @@ -947,10 +937,14 @@ void retro_set_video_refresh(retro_video_refresh_t cb) void retro_reset(void) { + check_variables(); + for (int i = 0; i < emulated_devices; i++) { + init_for_current_model(i); GB_reset(&gameboy[i]); } + geometry_updated = true; } void retro_run(void) From 2bfca48e0f5f1c4eaa982bd2e4d50a2735c13ae2 Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Fri, 9 Oct 2020 23:01:42 -0500 Subject: [PATCH 014/365] libretro: fix core version --- libretro/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index c72b1f7..2ed87b8 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -17,8 +17,6 @@ filter_out2 = $(call filter_out1,$(call filter_out1,$1)) unixpath = $(subst \,/,$1) unixcygpath = /$(subst :,,$(call unixpath,$1)) -CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" - ifeq ($(platform),) platform = unix ifeq ($(shell uname -a),) @@ -302,6 +300,8 @@ endif include Makefile.common +CFLAGS += -DSAMEBOY_CORE_VERSION=\"$(VERSION)\" + OBJECTS := $(patsubst $(CORE_DIR)/%.c,$(CORE_DIR)/build/obj/%_libretro.c.o,$(SOURCES_C)) OBJOUT = -o From 526c2e029a8475806e40f2b027cc5cae42f96b52 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Oct 2020 14:50:11 +0300 Subject: [PATCH 015/365] Fix #296 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97cd2e4..1107fdc 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ SameBoy is an open-source project licensed under the MIT license, and you're wel ## Compilation SameBoy requires the following tools and libraries to build: - * clang + * clang (Recommended; required for macOS) or GCC * make * macOS Cocoa port: macOS SDK and Xcode (For command line tools and ibtool) * SDL port: libsdl2 From 714227883fbc196a73905bb38332aede65bfeed8 Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Sat, 10 Oct 2020 08:45:59 -0500 Subject: [PATCH 016/365] cross-compile friendly --- BootROMs/pb12.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BootROMs/pb12.c b/BootROMs/pb12.c index 3a72fab..cfedf6b 100644 --- a/BootROMs/pb12.c +++ b/BootROMs/pb12.c @@ -4,7 +4,6 @@ #include #include #include -#include void opts(uint8_t byte, uint8_t *options) { @@ -18,7 +17,8 @@ void write_all(int fd, const void *buf, size_t count) { while (count) { ssize_t written = write(fd, buf, count); if (written < 0) { - err(1, "write"); + fprintf(stderr, "write"); + exit(1); } count -= written; buf += written; From 696bebc673bf1d5ab7356e142c4a84f1fbbef184 Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Sat, 10 Oct 2020 17:14:10 +0000 Subject: [PATCH 017/365] libretro: joypad bitmasks --- libretro/libretro.c | 39 +- libretro/libretro.h | 1393 +++++++++++++++++++++++++++++++------------ 2 files changed, 1039 insertions(+), 393 deletions(-) diff --git a/libretro/libretro.c b/libretro/libretro.c index 3f70611..9e27f03 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -85,6 +85,8 @@ static retro_audio_sample_t audio_sample_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; +static bool libretro_supports_bitmasks = false; + static unsigned emulated_devices = 1; static bool initialized = false; static unsigned screen_layout = 0; @@ -119,24 +121,39 @@ static struct retro_rumble_interface rumble; static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) { + uint16_t joypad_bits = 0; + input_poll_cb(); + if (libretro_supports_bitmasks) { + joypad_bits = input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); + } + else { + unsigned j; + + for (j = 0; j < (RETRO_DEVICE_ID_JOYPAD_R3+1); j++) { + if (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, j)) { + joypad_bits |= (1 << j); + } + } + } + GB_set_key_state_for_player(gb, GB_KEY_RIGHT, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT)); GB_set_key_state_for_player(gb, GB_KEY_LEFT, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT)); GB_set_key_state_for_player(gb, GB_KEY_UP, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_UP)); GB_set_key_state_for_player(gb, GB_KEY_DOWN, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN)); GB_set_key_state_for_player(gb, GB_KEY_A, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_A)); GB_set_key_state_for_player(gb, GB_KEY_B, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_B)); GB_set_key_state_for_player(gb, GB_KEY_SELECT, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT)); GB_set_key_state_for_player(gb, GB_KEY_START, emulated_devices == 1 ? port : 0, - input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)); + joypad_bits & (1 << RETRO_DEVICE_ID_JOYPAD_START)); } @@ -840,6 +857,10 @@ void retro_init(void) else { log_cb = fallback_log; } + + if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) { + libretro_supports_bitmasks = true; + } } void retro_deinit(void) @@ -848,6 +869,8 @@ void retro_deinit(void) free(frame_buf_copy); frame_buf = NULL; frame_buf_copy = NULL; + + libretro_supports_bitmasks = false; } unsigned retro_api_version(void) diff --git a/libretro/libretro.h b/libretro/libretro.h index a4df6be..1fd2f5b 100644 --- a/libretro/libretro.h +++ b/libretro/libretro.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2016 The RetroArch team +/* Copyright (C) 2010-2018 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this libretro API header (libretro.h). @@ -32,7 +32,7 @@ extern "C" { #endif #ifndef __cplusplus -#if defined(_MSC_VER) && !defined(SN_TARGET_PS3) +#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(SN_TARGET_PS3) /* Hack applied for MSVC when compiling in C89 mode * as it isn't C99-compliant. */ #define bool unsigned char @@ -77,7 +77,7 @@ extern "C" { # endif #endif -/* Used for checking API/ABI mismatches that can break libretro +/* Used for checking API/ABI mismatches that can break libretro * implementations. * It is not incremented for compatible changes to the API. */ @@ -87,13 +87,13 @@ extern "C" { * Libretro's fundamental device abstractions. * * Libretro's input system consists of some standardized device types, - * such as a joypad (with/without analog), mouse, keyboard, lightgun + * such as a joypad (with/without analog), mouse, keyboard, lightgun * and a pointer. * - * The functionality of these devices are fixed, and individual cores + * The functionality of these devices are fixed, and individual cores * map their own concept of a controller to libretro's abstractions. - * This makes it possible for frontends to map the abstract types to a - * real input device, and not having to worry about binding input + * This makes it possible for frontends to map the abstract types to a + * real input device, and not having to worry about binding input * correctly to arbitrary controller layouts. */ @@ -104,43 +104,52 @@ extern "C" { /* Input disabled. */ #define RETRO_DEVICE_NONE 0 -/* The JOYPAD is called RetroPad. It is essentially a Super Nintendo - * controller, but with additional L2/R2/L3/R3 buttons, similar to a +/* The JOYPAD is called RetroPad. It is essentially a Super Nintendo + * controller, but with additional L2/R2/L3/R3 buttons, similar to a * PS1 DualShock. */ #define RETRO_DEVICE_JOYPAD 1 /* The mouse is a simple mouse, similar to Super Nintendo's mouse. * X and Y coordinates are reported relatively to last poll (poll callback). - * It is up to the libretro implementation to keep track of where the mouse + * It is up to the libretro implementation to keep track of where the mouse * pointer is supposed to be on the screen. - * The frontend must make sure not to interfere with its own hardware + * The frontend must make sure not to interfere with its own hardware * mouse pointer. */ #define RETRO_DEVICE_MOUSE 2 /* KEYBOARD device lets one poll for raw key pressed. - * It is poll based, so input callback will return with the current + * It is poll based, so input callback will return with the current * pressed state. * For event/text based keyboard input, see * RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. */ #define RETRO_DEVICE_KEYBOARD 3 -/* Lightgun X/Y coordinates are reported relatively to last poll, - * similar to mouse. */ +/* LIGHTGUN device is similar to Guncon-2 for PlayStation 2. + * It reports X/Y coordinates in screen space (similar to the pointer) + * in the range [-0x8000, 0x7fff] in both axes, with zero being center and + * -0x8000 being out of bounds. + * As well as reporting on/off screen state. It features a trigger, + * start/select buttons, auxiliary action buttons and a + * directional pad. A forced off-screen shot can be requested for + * auto-reloading function in some games. + */ #define RETRO_DEVICE_LIGHTGUN 4 /* The ANALOG device is an extension to JOYPAD (RetroPad). - * Similar to DualShock it adds two analog sticks. - * This is treated as a separate device type as it returns values in the - * full analog range of [-0x8000, 0x7fff]. Positive X axis is right. - * Positive Y axis is down. - * Only use ANALOG type when polling for analog values of the axes. + * Similar to DualShock2 it adds two analog sticks and all buttons can + * be analog. This is treated as a separate device type as it returns + * axis values in the full analog range of [-0x7fff, 0x7fff], + * although some devices may return -0x8000. + * Positive X axis is right. Positive Y axis is down. + * Buttons are returned in the range [0, 0x7fff]. + * Only use ANALOG type when polling for analog values. */ #define RETRO_DEVICE_ANALOG 5 /* Abstracts the concept of a pointing mechanism, e.g. touch. - * This allows libretro to query in absolute coordinates where on the + * This allows libretro to query in absolute coordinates where on the * screen a mouse (or something similar) is being placed. * For a touch centric device, coordinates reported are the coordinates * of the press. @@ -148,33 +157,34 @@ extern "C" { * Coordinates in X and Y are reported as: * [-0x7fff, 0x7fff]: -0x7fff corresponds to the far left/top of the screen, * and 0x7fff corresponds to the far right/bottom of the screen. - * The "screen" is here defined as area that is passed to the frontend and + * The "screen" is here defined as area that is passed to the frontend and * later displayed on the monitor. * * The frontend is free to scale/resize this screen as it sees fit, however, - * (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the + * (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the * game image, etc. * - * To check if the pointer coordinates are valid (e.g. a touch display + * To check if the pointer coordinates are valid (e.g. a touch display * actually being touched), PRESSED returns 1 or 0. * - * If using a mouse on a desktop, PRESSED will usually correspond to the + * If using a mouse on a desktop, PRESSED will usually correspond to the * left mouse button, but this is a frontend decision. * PRESSED will only return 1 if the pointer is inside the game screen. * - * For multi-touch, the index variable can be used to successively query + * For multi-touch, the index variable can be used to successively query * more presses. * If index = 0 returns true for _PRESSED, coordinates can be extracted - * with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with + * with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with * index = 1, and so on. - * Eventually _PRESSED will return false for an index. No further presses + * Eventually _PRESSED will return false for an index. No further presses * are registered at this point. */ #define RETRO_DEVICE_POINTER 6 /* Buttons for the RetroPad (JOYPAD). - * The placement of these is equivalent to placements on the + * The placement of these is equivalent to placements on the * Super Nintendo controller. - * L2/R2/L3/R3 buttons correspond to the PS1 DualShock. */ + * L2/R2/L3/R3 buttons correspond to the PS1 DualShock. + * Also used as id values for RETRO_DEVICE_INDEX_ANALOG_BUTTON */ #define RETRO_DEVICE_ID_JOYPAD_B 0 #define RETRO_DEVICE_ID_JOYPAD_Y 1 #define RETRO_DEVICE_ID_JOYPAD_SELECT 2 @@ -192,11 +202,14 @@ extern "C" { #define RETRO_DEVICE_ID_JOYPAD_L3 14 #define RETRO_DEVICE_ID_JOYPAD_R3 15 +#define RETRO_DEVICE_ID_JOYPAD_MASK 256 + /* Index / Id values for ANALOG device. */ -#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 -#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 -#define RETRO_DEVICE_ID_ANALOG_X 0 -#define RETRO_DEVICE_ID_ANALOG_Y 1 +#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 +#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 +#define RETRO_DEVICE_INDEX_ANALOG_BUTTON 2 +#define RETRO_DEVICE_ID_ANALOG_X 0 +#define RETRO_DEVICE_ID_ANALOG_Y 1 /* Id values for MOUSE. */ #define RETRO_DEVICE_ID_MOUSE_X 0 @@ -208,20 +221,36 @@ extern "C" { #define RETRO_DEVICE_ID_MOUSE_MIDDLE 6 #define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP 7 #define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN 8 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_4 9 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_5 10 -/* Id values for LIGHTGUN types. */ -#define RETRO_DEVICE_ID_LIGHTGUN_X 0 -#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 -#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 -#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 -#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 -#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 -#define RETRO_DEVICE_ID_LIGHTGUN_START 6 +/* Id values for LIGHTGUN. */ +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X 13 /*Absolute Position*/ +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y 14 /*Absolute*/ +#define RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN 15 /*Status Check*/ +#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 +#define RETRO_DEVICE_ID_LIGHTGUN_RELOAD 16 /*Forced off-screen shot*/ +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_A 3 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_B 4 +#define RETRO_DEVICE_ID_LIGHTGUN_START 6 +#define RETRO_DEVICE_ID_LIGHTGUN_SELECT 7 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_C 8 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP 9 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN 10 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT 11 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT 12 +/* deprecated */ +#define RETRO_DEVICE_ID_LIGHTGUN_X 0 /*Relative Position*/ +#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 /*Relative*/ +#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 /*Use Aux:A*/ +#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 /*Use Aux:B*/ +#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 /*Use Start*/ /* Id values for POINTER. */ #define RETRO_DEVICE_ID_POINTER_X 0 #define RETRO_DEVICE_ID_POINTER_Y 1 #define RETRO_DEVICE_ID_POINTER_PRESSED 2 +#define RETRO_DEVICE_ID_POINTER_COUNT 3 /* Returned from retro_get_region(). */ #define RETRO_REGION_NTSC 0 @@ -230,28 +259,33 @@ extern "C" { /* Id values for LANGUAGE */ enum retro_language { - RETRO_LANGUAGE_ENGLISH = 0, - RETRO_LANGUAGE_JAPANESE = 1, - RETRO_LANGUAGE_FRENCH = 2, - RETRO_LANGUAGE_SPANISH = 3, - RETRO_LANGUAGE_GERMAN = 4, - RETRO_LANGUAGE_ITALIAN = 5, - RETRO_LANGUAGE_DUTCH = 6, - RETRO_LANGUAGE_PORTUGUESE = 7, - RETRO_LANGUAGE_RUSSIAN = 8, - RETRO_LANGUAGE_KOREAN = 9, - RETRO_LANGUAGE_CHINESE_TRADITIONAL = 10, - RETRO_LANGUAGE_CHINESE_SIMPLIFIED = 11, - RETRO_LANGUAGE_ESPERANTO = 12, - RETRO_LANGUAGE_POLISH = 13, + RETRO_LANGUAGE_ENGLISH = 0, + RETRO_LANGUAGE_JAPANESE = 1, + RETRO_LANGUAGE_FRENCH = 2, + RETRO_LANGUAGE_SPANISH = 3, + RETRO_LANGUAGE_GERMAN = 4, + RETRO_LANGUAGE_ITALIAN = 5, + RETRO_LANGUAGE_DUTCH = 6, + RETRO_LANGUAGE_PORTUGUESE_BRAZIL = 7, + RETRO_LANGUAGE_PORTUGUESE_PORTUGAL = 8, + RETRO_LANGUAGE_RUSSIAN = 9, + RETRO_LANGUAGE_KOREAN = 10, + RETRO_LANGUAGE_CHINESE_TRADITIONAL = 11, + RETRO_LANGUAGE_CHINESE_SIMPLIFIED = 12, + RETRO_LANGUAGE_ESPERANTO = 13, + RETRO_LANGUAGE_POLISH = 14, + RETRO_LANGUAGE_VIETNAMESE = 15, + RETRO_LANGUAGE_ARABIC = 16, + RETRO_LANGUAGE_GREEK = 17, + RETRO_LANGUAGE_TURKISH = 18, RETRO_LANGUAGE_LAST, /* Ensure sizeof(enum) == sizeof(int) */ - RETRO_LANGUAGE_DUMMY = INT_MAX + RETRO_LANGUAGE_DUMMY = INT_MAX }; /* Passed to retro_get_memory_data/size(). - * If the memory type doesn't apply to the + * If the memory type doesn't apply to the * implementation NULL/0 can be returned. */ #define RETRO_MEMORY_MASK 0xff @@ -349,6 +383,10 @@ enum retro_key RETROK_x = 120, RETROK_y = 121, RETROK_z = 122, + RETROK_LEFTBRACE = 123, + RETROK_BAR = 124, + RETROK_RIGHTBRACE = 125, + RETROK_TILDE = 126, RETROK_DELETE = 127, RETROK_KP0 = 256, @@ -419,6 +457,7 @@ enum retro_key RETROK_POWER = 320, RETROK_EURO = 321, RETROK_UNDO = 322, + RETROK_OEM_102 = 323, RETROK_LAST, @@ -441,7 +480,7 @@ enum retro_mod RETROKMOD_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ }; -/* If set, this call is not part of the public libretro API yet. It can +/* If set, this call is not part of the public libretro API yet. It can * change or be removed at any time. */ #define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000 /* Environment callback to be used internally in frontend. */ @@ -450,12 +489,14 @@ enum retro_mod /* Environment commands. */ #define RETRO_ENVIRONMENT_SET_ROTATION 1 /* const unsigned * -- * Sets screen rotation of graphics. - * Is only implemented if rotation can be accelerated by hardware. - * Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, + * Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, * 270 degrees counter-clockwise respectively. */ #define RETRO_ENVIRONMENT_GET_OVERSCAN 2 /* bool * -- - * Boolean value whether or not the implementation should use overscan, + * NOTE: As of 2019 this callback is considered deprecated in favor of + * using core options to manage overscan in a more nuanced, core-specific way. + * + * Boolean value whether or not the implementation should use overscan, * or crop away overscan. */ #define RETRO_ENVIRONMENT_GET_CAN_DUPE 3 /* bool * -- @@ -463,15 +504,15 @@ enum retro_mod * passing NULL to video frame callback. */ - /* Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), + /* Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), * and reserved to avoid possible ABI clash. */ #define RETRO_ENVIRONMENT_SET_MESSAGE 6 /* const struct retro_message * -- - * Sets a message to be displayed in implementation-specific manner + * Sets a message to be displayed in implementation-specific manner * for a certain amount of 'frames'. - * Should not be used for trivial messages, which should simply be - * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a + * Should not be used for trivial messages, which should simply be + * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a * fallback, stderr). */ #define RETRO_ENVIRONMENT_SHUTDOWN 7 /* N/A (NULL) -- @@ -499,15 +540,15 @@ enum retro_mod #define RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY 9 /* const char ** -- * Returns the "system" directory of the frontend. - * This directory can be used to store system specific + * This directory can be used to store system specific * content such as BIOSes, configuration data, etc. * The returned value can be NULL. * If so, no such directory is defined, * and it's up to the implementation to find a suitable directory. * - * NOTE: Some cores used this folder also for "save" data such as + * NOTE: Some cores used this folder also for "save" data such as * memory cards, etc, for lack of a better place to put it. - * This is now discouraged, and if possible, cores should try to + * This is now discouraged, and if possible, cores should try to * use the new GET_SAVE_DIRECTORY. */ #define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10 @@ -515,19 +556,19 @@ enum retro_mod * Sets the internal pixel format used by the implementation. * The default pixel format is RETRO_PIXEL_FORMAT_0RGB1555. * This pixel format however, is deprecated (see enum retro_pixel_format). - * If the call returns false, the frontend does not support this pixel + * If the call returns false, the frontend does not support this pixel * format. * - * This function should be called inside retro_load_game() or + * This function should be called inside retro_load_game() or * retro_get_system_av_info(). */ #define RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS 11 /* const struct retro_input_descriptor * -- * Sets an array of retro_input_descriptors. * It is up to the frontend to present this in a usable way. - * The array is terminated by retro_input_descriptor::description + * The array is terminated by retro_input_descriptor::description * being set to NULL. - * This function can be called at any time, but it is recommended + * This function can be called at any time, but it is recommended * to call it as early as possible. */ #define RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK 12 @@ -536,52 +577,55 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE 13 /* const struct retro_disk_control_callback * -- - * Sets an interface which frontend can use to eject and insert + * Sets an interface which frontend can use to eject and insert * disk images. - * This is used for games which consist of multiple images and + * This is used for games which consist of multiple images and * must be manually swapped out by the user (e.g. PSX). */ #define RETRO_ENVIRONMENT_SET_HW_RENDER 14 /* struct retro_hw_render_callback * -- - * Sets an interface to let a libretro core render with + * Sets an interface to let a libretro core render with * hardware acceleration. * Should be called in retro_load_game(). - * If successful, libretro cores will be able to render to a + * If successful, libretro cores will be able to render to a * frontend-provided framebuffer. - * The size of this framebuffer will be at least as large as + * The size of this framebuffer will be at least as large as * max_width/max_height provided in get_av_info(). - * If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or + * If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or * NULL to retro_video_refresh_t. */ #define RETRO_ENVIRONMENT_GET_VARIABLE 15 /* struct retro_variable * -- * Interface to acquire user-defined information from environment * that cannot feasibly be supported in a multi-system way. - * 'key' should be set to a key which has already been set by + * 'key' should be set to a key which has already been set by * SET_VARIABLES. * 'data' will be set to a value or NULL. */ #define RETRO_ENVIRONMENT_SET_VARIABLES 16 /* const struct retro_variable * -- * Allows an implementation to signal the environment - * which variables it might want to check for later using + * which variables it might want to check for later using * GET_VARIABLE. - * This allows the frontend to present these variables to + * This allows the frontend to present these variables to * a user dynamically. - * This should be called as early as possible (ideally in - * retro_set_environment). + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterward it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. * - * 'data' points to an array of retro_variable structs + * 'data' points to an array of retro_variable structs * terminated by a { NULL, NULL } element. - * retro_variable::key should be namespaced to not collide - * with other implementations' keys. E.g. A core called + * retro_variable::key should be namespaced to not collide + * with other implementations' keys. E.g. A core called * 'foo' should use keys named as 'foo_option'. - * retro_variable::value should contain a human readable - * description of the key as well as a '|' delimited list + * retro_variable::value should contain a human readable + * description of the key as well as a '|' delimited list * of expected values. * - * The number of possible options should be very limited, - * i.e. it should be feasible to cycle through options + * The number of possible options should be very limited, + * i.e. it should be feasible to cycle through options * without a keyboard. * * First entry should be treated as a default. @@ -589,11 +633,11 @@ enum retro_mod * Example entry: * { "foo_option", "Speed hack coprocessor X; false|true" } * - * Text before first ';' is description. This ';' must be - * followed by a space, and followed by a list of possible + * Text before first ';' is description. This ';' must be + * followed by a space, and followed by a list of possible * values split up with '|'. * - * Only strings are operated on. The possible values will + * Only strings are operated on. The possible values will * generally be displayed and stored as-is by the frontend. */ #define RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE 17 @@ -604,72 +648,72 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME 18 /* const bool * -- - * If true, the libretro implementation supports calls to + * If true, the libretro implementation supports calls to * retro_load_game() with NULL as argument. * Used by cores which can run without particular game data. * This should be called within retro_set_environment() only. */ #define RETRO_ENVIRONMENT_GET_LIBRETRO_PATH 19 /* const char ** -- - * Retrieves the absolute path from where this libretro + * Retrieves the absolute path from where this libretro * implementation was loaded. - * NULL is returned if the libretro was loaded statically - * (i.e. linked statically to frontend), or if the path cannot be + * NULL is returned if the libretro was loaded statically + * (i.e. linked statically to frontend), or if the path cannot be * determined. - * Mostly useful in cooperation with SET_SUPPORT_NO_GAME as assets can + * Mostly useful in cooperation with SET_SUPPORT_NO_GAME as assets can * be loaded without ugly hacks. */ - - /* Environment 20 was an obsolete version of SET_AUDIO_CALLBACK. + + /* Environment 20 was an obsolete version of SET_AUDIO_CALLBACK. * It was not used by any known core at the time, * and was removed from the API. */ +#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 + /* const struct retro_frame_time_callback * -- + * Lets the core know how much time has passed since last + * invocation of retro_run(). + * The frontend can tamper with the timing to fake fast-forward, + * slow-motion, frame stepping, etc. + * In this case the delta time will use the reference value + * in frame_time_callback.. + */ #define RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK 22 /* const struct retro_audio_callback * -- - * Sets an interface which is used to notify a libretro core about audio + * Sets an interface which is used to notify a libretro core about audio * being available for writing. - * The callback can be called from any thread, so a core using this must + * The callback can be called from any thread, so a core using this must * have a thread safe audio implementation. - * It is intended for games where audio and video are completely + * It is intended for games where audio and video are completely * asynchronous and audio can be generated on the fly. - * This interface is not recommended for use with emulators which have + * This interface is not recommended for use with emulators which have * highly synchronous audio. * - * The callback only notifies about writability; the libretro core still + * The callback only notifies about writability; the libretro core still * has to call the normal audio callbacks - * to write audio. The audio callbacks must be called from within the + * to write audio. The audio callbacks must be called from within the * notification callback. * The amount of audio data to write is up to the implementation. * Generally, the audio callback will be called continously in a loop. * - * Due to thread safety guarantees and lack of sync between audio and - * video, a frontend can selectively disallow this interface based on - * internal configuration. A core using this interface must also + * Due to thread safety guarantees and lack of sync between audio and + * video, a frontend can selectively disallow this interface based on + * internal configuration. A core using this interface must also * implement the "normal" audio interface. * - * A libretro core using SET_AUDIO_CALLBACK should also make use of + * A libretro core using SET_AUDIO_CALLBACK should also make use of * SET_FRAME_TIME_CALLBACK. */ -#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 - /* const struct retro_frame_time_callback * -- - * Lets the core know how much time has passed since last - * invocation of retro_run(). - * The frontend can tamper with the timing to fake fast-forward, - * slow-motion, frame stepping, etc. - * In this case the delta time will use the reference value - * in frame_time_callback.. - */ #define RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE 23 /* struct retro_rumble_interface * -- - * Gets an interface which is used by a libretro core to set + * Gets an interface which is used by a libretro core to set * state of rumble motors in controllers. - * A strong and weak motor is supported, and they can be + * A strong and weak motor is supported, and they can be * controlled indepedently. */ #define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24 /* uint64_t * -- - * Gets a bitmask telling which device type are expected to be + * Gets a bitmask telling which device type are expected to be * handled properly in a call to retro_input_state_t. - * Devices which are not handled or recognized always return + * Devices which are not handled or recognized always return * 0 in retro_input_state_t. * Example bitmask: caps = (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG). * Should only be called in retro_run(). @@ -678,56 +722,56 @@ enum retro_mod /* struct retro_sensor_interface * -- * Gets access to the sensor interface. * The purpose of this interface is to allow - * setting state related to sensors such as polling rate, + * setting state related to sensors such as polling rate, * enabling/disable it entirely, etc. - * Reading sensor state is done via the normal + * Reading sensor state is done via the normal * input_state_callback API. */ #define RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE (26 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* struct retro_camera_callback * -- * Gets an interface to a video camera driver. - * A libretro core can use this interface to get access to a + * A libretro core can use this interface to get access to a * video camera. - * New video frames are delivered in a callback in same + * New video frames are delivered in a callback in same * thread as retro_run(). * * GET_CAMERA_INTERFACE should be called in retro_load_game(). * - * Depending on the camera implementation used, camera frames + * Depending on the camera implementation used, camera frames * will be delivered as a raw framebuffer, * or as an OpenGL texture directly. * - * The core has to tell the frontend here which types of + * The core has to tell the frontend here which types of * buffers can be handled properly. - * An OpenGL texture can only be handled when using a + * An OpenGL texture can only be handled when using a * libretro GL core (SET_HW_RENDER). - * It is recommended to use a libretro GL core when + * It is recommended to use a libretro GL core when * using camera interface. * - * The camera is not started automatically. The retrieved start/stop + * The camera is not started automatically. The retrieved start/stop * functions must be used to explicitly * start and stop the camera driver. */ #define RETRO_ENVIRONMENT_GET_LOG_INTERFACE 27 /* struct retro_log_callback * -- - * Gets an interface for logging. This is useful for + * Gets an interface for logging. This is useful for * logging in a cross-platform way - * as certain platforms cannot use use stderr for logging. + * as certain platforms cannot use stderr for logging. * It also allows the frontend to * show logging information in a more suitable way. - * If this interface is not used, libretro cores should + * If this interface is not used, libretro cores should * log to stderr as desired. */ #define RETRO_ENVIRONMENT_GET_PERF_INTERFACE 28 /* struct retro_perf_callback * -- - * Gets an interface for performance counters. This is useful - * for performance logging in a cross-platform way and for detecting + * Gets an interface for performance counters. This is useful + * for performance logging in a cross-platform way and for detecting * architecture-specific features, such as SIMD support. */ #define RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE 29 /* struct retro_location_callback * -- * Gets access to the location interface. - * The purpose of this interface is to be able to retrieve + * The purpose of this interface is to be able to retrieve * location-based information from the host device, * such as current latitude / longitude. */ @@ -735,7 +779,7 @@ enum retro_mod #define RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY 30 /* const char ** -- * Returns the "core assets" directory of the frontend. - * This directory can be used to store specific assets that the + * This directory can be used to store specific assets that the * core relies upon, such as art assets, * input data, etc etc. * The returned value can be NULL. @@ -744,76 +788,77 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY 31 /* const char ** -- - * Returns the "save" directory of the frontend. - * This directory can be used to store SRAM, memory cards, - * high scores, etc, if the libretro core + * Returns the "save" directory of the frontend, unless there is no + * save directory available. The save directory should be used to + * store SRAM, memory cards, high scores, etc, if the libretro core * cannot use the regular memory interface (retro_get_memory_data()). * - * NOTE: libretro cores used to check GET_SYSTEM_DIRECTORY for - * similar things before. - * They should still check GET_SYSTEM_DIRECTORY if they want to - * be backwards compatible. - * The path here can be NULL. It should only be non-NULL if the - * frontend user has set a specific save path. + * If the frontend cannot designate a save directory, it will return + * NULL to indicate that the core should attempt to operate without a + * save directory set. + * + * NOTE: early libretro cores used the system directory for save + * files. Cores that need to be backwards-compatible can still check + * GET_SYSTEM_DIRECTORY. */ #define RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO 32 /* const struct retro_system_av_info * -- - * Sets a new av_info structure. This can only be called from + * Sets a new av_info structure. This can only be called from * within retro_run(). - * This should *only* be used if the core is completely altering the + * This should *only* be used if the core is completely altering the * internal resolutions, aspect ratios, timings, sampling rate, etc. - * Calling this can require a full reinitialization of video/audio + * Calling this can require a full reinitialization of video/audio * drivers in the frontend, * - * so it is important to call it very sparingly, and usually only with + * so it is important to call it very sparingly, and usually only with * the users explicit consent. - * An eventual driver reinitialize will happen so that video and + * An eventual driver reinitialize will happen so that video and * audio callbacks - * happening after this call within the same retro_run() call will + * happening after this call within the same retro_run() call will * target the newly initialized driver. * - * This callback makes it possible to support configurable resolutions + * This callback makes it possible to support configurable resolutions * in games, which can be useful to * avoid setting the "worst case" in max_width/max_height. * - * ***HIGHLY RECOMMENDED*** Do not call this callback every time + * ***HIGHLY RECOMMENDED*** Do not call this callback every time * resolution changes in an emulator core if it's - * expected to be a temporary change, for the reasons of possible + * expected to be a temporary change, for the reasons of possible * driver reinitialization. - * This call is not a free pass for not trying to provide - * correct values in retro_get_system_av_info(). If you need to change - * things like aspect ratio or nominal width/height, - * use RETRO_ENVIRONMENT_SET_GEOMETRY, which is a softer variant + * This call is not a free pass for not trying to provide + * correct values in retro_get_system_av_info(). If you need to change + * things like aspect ratio or nominal width/height, + * use RETRO_ENVIRONMENT_SET_GEOMETRY, which is a softer variant * of SET_SYSTEM_AV_INFO. * - * If this returns false, the frontend does not acknowledge a + * If this returns false, the frontend does not acknowledge a * changed av_info struct. */ #define RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK 33 /* const struct retro_get_proc_address_interface * -- - * Allows a libretro core to announce support for the + * Allows a libretro core to announce support for the * get_proc_address() interface. - * This interface allows for a standard way to extend libretro where + * This interface allows for a standard way to extend libretro where * use of environment calls are too indirect, * e.g. for cases where the frontend wants to call directly into the core. * - * If a core wants to expose this interface, SET_PROC_ADDRESS_CALLBACK + * If a core wants to expose this interface, SET_PROC_ADDRESS_CALLBACK * **MUST** be called from within retro_set_environment(). */ #define RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO 34 /* const struct retro_subsystem_info * -- * This environment call introduces the concept of libretro "subsystems". - * A subsystem is a variant of a libretro core which supports + * A subsystem is a variant of a libretro core which supports * different kinds of games. - * The purpose of this is to support e.g. emulators which might + * The purpose of this is to support e.g. emulators which might * have special needs, e.g. Super Nintendo's Super GameBoy, Sufami Turbo. - * It can also be used to pick among subsystems in an explicit way + * It can also be used to pick among subsystems in an explicit way * if the libretro implementation is a multi-system emulator itself. * * Loading a game via a subsystem is done with retro_load_game_special(), - * and this environment call allows a libretro core to expose which + * and this environment call allows a libretro core to expose which * subsystems are supported for use with retro_load_game_special(). - * A core passes an array of retro_game_special_info which is terminated + * A core passes an array of retro_game_special_info which is terminated * with a zeroed out retro_game_special_info struct. * * If a core wants to use this functionality, SET_SUBSYSTEM_INFO @@ -821,68 +866,81 @@ enum retro_mod */ #define RETRO_ENVIRONMENT_SET_CONTROLLER_INFO 35 /* const struct retro_controller_info * -- - * This environment call lets a libretro core tell the frontend - * which controller types are recognized in calls to + * This environment call lets a libretro core tell the frontend + * which controller subclasses are recognized in calls to * retro_set_controller_port_device(). * - * Some emulators such as Super Nintendo - * support multiple lightgun types which must be specifically - * selected from. - * It is therefore sometimes necessary for a frontend to be able - * to tell the core about a special kind of input device which is - * not covered by the libretro input API. + * Some emulators such as Super Nintendo support multiple lightgun + * types which must be specifically selected from. It is therefore + * sometimes necessary for a frontend to be able to tell the core + * about a special kind of input device which is not specifcally + * provided by the Libretro API. * - * In order for a frontend to understand the workings of an input device, - * it must be a specialized type - * of the generic device types already defined in the libretro API. + * In order for a frontend to understand the workings of those devices, + * they must be defined as a specialized subclass of the generic device + * types already defined in the libretro API. * - * Which devices are supported can vary per input port. - * The core must pass an array of const struct retro_controller_info which - * is terminated with a blanked out struct. Each element of the struct - * corresponds to an ascending port index to - * retro_set_controller_port_device(). - * Even if special device types are set in the libretro core, + * The core must pass an array of const struct retro_controller_info which + * is terminated with a blanked out struct. Each element of the + * retro_controller_info struct corresponds to the ascending port index + * that is passed to retro_set_controller_port_device() when that function + * is called to indicate to the core that the frontend has changed the + * active device subclass. SEE ALSO: retro_set_controller_port_device() + * + * The ascending input port indexes provided by the core in the struct + * are generally presented by frontends as ascending User # or Player #, + * such as Player 1, Player 2, Player 3, etc. Which device subclasses are + * supported can vary per input port. + * + * The first inner element of each entry in the retro_controller_info array + * is a retro_controller_description struct that specifies the names and + * codes of all device subclasses that are available for the corresponding + * User or Player, beginning with the generic Libretro device that the + * subclasses are derived from. The second inner element of each entry is the + * total number of subclasses that are listed in the retro_controller_description. + * + * NOTE: Even if special device types are set in the libretro core, * libretro should only poll input based on the base input device types. */ #define RETRO_ENVIRONMENT_SET_MEMORY_MAPS (36 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* const struct retro_memory_map * -- - * This environment call lets a libretro core tell the frontend + * This environment call lets a libretro core tell the frontend * about the memory maps this core emulates. * This can be used to implement, for example, cheats in a core-agnostic way. * - * Should only be used by emulators; it doesn't make much sense for + * Should only be used by emulators; it doesn't make much sense for * anything else. - * It is recommended to expose all relevant pointers through + * It is recommended to expose all relevant pointers through * retro_get_memory_* as well. * * Can be called from retro_init and retro_load_game. */ #define RETRO_ENVIRONMENT_SET_GEOMETRY 37 /* const struct retro_game_geometry * -- - * This environment call is similar to SET_SYSTEM_AV_INFO for changing - * video parameters, but provides a guarantee that drivers will not be + * This environment call is similar to SET_SYSTEM_AV_INFO for changing + * video parameters, but provides a guarantee that drivers will not be * reinitialized. * This can only be called from within retro_run(). * - * The purpose of this call is to allow a core to alter nominal - * width/heights as well as aspect ratios on-the-fly, which can be + * The purpose of this call is to allow a core to alter nominal + * width/heights as well as aspect ratios on-the-fly, which can be * useful for some emulators to change in run-time. * * max_width/max_height arguments are ignored and cannot be changed - * with this call as this could potentially require a reinitialization or a + * with this call as this could potentially require a reinitialization or a * non-constant time operation. * If max_width/max_height are to be changed, SET_SYSTEM_AV_INFO is required. * - * A frontend must guarantee that this environment call completes in + * A frontend must guarantee that this environment call completes in * constant time. */ -#define RETRO_ENVIRONMENT_GET_USERNAME 38 +#define RETRO_ENVIRONMENT_GET_USERNAME 38 /* const char ** * Returns the specified username of the frontend, if specified by the user. - * This username can be used as a nickname for a core that has online facilities + * This username can be used as a nickname for a core that has online facilities * or any other mode where personalization of the user is desirable. * The returned value can be NULL. - * If this environ callback is used by a core that requires a valid username, + * If this environ callback is used by a core that requires a valid username, * a default username should be specified by the core. */ #define RETRO_ENVIRONMENT_GET_LANGUAGE 39 @@ -920,20 +978,6 @@ enum retro_mod * A frontend must make sure that the pointer obtained from this function is * writeable (and readable). */ - -enum retro_hw_render_interface_type -{ - RETRO_HW_RENDER_INTERFACE_VULKAN = 0, - RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX -}; - -/* Base struct. All retro_hw_render_interface_* types - * contain at least these fields. */ -struct retro_hw_render_interface -{ - enum retro_hw_render_interface_type interface_type; - unsigned interface_version; -}; #define RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE (41 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* const struct retro_hw_render_interface ** -- * Returns an API specific rendering interface for accessing API specific data. @@ -945,7 +989,6 @@ struct retro_hw_render_interface * Similarly, after context_destroyed callback returns, * the contents of the HW_RENDER_INTERFACE are invalidated. */ - #define RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS (42 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* const bool * -- * If true, the libretro implementation supports achievements @@ -954,6 +997,483 @@ struct retro_hw_render_interface * * This must be called before the first call to retro_run. */ +#define RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE (43 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* const struct retro_hw_render_context_negotiation_interface * -- + * Sets an interface which lets the libretro core negotiate with frontend how a context is created. + * The semantics of this interface depends on which API is used in SET_HW_RENDER earlier. + * This interface will be used when the frontend is trying to create a HW rendering context, + * so it will be used after SET_HW_RENDER, but before the context_reset callback. + */ +#define RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS 44 + /* uint64_t * -- + * Sets quirk flags associated with serialization. The frontend will zero any flags it doesn't + * recognize or support. Should be set in either retro_init or retro_load_game, but not both. + */ +#define RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT (44 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* N/A (null) * -- + * The frontend will try to use a 'shared' hardware context (mostly applicable + * to OpenGL) when a hardware context is being set up. + * + * Returns true if the frontend supports shared hardware contexts and false + * if the frontend does not support shared hardware contexts. + * + * This will do nothing on its own until SET_HW_RENDER env callbacks are + * being used. + */ +#define RETRO_ENVIRONMENT_GET_VFS_INTERFACE (45 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_vfs_interface_info * -- + * Gets access to the VFS interface. + * VFS presence needs to be queried prior to load_game or any + * get_system/save/other_directory being called to let front end know + * core supports VFS before it starts handing out paths. + * It is recomended to do so in retro_set_environment + */ +#define RETRO_ENVIRONMENT_GET_LED_INTERFACE (46 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_led_interface * -- + * Gets an interface which is used by a libretro core to set + * state of LEDs. + */ +#define RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE (47 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* int * -- + * Tells the core if the frontend wants audio or video. + * If disabled, the frontend will discard the audio or video, + * so the core may decide to skip generating a frame or generating audio. + * This is mainly used for increasing performance. + * Bit 0 (value 1): Enable Video + * Bit 1 (value 2): Enable Audio + * Bit 2 (value 4): Use Fast Savestates. + * Bit 3 (value 8): Hard Disable Audio + * Other bits are reserved for future use and will default to zero. + * If video is disabled: + * * The frontend wants the core to not generate any video, + * including presenting frames via hardware acceleration. + * * The frontend's video frame callback will do nothing. + * * After running the frame, the video output of the next frame should be + * no different than if video was enabled, and saving and loading state + * should have no issues. + * If audio is disabled: + * * The frontend wants the core to not generate any audio. + * * The frontend's audio callbacks will do nothing. + * * After running the frame, the audio output of the next frame should be + * no different than if audio was enabled, and saving and loading state + * should have no issues. + * Fast Savestates: + * * Guaranteed to be created by the same binary that will load them. + * * Will not be written to or read from the disk. + * * Suggest that the core assumes loading state will succeed. + * * Suggest that the core updates its memory buffers in-place if possible. + * * Suggest that the core skips clearing memory. + * * Suggest that the core skips resetting the system. + * * Suggest that the core may skip validation steps. + * Hard Disable Audio: + * * Used for a secondary core when running ahead. + * * Indicates that the frontend will never need audio from the core. + * * Suggests that the core may stop synthesizing audio, but this should not + * compromise emulation accuracy. + * * Audio output for the next frame does not matter, and the frontend will + * never need an accurate audio state in the future. + * * State will never be saved when using Hard Disable Audio. + */ +#define RETRO_ENVIRONMENT_GET_MIDI_INTERFACE (48 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_midi_interface ** -- + * Returns a MIDI interface that can be used for raw data I/O. + */ + +#define RETRO_ENVIRONMENT_GET_FASTFORWARDING (49 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* bool * -- + * Boolean value that indicates whether or not the frontend is in + * fastforwarding mode. + */ + +#define RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE (50 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* float * -- + * Float value that lets us know what target refresh rate + * is curently in use by the frontend. + * + * The core can use the returned value to set an ideal + * refresh rate/framerate. + */ + +#define RETRO_ENVIRONMENT_GET_INPUT_BITMASKS (51 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* bool * -- + * Boolean value that indicates whether or not the frontend supports + * input bitmasks being returned by retro_input_state_t. The advantage + * of this is that retro_input_state_t has to be only called once to + * grab all button states instead of multiple times. + * + * If it returns true, you can pass RETRO_DEVICE_ID_JOYPAD_MASK as 'id' + * to retro_input_state_t (make sure 'device' is set to RETRO_DEVICE_JOYPAD). + * It will return a bitmask of all the digital buttons. + */ + +#define RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION 52 + /* unsigned * -- + * Unsigned value is the API version number of the core options + * interface supported by the frontend. If callback return false, + * API version is assumed to be 0. + * + * In legacy code, core options are set by passing an array of + * retro_variable structs to RETRO_ENVIRONMENT_SET_VARIABLES. + * This may be still be done regardless of the core options + * interface version. + * + * If version is 1 however, core options may instead be set by + * passing an array of retro_core_option_definition structs to + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS, or a 2D array of + * retro_core_option_definition structs to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. + * This allows the core to additionally set option sublabel information + * and/or provide localisation support. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS 53 + /* const struct retro_core_option_definition ** -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_ENHANCED_CORE_OPTIONS + * returns an API version of 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * + * 'data' points to an array of retro_core_option_definition structs + * terminated by a { NULL, NULL, NULL, {{0}}, NULL } element. + * retro_core_option_definition::key should be namespaced to not collide + * with other implementations' keys. e.g. A core called + * 'foo' should use keys named as 'foo_option'. + * retro_core_option_definition::desc should contain a human readable + * description of the key. + * retro_core_option_definition::info should contain any additional human + * readable information text that a typical user may need to + * understand the functionality of the option. + * retro_core_option_definition::values is an array of retro_core_option_value + * structs terminated by a { NULL, NULL } element. + * > retro_core_option_definition::values[index].value is an expected option + * value. + * > retro_core_option_definition::values[index].label is a human readable + * label used when displaying the value on screen. If NULL, + * the value itself is used. + * retro_core_option_definition::default_value is the default core option + * setting. It must match one of the expected option values in the + * retro_core_option_definition::values array. If it does not, or the + * default value is NULL, the first entry in the + * retro_core_option_definition::values array is treated as the default. + * + * The number of possible options should be very limited, + * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX. + * i.e. it should be feasible to cycle through options + * without a keyboard. + * + * First entry should be treated as a default. + * + * Example entry: + * { + * "foo_option", + * "Speed hack coprocessor X", + * "Provides increased performance at the expense of reduced accuracy", + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * Only strings are operated on. The possible values will + * generally be displayed and stored as-is by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL 54 + /* const struct retro_core_options_intl * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_ENHANCED_CORE_OPTIONS + * returns an API version of 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_intl struct. + * + * retro_core_options_intl::us is a pointer to an array of + * retro_core_option_definition structs defining the US English + * core options implementation. It must point to a valid array. + * + * retro_core_options_intl::local is a pointer to an array of + * retro_core_option_definition structs defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_intl::us is used by the frontend). Any items + * missing from this array will be read from retro_core_options_intl::us + * instead. + * + * NOTE: Default core option values are always taken from the + * retro_core_options_intl::us array. Any default values in + * retro_core_options_intl::local array will be ignored. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY 55 + /* struct retro_core_option_display * -- + * + * Allows an implementation to signal the environment to show + * or hide a variable when displaying core options. This is + * considered a *suggestion*. The frontend is free to ignore + * this callback, and its implementation not considered mandatory. + * + * 'data' points to a retro_core_option_display struct + * + * retro_core_option_display::key is a variable identifier + * which has already been set by SET_VARIABLES/SET_CORE_OPTIONS. + * + * retro_core_option_display::visible is a boolean, specifying + * whether variable should be displayed + * + * Note that all core option variables will be set visible by + * default when calling SET_VARIABLES/SET_CORE_OPTIONS. + */ + +/* VFS functionality */ + +/* File paths: + * File paths passed as parameters when using this API shall be well formed UNIX-style, + * using "/" (unquoted forward slash) as directory separator regardless of the platform's native separator. + * Paths shall also include at least one forward slash ("game.bin" is an invalid path, use "./game.bin" instead). + * Other than the directory separator, cores shall not make assumptions about path format: + * "C:/path/game.bin", "http://example.com/game.bin", "#game/game.bin", "./game.bin" (without quotes) are all valid paths. + * Cores may replace the basename or remove path components from the end, and/or add new components; + * however, cores shall not append "./", "../" or multiple consecutive forward slashes ("//") to paths they request to front end. + * The frontend is encouraged to make such paths work as well as it can, but is allowed to give up if the core alters paths too much. + * Frontends are encouraged, but not required, to support native file system paths (modulo replacing the directory separator, if applicable). + * Cores are allowed to try using them, but must remain functional if the front rejects such requests. + * Cores are encouraged to use the libretro-common filestream functions for file I/O, + * as they seamlessly integrate with VFS, deal with directory separator replacement as appropriate + * and provide platform-specific fallbacks in cases where front ends do not support VFS. */ + +/* Opaque file handle + * Introduced in VFS API v1 */ +struct retro_vfs_file_handle; + +/* Opaque directory handle + * Introduced in VFS API v3 */ +struct retro_vfs_dir_handle; + +/* File open flags + * Introduced in VFS API v1 */ +#define RETRO_VFS_FILE_ACCESS_READ (1 << 0) /* Read only mode */ +#define RETRO_VFS_FILE_ACCESS_WRITE (1 << 1) /* Write only mode, discard contents and overwrites existing file unless RETRO_VFS_FILE_ACCESS_UPDATE is also specified */ +#define RETRO_VFS_FILE_ACCESS_READ_WRITE (RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE) /* Read-write mode, discard contents and overwrites existing file unless RETRO_VFS_FILE_ACCESS_UPDATE is also specified*/ +#define RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING (1 << 2) /* Prevents discarding content of existing files opened for writing */ + +/* These are only hints. The frontend may choose to ignore them. Other than RAM/CPU/etc use, + and how they react to unlikely external interference (for example someone else writing to that file, + or the file's server going down), behavior will not change. */ +#define RETRO_VFS_FILE_ACCESS_HINT_NONE (0) +/* Indicate that the file will be accessed many times. The frontend should aggressively cache everything. */ +#define RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS (1 << 0) + +/* Seek positions */ +#define RETRO_VFS_SEEK_POSITION_START 0 +#define RETRO_VFS_SEEK_POSITION_CURRENT 1 +#define RETRO_VFS_SEEK_POSITION_END 2 + +/* stat() result flags + * Introduced in VFS API v3 */ +#define RETRO_VFS_STAT_IS_VALID (1 << 0) +#define RETRO_VFS_STAT_IS_DIRECTORY (1 << 1) +#define RETRO_VFS_STAT_IS_CHARACTER_SPECIAL (1 << 2) + +/* Get path from opaque handle. Returns the exact same path passed to file_open when getting the handle + * Introduced in VFS API v1 */ +typedef const char *(RETRO_CALLCONV *retro_vfs_get_path_t)(struct retro_vfs_file_handle *stream); + +/* Open a file for reading or writing. If path points to a directory, this will + * fail. Returns the opaque file handle, or NULL for error. + * Introduced in VFS API v1 */ +typedef struct retro_vfs_file_handle *(RETRO_CALLCONV *retro_vfs_open_t)(const char *path, unsigned mode, unsigned hints); + +/* Close the file and release its resources. Must be called if open_file returns non-NULL. Returns 0 on success, -1 on failure. + * Whether the call succeeds ot not, the handle passed as parameter becomes invalid and should no longer be used. + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_close_t)(struct retro_vfs_file_handle *stream); + +/* Return the size of the file in bytes, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_size_t)(struct retro_vfs_file_handle *stream); + +/* Truncate file to specified size. Returns 0 on success or -1 on error + * Introduced in VFS API v2 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_truncate_t)(struct retro_vfs_file_handle *stream, int64_t length); + +/* Get the current read / write position for the file. Returns -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_tell_t)(struct retro_vfs_file_handle *stream); + +/* Set the current read/write position for the file. Returns the new position, -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_seek_t)(struct retro_vfs_file_handle *stream, int64_t offset, int seek_position); + +/* Read data from a file. Returns the number of bytes read, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_read_t)(struct retro_vfs_file_handle *stream, void *s, uint64_t len); + +/* Write data to a file. Returns the number of bytes written, or -1 for error. + * Introduced in VFS API v1 */ +typedef int64_t (RETRO_CALLCONV *retro_vfs_write_t)(struct retro_vfs_file_handle *stream, const void *s, uint64_t len); + +/* Flush pending writes to file, if using buffered IO. Returns 0 on sucess, or -1 on failure. + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_flush_t)(struct retro_vfs_file_handle *stream); + +/* Delete the specified file. Returns 0 on success, -1 on failure + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_remove_t)(const char *path); + +/* Rename the specified file. Returns 0 on success, -1 on failure + * Introduced in VFS API v1 */ +typedef int (RETRO_CALLCONV *retro_vfs_rename_t)(const char *old_path, const char *new_path); + +/* Stat the specified file. Retruns a bitmask of RETRO_VFS_STAT_* flags, none are set if path was not valid. + * Additionally stores file size in given variable, unless NULL is given. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_stat_t)(const char *path, int32_t *size); + +/* Create the specified directory. Returns 0 on success, -1 on unknown failure, -2 if already exists. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_mkdir_t)(const char *dir); + +/* Open the specified directory for listing. Returns the opaque dir handle, or NULL for error. + * Support for the include_hidden argument may vary depending on the platform. + * Introduced in VFS API v3 */ +typedef struct retro_vfs_dir_handle *(RETRO_CALLCONV *retro_vfs_opendir_t)(const char *dir, bool include_hidden); + +/* Read the directory entry at the current position, and move the read pointer to the next position. + * Returns true on success, false if already on the last entry. + * Introduced in VFS API v3 */ +typedef bool (RETRO_CALLCONV *retro_vfs_readdir_t)(struct retro_vfs_dir_handle *dirstream); + +/* Get the name of the last entry read. Returns a string on success, or NULL for error. + * The returned string pointer is valid until the next call to readdir or closedir. + * Introduced in VFS API v3 */ +typedef const char *(RETRO_CALLCONV *retro_vfs_dirent_get_name_t)(struct retro_vfs_dir_handle *dirstream); + +/* Check if the last entry read was a directory. Returns true if it was, false otherwise (or on error). + * Introduced in VFS API v3 */ +typedef bool (RETRO_CALLCONV *retro_vfs_dirent_is_dir_t)(struct retro_vfs_dir_handle *dirstream); + +/* Close the directory and release its resources. Must be called if opendir returns non-NULL. Returns 0 on success, -1 on failure. + * Whether the call succeeds ot not, the handle passed as parameter becomes invalid and should no longer be used. + * Introduced in VFS API v3 */ +typedef int (RETRO_CALLCONV *retro_vfs_closedir_t)(struct retro_vfs_dir_handle *dirstream); + +struct retro_vfs_interface +{ + /* VFS API v1 */ + retro_vfs_get_path_t get_path; + retro_vfs_open_t open; + retro_vfs_close_t close; + retro_vfs_size_t size; + retro_vfs_tell_t tell; + retro_vfs_seek_t seek; + retro_vfs_read_t read; + retro_vfs_write_t write; + retro_vfs_flush_t flush; + retro_vfs_remove_t remove; + retro_vfs_rename_t rename; + /* VFS API v2 */ + retro_vfs_truncate_t truncate; + /* VFS API v3 */ + retro_vfs_stat_t stat; + retro_vfs_mkdir_t mkdir; + retro_vfs_opendir_t opendir; + retro_vfs_readdir_t readdir; + retro_vfs_dirent_get_name_t dirent_get_name; + retro_vfs_dirent_is_dir_t dirent_is_dir; + retro_vfs_closedir_t closedir; +}; + +struct retro_vfs_interface_info +{ + /* Set by core: should this be higher than the version the front end supports, + * front end will return false in the RETRO_ENVIRONMENT_GET_VFS_INTERFACE call + * Introduced in VFS API v1 */ + uint32_t required_interface_version; + + /* Frontend writes interface pointer here. The frontend also sets the actual + * version, must be at least required_interface_version. + * Introduced in VFS API v1 */ + struct retro_vfs_interface *iface; +}; + +enum retro_hw_render_interface_type +{ + RETRO_HW_RENDER_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_INTERFACE_D3D9 = 1, + RETRO_HW_RENDER_INTERFACE_D3D10 = 2, + RETRO_HW_RENDER_INTERFACE_D3D11 = 3, + RETRO_HW_RENDER_INTERFACE_D3D12 = 4, + RETRO_HW_RENDER_INTERFACE_GSKIT_PS2 = 5, + RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX +}; + +/* Base struct. All retro_hw_render_interface_* types + * contain at least these fields. */ +struct retro_hw_render_interface +{ + enum retro_hw_render_interface_type interface_type; + unsigned interface_version; +}; + +typedef void (RETRO_CALLCONV *retro_set_led_state_t)(int led, int state); +struct retro_led_interface +{ + retro_set_led_state_t set_led_state; +}; + +/* Retrieves the current state of the MIDI input. + * Returns true if it's enabled, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_input_enabled_t)(void); + +/* Retrieves the current state of the MIDI output. + * Returns true if it's enabled, false otherwise */ +typedef bool (RETRO_CALLCONV *retro_midi_output_enabled_t)(void); + +/* Reads next byte from the input stream. + * Returns true if byte is read, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_read_t)(uint8_t *byte); + +/* Writes byte to the output stream. + * 'delta_time' is in microseconds and represent time elapsed since previous write. + * Returns true if byte is written, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_write_t)(uint8_t byte, uint32_t delta_time); + +/* Flushes previously written data. + * Returns true if successful, false otherwise. */ +typedef bool (RETRO_CALLCONV *retro_midi_flush_t)(void); + +struct retro_midi_interface +{ + retro_midi_input_enabled_t input_enabled; + retro_midi_output_enabled_t output_enabled; + retro_midi_read_t read; + retro_midi_write_t write; + retro_midi_flush_t flush; +}; enum retro_hw_render_context_negotiation_interface_type { @@ -968,77 +1488,97 @@ struct retro_hw_render_context_negotiation_interface enum retro_hw_render_context_negotiation_interface_type interface_type; unsigned interface_version; }; -#define RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE (43 | RETRO_ENVIRONMENT_EXPERIMENTAL) - /* const struct retro_hw_render_context_negotiation_interface * -- - * Sets an interface which lets the libretro core negotiate with frontend how a context is created. - * The semantics of this interface depends on which API is used in SET_HW_RENDER earlier. - * This interface will be used when the frontend is trying to create a HW rendering context, - * so it will be used after SET_HW_RENDER, but before the context_reset callback. - */ -#define RETRO_MEMDESC_CONST (1 << 0) /* The frontend will never change this memory area once retro_load_game has returned. */ -#define RETRO_MEMDESC_BIGENDIAN (1 << 1) /* The memory area contains big endian data. Default is little endian. */ -#define RETRO_MEMDESC_ALIGN_2 (1 << 16) /* All memory access in this area is aligned to their own size, or 2, whichever is smaller. */ -#define RETRO_MEMDESC_ALIGN_4 (2 << 16) -#define RETRO_MEMDESC_ALIGN_8 (3 << 16) -#define RETRO_MEMDESC_MINSIZE_2 (1 << 24) /* All memory in this region is accessed at least 2 bytes at the time. */ -#define RETRO_MEMDESC_MINSIZE_4 (2 << 24) -#define RETRO_MEMDESC_MINSIZE_8 (3 << 24) +/* Serialized state is incomplete in some way. Set if serialization is + * usable in typical end-user cases but should not be relied upon to + * implement frame-sensitive frontend features such as netplay or + * rerecording. */ +#define RETRO_SERIALIZATION_QUIRK_INCOMPLETE (1 << 0) +/* The core must spend some time initializing before serialization is + * supported. retro_serialize() will initially fail; retro_unserialize() + * and retro_serialize_size() may or may not work correctly either. */ +#define RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE (1 << 1) +/* Serialization size may change within a session. */ +#define RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE (1 << 2) +/* Set by the frontend to acknowledge that it supports variable-sized + * states. */ +#define RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE (1 << 3) +/* Serialized state can only be loaded during the same session. */ +#define RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION (1 << 4) +/* Serialized state cannot be loaded on an architecture with a different + * endianness from the one it was saved on. */ +#define RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT (1 << 5) +/* Serialized state cannot be loaded on a different platform from the one it + * was saved on for reasons other than endianness, such as word size + * dependence */ +#define RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT (1 << 6) + +#define RETRO_MEMDESC_CONST (1 << 0) /* The frontend will never change this memory area once retro_load_game has returned. */ +#define RETRO_MEMDESC_BIGENDIAN (1 << 1) /* The memory area contains big endian data. Default is little endian. */ +#define RETRO_MEMDESC_SYSTEM_RAM (1 << 2) /* The memory area is system RAM. This is main RAM of the gaming system. */ +#define RETRO_MEMDESC_SAVE_RAM (1 << 3) /* The memory area is save RAM. This RAM is usually found on a game cartridge, backed up by a battery. */ +#define RETRO_MEMDESC_VIDEO_RAM (1 << 4) /* The memory area is video RAM (VRAM) */ +#define RETRO_MEMDESC_ALIGN_2 (1 << 16) /* All memory access in this area is aligned to their own size, or 2, whichever is smaller. */ +#define RETRO_MEMDESC_ALIGN_4 (2 << 16) +#define RETRO_MEMDESC_ALIGN_8 (3 << 16) +#define RETRO_MEMDESC_MINSIZE_2 (1 << 24) /* All memory in this region is accessed at least 2 bytes at the time. */ +#define RETRO_MEMDESC_MINSIZE_4 (2 << 24) +#define RETRO_MEMDESC_MINSIZE_8 (3 << 24) struct retro_memory_descriptor { uint64_t flags; /* Pointer to the start of the relevant ROM or RAM chip. - * It's strongly recommended to use 'offset' if possible, rather than + * It's strongly recommended to use 'offset' if possible, rather than * doing math on the pointer. * - * If the same byte is mapped my multiple descriptors, their descriptors + * If the same byte is mapped my multiple descriptors, their descriptors * must have the same pointer. - * If 'start' does not point to the first byte in the pointer, put the + * If 'start' does not point to the first byte in the pointer, put the * difference in 'offset' instead. * - * May be NULL if there's nothing usable here (e.g. hardware registers and + * May be NULL if there's nothing usable here (e.g. hardware registers and * open bus). No flags should be set if the pointer is NULL. * It's recommended to minimize the number of descriptors if possible, * but not mandatory. */ void *ptr; size_t offset; - /* This is the location in the emulated address space + /* This is the location in the emulated address space * where the mapping starts. */ size_t start; /* Which bits must be same as in 'start' for this mapping to apply. - * The first memory descriptor to claim a certain byte is the one + * The first memory descriptor to claim a certain byte is the one * that applies. * A bit which is set in 'start' must also be set in this. - * Can be zero, in which case each byte is assumed mapped exactly once. + * Can be zero, in which case each byte is assumed mapped exactly once. * In this case, 'len' must be a power of two. */ size_t select; - /* If this is nonzero, the set bits are assumed not connected to the + /* If this is nonzero, the set bits are assumed not connected to the * memory chip's address pins. */ size_t disconnect; /* This one tells the size of the current memory area. - * If, after start+disconnect are applied, the address is higher than + * If, after start+disconnect are applied, the address is higher than * this, the highest bit of the address is cleared. * * If the address is still too high, the next highest bit is cleared. - * Can be zero, in which case it's assumed to be infinite (as limited + * Can be zero, in which case it's assumed to be infinite (as limited * by 'select' and 'disconnect'). */ size_t len; - /* To go from emulated address to physical address, the following + /* To go from emulated address to physical address, the following * order applies: * Subtract 'start', pick off 'disconnect', apply 'len', add 'offset'. */ - /* The address space name must consist of only a-zA-Z0-9_-, + /* The address space name must consist of only a-zA-Z0-9_-, * should be as short as feasible (maximum length is 8 plus the NUL), - * and may not be any other address space plus one or more 0-9A-F + * and may not be any other address space plus one or more 0-9A-F * at the end. - * However, multiple memory descriptors for the same address space is - * allowed, and the address space name can be empty. NULL is treated + * However, multiple memory descriptors for the same address space is + * allowed, and the address space name can be empty. NULL is treated * as empty. * * Address space names are case sensitive, but avoid lowercase if possible. @@ -1052,11 +1592,11 @@ struct retro_memory_descriptor * 'a'+blank - valid ('a' is not in 0-9A-F) * 'a'+'A' - valid (neither is a prefix of each other) * 'AR'+blank - valid ('R' is not in 0-9A-F) - * 'ARB'+blank - valid (the B can't be part of the address either, because + * 'ARB'+blank - valid (the B can't be part of the address either, because * there is no namespace 'AR') - * blank+'B' - not valid, because it's ambigous which address space B1234 + * blank+'B' - not valid, because it's ambigous which address space B1234 * would refer to. - * The length can't be used for that purpose; the frontend may want + * The length can't be used for that purpose; the frontend may want * to append arbitrary data to an address, without a separator. */ const char *addrspace; @@ -1078,32 +1618,32 @@ struct retro_memory_descriptor * the most recent addition and continue on the next bit. * TODO: Can the above be optimized? Is "remove the lowest bit set in both * pointer and 'len'" equivalent? */ - + /* TODO: Some emulators (MAME?) emulate big endian systems by only accessing * the emulated memory in 32-bit chunks, native endian. But that's nothing * compared to Darek Mihocka * (section Emulation 103 - Nearly Free Byte Reversal) - he flips the ENTIRE * RAM backwards! I'll want to represent both of those, via some flags. - * + * * I suspect MAME either didn't think of that idea, or don't want the #ifdef. * Not sure which, nor do I really care. */ - + /* TODO: Some of those flags are unused and/or don't really make sense. Clean * them up. */ }; -/* The frontend may use the largest value of 'start'+'select' in a +/* The frontend may use the largest value of 'start'+'select' in a * certain namespace to infer the size of the address space. * - * If the address space is larger than that, a mapping with .ptr=NULL - * should be at the end of the array, with .select set to all ones for + * If the address space is larger than that, a mapping with .ptr=NULL + * should be at the end of the array, with .select set to all ones for * as long as the address space is big. * * Sample descriptors (minus .ptr, and RETRO_MEMFLAG_ on the flags): * SNES WRAM: * .start=0x7E0000, .len=0x20000 - * (Note that this must be mapped before the ROM in most cases; some of the - * ROM mappers + * (Note that this must be mapped before the ROM in most cases; some of the + * ROM mappers * try to claim $7E0000, or at least $7E8000.) * SNES SPC700 RAM: * .addrspace="S", .len=0x10000 @@ -1112,7 +1652,7 @@ struct retro_memory_descriptor * .flags=MIRROR, .start=0x800000, .select=0xC0E000, .len=0x2000 * SNES WRAM mirrors, alternate equivalent descriptor: * .flags=MIRROR, .select=0x40E000, .disconnect=~0x1FFF - * (Various similar constructions can be created by combining parts of + * (Various similar constructions can be created by combining parts of * the above two.) * SNES LoROM (512KB, mirrored a couple of times): * .flags=CONST, .start=0x008000, .select=0x408000, .disconnect=0x8000, .len=512*1024 @@ -1138,13 +1678,13 @@ struct retro_memory_map struct retro_controller_description { - /* Human-readable description of the controller. Even if using a generic - * input device type, this can be set to the particular device type the + /* Human-readable description of the controller. Even if using a generic + * input device type, this can be set to the particular device type the * core uses. */ const char *desc; - /* Device type passed to retro_set_controller_port_device(). If the device - * type is a sub-class of a generic input device type, use the + /* Device type passed to retro_set_controller_port_device(). If the device + * type is a sub-class of a generic input device type, use the * RETRO_DEVICE_SUBCLASS macro to create an ID. * * E.g. RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1). */ @@ -1162,8 +1702,8 @@ struct retro_subsystem_memory_info /* The extension associated with a memory type, e.g. "psram". */ const char *extension; - /* The memory type for retro_get_memory(). This should be at - * least 0x100 to avoid conflict with standardized + /* The memory type for retro_get_memory(). This should be at + * least 0x100 to avoid conflict with standardized * libretro memory types. */ unsigned type; }; @@ -1182,11 +1722,11 @@ struct retro_subsystem_rom_info /* Same definition as retro_get_system_info(). */ bool block_extract; - /* This is set if the content is required to load a game. + /* This is set if the content is required to load a game. * If this is set to false, a zeroed-out retro_game_info can be passed. */ bool required; - /* Content can have multiple associated persistent + /* Content can have multiple associated persistent * memory types (retro_get_memory()). */ const struct retro_subsystem_memory_info *memory; unsigned num_memory; @@ -1204,17 +1744,17 @@ struct retro_subsystem_info */ const char *ident; - /* Infos for each content file. The first entry is assumed to be the + /* Infos for each content file. The first entry is assumed to be the * "most significant" content for frontend purposes. - * E.g. with Super GameBoy, the first content should be the GameBoy ROM, + * E.g. with Super GameBoy, the first content should be the GameBoy ROM, * as it is the most "significant" content to a user. - * If a frontend creates new file paths based on the content used + * If a frontend creates new file paths based on the content used * (e.g. savestates), it should use the path for the first ROM to do so. */ const struct retro_subsystem_rom_info *roms; /* Number of content files associated with a subsystem. */ unsigned num_roms; - + /* The type passed to retro_load_game_special(). */ unsigned id; }; @@ -1225,13 +1765,13 @@ typedef void (RETRO_CALLCONV *retro_proc_address_t)(void); * (None here so far). * * Get a symbol from a libretro core. - * Cores should only return symbols which are actual + * Cores should only return symbols which are actual * extensions to the libretro API. * - * Frontends should not use this to obtain symbols to standard + * Frontends should not use this to obtain symbols to standard * libretro entry points (static linking or dlsym). * - * The symbol name must be equal to the function name, + * The symbol name must be equal to the function name, * e.g. if void retro_foo(void); exists, the symbol must be called "retro_foo". * The returned function pointer must be cast to the corresponding type. */ @@ -1285,6 +1825,7 @@ struct retro_log_callback #define RETRO_SIMD_POPCNT (1 << 18) #define RETRO_SIMD_MOVBE (1 << 19) #define RETRO_SIMD_CMOV (1 << 20) +#define RETRO_SIMD_ASIMD (1 << 21) typedef uint64_t retro_perf_tick_t; typedef int64_t retro_time_t; @@ -1305,7 +1846,7 @@ struct retro_perf_counter typedef retro_time_t (RETRO_CALLCONV *retro_perf_get_time_usec_t)(void); /* A simple counter. Usually nanoseconds, but can also be CPU cycles. - * Can be used directly if desired (when creating a more sophisticated + * Can be used directly if desired (when creating a more sophisticated * performance counter system). * */ typedef retro_perf_tick_t (RETRO_CALLCONV *retro_perf_get_counter_t)(void); @@ -1319,9 +1860,9 @@ typedef uint64_t (RETRO_CALLCONV *retro_get_cpu_features_t)(void); typedef void (RETRO_CALLCONV *retro_perf_log_t)(void); /* Register a performance counter. - * ident field must be set with a discrete value and other values in + * ident field must be set with a discrete value and other values in * retro_perf_counter must be 0. - * Registering can be called multiple times. To avoid calling to + * Registering can be called multiple times. To avoid calling to * frontend redundantly, you can check registered field first. */ typedef void (RETRO_CALLCONV *retro_perf_register_t)(struct retro_perf_counter *counter); @@ -1392,7 +1933,7 @@ enum retro_sensor_action #define RETRO_SENSOR_ACCELEROMETER_Y 1 #define RETRO_SENSOR_ACCELEROMETER_Z 2 -typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, +typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, enum retro_sensor_action action, unsigned rate); typedef float (RETRO_CALLCONV *retro_sensor_get_input_t)(unsigned port, unsigned id); @@ -1417,7 +1958,7 @@ typedef bool (RETRO_CALLCONV *retro_camera_start_t)(void); /* Stops the camera driver. Can only be called in retro_run(). */ typedef void (RETRO_CALLCONV *retro_camera_stop_t)(void); -/* Callback which signals when the camera driver is initialized +/* Callback which signals when the camera driver is initialized * and/or deinitialized. * retro_camera_start_t can be called in initialized callback. */ @@ -1427,36 +1968,36 @@ typedef void (RETRO_CALLCONV *retro_camera_lifetime_status_t)(void); * Width, height and pitch are similar to retro_video_refresh_t. * First pixel is top-left origin. */ -typedef void (RETRO_CALLCONV *retro_camera_frame_raw_framebuffer_t)(const uint32_t *buffer, +typedef void (RETRO_CALLCONV *retro_camera_frame_raw_framebuffer_t)(const uint32_t *buffer, unsigned width, unsigned height, size_t pitch); /* A callback for when OpenGL textures are used. * * texture_id is a texture owned by camera driver. - * Its state or content should be considered immutable, except for things like + * Its state or content should be considered immutable, except for things like * texture filtering and clamping. * * texture_target is the texture target for the GL texture. - * These can include e.g. GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE, and possibly + * These can include e.g. GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE, and possibly * more depending on extensions. * - * affine points to a packed 3x3 column-major matrix used to apply an affine + * affine points to a packed 3x3 column-major matrix used to apply an affine * transform to texture coordinates. (affine_matrix * vec3(coord_x, coord_y, 1.0)) - * After transform, normalized texture coord (0, 0) should be bottom-left + * After transform, normalized texture coord (0, 0) should be bottom-left * and (1, 1) should be top-right (or (width, height) for RECTANGLE). * - * GL-specific typedefs are avoided here to avoid relying on gl.h in + * GL-specific typedefs are avoided here to avoid relying on gl.h in * the API definition. */ -typedef void (RETRO_CALLCONV *retro_camera_frame_opengl_texture_t)(unsigned texture_id, +typedef void (RETRO_CALLCONV *retro_camera_frame_opengl_texture_t)(unsigned texture_id, unsigned texture_target, const float *affine); struct retro_camera_callback { - /* Set by libretro core. + /* Set by libretro core. * Example bitmask: caps = (1 << RETRO_CAMERA_BUFFER_OPENGL_TEXTURE) | (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER). */ - uint64_t caps; + uint64_t caps; /* Desired resolution for camera. Is only used as a hint. */ unsigned width; @@ -1470,22 +2011,22 @@ struct retro_camera_callback retro_camera_frame_raw_framebuffer_t frame_raw_framebuffer; /* Set by libretro core if OpenGL texture callbacks will be used. */ - retro_camera_frame_opengl_texture_t frame_opengl_texture; + retro_camera_frame_opengl_texture_t frame_opengl_texture; - /* Set by libretro core. Called after camera driver is initialized and + /* Set by libretro core. Called after camera driver is initialized and * ready to be started. * Can be NULL, in which this callback is not called. */ retro_camera_lifetime_status_t initialized; - /* Set by libretro core. Called right before camera driver is + /* Set by libretro core. Called right before camera driver is * deinitialized. * Can be NULL, in which this callback is not called. */ retro_camera_lifetime_status_t deinitialized; }; -/* Sets the interval of time and/or distance at which to update/poll +/* Sets the interval of time and/or distance at which to update/poll * location-based data. * * To ensure compatibility with all location-based implementations, @@ -1498,20 +2039,20 @@ typedef void (RETRO_CALLCONV *retro_location_set_interval_t)(unsigned interval_m unsigned interval_distance); /* Start location services. The device will start listening for changes to the - * current location at regular intervals (which are defined with + * current location at regular intervals (which are defined with * retro_location_set_interval_t). */ typedef bool (RETRO_CALLCONV *retro_location_start_t)(void); -/* Stop location services. The device will stop listening for changes +/* Stop location services. The device will stop listening for changes * to the current location. */ typedef void (RETRO_CALLCONV *retro_location_stop_t)(void); -/* Get the position of the current location. Will set parameters to +/* Get the position of the current location. Will set parameters to * 0 if no new location update has happened since the last time. */ typedef bool (RETRO_CALLCONV *retro_location_get_position_t)(double *lat, double *lon, double *horiz_accuracy, double *vert_accuracy); -/* Callback which signals when the location driver is initialized +/* Callback which signals when the location driver is initialized * and/or deinitialized. * retro_location_start_t can be called in initialized callback. */ @@ -1536,14 +2077,14 @@ enum retro_rumble_effect RETRO_RUMBLE_DUMMY = INT_MAX }; -/* Sets rumble state for joypad plugged in port 'port'. +/* Sets rumble state for joypad plugged in port 'port'. * Rumble effects are controlled independently, * and setting e.g. strong rumble does not override weak rumble. * Strength has a range of [0, 0xffff]. * - * Returns true if rumble state request was honored. + * Returns true if rumble state request was honored. * Calling this before first retro_run() is likely to return false. */ -typedef bool (RETRO_CALLCONV *retro_set_rumble_state_t)(unsigned port, +typedef bool (RETRO_CALLCONV *retro_set_rumble_state_t)(unsigned port, enum retro_rumble_effect effect, uint16_t strength); struct retro_rumble_interface @@ -1554,10 +2095,10 @@ struct retro_rumble_interface /* Notifies libretro that audio data should be written. */ typedef void (RETRO_CALLCONV *retro_audio_callback_t)(void); -/* True: Audio driver in frontend is active, and callback is +/* True: Audio driver in frontend is active, and callback is * expected to be called regularily. - * False: Audio driver in frontend is paused or inactive. - * Audio callback will not be called until set_state has been + * False: Audio driver in frontend is paused or inactive. + * Audio callback will not be called until set_state has been * called with true. * Initial state is false (inactive). */ @@ -1569,11 +2110,11 @@ struct retro_audio_callback retro_audio_set_state_callback_t set_state; }; -/* Notifies a libretro core of time spent since last invocation +/* Notifies a libretro core of time spent since last invocation * of retro_run() in microseconds. * * It will be called right before retro_run() every frame. - * The frontend can tamper with timing to support cases like + * The frontend can tamper with timing to support cases like * fast-forward, slow-motion and framestepping. * * In those scenarios the reference frame time value will be used. */ @@ -1582,8 +2123,8 @@ typedef void (RETRO_CALLCONV *retro_frame_time_callback_t)(retro_usec_t usec); struct retro_frame_time_callback { retro_frame_time_callback_t callback; - /* Represents the time of one frame. It is computed as - * 1000000 / fps, but the implementation will resolve the + /* Represents the time of one frame. It is computed as + * 1000000 / fps, but the implementation will resolve the * rounding to ensure that framestepping, etc is exact. */ retro_usec_t reference; }; @@ -1599,7 +2140,7 @@ struct retro_frame_time_callback * it should implement context_destroy callback. * If called, all GPU resources must be reinitialized. * Usually called when frontend reinits video driver. - * Also called first time video driver is initialized, + * Also called first time video driver is initialized, * allowing libretro core to initialize resources. */ typedef void (RETRO_CALLCONV *retro_hw_context_reset_t)(void); @@ -1616,7 +2157,7 @@ enum retro_hw_context_type { RETRO_HW_CONTEXT_NONE = 0, /* OpenGL 2.x. Driver can choose to use latest compatibility context. */ - RETRO_HW_CONTEXT_OPENGL = 1, + RETRO_HW_CONTEXT_OPENGL = 1, /* OpenGL ES 2.0. */ RETRO_HW_CONTEXT_OPENGLES2 = 2, /* Modern desktop core GL context. Use version_major/ @@ -1631,6 +2172,10 @@ enum retro_hw_context_type /* Vulkan, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE. */ RETRO_HW_CONTEXT_VULKAN = 6, + /* Direct3D, set version_major to select the type of interface + * returned by RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ + RETRO_HW_CONTEXT_DIRECT3D = 7, + RETRO_HW_CONTEXT_DUMMY = INT_MAX }; @@ -1642,10 +2187,10 @@ struct retro_hw_render_callback /* Called when a context has been created or when it has been reset. * An OpenGL context is only valid after context_reset() has been called. * - * When context_reset is called, OpenGL resources in the libretro + * When context_reset is called, OpenGL resources in the libretro * implementation are guaranteed to be invalid. * - * It is possible that context_reset is called multiple times during an + * It is possible that context_reset is called multiple times during an * application lifecycle. * If context_reset is called without any notification (context_destroy), * the OpenGL context was lost and resources should just be recreated @@ -1658,7 +2203,8 @@ struct retro_hw_render_callback * be providing preallocated framebuffers. */ retro_hw_get_current_framebuffer_t get_current_framebuffer; - /* Set by frontend. */ + /* Set by frontend. + * Can return all relevant functions, including glClear on Windows. */ retro_hw_get_proc_address_t get_proc_address; /* Set if render buffers should have depth component attached. @@ -1669,48 +2215,48 @@ struct retro_hw_render_callback * TODO: Obsolete. */ bool stencil; - /* If depth and stencil are true, a packed 24/8 buffer will be added. + /* If depth and stencil are true, a packed 24/8 buffer will be added. * Only attaching stencil is invalid and will be ignored. */ - /* Use conventional bottom-left origin convention. If false, + /* Use conventional bottom-left origin convention. If false, * standard libretro top-left origin semantics are used. * TODO: Move to GL specific interface. */ bool bottom_left_origin; - + /* Major version number for core GL context or GLES 3.1+. */ unsigned version_major; /* Minor version number for core GL context or GLES 3.1+. */ unsigned version_minor; - /* If this is true, the frontend will go very far to avoid + /* If this is true, the frontend will go very far to avoid * resetting context in scenarios like toggling fullscreen, etc. * TODO: Obsolete? Maybe frontend should just always assume this ... */ bool cache_context; - /* The reset callback might still be called in extreme situations + /* The reset callback might still be called in extreme situations * such as if the context is lost beyond recovery. * - * For optimal stability, set this to false, and allow context to be + * For optimal stability, set this to false, and allow context to be * reset at any time. */ - - /* A callback to be called before the context is destroyed in a + + /* A callback to be called before the context is destroyed in a * controlled way by the frontend. */ retro_hw_context_reset_t context_destroy; /* OpenGL resources can be deinitialized cleanly at this step. - * context_destroy can be set to NULL, in which resources will + * context_destroy can be set to NULL, in which resources will * just be destroyed without any notification. * - * Even when context_destroy is non-NULL, it is possible that + * Even when context_destroy is non-NULL, it is possible that * context_reset is called without any destroy notification. - * This happens if context is lost by external factors (such as + * This happens if context is lost by external factors (such as * notified by GL_ARB_robustness). * * In this case, the context is assumed to be already dead, - * and the libretro implementation must not try to free any OpenGL + * and the libretro implementation must not try to free any OpenGL * resources in the subsequent context_reset. */ @@ -1718,7 +2264,7 @@ struct retro_hw_render_callback bool debug_context; }; -/* Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. +/* Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. * Called by the frontend in response to keyboard events. * down is set if the key is being pressed, or false if it is being released. * keycode is the RETROK value of the char. @@ -1726,16 +2272,16 @@ struct retro_hw_render_callback * key_modifiers is a set of RETROKMOD values or'ed together. * * The pressed/keycode state can be indepedent of the character. - * It is also possible that multiple characters are generated from a + * It is also possible that multiple characters are generated from a * single keypress. * Keycode events should be treated separately from character events. * However, when possible, the frontend should try to synchronize these. * If only a character is posted, keycode should be RETROK_UNKNOWN. * - * Similarily if only a keycode event is generated with no corresponding + * Similarily if only a keycode event is generated with no corresponding * character, character should be 0. */ -typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode, +typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers); struct retro_keyboard_callback @@ -1744,15 +2290,15 @@ struct retro_keyboard_callback }; /* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. - * Should be set for implementations which can swap out multiple disk + * Should be set for implementations which can swap out multiple disk * images in runtime. * * If the implementation can do this automatically, it should strive to do so. * However, there are cases where the user must manually do so. * - * Overview: To swap a disk image, eject the disk image with + * Overview: To swap a disk image, eject the disk image with * set_eject_state(true). - * Set the disk index with set_image_index(index). Insert the disk again + * Set the disk index with set_image_index(index). Insert the disk again * with set_eject_state(false). */ @@ -1770,7 +2316,7 @@ typedef bool (RETRO_CALLCONV *retro_get_eject_state_t)(void); typedef unsigned (RETRO_CALLCONV *retro_get_image_index_t)(void); /* Sets image index. Can only be called when disk is ejected. - * The implementation supports setting "no disk" by using an + * The implementation supports setting "no disk" by using an * index >= get_num_images(). */ typedef bool (RETRO_CALLCONV *retro_set_image_index_t)(unsigned index); @@ -1784,11 +2330,11 @@ struct retro_game_info; * Arguments to pass in info have same requirements as retro_load_game(). * Virtual disk tray must be ejected when calling this. * - * Replacing a disk image with info = NULL will remove the disk image + * Replacing a disk image with info = NULL will remove the disk image * from the internal list. * As a result, calls to get_image_index() can change. * - * E.g. replace_image_index(1, NULL), and previous get_image_index() + * E.g. replace_image_index(1, NULL), and previous get_image_index() * returned 4 before. * Index 1 will be removed, and the new index is 3. */ @@ -1797,7 +2343,7 @@ typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index, /* Adds a new valid index (get_num_images()) to the internal disk list. * This will increment subsequent return values from get_num_images() by 1. - * This image index cannot be used until a disk image has been set + * This image index cannot be used until a disk image has been set * with replace_image_index. */ typedef bool (RETRO_CALLCONV *retro_add_image_index_t)(void); @@ -1828,7 +2374,7 @@ enum retro_pixel_format /* RGB565, native endian. * This pixel format is the recommended format to use if a 15/16-bit - * format is desired as it is the pixel format that is typically + * format is desired as it is the pixel format that is typically * available on a wide range of low-power devices. * * It is also natively supported in APIs like OpenGL ES. */ @@ -1858,43 +2404,52 @@ struct retro_input_descriptor /* Human readable description for parameters. * The pointer must remain valid until * retro_unload_game() is called. */ - const char *description; + const char *description; }; struct retro_system_info { - /* All pointers are owned by libretro implementation, and pointers must + /* All pointers are owned by libretro implementation, and pointers must * remain valid until retro_deinit() is called. */ - const char *library_name; /* Descriptive name of library. Should not + const char *library_name; /* Descriptive name of library. Should not * contain any version numbers, etc. */ const char *library_version; /* Descriptive version of core. */ - const char *valid_extensions; /* A string listing probably content - * extensions the core will be able to + const char *valid_extensions; /* A string listing probably content + * extensions the core will be able to * load, separated with pipe. * I.e. "bin|rom|iso". - * Typically used for a GUI to filter + * Typically used for a GUI to filter * out extensions. */ - /* If true, retro_load_game() is guaranteed to provide a valid pathname - * in retro_game_info::path. - * ::data and ::size are both invalid. + /* Libretro cores that need to have direct access to their content + * files, including cores which use the path of the content files to + * determine the paths of other files, should set need_fullpath to true. * - * If false, ::data and ::size are guaranteed to be valid, but ::path - * might not be valid. + * Cores should strive for setting need_fullpath to false, + * as it allows the frontend to perform patching, etc. * - * This is typically set to true for libretro implementations that must - * load from file. - * Implementations should strive for setting this to false, as it allows - * the frontend to perform patching, etc. */ - bool need_fullpath; + * If need_fullpath is true and retro_load_game() is called: + * - retro_game_info::path is guaranteed to have a valid path + * - retro_game_info::data and retro_game_info::size are invalid + * + * If need_fullpath is false and retro_load_game() is called: + * - retro_game_info::path may be NULL + * - retro_game_info::data and retro_game_info::size are guaranteed + * to be valid + * + * See also: + * - RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY + * - RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY + */ + bool need_fullpath; - /* If true, the frontend is not allowed to extract any archives before + /* If true, the frontend is not allowed to extract any archives before * loading the real content. - * Necessary for certain libretro implementations that load games + * Necessary for certain libretro implementations that load games * from zipped archives. */ - bool block_extract; + bool block_extract; }; struct retro_game_geometry @@ -1926,27 +2481,87 @@ struct retro_system_av_info struct retro_variable { /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. - * If NULL, obtains the complete environment string if more + * If NULL, obtains the complete environment string if more * complex parsing is necessary. - * The environment string is formatted as key-value pairs + * The environment string is formatted as key-value pairs * delimited by semicolons as so: * "key1=value1;key2=value2;..." */ const char *key; - + /* Value to be obtained. If key does not exist, it is set to NULL. */ const char *value; }; +struct retro_core_option_display +{ + /* Variable to configure in RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY */ + const char *key; + + /* Specifies whether variable should be displayed + * when presenting core options to the user */ + bool visible; +}; + +/* Maximum number of values permitted for a core option + * NOTE: This may be increased on a core-by-core basis + * if required (doing so has no effect on the frontend) */ +#define RETRO_NUM_CORE_OPTION_VALUES_MAX 128 + +struct retro_core_option_value +{ + /* Expected option value */ + const char *value; + + /* Human-readable value label. If NULL, value itself + * will be displayed by the frontend */ + const char *label; +}; + +struct retro_core_option_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. */ + const char *key; + + /* Human-readable core option description (used as menu label) */ + const char *desc; + + /* Human-readable core option information (used as menu sublabel) */ + const char *info; + + /* Array of retro_core_option_value structs, terminated by NULL */ + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; + + /* Default core option value. Must match one of the values + * in the retro_core_option_value array, otherwise will be + * ignored */ + const char *default_value; +}; + +struct retro_core_options_intl +{ + /* Pointer to an array of retro_core_option_definition structs + * - US English implementation + * - Must point to a valid array */ + struct retro_core_option_definition *us; + + /* Pointer to an array of retro_core_option_definition structs + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_option_definition *local; +}; + struct retro_game_info { const char *path; /* Path to game, UTF-8 encoded. - * Usually used as a reference. - * May be NULL if rom was loaded from stdin - * or similar. - * retro_system_info::need_fullpath guaranteed + * Sometimes used as a reference for building other paths. + * May be NULL if game was loaded from stdin or similar, + * but in this case some cores will be unable to load `data`. + * So, it is preferable to fabricate something here instead + * of passing NULL, which will help more cores to succeed. + * retro_system_info::need_fullpath requires * that this path is valid. */ - const void *data; /* Memory buffer of loaded game. Will be NULL + const void *data; /* Memory buffer of loaded game. Will be NULL * if need_fullpath was set. */ size_t size; /* Size of memory buffer. */ const char *meta; /* String of implementation specific meta-data. */ @@ -1984,25 +2599,25 @@ struct retro_framebuffer /* Callbacks */ -/* Environment callback. Gives implementations a way of performing +/* Environment callback. Gives implementations a way of performing * uncommon tasks. Extensible. */ typedef bool (RETRO_CALLCONV *retro_environment_t)(unsigned cmd, void *data); -/* Render a frame. Pixel format is 15-bit 0RGB1555 native endian +/* Render a frame. Pixel format is 15-bit 0RGB1555 native endian * unless changed (see RETRO_ENVIRONMENT_SET_PIXEL_FORMAT). * * Width and height specify dimensions of buffer. * Pitch specifices length in bytes between two lines in buffer. * - * For performance reasons, it is highly recommended to have a frame + * For performance reasons, it is highly recommended to have a frame * that is packed in memory, i.e. pitch == width * byte_per_pixel. - * Certain graphic APIs, such as OpenGL ES, do not like textures + * Certain graphic APIs, such as OpenGL ES, do not like textures * that are not packed in memory. */ typedef void (RETRO_CALLCONV *retro_video_refresh_t)(const void *data, unsigned width, unsigned height, size_t pitch); -/* Renders a single audio frame. Should only be used if implementation +/* Renders a single audio frame. Should only be used if implementation * generates a single sample at a time. * Format is signed 16-bit native endian. */ @@ -2020,20 +2635,20 @@ typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_t)(const int16_t *data, /* Polls input. */ typedef void (RETRO_CALLCONV *retro_input_poll_t)(void); -/* Queries for input for player 'port'. device will be masked with +/* Queries for input for player 'port'. device will be masked with * RETRO_DEVICE_MASK. * - * Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that + * Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that * have been set with retro_set_controller_port_device() * will still use the higher level RETRO_DEVICE_JOYPAD to request input. */ -typedef int16_t (RETRO_CALLCONV *retro_input_state_t)(unsigned port, unsigned device, +typedef int16_t (RETRO_CALLCONV *retro_input_state_t)(unsigned port, unsigned device, unsigned index, unsigned id); -/* Sets callbacks. retro_set_environment() is guaranteed to be called +/* Sets callbacks. retro_set_environment() is guaranteed to be called * before retro_init(). * - * The rest of the set_* functions are guaranteed to have been called + * The rest of the set_* functions are guaranteed to have been called * before the first call to retro_run() is made. */ RETRO_API void retro_set_environment(retro_environment_t); RETRO_API void retro_set_video_refresh(retro_video_refresh_t); @@ -2050,27 +2665,33 @@ RETRO_API void retro_deinit(void); * when the API is revised. */ RETRO_API unsigned retro_api_version(void); -/* Gets statically known system info. Pointers provided in *info +/* Gets statically known system info. Pointers provided in *info * must be statically allocated. * Can be called at any time, even before retro_init(). */ RETRO_API void retro_get_system_info(struct retro_system_info *info); /* Gets information about system audio/video timings and geometry. * Can be called only after retro_load_game() has successfully completed. - * NOTE: The implementation of this function might not initialize every + * NOTE: The implementation of this function might not initialize every * variable if needed. - * E.g. geom.aspect_ratio might not be initialized if core doesn't + * E.g. geom.aspect_ratio might not be initialized if core doesn't * desire a particular aspect ratio. */ RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info); /* Sets device to be used for player 'port'. - * By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all + * By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all * available ports. - * Setting a particular device type is not a guarantee that libretro cores - * will only poll input based on that particular device type. It is only a - * hint to the libretro core when a core cannot automatically detect the - * appropriate input device type on its own. It is also relevant when a - * core can change its behavior depending on device type. */ + * Setting a particular device type is not a guarantee that libretro cores + * will only poll input based on that particular device type. It is only a + * hint to the libretro core when a core cannot automatically detect the + * appropriate input device type on its own. It is also relevant when a + * core can change its behavior depending on device type. + * + * As part of the core's implementation of retro_set_controller_port_device, + * the core should call RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS to notify the + * frontend if the descriptions for any controls have changed as a + * result of changing the device type. + */ RETRO_API void retro_set_controller_port_device(unsigned port, unsigned device); /* Resets the current game. */ @@ -2078,18 +2699,18 @@ RETRO_API void retro_reset(void); /* Runs the game for one video frame. * During retro_run(), input_poll callback must be called at least once. - * + * * If a frame is not rendered for reasons where a game "dropped" a frame, - * this still counts as a frame, and retro_run() should explicitly dupe + * this still counts as a frame, and retro_run() should explicitly dupe * a frame if GET_CAN_DUPE returns true. * In this case, the video callback can take a NULL argument for data. */ RETRO_API void retro_run(void); -/* Returns the amount of data the implementation requires to serialize +/* Returns the amount of data the implementation requires to serialize * internal state (save states). - * Between calls to retro_load_game() and retro_unload_game(), the - * returned size is never allowed to be larger than a previous returned + * Between calls to retro_load_game() and retro_unload_game(), the + * returned size is never allowed to be larger than a previous returned * value, to ensure that the frontend can allocate a save state buffer once. */ RETRO_API size_t retro_serialize_size(void); @@ -2102,7 +2723,9 @@ RETRO_API bool retro_unserialize(const void *data, size_t size); RETRO_API void retro_cheat_reset(void); RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *code); -/* Loads a game. */ +/* Loads a game. + * Return true to indicate successful loading and false to indicate load failure. + */ RETRO_API bool retro_load_game(const struct retro_game_info *game); /* Loads a "special" kind of game. Should not be used, @@ -2112,7 +2735,7 @@ RETRO_API bool retro_load_game_special( const struct retro_game_info *info, size_t num_info ); -/* Unloads a currently loaded game. */ +/* Unloads the currently loaded game. Called before retro_deinit(void). */ RETRO_API void retro_unload_game(void); /* Gets region of game. */ From eb295de2184e9654c7de83348a7e490fd40ac49e Mon Sep 17 00:00:00 2001 From: SimpleTease <31772993+SimpleTease@users.noreply.github.com> Date: Sat, 10 Oct 2020 23:33:10 +0000 Subject: [PATCH 018/365] shared version.mk --- Makefile | 2 +- libretro/Makefile.common | 2 +- version.mk | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 version.mk diff --git a/Makefile b/Makefile index 0881fe9..c3d030a 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.13.6 +include version.mk export VERSION CONF ?= debug SDL_AUDIO_DRIVER ?= sdl diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 430c03d..fabe3ad 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -1,4 +1,4 @@ -VERSION := 0.13.6 +include $(CORE_DIR)/version.mk INCFLAGS := -I$(CORE_DIR) diff --git a/version.mk b/version.mk new file mode 100644 index 0000000..35ae0ad --- /dev/null +++ b/version.mk @@ -0,0 +1 @@ +VERSION := 0.13.6 From 6e0c09f78c1aa8da47bde45c27b9bee09686d7fe Mon Sep 17 00:00:00 2001 From: James Larrowe Date: Tue, 13 Oct 2020 18:19:29 -0400 Subject: [PATCH 019/365] Update RGBDS links in README and build-faq The repo's owner has changed twice since this link was used; once from bentley to the neutral rednex organization, and then from rednex to gbdev --- README.md | 2 +- build-faq.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1107fdc..5ed0268 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ SameBoy requires the following tools and libraries to build: * make * macOS Cocoa port: macOS SDK and Xcode (For command line tools and ibtool) * SDL port: libsdl2 - * [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation + * [rgbds](https://github.com/gbdev/rgbds/releases/), for boot ROM compilation On Windows, SameBoy also requires: * Visual Studio (For headers, etc.) diff --git a/build-faq.md b/build-faq.md index 9def134..0921436 100644 --- a/build-faq.md +++ b/build-faq.md @@ -24,7 +24,7 @@ The following examples will be referenced later: ### rgbds -After downloading [rgbds](https://github.com/bentley/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`. +After downloading [rgbds](https://github.com/gbdev/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`. ### GnuWin From 03cbab2f85cfd62aada063ec0371a9c02db9bf5b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 15 Oct 2020 19:21:37 +0300 Subject: [PATCH 020/365] Windows is no longer officially supported in the standalone builds --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ed0268..9721be9 100644 --- a/README.md +++ b/README.md @@ -53,4 +53,4 @@ To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl By default, the SDL port will look for resource files with a path relative to executable. If you are packaging SameBoy, you may wish to override this by setting the `DATA_DIR` variable during compilation to the target path of the directory containing all files (apart from the executable, that's not necessary) from the `build/bin/SDL` directory in the source tree. Make sure the variable ends with a `/` character. -SameBoy was compiled and tested on macOS, Ubuntu and 64-bit Windows 7. +SameBoy is compiled and tested on macOS and Ubuntu. From 88198e64f4398755d0892b63a4176dfabe6f690e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Nov 2020 16:28:48 +0200 Subject: [PATCH 021/365] Minor bug fixes --- Core/cheats.c | 6 +++--- Core/debugger.c | 2 +- Core/save_state.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/cheats.c b/Core/cheats.c index 14875e0..451dddb 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -69,7 +69,7 @@ void GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, u cheat->enabled = enabled; strncpy(cheat->description, description, sizeof(cheat->description)); cheat->description[sizeof(cheat->description) - 1] = 0; - gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(*cheat)); + gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(gb->cheats[0])); gb->cheats[gb->cheat_count - 1] = cheat; GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)]; @@ -100,7 +100,7 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat) gb->cheats = NULL; } else { - gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(*cheat)); + gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(gb->cheats[0])); } break; } @@ -222,7 +222,7 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des } else { (*hash)->size++; - *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); + *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size); (*hash)->cheats[(*hash)->size - 1] = cheat; } } diff --git a/Core/debugger.c b/Core/debugger.c index 002d455..c27acd3 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -473,7 +473,7 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, size_t length, bool *error, uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) { - /* Disable watchpoints while evaulating expressions */ + /* Disable watchpoints while evaluating expressions */ uint16_t n_watchpoints = gb->n_watchpoints; gb->n_watchpoints = 0; diff --git a/Core/save_state.c b/Core/save_state.c index 9ef6ae3..827cf57 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -150,7 +150,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t { if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) { /* This is a save state with a bad printer struct from a 32-bit OS */ - memcpy(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam); + memmove(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam); } if (save->ram_size == 0) { /* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially From 60b89787622840b2f5cf1490d401a82fa0257a5d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 13 Nov 2020 23:07:35 +0200 Subject: [PATCH 022/365] Local link cable and infrared emulation in the Cocoa port --- Cocoa/AppDelegate.h | 3 +- Cocoa/AppDelegate.m | 25 ++++- Cocoa/Document.h | 5 +- Cocoa/Document.m | 220 +++++++++++++++++++++++++++++++++++++++----- Cocoa/Document.xib | 11 ++- Cocoa/GBView.h | 2 + Cocoa/GBView.m | 118 +++++++++++++++++++----- Cocoa/MainMenu.xib | 17 +++- Core/gb.h | 2 - 9 files changed, 351 insertions(+), 52 deletions(-) diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index 22e0c36..8f91565 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -1,6 +1,6 @@ #import -@interface AppDelegate : NSObject +@interface AppDelegate : NSObject @property IBOutlet NSWindow *preferencesWindow; @property (strong) IBOutlet NSView *graphicsTab; @@ -10,6 +10,7 @@ - (IBAction)showPreferences: (id) sender; - (IBAction)toggleDeveloperMode:(id)sender; - (IBAction)switchPreferencesTab:(id)sender; +@property (weak) IBOutlet NSMenuItem *linkCableMenuItem; @end diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 133fab7..282105e 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -81,10 +81,29 @@ if ([anItem action] == @selector(toggleDeveloperMode:)) { [(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]]; } - + + if (anItem == self.linkCableMenuItem) { + return [[NSDocumentController sharedDocumentController] documents].count > 1; + } return true; } +- (void)menuNeedsUpdate:(NSMenu *)menu +{ + NSMutableArray *items = [NSMutableArray array]; + NSDocument *currentDocument = [[NSDocumentController sharedDocumentController] currentDocument]; + + for (NSDocument *document in [[NSDocumentController sharedDocumentController] documents]) { + if (document == currentDocument) continue; + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:document.displayName action:@selector(connectLinkCable:) keyEquivalent:@""]; + item.representedObject = document; + item.image = [[NSWorkspace sharedWorkspace] iconForFile:document.fileURL.path]; + [item.image setSize:NSMakeSize(16, 16)]; + [items addObject:item]; + } + menu.itemArray = items; +} + - (IBAction) showPreferences: (id) sender { NSArray *objects; @@ -109,4 +128,8 @@ { [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; } + +- (IBAction)nop:(id)sender +{ +} @end diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 660d7bc..bf5d9c0 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -6,6 +6,7 @@ @class GBCheatWindowController; @interface Document : NSDocument +@property (readonly) GB_gameboy_t *gb; @property (strong) IBOutlet GBView *view; @property (strong) IBOutlet NSTextView *consoleOutput; @property (strong) IBOutlet NSPanel *consoleWindow; @@ -36,10 +37,12 @@ @property (strong) IBOutlet NSBox *debuggerVerticalLine; @property (strong) IBOutlet NSPanel *cheatsWindow; @property (strong) IBOutlet GBCheatWindowController *cheatWindowController; +@property (readonly) Document *partner; +@property (readonly) bool isSlave; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) performAtomicBlock: (void (^)())block; - +-(void) connectLinkCable:(NSMenuItem *)sender; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 653179d..84e181f 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -28,6 +28,7 @@ enum model { NSMutableAttributedString *pending_console_output; NSRecursiveLock *console_output_lock; NSTimer *console_output_timer; + NSTimer *hex_timer; bool fullScreen; bool in_sync_input; @@ -47,7 +48,7 @@ enum model { bool oamUpdating; NSMutableData *currentPrinterImageData; - enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy} accessory; + enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory; bool rom_warning_issued; @@ -66,6 +67,12 @@ enum model { size_t audioBufferNeeded; bool borderModeChanged; + + /* Link cable*/ + Document *master; + Document *slave; + signed linkOffset; + bool linkCableBit; } @property GBAudioClient *audioClient; @@ -81,6 +88,10 @@ enum model { - (void) gotNewSample:(GB_sample_t *)sample; - (void) rumbleChanged:(double)amp; - (void) loadBootROM:(GB_boot_rom_t)type; +- (void)linkCableBitStart:(bool)bit; +- (bool)linkCableBitEnd; +- (void)infraredStateChanged:(bool)state; + @end static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) @@ -160,6 +171,26 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [self rumbleChanged:amp]; } + +static void linkCableBitStart(GB_gameboy_t *gb, bool bit_to_send) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self linkCableBitStart:bit_to_send]; +} + +static bool linkCableBitEnd(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + return [self linkCableBitEnd]; +} + +static void infraredStateChanged(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + [self infraredStateChanged:on]; +} + + @implementation Document { GB_gameboy_t gb; @@ -264,6 +295,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); GB_apu_set_sample_callback(&gb, audioCallback); GB_set_rumble_callback(&gb, rumbleCallback); + GB_set_infrared_callback(&gb, infraredStateChanged); [self updateRumbleMode]; } @@ -335,9 +367,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [_view setRumble:amp]; } -- (void) run +- (void) preRun { - running = true; GB_set_pixels_output(&gb, self.view.pixels); GB_set_sample_rate(&gb, 96000); self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { @@ -368,7 +399,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { [self.audioClient start]; } - NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; + hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; /* Clear pending alarms, don't play alarms while playing */ @@ -388,19 +419,58 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) } } } - - while (running) { - if (rewind) { - rewind = false; - GB_rewind_pop(&gb); - if (!GB_rewind_pop(&gb)) { - rewind = self.view.isRewinding; +} + +static unsigned *multiplication_table_for_frequency(unsigned frequency) +{ + unsigned *ret = malloc(sizeof(*ret) * 0x100); + for (unsigned i = 0; i < 0x100; i++) { + ret[i] = i * frequency; + } + return ret; +} + +- (void) run +{ + assert(!master); + running = true; + [self preRun]; + if (slave) { + [slave preRun]; + unsigned *masterTable = multiplication_table_for_frequency(GB_get_clock_rate(&gb)); + unsigned *slaveTable = multiplication_table_for_frequency(GB_get_clock_rate(&slave->gb)); + while (running) { + if (linkOffset <= 0) { + linkOffset += slaveTable[GB_run(&gb)]; + } + else { + linkOffset -= masterTable[GB_run(&slave->gb)]; } } - else { - GB_run(&gb); + free(masterTable); + free(slaveTable); + [slave postRun]; + } + else { + while (running) { + if (rewind) { + rewind = false; + GB_rewind_pop(&gb); + if (!GB_rewind_pop(&gb)) { + rewind = self.view.isRewinding; + } + } + else { + GB_run(&gb); + } } } + [self postRun]; + stopping = false; +} + +- (void)postRun +{ [hex_timer invalidate]; [audioLock lock]; memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer)); @@ -421,7 +491,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil]; friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""]; friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - + notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName]; notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName]; notification.identifier = self.fileName; @@ -431,18 +501,31 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"]; } [_view setRumble:0]; - stopping = false; } - (void) start { - if (running) return; self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; + if (master) { + [master start]; + return; + } + if (running) return; [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; } - (void) stop { + if (master) { + if (!master->running) return; + GB_debugger_set_disabled(&gb, true); + if (GB_debugger_is_stopped(&gb)) { + [self interruptDebugInputRead]; + } + [master stop]; + GB_debugger_set_disabled(&gb, false); + return; + } if (!running) return; GB_debugger_set_disabled(&gb, true); if (GB_debugger_is_stopped(&gb)) { @@ -519,6 +602,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)togglePause:(id)sender { + if (master) { + [master togglePause:sender]; + return; + } if (running) { [self stop]; } @@ -749,6 +836,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (void)close { + [self disconnectLinkCable]; [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"]; [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"]; [self stop]; @@ -760,9 +848,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) { [self log:"^C\n"]; GB_debugger_break(&gb); - if (!running) { - [self start]; - } + [self start]; [self.consoleWindow makeKeyAndOrderFront:nil]; [self.consoleInput becomeFirstResponder]; } @@ -781,10 +867,13 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (BOOL)validateUserInterfaceItem:(id)anItem { if ([anItem action] == @selector(mute:)) { - [(NSMenuItem*)anItem setState:!self.audioClient.isPlaying]; + [(NSMenuItem *)anItem setState:!self.audioClient.isPlaying]; } else if ([anItem action] == @selector(togglePause:)) { - [(NSMenuItem*)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))]; + if (master) { + [(NSMenuItem *)anItem setState:(!master->running) || (GB_debugger_is_stopped(&gb)) || (GB_debugger_is_stopped(&gb))]; + } + [(NSMenuItem *)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))]; return !GB_debugger_is_stopped(&gb); } else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) { @@ -804,6 +893,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) else if ([anItem action] == @selector(connectWorkboy:)) { [(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy]; } + else if ([anItem action] == @selector(connectLinkCable:)) { + [(NSMenuItem*)anItem setState:[(NSMenuItem *)anItem representedObject] == master || + [(NSMenuItem *)anItem representedObject] == slave]; + } else if ([anItem action] == @selector(toggleCheats:)) { [(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)]; } @@ -1090,6 +1183,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) { while (!GB_is_inited(&gb)); bool was_running = running && !GB_debugger_is_stopped(&gb); + if (master) { + was_running |= master->running; + } if (was_running) { [self stop]; } @@ -1700,6 +1796,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)disconnectAllAccessories:(id)sender { + [self disconnectLinkCable]; [self performAtomicBlock:^{ accessory = GBAccessoryNone; GB_disconnect_serial(&gb); @@ -1708,6 +1805,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)connectPrinter:(id)sender { + [self disconnectLinkCable]; [self performAtomicBlock:^{ accessory = GBAccessoryPrinter; GB_connect_printer(&gb, printImage); @@ -1716,6 +1814,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (IBAction)connectWorkboy:(id)sender { + [self disconnectLinkCable]; [self performAtomicBlock:^{ accessory = GBAccessoryWorkboy; GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime); @@ -1832,4 +1931,83 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) { GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb)); } + +- (void)disconnectLinkCable +{ + bool wasRunning = self->running; + Document *partner = master ?: slave; + if (partner) { + [self stop]; + partner->master = nil; + partner->slave = nil; + master = nil; + slave = nil; + if (wasRunning) { + [partner start]; + [self start]; + } + GB_set_turbo_mode(&gb, false, false); + GB_set_turbo_mode(&partner->gb, false, false); + partner->accessory = GBAccessoryNone; + accessory = GBAccessoryNone; + } +} + +- (void)connectLinkCable:(NSMenuItem *)sender +{ + [self disconnectAllAccessories:sender]; + Document *partner = [sender representedObject]; + [partner disconnectAllAccessories:sender]; + + bool wasRunning = self->running; + [self stop]; + [partner stop]; + GB_set_turbo_mode(&partner->gb, true, true); + slave = partner; + partner->master = self; + linkOffset = 0; + partner->accessory = GBAccessoryLinkCable; + accessory = GBAccessoryLinkCable; + GB_set_serial_transfer_bit_start_callback(&gb, linkCableBitStart); + GB_set_serial_transfer_bit_start_callback(&partner->gb, linkCableBitStart); + GB_set_serial_transfer_bit_end_callback(&gb, linkCableBitEnd); + GB_set_serial_transfer_bit_end_callback(&partner->gb, linkCableBitEnd); + if (wasRunning) { + [self start]; + } +} + +- (void)linkCableBitStart:(bool)bit +{ + linkCableBit = bit; +} + +-(bool)linkCableBitEnd +{ + bool ret = GB_serial_get_data_bit(&self.partner->gb); + GB_serial_set_data_bit(&self.partner->gb, linkCableBit); + return ret; +} + +- (void)infraredStateChanged:(bool)state +{ + if (self.partner) { + GB_set_infrared_input(&self.partner->gb, state); + } +} + +-(Document *)partner +{ + return slave ?: master; +} + +- (bool)isSlave +{ + return master; +} + +- (GB_gameboy_t *)gb +{ + return &gb; +} @end diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index d02f5bd..a2cf5ee 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -59,6 +59,9 @@ + + + @@ -115,7 +118,7 @@ - + @@ -152,7 +155,7 @@ - + @@ -186,7 +189,7 @@ - + @@ -975,7 +978,7 @@ - + diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index 80721cd..26fce14 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,6 +1,7 @@ #import #include #import +@class Document; typedef enum { GB_FRAME_BLENDING_MODE_DISABLED, @@ -13,6 +14,7 @@ typedef enum { @interface GBView : NSView - (void) flip; - (uint32_t *) pixels; +@property (weak) IBOutlet Document *document; @property GB_gameboy_t *gb; @property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; @property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 6854ba7..0d834c0 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -5,6 +5,7 @@ #import "GBViewMetal.h" #import "GBButtons.h" #import "NSString+StringForKey.h" +#import "Document.h" #define JOYSTICK_HIGH 0x4000 #define JOYSTICK_LOW 0x3800 @@ -257,6 +258,9 @@ static const uint8_t workboy_vk_to_key[] = { { if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) { GB_set_clock_multiplier(_gb, analogClockMultiplier); + if (self.document.partner) { + GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier); + } if (analogClockMultiplier == 1.0) { analogClockMultiplierValid = false; } @@ -265,10 +269,16 @@ static const uint8_t workboy_vk_to_key[] = { if (underclockKeyDown && clockMultiplier > 0.5) { clockMultiplier -= 1.0/16; GB_set_clock_multiplier(_gb, clockMultiplier); + if (self.document.partner) { + GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier); + } } if (!underclockKeyDown && clockMultiplier < 1.0) { clockMultiplier += 1.0/16; GB_set_clock_multiplier(_gb, clockMultiplier); + if (self.document.partner) { + GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier); + } } } current_buffer = (current_buffer + 1) % self.numberOfBuffers; @@ -299,6 +309,9 @@ static const uint8_t workboy_vk_to_key[] = { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } for (unsigned player = 0; player < player_count; player++) { for (GBButton button = 0; button < GBButtonCount; button++) { NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; @@ -308,13 +321,20 @@ static const uint8_t workboy_vk_to_key[] = { handled = true; switch (button) { case GBTurbo: - GB_set_turbo_mode(_gb, true, self.isRewinding); + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, true, false); + } + else { + GB_set_turbo_mode(_gb, true, self.isRewinding); + } analogClockMultiplierValid = false; break; case GBRewind: - self.isRewinding = true; - GB_set_turbo_mode(_gb, false, false); + if (!self.document.partner) { + self.isRewinding = true; + GB_set_turbo_mode(_gb, false, false); + } break; case GBUnderclock: @@ -323,7 +343,17 @@ static const uint8_t workboy_vk_to_key[] = { break; default: - GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true); + if (self.document.partner) { + if (player == 0) { + GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true); + } + else { + GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true); + } + } + else { + GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true); + } break; } } @@ -351,6 +381,9 @@ static const uint8_t workboy_vk_to_key[] = { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } for (unsigned player = 0; player < player_count; player++) { for (GBButton button = 0; button < GBButtonCount; button++) { NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)]; @@ -360,7 +393,12 @@ static const uint8_t workboy_vk_to_key[] = { handled = true; switch (button) { case GBTurbo: - GB_set_turbo_mode(_gb, false, false); + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, false, false); + } + else { + GB_set_turbo_mode(_gb, false, false); + } analogClockMultiplierValid = false; break; @@ -374,7 +412,17 @@ static const uint8_t workboy_vk_to_key[] = { break; default: - GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false); + if (self.document.partner) { + if (player == 0) { + GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, false); + } + else { + GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, false); + } + } + else { + GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false); + } break; } } @@ -415,13 +463,11 @@ static const uint8_t workboy_vk_to_key[] = { - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button { if (![self.window isMainWindow]) return; - if (controller != lastController) { - [self setRumble:0]; - lastController = controller; - } - unsigned player_count = GB_get_player_count(_gb); + if (self.document.partner) { + player_count = 2; + } IOPMAssertionID assertionID; IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID); @@ -447,33 +493,63 @@ static const uint8_t workboy_vk_to_key[] = { usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3]; } + GB_gameboy_t *effectiveGB = _gb; + unsigned effectivePlayer = player; + + if (player && self.document.partner) { + effectiveGB = self.document.partner.gb; + effectivePlayer = 0; + if (controller != self.document.partner.view->lastController) { + [self setRumble:0]; + self.document.partner.view->lastController = controller; + } + } + else { + if (controller != lastController) { + [self setRumble:0]; + lastController = controller; + } + } + switch (usage) { case JOYButtonUsageNone: break; - case JOYButtonUsageA: GB_set_key_state_for_player(_gb, GB_KEY_A, player, button.isPressed); break; - case JOYButtonUsageB: GB_set_key_state_for_player(_gb, GB_KEY_B, player, button.isPressed); break; + case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break; + case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break; case JOYButtonUsageC: break; case JOYButtonUsageStart: - case JOYButtonUsageX: GB_set_key_state_for_player(_gb, GB_KEY_START, player, button.isPressed); break; + case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break; case JOYButtonUsageSelect: - case JOYButtonUsageY: GB_set_key_state_for_player(_gb, GB_KEY_SELECT, player, button.isPressed); break; + case JOYButtonUsageY: GB_set_key_state_for_player(effectiveGB, GB_KEY_SELECT, effectivePlayer, button.isPressed); break; case JOYButtonUsageR2: case JOYButtonUsageL2: case JOYButtonUsageZ: { self.isRewinding = button.isPressed; if (button.isPressed) { - GB_set_turbo_mode(_gb, false, false); + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, false, false); + } + else { + GB_set_turbo_mode(_gb, false, false); + } } break; } - case JOYButtonUsageL1: GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break; + case JOYButtonUsageL1: { + if (self.document.isSlave) { + GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false); break; + } + else { + GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break; + } + } case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break; - case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, button.isPressed); break; - case JOYButtonUsageDPadRight: GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, button.isPressed); break; - case JOYButtonUsageDPadUp: GB_set_key_state_for_player(_gb, GB_KEY_UP, player, button.isPressed); break; - case JOYButtonUsageDPadDown: GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, button.isPressed); break; + case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, button.isPressed); break; + case JOYButtonUsageDPadRight: GB_set_key_state_for_player(effectiveGB, GB_KEY_RIGHT, effectivePlayer, button.isPressed); break; + case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break; + case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break; default: break; diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 586d872..04bcf8f 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -12,7 +12,11 @@ - + + + + + @@ -373,6 +377,17 @@ + + + + + + + + + + + diff --git a/Core/gb.h b/Core/gb.h index 9043936..c42af4b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -803,9 +803,7 @@ void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callbac void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback); void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); -#ifdef GB_INTERNAL uint32_t GB_get_clock_rate(GB_gameboy_t *gb); -#endif void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); unsigned GB_get_screen_width(GB_gameboy_t *gb); From c36bdc22f66fbc58c82d4e3812e0110e50ece208 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Nov 2020 13:55:39 +0200 Subject: [PATCH 023/365] More accurate interrupt emulation --- Core/sm83_cpu.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 3b3eceb..ffc5997 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -261,7 +261,20 @@ static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id) } GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ gb->pending_cycles = 4; +} +static void cycle_oam_bug_pc(GB_gameboy_t *gb) +{ + if (GB_is_cgb(gb)) { + /* Slight optimization */ + gb->pending_cycles += 4; + return; + } + if (gb->pending_cycles) { + GB_advance_cycles(gb, gb->pending_cycles); + } + GB_trigger_oam_bug(gb, gb->pc); /* Todo: test T-cycle timing */ + gb->pending_cycles = 4; } static void flush_pending_cycles(GB_gameboy_t *gb) @@ -1538,8 +1551,9 @@ void GB_cpu_run(GB_gameboy_t *gb) gb->halted = false; uint16_t call_addr = gb->pc; - cycle_no_access(gb); - cycle_no_access(gb); + gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); + cycle_oam_bug_pc(gb); + gb->pc--; GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ cycle_no_access(gb); From 7fdc58a07ef48b155c28d97f4004f81de7a82c36 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Nov 2020 16:24:16 +0200 Subject: [PATCH 024/365] Implement CGB-mode TILE_SEL mixing, fixes cgb-acid-hell and m3_lcdc_tile_sel_change2, closes #308 --- Core/display.c | 76 +++++++++++++++++++++++++++++++++++++++++-------- Core/gb.h | 2 ++ Core/sm83_cpu.c | 30 +++++++++++++++++++ 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/Core/display.c b/Core/display.c index 2eb8c42..b57317b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -430,6 +430,23 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index) } } +static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use) +{ + /* + Based on Matt Currie's research here: + https://github.com/mattcurrie/mealybug-tearoom-tests/blob/master/the-comprehensive-game-boy-ppu-documentation.md#tile_sel-bit-4 + */ + + *should_use = true; + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + *should_use = !(gb->current_tile & 0x80); + /* if (gb->model != GB_MODEL_CGB_D) */ return gb->current_tile; + // TODO: CGB D behaves differently + } + return gb->data_for_sel_glitch; +} + + static void render_pixel_if_possible(GB_gameboy_t *gb) { GB_fifo_item_t *fifo_item = NULL; @@ -621,6 +638,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) break; case GB_FETCHER_GET_TILE_DATA_LOWER: { + bool use_glitched = false; + if (gb->tile_sel_glitch) { + gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched); + } uint8_t y_flip = 0; uint16_t tile_address = 0; uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); @@ -638,20 +659,32 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } - gb->current_tile_data[0] = + if (!use_glitched) { + gb->current_tile_data[0] = + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[0] = 0xFF; + } + } + else { + gb->data_for_sel_glitch = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; - if (gb->vram_ppu_blocked) { - gb->current_tile_data[0] = 0xFF; + if (gb->vram_ppu_blocked) { + gb->data_for_sel_glitch = 0xFF; + } } } gb->fetcher_state++; break; case GB_FETCHER_GET_TILE_DATA_HIGH: { - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. - Additionally, on CGB-D and newer mixing two tiles by changing the tileset - bit mid-fetching causes a glitched mixing of the two, in comparison to the - more logical DMG version. */ + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + + bool use_glitched = false; + if (gb->tile_sel_glitch) { + gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched); + } + uint16_t tile_address = 0; uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); @@ -669,10 +702,20 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) y_flip = 0x7; } gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1; - gb->current_tile_data[1] = - gb->vram[gb->last_tile_data_address]; - if (gb->vram_ppu_blocked) { - gb->current_tile_data[1] = 0xFF; + if (!use_glitched) { + gb->current_tile_data[1] = + gb->vram[gb->last_tile_data_address]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[1] = 0xFF; + } + } + else { + if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) { + gb->data_for_sel_glitch = gb->vram[gb->last_tile_data_address]; + if (gb->vram_ppu_blocked) { + gb->data_for_sel_glitch = 0xFF; + } + } } } if (gb->wx_triggered) { @@ -1112,7 +1155,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) object->flags & 0x80, gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, object->flags & 0x20); - + + gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1]; gb->n_visible_objs--; } @@ -1128,6 +1172,14 @@ abort_fetching_object: GB_SLEEP(gb, display, 21, 1); } + /* TODO: Verify */ + if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { + gb->data_for_sel_glitch = gb->current_tile_data[0]; + } + else { + gb->data_for_sel_glitch = gb->current_tile_data[1]; + } + while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) { /* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */ uint32_t *dest = NULL; diff --git a/Core/gb.h b/Core/gb.h index c42af4b..0d0aaa9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -548,6 +548,7 @@ struct GB_gameboy_internal_s { uint16_t last_tile_data_address; uint16_t last_tile_index_address; bool cgb_repeated_a_frame; + uint8_t data_for_sel_glitch; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ @@ -692,6 +693,7 @@ struct GB_gameboy_internal_s { /* Temporary state */ bool wx_just_changed; + bool tile_sel_glitch; ); }; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index ffc5997..7107ed1 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -21,10 +21,12 @@ typedef enum { GB_CONFLICT_DMG_LCDC, GB_CONFLICT_SGB_LCDC, GB_CONFLICT_WX, + GB_CONFLICT_CGB_LCDC, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ static const GB_conflict_t cgb_conflict_map[0x80] = { + [GB_IO_LCDC] = GB_CONFLICT_CGB_LCDC, [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, [GB_IO_STAT] = GB_CONFLICT_STAT_CGB, @@ -241,6 +243,34 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->wx_just_changed = false; gb->pending_cycles = 3; return; + + case GB_CONFLICT_CGB_LCDC: + if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) { + // Todo: This is difference is because my timing is off in one of the models + if (gb->model > GB_MODEL_CGB_C) { + GB_advance_cycles(gb, gb->pending_cycles); + gb->tile_sel_glitch = true; + GB_advance_cycles(gb, 1); + gb->tile_sel_glitch = false; + GB_write_memory(gb, addr, value); + gb->pending_cycles = 3; + } + else { + GB_advance_cycles(gb, gb->pending_cycles - 1); + gb->tile_sel_glitch = true; + GB_advance_cycles(gb, 1); + gb->tile_sel_glitch = false; + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + } + else { + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + return; + } } From cd2310f0a749499130cd148cafa2386c32491832 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Nov 2020 19:39:54 +0200 Subject: [PATCH 025/365] Wave RAM reads 0xFF while active on AGBs --- Core/apu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index 7e7ab31..2b5dc3b 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -596,6 +596,9 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) { return 0xFF; } + if (gb->model == GB_MODEL_AGB) { + return 0xFF; + } reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2; } From b7f34547631dba70f84ec670ec701af0e3f9ff56 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Nov 2020 22:12:15 +0200 Subject: [PATCH 026/365] More accurate emulation of the IR port --- Core/memory.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index f73209e..0d33e6c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -115,7 +115,7 @@ static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) static bool effective_ir_input(GB_gameboy_t *gb) { - return gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir; + return gb->infrared_input || gb->cart_ir; } static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) @@ -428,9 +428,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_RP: { if (!gb->cgb_mode) return 0xFF; /* You will read your own IR LED if it's on. */ - uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; - if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && effective_ir_input(gb)) { - ret |= 2; + uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x2E; + if (gb->model != GB_MODEL_CGB_E) { + ret |= 0x10; + } + if (((gb->io_registers[GB_IO_RP] & 0xC1) == 0xC0 && effective_ir_input(gb)) && gb->model != GB_MODEL_AGB) { + ret &= ~2; } return ret; } From 1d9ac5ccc3f54a5d527e6f7dab554015107ad5d5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 21 Nov 2020 00:52:54 +0200 Subject: [PATCH 027/365] More accurate IR emulation, simplify API --- Cocoa/Document.m | 2 +- Core/gb.c | 11 ----------- Core/gb.h | 19 ++++--------------- Core/memory.c | 39 +++++++++++++-------------------------- Core/timing.c | 35 +++++++++++++++++++++++++---------- 5 files changed, 43 insertions(+), 63 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 84e181f..ea7ef49 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -184,7 +184,7 @@ static bool linkCableBitEnd(GB_gameboy_t *gb) return [self linkCableBitEnd]; } -static void infraredStateChanged(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update) +static void infraredStateChanged(GB_gameboy_t *gb, bool on) { Document *self = (__bridge Document *)GB_get_user_data(gb); [self infraredStateChanged:on]; diff --git a/Core/gb.c b/Core/gb.c index 7325d79..57c3788 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1073,17 +1073,6 @@ void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) void GB_set_infrared_input(GB_gameboy_t *gb, bool state) { gb->infrared_input = state; - gb->cycles_since_input_ir_change = 0; - gb->ir_queue_length = 0; -} - -void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change) -{ - if (gb->ir_queue_length == GB_MAX_IR_QUEUE) { - GB_log(gb, "IR Queue is full\n"); - return; - } - gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change}; } void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) diff --git a/Core/gb.h b/Core/gb.h index 0d0aaa9..7610fca 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -125,8 +125,6 @@ typedef enum { GB_BORDER_ALWAYS, } GB_border_mode_t; -#define GB_MAX_IR_QUEUE 256 - enum { /* Joypad and Serial */ GB_IO_JOYP = 0x00, // Joypad (R/W) @@ -272,7 +270,7 @@ typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb); typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); -typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update); +typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on); typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, double rumble_amplitude); typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send); typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb); @@ -283,11 +281,6 @@ typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb); typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb); typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type); -typedef struct { - bool state; - uint64_t delay; -} GB_ir_queue_item_t; - struct GB_breakpoint_s; struct GB_watchpoint_s; @@ -374,6 +367,9 @@ struct GB_gameboy_internal_s { uint8_t extra_oam[0xff00 - 0xfea0]; uint32_t ram_size; // Different between CGB and DMG GB_workboy_t workboy; + + int32_t ir_sensor; + bool effective_ir_input; ); /* DMA and HDMA */ @@ -613,12 +609,6 @@ struct GB_gameboy_internal_s { GB_print_image_callback_t printer_callback; GB_workboy_set_time_callback workboy_set_time_callback; GB_workboy_get_time_callback workboy_get_time_callback; - - /* IR */ - uint64_t cycles_since_ir_change; // In 8MHz units - uint64_t cycles_since_input_ir_change; // In 8MHz units - GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE]; - size_t ir_queue_length; /*** Debugger ***/ volatile bool debug_stopped, debug_disable; @@ -771,7 +761,6 @@ void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode); void GB_set_infrared_input(GB_gameboy_t *gb, bool state); -void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, uint64_t cycles_after_previous_change); /* In 8MHz units*/ void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback); void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback); diff --git a/Core/memory.c b/Core/memory.c index 0d33e6c..40af0e5 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -113,11 +113,6 @@ static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); } -static bool effective_ir_input(GB_gameboy_t *gb) -{ - return gb->infrared_input || gb->cart_ir; -} - static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0x100 && !gb->boot_rom_finished) { @@ -173,7 +168,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) case 0xD: // RTC status return 1; case 0xE: // IR mode - return effective_ir_input(gb); // TODO: What are the other bits? + return gb->effective_ir_input; // TODO: What are the other bits? default: GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr); return 1; // TODO: What happens in this case? @@ -191,7 +186,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { - return 0xc0 | effective_ir_input(gb); + return 0xc0 | gb->effective_ir_input; } if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && @@ -432,7 +427,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] & 0xC1) == 0xC0 && effective_ir_input(gb)) && gb->model != GB_MODEL_AGB) { + if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model != GB_MODEL_AGB) { ret &= ~2; } return ret; @@ -655,14 +650,11 @@ static bool huc3_write(GB_gameboy_t *gb, uint8_t value) // Not sure what writes here mean, they're always 0xFE return true; case 0xE: { // IR mode - bool old_input = effective_ir_input(gb); - gb->cart_ir = value & 1; - bool new_input = effective_ir_input(gb); - if (new_input != old_input) { + if (gb->cart_ir != (value & 1)) { + gb->cart_ir = value & 1; if (gb->infrared_callback) { - gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + gb->infrared_callback(gb, value & 1); } - gb->cycles_since_ir_change = 0; } return true; } @@ -691,14 +683,11 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) && gb->cartridge_type->mbc_type != GB_HUC1) return; if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) { - bool old_input = effective_ir_input(gb); - gb->cart_ir = value & 1; - bool new_input = effective_ir_input(gb); - if (new_input != old_input) { + if (gb->cart_ir != (value & 1)) { + gb->cart_ir = value & 1; if (gb->infrared_callback) { - gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + gb->infrared_callback(gb, value & 1); } - gb->cycles_since_ir_change = 0; } return; } @@ -1111,15 +1100,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (!GB_is_cgb(gb)) { return; } - bool old_input = effective_ir_input(gb); - gb->io_registers[GB_IO_RP] = value; - bool new_input = effective_ir_input(gb); - if (new_input != old_input) { + if ((gb->io_registers[GB_IO_RP] ^ value) & 1) { if (gb->infrared_callback) { - gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + gb->infrared_callback(gb, value & 1); } - gb->cycles_since_ir_change = 0; } + gb->io_registers[GB_IO_RP] = value; + return; } diff --git a/Core/timing.c b/Core/timing.c index 965ba27..44ff8f7 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -89,15 +89,32 @@ void GB_timing_sync(GB_gameboy_t *gb) } #endif -static void GB_ir_run(GB_gameboy_t *gb) + +#define IR_DECAY 31500 +#define IR_THRESHOLD 19900 +#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY + +static void GB_ir_run(GB_gameboy_t *gb, uint32_t cycles) { - if (gb->ir_queue_length == 0) return; - if (gb->cycles_since_input_ir_change >= gb->ir_queue[0].delay) { - gb->cycles_since_input_ir_change -= gb->ir_queue[0].delay; - gb->infrared_input = gb->ir_queue[0].state; - gb->ir_queue_length--; - memmove(&gb->ir_queue[0], &gb->ir_queue[1], sizeof(gb->ir_queue[0]) * (gb->ir_queue_length)); + if (gb->model == GB_MODEL_AGB) return; + if (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1)) { + gb->ir_sensor += cycles; + if (gb->ir_sensor > IR_MAX) { + gb->ir_sensor = IR_MAX; + } + + gb->effective_ir_input = gb->ir_sensor >= IR_THRESHOLD && gb->ir_sensor <= IR_THRESHOLD + IR_DECAY; } + else { + if (gb->ir_sensor <= cycles) { + gb->ir_sensor = 0; + } + else { + gb->ir_sensor -= cycles; + } + gb->effective_ir_input = false; + } + } static void advance_tima_state_machine(GB_gameboy_t *gb) @@ -234,8 +251,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->double_speed_alignment += cycles; gb->hdma_cycles += cycles; gb->apu_output.sample_cycles += cycles; - gb->cycles_since_ir_change += cycles; - gb->cycles_since_input_ir_change += cycles; gb->cycles_since_last_sync += cycles; gb->cycles_since_run += cycles; @@ -252,7 +267,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) } GB_apu_run(gb); GB_display_run(gb, cycles); - GB_ir_run(gb); + GB_ir_run(gb, cycles); } /* From bdd27ce50d4b27898e11e8a34c9371e210f10846 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 21 Nov 2020 15:36:21 +0200 Subject: [PATCH 028/365] IR support in the libretro port --- libretro/libretro.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 9e27f03..0fb8dc5 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -213,6 +213,16 @@ static bool serial_end2(GB_gameboy_t *gb) return ret; } +static void infrared_callback1(GB_gameboy_t *gb, bool output) +{ + GB_set_infrared_input(&gameboy[1], output); +} + +static void infrared_callback2(GB_gameboy_t *gb, bool output) +{ + GB_set_infrared_input(&gameboy[0], output); +} + static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { return r <<16 | g <<8 | b; @@ -351,12 +361,16 @@ static void set_link_cable_state(bool state) GB_set_serial_transfer_bit_end_callback(&gameboy[0], serial_end1); GB_set_serial_transfer_bit_start_callback(&gameboy[1], serial_start2); GB_set_serial_transfer_bit_end_callback(&gameboy[1], serial_end2); + GB_set_infrared_callback(&gameboy[0], infrared_callback1); + GB_set_infrared_callback(&gameboy[1], infrared_callback2); } else if (!state) { GB_set_serial_transfer_bit_start_callback(&gameboy[0], NULL); GB_set_serial_transfer_bit_end_callback(&gameboy[0], NULL); GB_set_serial_transfer_bit_start_callback(&gameboy[1], NULL); GB_set_serial_transfer_bit_end_callback(&gameboy[1], NULL); + GB_set_infrared_callback(&gameboy[0], NULL); + GB_set_infrared_callback(&gameboy[1], NULL); } } From 027cecde2493f022e6e2881e6c8494183a3f39e2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 21 Nov 2020 16:19:58 +0200 Subject: [PATCH 029/365] Added debugger "undo" command. Closes #156 --- Core/debugger.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++- Core/gb.c | 7 +++++++ Core/gb.h | 4 ++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index c27acd3..3e1b66c 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1889,6 +1889,29 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return true; } +static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!gb->undo_label) { + GB_log(gb, "No undo state available\n"); + return true; + } + uint16_t pc = gb->pc; + GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size(gb)); + GB_log(gb, "Reverted a \"%s\" command.\n", gb->undo_label); + if (pc != gb->pc) { + GB_cpu_disassemble(gb, gb->pc, 5); + } + gb->undo_label = NULL; + + return true; +} + static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command); #define HELP_NEWLINE "\n " @@ -1899,6 +1922,7 @@ static const debugger_command_t commands[] = { {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, + {"undo", 1, undo, "Reverts the last command"}, {"backtrace", 2, backtrace, "Displays the current call stack"}, {"bt", 2, }, /* Alias */ {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"}, @@ -2184,7 +2208,30 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) const debugger_command_t *command = find_command(command_string); if (command) { - return command->implementation(gb, arguments, modifiers, command); + uint8_t *old_state = malloc(GB_get_save_state_size(gb)); + GB_save_state_to_buffer(gb, old_state); + bool ret = command->implementation(gb, arguments, modifiers, command); + if (!ret) { // Command continues, save state in any case + free(gb->undo_state); + gb->undo_state = old_state; + gb->undo_label = command->command; + } + else { + uint8_t *new_state = malloc(GB_get_save_state_size(gb)); + GB_save_state_to_buffer(gb, new_state); + if (memcmp(new_state, old_state, GB_get_save_state_size(gb)) != 0) { + // State changed, save the old state as the new undo state + free(gb->undo_state); + gb->undo_state = old_state; + gb->undo_label = command->command; + } + else { + // Nothing changed, just free the old state + free(old_state); + } + free(new_state); + } + return ret; } else { GB_log(gb, "%s: no such command.\n", command_string); @@ -2260,6 +2307,11 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add void GB_debugger_run(GB_gameboy_t *gb) { if (gb->debug_disable) return; + + if (!gb->undo_state) { + gb->undo_state = malloc(GB_get_save_state_size(gb)); + GB_save_state_to_buffer(gb, gb->undo_state); + } char *input = NULL; if (gb->debug_next_command && gb->debug_call_depth <= 0 && !gb->halted) { diff --git a/Core/gb.c b/Core/gb.c index 57c3788..4788ad9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -202,6 +202,9 @@ void GB_free(GB_gameboy_t *gb) if (gb->nontrivial_jump_state) { free(gb->nontrivial_jump_state); } + if (gb->undo_state) { + free(gb->undo_state); + } #ifndef GB_DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); #endif @@ -1434,6 +1437,10 @@ void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) gb->ram = realloc(gb->ram, gb->ram_size = 0x2000); gb->vram = realloc(gb->vram, gb->vram_size = 0x2000); } + if (gb->undo_state) { + free(gb->undo_state); + gb->undo_state = NULL; + } GB_rewind_free(gb); GB_reset(gb); load_default_border(gb); diff --git a/Core/gb.h b/Core/gb.h index 7610fca..ed736e0 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -646,6 +646,10 @@ struct GB_gameboy_internal_s { /* Ticks command */ uint64_t debugger_ticks; + /* Undo */ + uint8_t *undo_state; + const char *undo_label; + /* Rewind */ #define GB_REWIND_FRAMES_PER_KEY 255 size_t rewind_buffer_length; From bbf609f46b919ab603c9b28424fb724dd92a16fc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 21 Nov 2020 21:05:03 +0200 Subject: [PATCH 030/365] Add TGA output option to the tester, closes #310 --- Tester/main.c | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/Tester/main.c b/Tester/main.c index 16dbf7b..6faab3b 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -31,16 +31,23 @@ static unsigned int test_length = 60 * 40; GB_gameboy_t gb; static unsigned int frames = 0; -const char bmp_header[] = { -0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00, -0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF, -0xFF, 0xFF, 0x01, 0x00, 0x20, 0x00, 0x03, 0x00, -0x00, 0x00, 0x02, 0x68, 0x01, 0x00, 0x12, 0x0B, -0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +static bool use_tga = false; +static const uint8_t bmp_header[] = { + 0x42, 0x4D, 0x48, 0x68, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x38, 0x00, + 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x70, 0xFF, + 0xFF, 0xFF, 0x01, 0x00, 0x20, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x68, 0x01, 0x00, 0x12, 0x0B, + 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t tga_header[] = { + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x90, 0x00, + 0x20, 0x28, }; uint32_t bitmap[160*144]; @@ -139,7 +146,12 @@ static void vblank(GB_gameboy_t *gb) /* Let the test run for extra four seconds if the screen is off/disabled */ if (!is_screen_blank || frames >= test_length + 60 * 4) { FILE *f = fopen(bmp_filename, "wb"); - fwrite(&bmp_header, 1, sizeof(bmp_header), f); + if (use_tga) { + fwrite(&tga_header, 1, sizeof(tga_header), f); + } + else { + fwrite(&bmp_header, 1, sizeof(bmp_header), f); + } fwrite(&bitmap, 1, sizeof(bitmap), f); fclose(f); if (!gb->boot_rom_finished) { @@ -215,6 +227,9 @@ static char *executable_relative_path(const char *filename) static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { + if (use_tga) { + return (r << 16) | (g << 8) | (b); + } return (r << 24) | (g << 16) | (b << 8); } @@ -268,6 +283,12 @@ int main(int argc, char **argv) dmg = true; continue; } + + if (strcmp(argv[i], "--tga") == 0) { + fprintf(stderr, "Using TGA output\n"); + use_tga = true; + continue; + } if (strcmp(argv[i], "--start") == 0) { fprintf(stderr, "Pushing Start and A\n"); @@ -312,7 +333,7 @@ int main(int argc, char **argv) size_t path_length = strlen(filename); char bitmap_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .bmp + NULL */ - replace_extension(filename, path_length, bitmap_path, ".bmp"); + replace_extension(filename, path_length, bitmap_path, use_tga? ".tga" : ".bmp"); bmp_filename = &bitmap_path[0]; char log_path[path_length + 5]; From 67c0e03f3b88532a726fa33ca202ce72a8e76ef2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 22 Nov 2020 00:21:19 +0200 Subject: [PATCH 031/365] Fix a window bug in CGB mode, fixes #123 --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index b57317b..c876599 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1021,7 +1021,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) bool should_activate_window = false; if (gb->io_registers[GB_IO_WX] == 0) { static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14}; - if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { + if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7] && !GB_is_cgb(gb)) { should_activate_window = true; } } From 0485124076aef3e66c9b1095ec0b30ee2329ab51 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 28 Nov 2020 19:31:25 +0200 Subject: [PATCH 032/365] Redo channel 1 sweep based on DMG schematics; emulates two newly discovered behaviors and also fixes #309 --- Core/apu.c | 51 ++++++++++++++++++++----------------------------- Core/apu.h | 4 ++-- Core/debugger.c | 2 +- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 2b5dc3b..9275f81 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -230,15 +230,6 @@ static void render(GB_gameboy_t *gb) gb->apu_output.sample_callback(gb, &filtered_output); } -static uint16_t new_sweep_sample_length(GB_gameboy_t *gb) -{ - uint16_t delta = gb->apu.shadow_sweep_sample_length >> (gb->io_registers[GB_IO_NR10] & 7); - if (gb->io_registers[GB_IO_NR10] & 8) { - return gb->apu.shadow_sweep_sample_length - delta; - } - return gb->apu.shadow_sweep_sample_length + delta; -} - static void update_square_sample(GB_gameboy_t *gb, unsigned index) { if (gb->apu.square_channels[index].current_sample_index & 0x80) return; @@ -405,21 +396,22 @@ void GB_apu_div_event(GB_gameboy_t *gb) } if ((gb->apu.div_divider & 3) == 3) { - if (!gb->apu.sweep_enabled) { - return; - } if (gb->apu.square_sweep_countdown) { - if (!--gb->apu.square_sweep_countdown) { + if (!--gb->apu.square_sweep_countdown && gb->apu.sweep_enabled) { if ((gb->io_registers[GB_IO_NR10] & 0x70) && (gb->io_registers[GB_IO_NR10] & 0x07)) { gb->apu.square_channels[GB_SQUARE_1].sample_length = - gb->apu.shadow_sweep_sample_length = - gb->apu.new_sweep_sample_length; + gb->apu.sweep_length_addend + gb->apu.shadow_sweep_sample_length + !!(gb->io_registers[GB_IO_NR10] & 0x8); + gb->apu.square_channels[GB_SQUARE_1].sample_length &= 0x7FF; } + gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; + gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); if (gb->io_registers[GB_IO_NR10] & 0x70) { /* Recalculation and overflow check only occurs after a delay */ - gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div; + gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; } + + gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x70; gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; @@ -447,13 +439,14 @@ void GB_apu_run(GB_gameboy_t *gb) } else { /* APU bug: sweep frequency is checked after adding the sweep delta twice */ - gb->apu.new_sweep_sample_length = new_sweep_sample_length(gb); - if (gb->apu.new_sweep_sample_length > 0x7ff) { + gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; + if (gb->io_registers[GB_IO_NR10] & 8) { + gb->apu.sweep_length_addend ^= 0x7FF; + } + if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) { gb->apu.is_active[GB_SQUARE_1] = false; update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); - gb->apu.sweep_enabled = false; } - gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8; gb->apu.square_sweep_calculate_countdown = 0; } } @@ -672,10 +665,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) /* Square channels */ case GB_IO_NR10: - if (gb->apu.sweep_decreasing && !(value & 8)) { + if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) { gb->apu.is_active[GB_SQUARE_1] = false; update_sample(gb, GB_SQUARE_1, 0, 0); - gb->apu.sweep_enabled = false; gb->apu.square_sweep_calculate_countdown = 0; } if ((value & 0x70) == 0) { @@ -744,12 +736,12 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].sample_length &= 0xFF; gb->apu.square_channels[index].sample_length |= (value & 7) << 8; - if (index == GB_SQUARE_1) { - gb->apu.shadow_sweep_sample_length = - gb->apu.new_sweep_sample_length = - gb->apu.square_channels[0].sample_length; - } if (value & 0x80) { + if (index == GB_SQUARE_1) { + gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; + gb->apu.shadow_sweep_sample_length = 0; + gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x70; + } /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by turning the APU off. */ if (!gb->apu.is_active[index]) { @@ -782,15 +774,14 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (index == GB_SQUARE_1) { - gb->apu.sweep_decreasing = false; if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ - gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div; + gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; + gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); } else { gb->apu.square_sweep_calculate_countdown = 0; } - gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x77; gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; } diff --git a/Core/apu.h b/Core/apu.h index a3a36a6..0512723 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -64,10 +64,10 @@ typedef struct uint8_t square_sweep_countdown; // In 128Hz uint8_t square_sweep_calculate_countdown; // In 2 MHz - uint16_t new_sweep_sample_length; + uint16_t sweep_length_addend; uint16_t shadow_sweep_sample_length; bool sweep_enabled; - bool sweep_decreasing; + GB_PADDING(bool, sweep_decreasing); struct { uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks diff --git a/Core/debugger.c b/Core/debugger.c index 3e1b66c..28db01c 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1780,7 +1780,7 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg if (channel == GB_SQUARE_1) { GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n", gb->apu.sweep_enabled? "active" : "inactive", - gb->apu.sweep_decreasing? "decreasing" : "increasing", + (gb->io_registers[GB_IO_NR10] & 0x8) ? "decreasing" : "increasing", gb->apu.square_sweep_calculate_countdown); } From 74cf452a48d7ad6676b2e5f5580bf8e47b2178de Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 1 Dec 2020 14:17:35 +0200 Subject: [PATCH 033/365] Further accuracy improvements to sweep; passes Blargg's APU tests again, this time for real --- Core/apu.c | 73 +++++++++++++++++++++++-------------------------- Core/apu.h | 2 +- Core/debugger.c | 15 +++++++--- Core/rumble.c | 2 +- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 9275f81..c568d08 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -329,6 +329,24 @@ static void tick_noise_envelope(GB_gameboy_t *gb) } } +static void trigger_sweep_calculation(GB_gameboy_t *gb) +{ + if ((gb->io_registers[GB_IO_NR10] & 0x70) && gb->apu.square_sweep_countdown == 7) { + if (gb->io_registers[GB_IO_NR10] & 0x07) { + gb->apu.square_channels[GB_SQUARE_1].sample_length = + gb->apu.sweep_length_addend + gb->apu.shadow_sweep_sample_length + !!(gb->io_registers[GB_IO_NR10] & 0x8); + gb->apu.square_channels[GB_SQUARE_1].sample_length &= 0x7FF; + } + gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; + gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); + + /* Recalculation and overflow check only occurs after a delay */ + gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; + + gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; + } +} + void GB_apu_div_event(GB_gameboy_t *gb) { if (!gb->apu.global_enable) return; @@ -396,27 +414,9 @@ void GB_apu_div_event(GB_gameboy_t *gb) } if ((gb->apu.div_divider & 3) == 3) { - if (gb->apu.square_sweep_countdown) { - if (!--gb->apu.square_sweep_countdown && gb->apu.sweep_enabled) { - if ((gb->io_registers[GB_IO_NR10] & 0x70) && (gb->io_registers[GB_IO_NR10] & 0x07)) { - gb->apu.square_channels[GB_SQUARE_1].sample_length = - gb->apu.sweep_length_addend + gb->apu.shadow_sweep_sample_length + !!(gb->io_registers[GB_IO_NR10] & 0x8); - gb->apu.square_channels[GB_SQUARE_1].sample_length &= 0x7FF; - } - gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; - gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); - - if (gb->io_registers[GB_IO_NR10] & 0x70) { - /* Recalculation and overflow check only occurs after a delay */ - gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; - } - - gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x70; - - gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); - if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; - } - } + gb->apu.square_sweep_countdown++; + gb->apu.square_sweep_countdown &= 7; + trigger_sweep_calculation(gb); } } @@ -433,7 +433,8 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.lf_div ^= cycles & 1; gb->apu.noise_channel.alignment += cycles; - if (gb->apu.square_sweep_calculate_countdown) { + if (gb->apu.square_sweep_calculate_countdown && + ((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.square_sweep_calculate_countdown <= 7)) { // Calculation is paused if the lower bits if (gb->apu.square_sweep_calculate_countdown > cycles) { gb->apu.square_sweep_calculate_countdown -= cycles; } @@ -664,18 +665,16 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) break; /* Square channels */ - case GB_IO_NR10: - if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) { + case GB_IO_NR10:{ + bool old_negate = gb->io_registers[GB_IO_NR10] & 8; + gb->io_registers[GB_IO_NR10] = value; + if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend + old_negate > 0x7FF && !(value & 8)) { gb->apu.is_active[GB_SQUARE_1] = false; update_sample(gb, GB_SQUARE_1, 0, 0); - gb->apu.square_sweep_calculate_countdown = 0; - } - if ((value & 0x70) == 0) { - /* Todo: what happens if we set period to 0 while a calculate event is scheduled, and then - re-set it to non-zero? */ - gb->apu.square_sweep_calculate_countdown = 0; } + trigger_sweep_calculation(gb); break; + } case GB_IO_NR11: case GB_IO_NR21: { @@ -737,11 +736,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].sample_length &= 0xFF; gb->apu.square_channels[index].sample_length |= (value & 7) << 8; if (value & 0x80) { - if (index == GB_SQUARE_1) { - gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; - gb->apu.shadow_sweep_sample_length = 0; - gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x70; - } /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by turning the APU off. */ if (!gb->apu.is_active[index]) { @@ -776,16 +770,17 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (index == GB_SQUARE_1) { if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ + gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; + gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); } else { - gb->apu.square_sweep_calculate_countdown = 0; + gb->apu.shadow_sweep_sample_length = 0; + gb->apu.sweep_length_addend = 0; } - gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7); - if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8; + gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } - } /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ diff --git a/Core/apu.h b/Core/apu.h index 0512723..a8b5697 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -66,7 +66,7 @@ typedef struct uint8_t square_sweep_calculate_countdown; // In 2 MHz uint16_t sweep_length_addend; uint16_t shadow_sweep_sample_length; - bool sweep_enabled; + GB_PADDING(bool, sweep_enabled); GB_PADDING(bool, sweep_decreasing); struct { diff --git a/Core/debugger.c b/Core/debugger.c index 28db01c..9825d95 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1778,10 +1778,17 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); if (channel == GB_SQUARE_1) { - GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n", - gb->apu.sweep_enabled? "active" : "inactive", - (gb->io_registers[GB_IO_NR10] & 0x8) ? "decreasing" : "increasing", - gb->apu.square_sweep_calculate_countdown); + GB_log(gb, " Frequency sweep %s and %s\n", + ((gb->io_registers[GB_IO_NR10] & 0x7) && (gb->io_registers[GB_IO_NR10] & 0x70))? "active" : "inactive", + (gb->io_registers[GB_IO_NR10] & 0x8) ? "decreasing" : "increasing"); + if (gb->apu.square_sweep_calculate_countdown) { + GB_log(gb, " On going frequency calculation will be ready in %u APU ticks\n", + gb->apu.square_sweep_calculate_countdown); + } + else { + GB_log(gb, " Shadow frequency register: 0x%03x\n", gb->apu.shadow_sweep_sample_length); + GB_log(gb, " Sweep addend register: 0x%03x\n", gb->apu.sweep_length_addend); + } } if (gb->apu.square_channels[channel].length_enabled) { diff --git a/Core/rumble.c b/Core/rumble.c index 8cbe20d..22321fb 100644 --- a/Core/rumble.c +++ b/Core/rumble.c @@ -32,7 +32,7 @@ void GB_handle_rumble(GB_gameboy_t *gb) ch4_rumble = MAX(ch4_rumble, 0.0); double ch1_rumble = 0; - if (gb->apu.sweep_enabled && ((gb->io_registers[GB_IO_NR10] >> 4) & 7)) { + if ((gb->io_registers[GB_IO_NR10] & 0x7) && (gb->io_registers[GB_IO_NR10] & 0x70)) { double sweep_speed = (gb->io_registers[GB_IO_NR10] & 7) / (double)((gb->io_registers[GB_IO_NR10] >> 4) & 7); ch1_rumble = gb->apu.square_channels[GB_SQUARE_1].current_volume * ch1_volume / 32.0 * sweep_speed / 8.0 - 0.5; ch1_rumble = MIN(ch1_rumble, 1.0); From 13bc8679f9b4cc14290bf12644224c08154b18aa Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 1 Dec 2020 14:18:19 +0200 Subject: [PATCH 034/365] Correct preservation of NRx1's state on pre-CGB models --- Core/apu.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index c568d08..22808da 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -633,11 +633,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) break; case GB_IO_NR52: { - uint8_t old_nrx1[] = { - gb->io_registers[GB_IO_NR11], - gb->io_registers[GB_IO_NR21], - gb->io_registers[GB_IO_NR31], - gb->io_registers[GB_IO_NR41] + uint8_t old_pulse_lengths[] = { + gb->apu.square_channels[0].pulse_length, + gb->apu.square_channels[1].pulse_length, + gb->apu.wave_channel.pulse_length, + gb->apu.noise_channel.pulse_length }; if ((value & 0x80) && !gb->apu.global_enable) { GB_apu_init(gb); @@ -649,17 +649,14 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } memset(&gb->apu, 0, sizeof(gb->apu)); memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); - old_nrx1[0] &= 0x3F; - old_nrx1[1] &= 0x3F; - gb->apu.global_enable = false; } if (!GB_is_cgb(gb) && (value & 0x80)) { - GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]); - GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]); - GB_apu_write(gb, GB_IO_NR31, old_nrx1[2]); - GB_apu_write(gb, GB_IO_NR41, old_nrx1[3]); + gb->apu.square_channels[0].pulse_length = old_pulse_lengths[0]; + gb->apu.square_channels[1].pulse_length = old_pulse_lengths[1]; + gb->apu.wave_channel.pulse_length = old_pulse_lengths[2]; + gb->apu.noise_channel.pulse_length = old_pulse_lengths[3]; } } break; From 1baa0446a94c9cc1e2400c31303bbf00a3fd3821 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 1 Dec 2020 22:37:13 +0200 Subject: [PATCH 035/365] More sweep improvements --- Core/apu.c | 29 ++++++++++++++++++++++------- Core/apu.h | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 22808da..4366fb2 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -337,11 +337,13 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb) gb->apu.sweep_length_addend + gb->apu.shadow_sweep_sample_length + !!(gb->io_registers[GB_IO_NR10] & 0x8); gb->apu.square_channels[GB_SQUARE_1].sample_length &= 0x7FF; } - gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; - gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); + if (gb->apu.channel_1_restart_hold == 0) { + gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; + gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); + } /* Recalculation and overflow check only occurs after a delay */ - gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; + gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } @@ -440,7 +442,9 @@ void GB_apu_run(GB_gameboy_t *gb) } else { /* APU bug: sweep frequency is checked after adding the sweep delta twice */ - gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; + if (gb->apu.channel_1_restart_hold == 0) { + gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; + } if (gb->io_registers[GB_IO_NR10] & 8) { gb->apu.sweep_length_addend ^= 0x7FF; } @@ -451,6 +455,15 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.square_sweep_calculate_countdown = 0; } } + + if (gb->apu.channel_1_restart_hold) { + if (gb->apu.channel_1_restart_hold > cycles) { + gb->apu.channel_1_restart_hold -= cycles; + } + else { + gb->apu.channel_1_restart_hold = 0; + } + } UNROLL for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { @@ -665,7 +678,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR10:{ bool old_negate = gb->io_registers[GB_IO_NR10] & 8; gb->io_registers[GB_IO_NR10] = value; - if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend + old_negate > 0x7FF && !(value & 8)) { + if (gb->apu.square_sweep_calculate_countdown == 0 && + gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend + old_negate > 0x7FF && + !(value & 8)) { gb->apu.is_active[GB_SQUARE_1] = false; update_sample(gb, GB_SQUARE_1, 0, 0); } @@ -765,17 +780,17 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (index == GB_SQUARE_1) { + gb->apu.shadow_sweep_sample_length = 0; if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ - gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); } else { - gb->apu.shadow_sweep_sample_length = 0; gb->apu.sweep_length_addend = 0; } + gb->apu.channel_1_restart_hold = 4 - gb->apu.lf_div; gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } } diff --git a/Core/apu.h b/Core/apu.h index a8b5697..07182c9 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -120,6 +120,7 @@ typedef struct uint8_t skip_div_event; bool current_lfsr_sample; uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch + uint8_t channel_1_restart_hold; } GB_apu_t; typedef enum { From 7de6194e28771d1231d4ca1618f82c5977ab1514 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 16:02:25 +0200 Subject: [PATCH 036/365] Redo channel 4's timing accurately, emulate NR43 write quirks --- Core/apu.c | 127 ++++++++++++++++++++++++++++++------------------ Core/apu.h | 8 ++- Core/debugger.c | 6 +-- Core/rumble.c | 7 ++- 4 files changed, 93 insertions(+), 55 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 4366fb2..d689515 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -422,6 +422,28 @@ void GB_apu_div_event(GB_gameboy_t *gb) } } +static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset) +{ + unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; + bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; + gb->apu.noise_channel.lfsr >>= 1; + + if (new_high_bit) { + gb->apu.noise_channel.lfsr |= high_bit_mask; + } + else { + /* This code is not redundent, it's relevant when switching LFSR widths */ + gb->apu.noise_channel.lfsr &= ~high_bit_mask; + } + + gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + cycles_offset); + } +} void GB_apu_run(GB_gameboy_t *gb) { @@ -506,39 +528,38 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.wave_channel.wave_form_just_read = false; } } - - if (gb->apu.is_active[GB_NOISE]) { + + // The noise channel can step even if inactive on the DMG + if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) { uint8_t cycles_left = cycles; - while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) { - cycles_left -= gb->apu.noise_channel.sample_countdown + 1; - gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3; + unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; + if (!divisor) divisor = 2; + if (gb->apu.noise_channel.counter_countdown == 0) { + gb->apu.noise_channel.counter_countdown = divisor; + } + while (unlikely(cycles_left >= gb->apu.noise_channel.counter_countdown)) { + cycles_left -= gb->apu.noise_channel.counter_countdown; + gb->apu.noise_channel.counter_countdown = divisor + gb->apu.channel_4_delta; + gb->apu.channel_4_delta = 0; + bool old_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; + gb->apu.noise_channel.counter++; + gb->apu.noise_channel.counter &= 0x3FFF; + bool new_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; /* Step LFSR */ - unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; - bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1; - gb->apu.noise_channel.lfsr >>= 1; - - if (new_high_bit) { - gb->apu.noise_channel.lfsr |= high_bit_mask; + if (new_bit && !old_bit) { + step_lfsr(gb, cycles - cycles_left); + if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) { + gb->apu.pcm_mask[1] &= 0x0F; + } } - else { - /* This code is not redundent, it's relevant when switching LFSR widths */ - gb->apu.noise_channel.lfsr &= ~high_bit_mask; - } - - gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; - - if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) { - gb->apu.pcm_mask[1] &= 0x0F; - } - - update_sample(gb, GB_NOISE, - gb->apu.current_lfsr_sample ? - gb->apu.noise_channel.current_volume : 0, - 0); } if (cycles_left) { - gb->apu.noise_channel.sample_countdown -= cycles_left; + gb->apu.noise_channel.counter_countdown -= cycles_left; + gb->apu.channel_4_countdown_reloaded = false; + } + else { + gb->apu.channel_4_countdown_reloaded = true; } } } @@ -938,33 +959,43 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR43: { gb->apu.noise_channel.narrow = value & 8; - unsigned divisor = (value & 0x07) << 1; - if (!divisor) divisor = 1; - gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1; - - /* Todo: changing the frequency sometimes delays the next sample. This is probably - due to how the frequency is actually calculated in the noise channel, which is probably - not by calculating the effective sample length and counting simiarly to the other channels. - This is not emulated correctly. */ + bool old_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; + gb->io_registers[GB_IO_NR43] = value; + bool new_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; + if (gb->apu.channel_4_countdown_reloaded) { + unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; + if (!divisor) divisor = 2; + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); + gb->apu.channel_4_delta = 0; + } + /* Step LFSR */ + if (new_bit && !old_bit) { + step_lfsr(gb, 0); + } break; } case GB_IO_NR44: { if (value & 0x80) { - gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div; - - /* I'm COMPLETELY unsure about this logic, but it passes all relevant tests. - See comment in NR43. */ - if ((gb->io_registers[GB_IO_NR43] & 7) && (gb->apu.noise_channel.alignment & 2) == 0) { - if ((gb->io_registers[GB_IO_NR43] & 7) == 1) { - gb->apu.noise_channel.sample_countdown += 2; - } - else { - gb->apu.noise_channel.sample_countdown -= 2; - } + unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; + if (!divisor) divisor = 2; + gb->apu.channel_4_delta = 0; + gb->apu.noise_channel.counter_countdown = divisor + 4; + if (divisor == 2) { + gb->apu.noise_channel.counter_countdown += 1 - gb->apu.lf_div; } - if (gb->apu.is_active[GB_NOISE]) { - gb->apu.noise_channel.sample_countdown += 2; + else { + gb->apu.noise_channel.counter_countdown += (uint8_t[]){2, 1, 0, 3}[gb->apu.noise_channel.alignment & 3]; + if (((gb->apu.noise_channel.alignment + 1) & 3) < 2) { + if ((gb->io_registers[GB_IO_NR43] & 0x07) == 1) { + gb->apu.noise_channel.counter_countdown -= 2; + gb->apu.channel_4_delta = 2; + } + else { + gb->apu.noise_channel.counter_countdown -= 4; + } + } } gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; diff --git a/Core/apu.h b/Core/apu.h index 07182c9..df1be40 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -105,8 +105,9 @@ typedef struct uint16_t lfsr; bool narrow; - uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length) - uint16_t sample_length; // From NR43, in APU ticks + uint8_t counter_countdown; // Counts from 0-7 to 0 to tick counter (Scaled from 512KHz to 2MHz) + uint8_t __padding; + uint16_t counter; // A bit from this 14-bit register ticks LFSR bool length_enabled; // NR44 uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of @@ -121,6 +122,9 @@ typedef struct bool current_lfsr_sample; uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch uint8_t channel_1_restart_hold; + int8_t channel_4_delta; + bool channel_4_countdown_reloaded; + } GB_apu_t; typedef enum { diff --git a/Core/debugger.c b/Core/debugger.c index 9825d95..e62fd51 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1821,10 +1821,10 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "\nCH4:\n"); - GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n", + GB_log(gb, " Current volume: %u, current internal counter: 0x%x (next increase in %u ticks)\n", gb->apu.noise_channel.current_volume, - gb->apu.noise_channel.sample_length * 4 + 3, - gb->apu.noise_channel.sample_countdown); + gb->apu.noise_channel.counter, + gb->apu.noise_channel.counter_countdown); GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n", gb->apu.noise_channel.volume_countdown, diff --git a/Core/rumble.c b/Core/rumble.c index 22321fb..87eb870 100644 --- a/Core/rumble.c +++ b/Core/rumble.c @@ -25,8 +25,11 @@ void GB_handle_rumble(GB_gameboy_t *gb) unsigned volume = (gb->io_registers[GB_IO_NR50] & 7) + 1 + ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1; unsigned ch4_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 8) + !!(gb->io_registers[GB_IO_NR51] & 0x80)); unsigned ch1_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 1) + !!(gb->io_registers[GB_IO_NR51] & 0x10)); - - double ch4_rumble = (MIN(gb->apu.noise_channel.sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0; + unsigned ch4_divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 1; + if (!ch4_divisor) ch4_divisor = 1; + unsigned ch4_sample_length = (ch4_divisor << (gb->io_registers[GB_IO_NR43] >> 4)) - 1; + + double ch4_rumble = (MIN(ch4_sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0; ch4_rumble = MIN(ch4_rumble, 1.0); ch4_rumble = MAX(ch4_rumble, 0.0); From 6b30de5fb1c9c7c177ea544db9fe82e7d40a2171 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 16:02:46 +0200 Subject: [PATCH 037/365] Fixed dark colors on Metal without frame blending --- Shaders/MasterShader.metal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index b900176..2f3113e 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -66,7 +66,7 @@ fragment float4 fragment_shader(rasterizer_data in [[stage_in]], switch (*frame_blending_mode) { default: case DISABLED: - return scale(image, in.texcoords, input_resolution, *output_resolution); + return pow(scale(image, in.texcoords, input_resolution, *output_resolution), 1 / GAMMA); case SIMPLE: ratio = 0.5; break; From dffc12331b0dc1ac8bd1df705f9858706ebc8713 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 18:11:35 +0200 Subject: [PATCH 038/365] Emulate the delayed NR44 write on the DMG --- Core/apu.c | 94 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index d689515..fdd21db 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -451,6 +451,22 @@ void GB_apu_run(GB_gameboy_t *gb) uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; + bool start_ch4 = false; + if (gb->apu.channel_4_dmg_delayed_start) { + if (gb->apu.channel_4_dmg_delayed_start == cycles) { + gb->apu.channel_4_dmg_delayed_start = 0; + start_ch4 = true; + } + else if (gb->apu.channel_4_dmg_delayed_start > cycles) { + gb->apu.channel_4_dmg_delayed_start -= cycles; + } + else { + /* Split it into two */ + cycles -= gb->apu.channel_4_dmg_delayed_start; + gb->apu.apu_cycles = gb->apu.channel_4_dmg_delayed_start * 2; + GB_apu_run(gb); + } + } if (likely(!gb->stopped || GB_is_cgb(gb))) { /* To align the square signal to 1MHz */ @@ -572,6 +588,9 @@ void GB_apu_run(GB_gameboy_t *gb) render(gb); } } + if (start_ch4) { + GB_apu_write(gb, GB_IO_NR44, gb->io_registers[GB_IO_NR44] | 0x80); + } } void GB_apu_init(GB_gameboy_t *gb) { @@ -978,49 +997,54 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR44: { if (value & 0x80) { - unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; - if (!divisor) divisor = 2; - gb->apu.channel_4_delta = 0; - gb->apu.noise_channel.counter_countdown = divisor + 4; - if (divisor == 2) { - gb->apu.noise_channel.counter_countdown += 1 - gb->apu.lf_div; + if (!GB_is_cgb(gb) && (gb->apu.noise_channel.alignment & 3) != 0) { + gb->apu.channel_4_dmg_delayed_start = 6; } else { - gb->apu.noise_channel.counter_countdown += (uint8_t[]){2, 1, 0, 3}[gb->apu.noise_channel.alignment & 3]; - if (((gb->apu.noise_channel.alignment + 1) & 3) < 2) { - if ((gb->io_registers[GB_IO_NR43] & 0x07) == 1) { - gb->apu.noise_channel.counter_countdown -= 2; - gb->apu.channel_4_delta = 2; - } - else { - gb->apu.noise_channel.counter_countdown -= 4; + unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; + if (!divisor) divisor = 2; + gb->apu.channel_4_delta = 0; + gb->apu.noise_channel.counter_countdown = divisor + 4; + if (divisor == 2) { + gb->apu.noise_channel.counter_countdown += 1 - gb->apu.lf_div; + } + else { + gb->apu.noise_channel.counter_countdown += (uint8_t[]){2, 1, 0, 3}[gb->apu.noise_channel.alignment & 3]; + if (((gb->apu.noise_channel.alignment + 1) & 3) < 2) { + if ((gb->io_registers[GB_IO_NR43] & 0x07) == 1) { + gb->apu.noise_channel.counter_countdown -= 2; + gb->apu.channel_4_delta = 2; + } + else { + gb->apu.noise_channel.counter_countdown -= 4; + } } } - } - gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; + gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; - /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously - started sound). The playback itself is not instant which is why we don't update the sample for other - cases. */ - if (gb->apu.is_active[GB_NOISE]) { - update_sample(gb, GB_NOISE, - gb->apu.current_lfsr_sample ? - gb->apu.noise_channel.current_volume : 0, - 0); - } - gb->apu.noise_channel.lfsr = 0; - gb->apu.current_lfsr_sample = false; - gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; + /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously + started sound). The playback itself is not instant which is why we don't update the sample for other + cases. */ + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_volume : 0, + 0); + } + gb->apu.noise_channel.lfsr = 0; + gb->apu.current_lfsr_sample = false; + gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; - if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { - gb->apu.is_active[GB_NOISE] = true; - update_sample(gb, GB_NOISE, 0, 0); - } + if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { + gb->apu.is_active[GB_NOISE] = true; + update_sample(gb, GB_NOISE, 0, 0); + } - if (gb->apu.noise_channel.pulse_length == 0) { - gb->apu.noise_channel.pulse_length = 0x40; - gb->apu.noise_channel.length_enabled = false; + if (gb->apu.noise_channel.pulse_length == 0) { + gb->apu.noise_channel.pulse_length = 0x40; + gb->apu.noise_channel.length_enabled = false; + } } } From 4f408eae7cc2d390ff941a8669d0f513d61fd915 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 18:13:55 +0200 Subject: [PATCH 039/365] Whoops --- Core/apu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/apu.h b/Core/apu.h index df1be40..9d5fc80 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -124,7 +124,7 @@ typedef struct uint8_t channel_1_restart_hold; int8_t channel_4_delta; bool channel_4_countdown_reloaded; - + uint8_t channel_4_dmg_delayed_start; } GB_apu_t; typedef enum { From 770885440f5fe656d6dc04acdcfa77a4ef711a85 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 19:09:53 +0200 Subject: [PATCH 040/365] Minor changes to debugger output --- Core/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/debugger.c b/Core/debugger.c index e62fd51..f6b4e4f 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1821,7 +1821,7 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "\nCH4:\n"); - GB_log(gb, " Current volume: %u, current internal counter: 0x%x (next increase in %u ticks)\n", + GB_log(gb, " Current volume: %u, current internal counter: 0x%04x (next increase in %u ticks)\n", gb->apu.noise_channel.current_volume, gb->apu.noise_channel.counter, gb->apu.noise_channel.counter_countdown); From 555835549a6239fda6051873a5363a20ee9b93dc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 20:35:18 +0200 Subject: [PATCH 041/365] More accurate pausing behavior, including revision differences --- Core/apu.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index fdd21db..e37b265 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -474,7 +474,8 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.noise_channel.alignment += cycles; if (gb->apu.square_sweep_calculate_countdown && - ((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.square_sweep_calculate_countdown <= 7)) { // Calculation is paused if the lower bits + ((gb->io_registers[GB_IO_NR10] & 7) || + gb->apu.square_sweep_calculate_countdown <= (gb->model > GB_MODEL_CGB_C? 3 : 1))) { // Calculation is paused if the lower bits if (gb->apu.square_sweep_calculate_countdown > cycles) { gb->apu.square_sweep_calculate_countdown -= cycles; } @@ -823,7 +824,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.shadow_sweep_sample_length = 0; if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ - gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 7 - gb->apu.lf_div; + gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; + if (gb->model > GB_MODEL_CGB_C) { + gb->apu.square_sweep_calculate_countdown += 2; + } gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; gb->apu.sweep_length_addend >>= (gb->io_registers[GB_IO_NR10] & 7); } From db483ce95f31655fec74ddc53495d78f33903c77 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 20:40:35 +0200 Subject: [PATCH 042/365] Warn about potential odd-mode triggers --- Core/sm83_cpu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 7107ed1..5631853 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -351,6 +351,9 @@ static void leave_stop_mode(GB_gameboy_t *gb) static void stop(GB_gameboy_t *gb, uint8_t opcode) { if (gb->io_registers[GB_IO_KEY1] & 0x1) { + if (gb->cgb_double_speed && gb->io_registers[GB_IO_LCDC] & 0x80) { + GB_log(gb, "Returning from double speed mode while the PPU is on may trigger odd-mode\n"); + } flush_pending_cycles(gb); bool needs_alignment = false; From 7a3ebb708c528f3e1c069f03bc1a185b1b02744b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 12 Dec 2020 22:55:14 +0200 Subject: [PATCH 043/365] LCDC write timing regression fix --- Core/sm83_cpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 5631853..c93de07 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -249,6 +249,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) // Todo: This is difference is because my timing is off in one of the models if (gb->model > GB_MODEL_CGB_C) { GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first gb->tile_sel_glitch = true; GB_advance_cycles(gb, 1); gb->tile_sel_glitch = false; @@ -257,6 +258,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } else { GB_advance_cycles(gb, gb->pending_cycles - 1); + GB_write_memory(gb, addr, value ^ 0x10); // Write with the old TILE_SET first gb->tile_sel_glitch = true; GB_advance_cycles(gb, 1); gb->tile_sel_glitch = false; From 8a13b7be24cf412b0556875d7a8607c7e21462b1 Mon Sep 17 00:00:00 2001 From: Dalton Messmer <33463986+messmerd@users.noreply.github.com> Date: Sat, 19 Dec 2020 00:58:19 -0500 Subject: [PATCH 044/365] Add .gitattributes line ending settings Always use LF line endings for shaders --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index 427cb28..2149ea1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,7 @@ +# Always use LF line endings for shaders +*.fsh text eol=lf +*.metal text eol=lf + HexFiend/* linguist-vendored *.inc linguist-language=C Core/*.h linguist-language=C From 8f64f49c3be597f57377e4926ca2a61f2848c8c9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 23 Dec 2020 23:49:57 +0200 Subject: [PATCH 045/365] More accurate emulation of window timing, actual correct fix of #123 --- Core/display.c | 44 +++++++++++++++++++++----------------------- Core/sm83_cpu.c | 5 ++--- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Core/display.c b/Core/display.c index c876599..5627678 100644 --- a/Core/display.c +++ b/Core/display.c @@ -821,7 +821,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 28); GB_STATE(gb, display, 29); GB_STATE(gb, display, 30); - // GB_STATE(gb, display, 31); + GB_STATE(gb, display, 31); GB_STATE(gb, display, 32); GB_STATE(gb, display, 33); GB_STATE(gb, display, 34); @@ -853,13 +853,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Handle mode 2 on the very first line 0 */ gb->current_line = 0; gb->window_y = -1; - /* Todo: verify timings */ - if (gb->io_registers[GB_IO_WY] == 0) { - gb->wy_triggered = true; - } - else { - gb->wy_triggered = false; - } + gb->wy_triggered = false; gb->ly_for_comparison = 0; gb->io_registers[GB_IO_STAT] &= ~3; @@ -910,11 +904,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Lines 0 - 143 */ gb->window_y = -1; for (; gb->current_line < LINES; gb->current_line++) { - /* Todo: verify timings */ - if ((gb->io_registers[GB_IO_WY] == gb->current_line || - (gb->current_line != 0 && gb->io_registers[GB_IO_WY] == gb->current_line - 1))) { - gb->wy_triggered = true; - } gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed; gb->accessed_oam_row = 0; @@ -994,6 +983,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += 2; GB_SLEEP(gb, display, 32, 2); mode_3_start: + /* Todo: verify timings */ + if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + (gb->io_registers[GB_IO_WY] == 0) && + gb->current_line == 0) { + gb->wy_triggered = true; + } fifo_clear(&gb->bg_fifo); fifo_clear(&gb->oam_fifo); @@ -1021,7 +1016,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) bool should_activate_window = false; if (gb->io_registers[GB_IO_WX] == 0) { static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14}; - if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7] && !GB_is_cgb(gb)) { + if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) { should_activate_window = true; } } @@ -1243,7 +1238,17 @@ abort_fetching_object: if (gb->hdma_on_hblank) { gb->hdma_starting = true; } - GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line); + GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2); + /* + TODO: Verify double speed timing + TODO: Line 0 behaves differently when enabling the window during the transition between mode 2 to 3 + */ + if ((gb->io_registers[GB_IO_LCDC] & 0x20) && + (gb->io_registers[GB_IO_WY] == gb->current_line || + gb->io_registers[GB_IO_WY] == gb->current_line + 1)) { + gb->wy_triggered = true; + } + GB_SLEEP(gb, display, 31, 2); gb->mode_for_interrupt = 2; // Todo: unverified timing @@ -1337,14 +1342,7 @@ abort_fetching_object: gb->current_line = 0; - /* Todo: verify timings */ - if ((gb->io_registers[GB_IO_LCDC] & 0x20) && - (gb->io_registers[GB_IO_WY] == 0)) { - gb->wy_triggered = true; - } - else { - gb->wy_triggered = false; - } + gb->wy_triggered = false; // TODO: not the correct timing gb->current_lcd_line = 0; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index c93de07..e423fba 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -380,9 +380,8 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) else { GB_timing_sync(gb); if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { - /* HW Bug? When STOP is executed while a button is down, the CPU halts forever - yet the other hardware keeps running. */ - gb->interrupt_enable = 0; + /* TODO: HW Bug? When STOP is executed while a button is down, the CPU enters halt + mode instead. Fine details not confirmed yet. */ gb->halted = true; } else { From aa2bdf2a1c2e1e004703b6e8bb61cb1cd7e257fc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 23 Dec 2020 23:50:19 +0200 Subject: [PATCH 046/365] Better support for non-QWERTY Latin layouts --- SDL/gui.c | 4 ++-- SDL/gui.h | 9 +++++++++ SDL/main.c | 3 +-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 62656e8..3848d15 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1206,7 +1206,7 @@ void run_gui(bool is_running) } case SDL_KEYDOWN: - if (event.key.keysym.scancode == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) { + if (event_hotkey_code(&event) == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) { if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } @@ -1215,7 +1215,7 @@ void run_gui(bool is_running) } update_viewport(); } - if (event.key.keysym.scancode == SDL_SCANCODE_O) { + if (event_hotkey_code(&event) == SDL_SCANCODE_O) { if (event.key.keysym.mod & MODIFIER) { char *filename = do_open_rom_dialog(); if (filename) { diff --git a/SDL/gui.h b/SDL/gui.h index c950907..f55464d 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -122,4 +122,13 @@ void connect_joypad(void); joypad_button_t get_joypad_button(uint8_t physical_button); joypad_axis_t get_joypad_axis(uint8_t physical_axis); +static SDL_Scancode event_hotkey_code(SDL_Event *event) +{ + if (event->key.keysym.sym >= SDLK_a && event->key.keysym.sym < SDLK_z) { + return SDL_SCANCODE_A + event->key.keysym.sym - SDLK_a; + } + + return event->key.keysym.scancode; +} + #endif diff --git a/SDL/main.c b/SDL/main.c index e79d0b3..45d016d 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -233,7 +233,7 @@ static void handle_events(GB_gameboy_t *gb) }; case SDL_KEYDOWN: - switch (event.key.keysym.scancode) { + switch (event_hotkey_code(&event)) { case SDL_SCANCODE_ESCAPE: { open_menu(); break; @@ -241,7 +241,6 @@ static void handle_events(GB_gameboy_t *gb) case SDL_SCANCODE_C: if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) { GB_debugger_break(gb); - } break; From 66f62d696c9664121887de43c33d234c17f17e63 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 24 Dec 2020 20:50:47 +0200 Subject: [PATCH 047/365] More window fixes --- Core/display.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Core/display.c b/Core/display.c index 5627678..031b23d 100644 --- a/Core/display.c +++ b/Core/display.c @@ -983,10 +983,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += 2; GB_SLEEP(gb, display, 32, 2); mode_3_start: - /* Todo: verify timings */ + /* TODO: Timing seems incorrect, might need an access conflict handling. */ if ((gb->io_registers[GB_IO_LCDC] & 0x20) && - (gb->io_registers[GB_IO_WY] == 0) && - gb->current_line == 0) { + gb->io_registers[GB_IO_WY] == gb->current_line) { gb->wy_triggered = true; } @@ -1241,11 +1240,10 @@ abort_fetching_object: GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2); /* TODO: Verify double speed timing - TODO: Line 0 behaves differently when enabling the window during the transition between mode 2 to 3 + TODO: Timing differs on a DMG */ if ((gb->io_registers[GB_IO_LCDC] & 0x20) && - (gb->io_registers[GB_IO_WY] == gb->current_line || - gb->io_registers[GB_IO_WY] == gb->current_line + 1)) { + (gb->io_registers[GB_IO_WY] == gb->current_line)) { gb->wy_triggered = true; } GB_SLEEP(gb, display, 31, 2); From b5a611c5db46d6a0649d04d24d8d6339200f9ca1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 24 Dec 2020 23:17:20 +0200 Subject: [PATCH 048/365] More accurate color correction curves --- Core/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 031b23d..f5e81a8 100644 --- a/Core/display.c +++ b/Core/display.c @@ -215,12 +215,12 @@ static inline uint8_t scale_channel(uint8_t x) static inline uint8_t scale_channel_with_curve(uint8_t x) { - return (uint8_t[]){0,5,8,11,16,22,28,36,43,51,59,67,77,87,97,107,119,130,141,153,166,177,188,200,209,221,230,238,245,249,252,255}[x]; + return (uint8_t[]){0,6,12,20,28,36,45,56,66,76,88,100,113,125,137,149,161,172,182,192,202,210,218,225,232,238,243,247,250,252,254,255}[x]; } static inline uint8_t scale_channel_with_curve_agb(uint8_t x) { - return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x]; + return (uint8_t[]){0,3,8,14,20,26,33,40,47,54,62,70,78,86,94,103,112,120,129,138,147,157,166,176,185,195,205,215,225,235,245,255}[x]; } static inline uint8_t scale_channel_with_curve_sgb(uint8_t x) From 159d9d0348bbc14a71a59180f3c5199eb278dd07 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 25 Dec 2020 14:14:17 +0200 Subject: [PATCH 049/365] Color temperature control --- Cocoa/Document.m | 13 ++++++++++ Cocoa/GBPreferencesWindow.h | 2 +- Cocoa/GBPreferencesWindow.m | 20 +++++++++++++++ Cocoa/Preferences.xib | 5 ++-- Core/display.c | 51 ++++++++++++++++++++++++++++++++----- Core/display.h | 1 + Core/gb.h | 1 + SDL/gui.c | 43 +++++++++++++++++++++++++++++++ SDL/gui.h | 4 +++ SDL/main.c | 3 +++ 10 files changed, 134 insertions(+), 9 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ea7ef49..a354e03 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -689,6 +689,11 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) name:@"GBColorCorrectionChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateLightTemperature) + name:@"GBLightTemperatureChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateFrameBlendingMode) name:@"GBFrameBlendingModeChanged" @@ -1835,6 +1840,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } +- (void) updateLightTemperature +{ + if (GB_is_inited(&gb)) { + GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); + } +} + + - (void) updateFrameBlendingMode { self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index ee697a8..f0b7506 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -17,7 +17,7 @@ @property (strong) IBOutlet NSMenuItem *bootROMsFolderItem; @property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton; @property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton; - +@property (weak) IBOutlet NSSlider *temperatureSlider; @property (weak) IBOutlet NSPopUpButton *dmgPopupButton; @property (weak) IBOutlet NSPopUpButton *sgbPopupButton; @property (weak) IBOutlet NSPopUpButton *cgbPopupButton; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 491f0c0..dd13ca1 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -26,6 +26,7 @@ NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton; NSPopUpButton *_preferredJoypadButton; NSPopUpButton *_rumbleModePopupButton; + NSSlider *_temperatureSlider; } + (NSArray *)filterList @@ -91,11 +92,23 @@ [_colorCorrectionPopupButton selectItemAtIndex:mode]; } + - (NSPopUpButton *)colorCorrectionPopupButton { return _colorCorrectionPopupButton; } +- (void)setTemperatureSlider:(NSSlider *)temperatureSlider +{ + _temperatureSlider = temperatureSlider; + [temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256]; + temperatureSlider.continuous = YES; +} + +- (NSSlider *)temperatureSlider +{ + return _temperatureSlider; +} - (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton { _frameBlendingModePopupButton = frameBlendingModePopupButton; @@ -284,6 +297,13 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; } +- (IBAction)lightTemperatureChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) + forKey:@"GBLightTemperature"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil]; +} + - (IBAction)franeBlendingModeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 99c6543..73eb0ab 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -79,15 +79,16 @@ + - + - + diff --git a/Core/display.c b/Core/display.c index f5e81a8..6ac6be8 100644 --- a/Core/display.c +++ b/Core/display.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "gb.h" /* FIFO functions */ @@ -208,6 +209,26 @@ static void display_vblank(GB_gameboy_t *gb) GB_timing_sync(gb); } +static inline void temperature_tint(double temperature, double *r, double *g, double *b) +{ + if (temperature >= 0) { + *r = 1; + *g = pow(1 - temperature, 0.375); + if (temperature >= 0.75) { + *b = 0; + } + else { + *b = sqrt(0.75 - temperature); + } + } + else { + *b = 1; + double squared = pow(temperature, 2); + *g = 0.125 * squared + 0.3 * temperature + 1.0; + *r = 0.21875 * squared + 0.5 * temperature + 1.0; + } +} + static inline uint8_t scale_channel(uint8_t x) { return (x << 3) | (x >> 2); @@ -240,13 +261,12 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) g = scale_channel(g); b = scale_channel(b); } + else if (GB_is_sgb(gb) || for_border) { + r = scale_channel_with_curve_sgb(r); + g = scale_channel_with_curve_sgb(g); + b = scale_channel_with_curve_sgb(b); + } else { - if (GB_is_sgb(gb) || for_border) { - return gb->rgb_encode_callback(gb, - scale_channel_with_curve_sgb(r), - scale_channel_with_curve_sgb(g), - scale_channel_with_curve_sgb(b)); - } bool agb = gb->model == GB_MODEL_AGB; r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r); g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g); @@ -301,6 +321,14 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) } } + if (gb->light_temperature) { + double light_r, light_g, light_b; + temperature_tint(gb->light_temperature, &light_r, &light_g, &light_b); + r = round(light_r * r); + g = round(light_g * g); + b = round(light_b * b); + } + return gb->rgb_encode_callback(gb, r, g, b); } @@ -324,6 +352,17 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m } } +void GB_set_light_temperature(GB_gameboy_t *gb, double temperature) +{ + gb->light_temperature = temperature; + if (GB_is_cgb(gb)) { + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + } +} + /* STAT interrupt is implemented based on this finding: http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531 diff --git a/Core/display.h b/Core/display.h index 5bdeba8..fdaf172 100644 --- a/Core/display.h +++ b/Core/display.h @@ -58,5 +58,6 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); +void GB_set_light_temperature(GB_gameboy_t *gb, double temperature); bool GB_is_odd_frame(GB_gameboy_t *gb); #endif /* display_h */ diff --git a/Core/gb.h b/Core/gb.h index ed736e0..c2e96db 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -573,6 +573,7 @@ struct GB_gameboy_internal_s { uint32_t sprite_palettes_rgb[0x20]; const GB_palette_t *dmg_palette; GB_color_correction_mode_t color_correction_mode; + double light_temperature; bool keys[4][GB_KEY_MAX]; GB_border_mode_t border_mode; GB_sgb_border_t borrowed_border; diff --git a/SDL/gui.c b/SDL/gui.c index 3848d15..63e42d8 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -110,6 +110,7 @@ configuration_t configuration = .volume = 100, .rumble_mode = GB_RUMBLE_ALL_GAMES, .default_scale = 2, + .color_temperature = 10, }; @@ -453,6 +454,33 @@ const char *current_color_correction_mode(unsigned index) [configuration.color_correction_mode]; } +const char *current_color_temperature(unsigned index) +{ + return (const char *[]){"12000K", + "11450K", + "10900K", + "10350K", + "9800K", + "9250K", + "8700K", + "8150K", + "7600K", + "7050K", + "6500K (White)", + "5950K", + "5400K", + "4850K", + "4300K", + "3750K", + "3200K", + "2650K", + "2100K", + "1550K", + "1000K"} + [configuration.color_temperature]; +} + + const char *current_palette(unsigned index) { return (const char *[]){"Greyscale", "Lime (Game Boy)", "Olive (Pocket)", "Teal (Light)"} @@ -533,6 +561,20 @@ static void cycle_color_correction_backwards(unsigned index) } } +static void decrease_color_temperature(unsigned index) +{ + if (configuration.color_temperature < 20) { + configuration.color_temperature++; + } +} + +static void increase_color_temperature(unsigned index) +{ + if (configuration.color_temperature > 0) { + configuration.color_temperature--; + } +} + static void cycle_palette(unsigned index) { if (configuration.dmg_palette == 3) { @@ -684,6 +726,7 @@ static const struct menu_item graphics_menu[] = { {"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards}, {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, + {"Ambient Light:", decrease_color_temperature, current_color_temperature, increase_color_temperature}, {"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards}, {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, {"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards}, diff --git a/SDL/gui.h b/SDL/gui.h index f55464d..84930e0 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -110,6 +110,10 @@ typedef struct { GB_rumble_mode_t rumble_mode; uint8_t default_scale; + + /* v0.14 */ + unsigned padding; + uint8_t color_temperature; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index 45d016d..a20d644 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -120,6 +120,7 @@ static void open_menu(void) GB_audio_set_paused(false); } GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0); GB_set_border_mode(&gb, configuration.border_mode); update_palette(); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); @@ -496,6 +497,7 @@ restart: GB_set_rumble_mode(&gb, configuration.rumble_mode); GB_set_sample_rate(&gb, GB_audio_get_frequency()); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); + GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0); update_palette(); if ((unsigned)configuration.border_mode <= GB_BORDER_ALWAYS) { GB_set_border_mode(&gb, configuration.border_mode); @@ -646,6 +648,7 @@ int main(int argc, char **argv) configuration.dmg_palette %= 3; configuration.border_mode %= GB_BORDER_ALWAYS + 1; configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1; + configuration.color_temperature %= 21; } if (configuration.model >= MODEL_MAX) { From 4bbd27735fb6d12ba95deb10437214b2172aeec3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 25 Dec 2020 20:40:39 +0200 Subject: [PATCH 050/365] Fix a regression in speed switch timing, reset DIV on speed switch, better odd-mode detection and avoidance --- Core/memory.c | 1 + Core/sm83_cpu.c | 16 ++++++++-------- Core/timing.c | 8 ++++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 40af0e5..cff31b6 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -895,6 +895,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->display_cycles = 0; gb->display_state = 0; + gb->double_speed_alignment = 0; if (GB_is_sgb(gb)) { gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; } diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index e423fba..e56040b 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -332,6 +332,7 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode) static void enter_stop_mode(GB_gameboy_t *gb) { + GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); gb->stopped = true; gb->oam_ppu_blocked = !gb->oam_read_blocked; gb->vram_ppu_blocked = !gb->vram_read_blocked; @@ -340,30 +341,30 @@ static void enter_stop_mode(GB_gameboy_t *gb) static void leave_stop_mode(GB_gameboy_t *gb) { - /* The CPU takes more time to wake up then the other components */ - for (unsigned i = 0x200; i--;) { - GB_advance_cycles(gb, 0x10); - } gb->stopped = false; gb->oam_ppu_blocked = false; gb->vram_ppu_blocked = false; gb->cgb_palettes_ppu_blocked = false; + /* The CPU takes more time to wake up then the other components */ + for (unsigned i = 0x2000; i--;) { + GB_advance_cycles(gb, 0x10); + } + GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); } static void stop(GB_gameboy_t *gb, uint8_t opcode) { if (gb->io_registers[GB_IO_KEY1] & 0x1) { - if (gb->cgb_double_speed && gb->io_registers[GB_IO_LCDC] & 0x80) { - GB_log(gb, "Returning from double speed mode while the PPU is on may trigger odd-mode\n"); - } flush_pending_cycles(gb); bool needs_alignment = false; GB_advance_cycles(gb, 0x4); /* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */ + if (gb->double_speed_alignment & 7) { GB_advance_cycles(gb, 0x4); needs_alignment = true; + GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n"); } gb->cgb_double_speed ^= true; @@ -388,7 +389,6 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) enter_stop_mode(gb); } } - /* Todo: is PC being actually read? */ gb->pc++; } diff --git a/Core/timing.c b/Core/timing.c index 44ff8f7..7009d7b 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -157,7 +157,9 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) { if (gb->stopped) { - gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + if (GB_is_cgb(gb)) { + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + } return; } @@ -248,7 +250,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) } // Not affected by speed boost - gb->double_speed_alignment += cycles; + if (gb->io_registers[GB_IO_LCDC] & 0x80) { + gb->double_speed_alignment += cycles; + } gb->hdma_cycles += cycles; gb->apu_output.sample_cycles += cycles; gb->cycles_since_last_sync += cycles; From 544d39f19d3707af33c889afb02c9a59dbabdf8c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 00:19:48 +0200 Subject: [PATCH 051/365] Further improvements to STOP timing --- Core/sm83_cpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index e56040b..cf73b31 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -346,9 +346,10 @@ static void leave_stop_mode(GB_gameboy_t *gb) gb->vram_ppu_blocked = false; gb->cgb_palettes_ppu_blocked = false; /* The CPU takes more time to wake up then the other components */ - for (unsigned i = 0x2000; i--;) { + for (unsigned i = 0x1FFF; i--;) { GB_advance_cycles(gb, 0x10); } + GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xF); GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); } From 6d5ce6c54d0e6311de3eb1ef5c961e0d80d79488 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 01:45:03 +0200 Subject: [PATCH 052/365] Better scrolling a spacing in the SDL UI --- SDL/gui.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 63e42d8..84c13e9 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -211,7 +211,7 @@ static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned heig continue; } - if (x > width - GLYPH_WIDTH || y == 0 || y - y_offset > 144 - GLYPH_HEIGHT) { + if (x > width - GLYPH_WIDTH || y == 0 || y - y_offset > 144 - GLYPH_HEIGHT || y <= y_offset) { break; } @@ -1384,9 +1384,17 @@ void run_gui(bool is_running) unsigned i = 0, y = 24; for (const struct menu_item *item = current_menu; item->string; item++, i++) { if (i == current_selection) { - if (y < scroll) { - scroll = y - 4; - goto rerender; + if (i == 0) { + if (y < scroll) { + scroll = (y - 4) / 12 * 12; + goto rerender; + } + } + else { + if (y < scroll + 24) { + scroll = (y - 24) / 12 * 12; + goto rerender; + } } } if (i == current_selection && i == 0 && scroll != 0) { @@ -1406,14 +1414,20 @@ void run_gui(bool is_running) i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE); y += 12; if (item->value_getter) { - draw_text_centered(pixels, width, height, y + y_offset, item->value_getter(i), gui_palette_native[3], gui_palette_native[0], + draw_text_centered(pixels, width, height, y + y_offset - 1, item->value_getter(i), gui_palette_native[3], gui_palette_native[0], i == current_selection ? DECORATION_ARROWS : DECORATION_NONE); y += 12; } } if (i == current_selection) { - if (y > scroll + 144) { - scroll = y - 144; + if (item[1].string) { + if (y > scroll + 120) { + scroll = (y - 120) / 12 * 12; + goto rerender; + } + } + else if (y > scroll + 144) { + scroll = (y - 144) / 12 * 12; goto rerender; } } From c471696fbb4efbb31caef6d599293ec5377349e6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 03:20:53 +0200 Subject: [PATCH 053/365] Scrollbar and mouse wheel support --- SDL/gui.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 84c13e9..900907c 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -197,7 +197,7 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne } } -static unsigned scroll = 0; +static signed scroll = 0; static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color) { y -= scroll; @@ -262,6 +262,10 @@ struct menu_item { }; static const struct menu_item *current_menu = NULL; static const struct menu_item *root_menu = NULL; +static unsigned menu_height; +static unsigned scrollbar_size; +static bool mouse_scroling = false; + static unsigned current_selection = 0; static enum { @@ -303,6 +307,23 @@ static void open_rom(unsigned index) } } +static void recalculate_menu_height(void) +{ + menu_height = 24; + scrollbar_size = 0; + if (gui_state == SHOWING_MENU) { + for (const struct menu_item *item = current_menu; item->string; item++) { + menu_height += 12; + if (item->backwards_handler) { + menu_height += 12; + } + } + } + if (menu_height > 144) { + scrollbar_size = 144 * 140 / menu_height; + } +} + static const struct menu_item paused_menu[] = { {"Resume", NULL}, {"Open ROM", open_rom}, @@ -323,6 +344,7 @@ static void return_to_root_menu(unsigned index) current_menu = root_menu; current_selection = 0; scroll = 0; + recalculate_menu_height(); } static void cycle_model(unsigned index) @@ -434,6 +456,7 @@ static void enter_emulation_menu(unsigned index) current_menu = emulation_menu; current_selection = 0; scroll = 0; + recalculate_menu_height(); } const char *current_scaling_mode(unsigned index) @@ -739,6 +762,7 @@ static void enter_graphics_menu(unsigned index) current_menu = graphics_menu; current_selection = 0; scroll = 0; + recalculate_menu_height(); } const char *highpass_filter_string(unsigned index) @@ -800,6 +824,7 @@ static void enter_audio_menu(unsigned index) current_menu = audio_menu; current_selection = 0; scroll = 0; + recalculate_menu_height(); } static void modify_key(unsigned index) @@ -841,6 +866,7 @@ static void enter_controls_menu(unsigned index) current_menu = controls_menu; current_selection = 0; scroll = 0; + recalculate_menu_height(); } static unsigned joypad_index = 0; @@ -976,6 +1002,7 @@ static void enter_joypad_menu(unsigned index) current_menu = joypad_menu; current_selection = 0; scroll = 0; + recalculate_menu_height(); } joypad_button_t get_joypad_button(uint8_t physical_button) @@ -1060,6 +1087,7 @@ void run_gui(bool is_running) gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE; bool should_render = true; current_menu = root_menu = is_running? paused_menu : nonpaused_menu; + recalculate_menu_height(); current_selection = 0; scroll = 0; do { @@ -1247,6 +1275,23 @@ void run_gui(bool is_running) } break; } + + case SDL_MOUSEWHEEL: { + if (menu_height > 144) { + scroll -= event.wheel.y; + if (scroll < 0) { + scroll = 0; + } + if (scroll >= menu_height - 144) { + scroll = menu_height - 144; + } + + mouse_scroling = true; + should_render = true; + } + break; + } + case SDL_KEYDOWN: if (event_hotkey_code(&event) == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) { @@ -1295,18 +1340,22 @@ void run_gui(bool is_running) gui_state = SHOWING_DROP_MESSAGE; } current_selection = 0; + mouse_scroling = false; scroll = 0; current_menu = root_menu; + recalculate_menu_height(); should_render = true; } } else if (gui_state == SHOWING_MENU) { if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) { current_selection++; + mouse_scroling = false; should_render = true; } else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) { current_selection--; + mouse_scroling = false; should_render = true; } else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && !current_menu[current_selection].backwards_handler) { @@ -1383,7 +1432,7 @@ void run_gui(bool is_running) draw_text_centered(pixels, width, height, 8 + y_offset, "SameBoy", gui_palette_native[3], gui_palette_native[0], false); unsigned i = 0, y = 24; for (const struct menu_item *item = current_menu; item->string; item++, i++) { - if (i == current_selection) { + if (i == current_selection && !mouse_scroling) { if (i == 0) { if (y < scroll) { scroll = (y - 4) / 12 * 12; @@ -1397,7 +1446,7 @@ void run_gui(bool is_running) } } } - if (i == current_selection && i == 0 && scroll != 0) { + if (i == current_selection && i == 0 && scroll != 0 && !mouse_scroling) { scroll = 0; goto rerender; } @@ -1433,6 +1482,22 @@ void run_gui(bool is_running) } } + if (scrollbar_size) { + unsigned scrollbar_offset = (140 - scrollbar_size) * scroll / (menu_height - 144); + if (scrollbar_offset + scrollbar_size > 140) { + scrollbar_offset = 140 - scrollbar_size; + } + for (unsigned y = 0; y < 140; y++) { + uint32_t *pixel = pixels + x_offset + 156 + width * (y + y_offset + 2); + if (y >= scrollbar_offset && y < scrollbar_offset + scrollbar_size) { + pixel[0] = pixel[1]= gui_palette_native[2]; + } + else { + pixel[0] = pixel[1]= gui_palette_native[1]; + } + + } + } break; case SHOWING_HELP: draw_text(pixels, width, height, 2 + x_offset, 2 + y_offset, help[current_help_page], gui_palette_native[3], gui_palette_native[0]); From e1f797c21251c86bf66b1570047eb9a9926d5464 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 13:13:43 +0200 Subject: [PATCH 054/365] Improved scrolling --- SDL/gui.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 900907c..5701495 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -177,8 +177,7 @@ static void rescale_window(void) SDL_SetWindowSize(window, GB_get_screen_width(&gb) * configuration.default_scale, GB_get_screen_height(&gb) * configuration.default_scale); } -/* Does NOT check for bounds! */ -static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color) +static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigned char ch, uint32_t color, uint32_t *mask_top, uint32_t *mask_bottom) { if (ch < ' ' || ch > font_max) { ch = '?'; @@ -188,7 +187,7 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne for (unsigned y = GLYPH_HEIGHT; y--;) { for (unsigned x = GLYPH_WIDTH; x--;) { - if (*(data++)) { + if (*(data++) && buffer >= mask_top && buffer < mask_bottom) { (*buffer) = color; } buffer++; @@ -198,7 +197,7 @@ static void draw_char(uint32_t *buffer, unsigned width, unsigned height, unsigne } static signed scroll = 0; -static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color) +static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color) { y -= scroll; unsigned orig_x = x; @@ -211,17 +210,17 @@ static void draw_unbordered_text(uint32_t *buffer, unsigned width, unsigned heig continue; } - if (x > width - GLYPH_WIDTH || y == 0 || y - y_offset > 144 - GLYPH_HEIGHT || y <= y_offset) { + if (x > width - GLYPH_WIDTH) { break; } - draw_char(&buffer[x + width * y], width, height, *string, color); + draw_char(&buffer[(signed)(x + width * y)], width, height, *string, color, &buffer[width * y_offset], &buffer[width * (y_offset + 144)]); x += GLYPH_WIDTH; string++; } } -static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border) +static void draw_text(uint32_t *buffer, unsigned width, unsigned height, unsigned x, signed y, const char *string, uint32_t color, uint32_t border) { draw_unbordered_text(buffer, width, height, x - 1, y, string, border); draw_unbordered_text(buffer, width, height, x + 1, y, string, border); @@ -1468,15 +1467,12 @@ void run_gui(bool is_running) y += 12; } } - if (i == current_selection) { - if (item[1].string) { - if (y > scroll + 120) { - scroll = (y - 120) / 12 * 12; - goto rerender; - } - } - else if (y > scroll + 144) { + if (i == current_selection && !mouse_scroling) { + if (y > scroll + 144) { scroll = (y - 144) / 12 * 12; + if (scroll > menu_height - 144) { + scroll = menu_height - 144; + } goto rerender; } } From 7fc59b5cf4e27c05c239ce1d5c1ac083bf58e832 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 15:10:11 +0200 Subject: [PATCH 055/365] Let the SDL port choose a boot ROMs folder --- Makefile | 2 +- OpenDialog/cocoa.m | 18 +++++++++++ OpenDialog/gtk.c | 69 ++++++++++++++++++++++++++++++++++++++++ OpenDialog/open_dialog.h | 2 +- OpenDialog/windows.c | 32 ++++++++++++++++++- SDL/font.c | 20 ++++++++++++ SDL/font.h | 2 ++ SDL/gui.c | 43 ++++++++++++++++++++++++- SDL/gui.h | 1 + SDL/main.c | 16 +++++++--- 10 files changed, 197 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index c3d030a..9d3ca7c 100644 --- a/Makefile +++ b/Makefile @@ -130,7 +130,7 @@ GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL) endif ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows -Drandom=rand --target=i386-pc-windows -LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lshell32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows +LDFLAGS += -lmsvcrt -lcomdlg32 -luser32 -lshell32 -lole32 -lSDL2main -Wl,/MANIFESTFILE:NUL --target=i386-pc-windows SDL_LDFLAGS := -lSDL2 GL_LDFLAGS := -lopengl32 else diff --git a/OpenDialog/cocoa.m b/OpenDialog/cocoa.m index 76b9606..aeeb98a 100644 --- a/OpenDialog/cocoa.m +++ b/OpenDialog/cocoa.m @@ -18,3 +18,21 @@ char *do_open_rom_dialog(void) return NULL; } } + +char *do_open_folder_dialog(void) +{ + @autoreleasepool { + NSWindow *key = [NSApp keyWindow]; + NSOpenPanel *dialog = [NSOpenPanel openPanel]; + dialog.title = @"Select Boot ROMs Folder"; + dialog.canChooseDirectories = true; + dialog.canChooseFiles = false; + [dialog runModal]; + [key makeKeyAndOrderFront:nil]; + NSString *ret = [[[dialog URLs] firstObject] path]; + if (ret) { + return strdup(ret.UTF8String); + } + return NULL; + } +} diff --git a/OpenDialog/gtk.c b/OpenDialog/gtk.c index 5b1caa3..378dcb4 100644 --- a/OpenDialog/gtk.c +++ b/OpenDialog/gtk.c @@ -6,6 +6,7 @@ #include #define GTK_FILE_CHOOSER_ACTION_OPEN 0 +#define GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER 2 #define GTK_RESPONSE_ACCEPT -3 #define GTK_RESPONSE_CANCEL -6 @@ -111,3 +112,71 @@ lazy_error: fprintf(stderr, "Failed to display GTK dialog\n"); return NULL; } + +char *do_open_folder_dialog(void) +{ + static void *handle = NULL; + + TRY_DLOPEN("libgtk-3.so"); + TRY_DLOPEN("libgtk-3.so.0"); + TRY_DLOPEN("libgtk-2.so"); + TRY_DLOPEN("libgtk-2.so.0"); + + if (!handle) { + goto lazy_error; + } + + + LAZY(gtk_init_check); + LAZY(gtk_file_chooser_dialog_new); + LAZY(gtk_dialog_run); + LAZY(g_free); + LAZY(gtk_widget_destroy); + LAZY(gtk_file_chooser_get_filename); + LAZY(g_log_set_default_handler); + LAZY(gtk_file_filter_new); + LAZY(gtk_file_filter_add_pattern); + LAZY(gtk_file_filter_set_name); + LAZY(gtk_file_chooser_add_filter); + LAZY(gtk_events_pending); + LAZY(gtk_main_iteration); + + /* Shut up GTK */ + g_log_set_default_handler(nop, NULL); + + gtk_init_check(0, 0); + + + void *dialog = gtk_file_chooser_dialog_new("Select Boot ROMs Folder", + 0, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL ); + + + int res = gtk_dialog_run (dialog); + char *ret = NULL; + + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + filename = gtk_file_chooser_get_filename(dialog); + ret = strdup(filename); + g_free(filename); + } + + while (gtk_events_pending()) { + gtk_main_iteration(); + } + + gtk_widget_destroy(dialog); + + while (gtk_events_pending()) { + gtk_main_iteration(); + } + return ret; + +lazy_error: + fprintf(stderr, "Failed to display GTK dialog\n"); + return NULL; +} diff --git a/OpenDialog/open_dialog.h b/OpenDialog/open_dialog.h index 85e5721..6d7fb5b 100644 --- a/OpenDialog/open_dialog.h +++ b/OpenDialog/open_dialog.h @@ -2,5 +2,5 @@ #define open_rom_h char *do_open_rom_dialog(void); - +char *do_open_folder_dialog(void); #endif /* open_rom_h */ diff --git a/OpenDialog/windows.c b/OpenDialog/windows.c index 52e281d..e711032 100644 --- a/OpenDialog/windows.c +++ b/OpenDialog/windows.c @@ -1,10 +1,11 @@ #include +#include #include "open_dialog.h" char *do_open_rom_dialog(void) { OPENFILENAMEW dialog; - wchar_t filename[MAX_PATH] = {0}; + static wchar_t filename[MAX_PATH] = {0}; memset(&dialog, 0, sizeof(dialog)); dialog.lStructSize = sizeof(dialog); @@ -25,3 +26,32 @@ char *do_open_rom_dialog(void) return NULL; } + +char *do_open_folder_dialog(void) +{ + + BROWSEINFOW dialog; + memset(&dialog, 0, sizeof(dialog)); + + dialog.ulFlags = BIF_USENEWUI; + dialog.lpszTitle = L"Select Boot ROMs Folder"; + + OleInitialize(NULL); + + LPITEMIDLIST list = SHBrowseForFolderW(&dialog); + static wchar_t filename[MAX_PATH] = {0}; + + if (list) { + if (!SHGetPathFromIDListW(list, filename)) { + OleUninitialize(); + return NULL; + } + char *ret = malloc(MAX_PATH * 4); + WideCharToMultiByte(CP_UTF8, 0, filename, sizeof(filename), ret, MAX_PATH * 4, NULL, NULL); + CoTaskMemFree(list); + OleUninitialize(); + return ret; + } + OleUninitialize(); + return NULL; +} diff --git a/SDL/font.c b/SDL/font.c index 93f3fa9..eb6243e 100644 --- a/SDL/font.c +++ b/SDL/font.c @@ -1033,6 +1033,26 @@ uint8_t font[] = { _, _, _, X, X, _, _, _, _, _, X, _, _, _, _, _, _, _, + + /* Elipsis */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, X, _, X, _, + _, _, _, _, _, _, + + /* Mojibake */ + X, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, X, _, }; const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' '; diff --git a/SDL/font.h b/SDL/font.h index 21753a8..06d7adf 100644 --- a/SDL/font.h +++ b/SDL/font.h @@ -12,5 +12,7 @@ extern const uint8_t font_max; #define CTRL_STRING "\x80\x81\x82" #define SHIFT_STRING "\x83" #define CMD_STRING "\x84\x85" +#define ELLIPSIS_STRING "\x87" +#define MOJIBAKE_STRING "\x88" #endif /* font_h */ diff --git a/SDL/gui.c b/SDL/gui.c index 5701495..c36dff4 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -442,9 +442,50 @@ const char *current_rewind_string(unsigned index) return "Custom"; } +const char *current_bootrom_string(unsigned index) +{ + if (!configuration.bootrom_path[0]) { + return "Built-in Boot ROMs"; + } + size_t length = strlen(configuration.bootrom_path); + static char ret[24] = {0,}; + if (length <= 23) { + strcpy(ret, configuration.bootrom_path); + } + else { + memcpy(ret, configuration.bootrom_path, 11); + memcpy(ret + 12, configuration.bootrom_path + length - 11, 11); + } + for (unsigned i = 0; i < 24; i++) { + if (ret[i] < 0) { + ret[i] = MOJIBAKE_STRING[0]; + } + } + if (length > 23) { + ret[11] = ELLIPSIS_STRING[0]; + } + return ret; +} + +static void toggle_bootrom(unsigned index) +{ + if (configuration.bootrom_path[0]) { + configuration.bootrom_path[0] = 0; + } + else { + char *folder = do_open_folder_dialog(); + if (!folder) return; + if (strlen(folder) < sizeof(configuration.bootrom_path) - 1) { + strcpy(configuration.bootrom_path, folder); + } + free(folder); + } +} + static const struct menu_item emulation_menu[] = { {"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards}, {"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards}, + {"Boot ROMs Folder:", toggle_bootrom, current_bootrom_string, toggle_bootrom}, {"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards}, {"Back", return_to_root_menu}, {NULL,} @@ -883,7 +924,7 @@ const char *current_joypad_name(unsigned index) // SDL returns a name with repeated and trailing spaces while (*orig_name && i < sizeof(name) - 2) { if (orig_name[0] != ' ' || orig_name[1] != ' ') { - name[i++] = *orig_name; + name[i++] = *orig_name > 0? *orig_name : MOJIBAKE_STRING[0]; } orig_name++; } diff --git a/SDL/gui.h b/SDL/gui.h index 84930e0..b507237 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -114,6 +114,7 @@ typedef struct { /* v0.14 */ unsigned padding; uint8_t color_temperature; + char bootrom_path[4096]; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index a20d644..06cde73 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -448,8 +448,6 @@ static bool handle_pending_command(void) static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) { - bool error = false; - start_capturing_logs(); static const char *const names[] = { [GB_BOOT_ROM_DMG0] = "dmg0_boot.bin", [GB_BOOT_ROM_DMG] = "dmg_boot.bin", @@ -460,8 +458,17 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) [GB_BOOT_ROM_CGB] = "cgb_boot.bin", [GB_BOOT_ROM_AGB] = "agb_boot.bin", }; - GB_load_boot_rom(gb, resource_path(names[type])); - end_capturing_logs(true, error); + bool use_built_in = true; + if (configuration.bootrom_path[0]) { + static char path[4096]; + snprintf(path, sizeof(path), "%s/%s", configuration.bootrom_path, names[type]); + use_built_in = GB_load_boot_rom(gb, path); + } + if (use_built_in) { + start_capturing_logs(); + GB_load_boot_rom(gb, resource_path(names[type])); + end_capturing_logs(true, false); + } } static void run(void) @@ -649,6 +656,7 @@ int main(int argc, char **argv) configuration.border_mode %= GB_BORDER_ALWAYS + 1; configuration.rumble_mode %= GB_RUMBLE_ALL_GAMES + 1; configuration.color_temperature %= 21; + configuration.bootrom_path[sizeof(configuration.bootrom_path) - 1] = 0; } if (configuration.model >= MODEL_MAX) { From 3dbd2eac91daed6a89f9a9b54c023c161eb594eb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 23:33:01 +0200 Subject: [PATCH 056/365] Something went wrong with the color temperature commit somehow --- Cocoa/Document.m | 1 + Cocoa/GBPreferencesWindow.m | 1 - Cocoa/Preferences.xib | 29 +++++++++++++++++++++++------ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index a354e03..ad443b6 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -286,6 +286,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput); GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); + GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); [self updatePalette]; GB_set_rgb_encode_callback(&gb, rgbEncode); diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index dd13ca1..6edc54e 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -102,7 +102,6 @@ { _temperatureSlider = temperatureSlider; [temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256]; - temperatureSlider.continuous = YES; } - (NSSlider *)temperatureSlider diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 73eb0ab..e5494fb 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -79,7 +79,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -134,7 +134,7 @@ - + @@ -143,7 +143,7 @@ - + @@ -263,8 +263,25 @@ + + + + + + + + + + + + + + + + + - + @@ -491,7 +508,7 @@ - + From 47ebc317331037fcf9b98ee10d66fa17cb953e66 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 23:52:41 +0200 Subject: [PATCH 057/365] Fixed a bug where the SDL and libretro frontend would not update the border when loading a new ROM --- Core/gb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 4788ad9..83cf23b 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -305,6 +305,7 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) fread(gb->rom, 1, gb->rom_size, f); fclose(f); GB_configure_cart(gb); + gb->tried_loading_sgb_border = false; return 0; } @@ -537,6 +538,7 @@ error: gb->rom_size = old_size; } fclose(f); + gb->tried_loading_sgb_border = false; return -1; } @@ -557,6 +559,7 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz memset(gb->rom, 0xff, gb->rom_size); memcpy(gb->rom, buffer, size); GB_configure_cart(gb); + gb->tried_loading_sgb_border = false; } typedef struct { From 1d34637bdaf7848aa5f99ea2c89087f45420973a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Dec 2020 23:56:26 +0200 Subject: [PATCH 058/365] Fix it harder --- Core/gb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 83cf23b..77ea144 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -306,6 +306,8 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) fclose(f); GB_configure_cart(gb); gb->tried_loading_sgb_border = false; + gb->has_sgb_border = false; + load_default_border(gb); return 0; } @@ -539,6 +541,8 @@ error: } fclose(f); gb->tried_loading_sgb_border = false; + gb->has_sgb_border = false; + load_default_border(gb); return -1; } @@ -560,6 +564,8 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz memcpy(gb->rom, buffer, size); GB_configure_cart(gb); gb->tried_loading_sgb_border = false; + gb->has_sgb_border = false; + load_default_border(gb); } typedef struct { From 9e808b255cbc07aea196067ab41c28f0d0854959 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 27 Dec 2020 00:03:40 +0200 Subject: [PATCH 059/365] Escape now returns to the previous menu if used from a submenu in the SDL port --- SDL/gui.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SDL/gui.c b/SDL/gui.c index c36dff4..a21526d 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1369,7 +1369,11 @@ void run_gui(bool is_running) } } else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { - if (is_running) { + if (gui_state == SHOWING_MENU && current_menu != root_menu) { + return_to_root_menu(0); + should_render = true; + } + else if (is_running) { return; } else { From e535d97e8430ab353b3b6de87d3f67afd9adad5e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 27 Dec 2020 00:23:16 +0200 Subject: [PATCH 060/365] Fix GCC9 build break --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9d3ca7c..a03f417 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ endif # These must come before the -Wno- flags WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option -WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context +WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation # Only add this flag if the compiler supports it ifeq ($(shell $(CC) -x c -c $(NULL) -o $(NULL) -Werror -Wpartial-availability 2> $(NULL); echo $$?),0) From 8e858c1bf146f7f62f7048f7089b9572bc3b2b30 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 27 Dec 2020 01:02:50 +0200 Subject: [PATCH 061/365] Capitalization --- Cocoa/Preferences.xib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index e5494fb..7ca5f28 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -274,7 +274,7 @@ - + From 5c854dbdca808c0258cb34a3cb11b1f9d2dd67ca Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 31 Dec 2020 00:06:36 +0200 Subject: [PATCH 062/365] Interference emulation --- Cocoa/Document.m | 12 ++++++++ Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 21 ++++++++++++++ Cocoa/Preferences.xib | 30 ++++++++++++++++---- Core/apu.c | 55 +++++++++++++++++++++++++++++++++++++ Core/apu.h | 4 +++ SDL/gui.c | 26 +++++++++++++++++- SDL/gui.h | 1 + SDL/main.c | 2 ++ 9 files changed, 145 insertions(+), 7 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ad443b6..0beceab 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -287,6 +287,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput); GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]); GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]); + GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]); GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]); [self updatePalette]; GB_set_rgb_encode_callback(&gb, rgbEncode); @@ -695,6 +696,11 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) name:@"GBLightTemperatureChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateInterferenceVolume) + name:@"GBInterferenceVolumeChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateFrameBlendingMode) name:@"GBFrameBlendingModeChanged" @@ -1848,6 +1854,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } +- (void) updateInterferenceVolume +{ + if (GB_is_inited(&gb)) { + GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]); + } +} - (void) updateFrameBlendingMode { diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index f0b7506..43a8f1d 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -18,6 +18,7 @@ @property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton; @property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton; @property (weak) IBOutlet NSSlider *temperatureSlider; +@property (weak) IBOutlet NSSlider *interferenceSlider; @property (weak) IBOutlet NSPopUpButton *dmgPopupButton; @property (weak) IBOutlet NSPopUpButton *sgbPopupButton; @property (weak) IBOutlet NSPopUpButton *cgbPopupButton; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 6edc54e..bd3a4a8 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -27,6 +27,7 @@ NSPopUpButton *_preferredJoypadButton; NSPopUpButton *_rumbleModePopupButton; NSSlider *_temperatureSlider; + NSSlider *_interferenceSlider; } + (NSArray *)filterList @@ -108,6 +109,18 @@ { return _temperatureSlider; } + +- (void)setInterferenceSlider:(NSSlider *)interferenceSlider +{ + _interferenceSlider = interferenceSlider; + [interferenceSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"] * 256]; +} + +- (NSSlider *)interferenceSlider +{ + return _interferenceSlider; +} + - (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton { _frameBlendingModePopupButton = frameBlendingModePopupButton; @@ -303,6 +316,14 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil]; } +- (IBAction)volumeTemperatureChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) + forKey:@"GBInterferenceVolume"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil]; + +} + - (IBAction)franeBlendingModeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 7ca5f28..248cfce 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -73,6 +73,7 @@ + @@ -174,7 +175,7 @@ - + @@ -264,7 +265,7 @@ - + @@ -446,11 +447,11 @@ - + - + @@ -470,7 +471,7 @@ - + @@ -478,8 +479,25 @@ + + + + + + + + + + + + + + + + + - + diff --git a/Core/apu.c b/Core/apu.c index e37b265..3abca7d 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -137,6 +137,45 @@ static double smooth(double x) return 3*x*x - 2*x*x*x; } +static signed interference(GB_gameboy_t *gb) +{ + /* These aren't scientifically measured, but based on ear based on several recordings */ + signed ret = 0; + if (gb->halted) { + if (gb->model != GB_MODEL_AGB) { + ret -= MAX_CH_AMP / 5; + } + else { + ret -= MAX_CH_AMP / 12; + } + } + if (gb->io_registers[GB_IO_LCDC] & 0x80) { + ret += MAX_CH_AMP / 7; + if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model != GB_MODEL_AGB) { + ret += MAX_CH_AMP / 14; + } + else if ((gb->io_registers[GB_IO_STAT] & 3) == 1) { + ret -= MAX_CH_AMP / 7; + } + } + + if (gb->apu.global_enable) { + ret += MAX_CH_AMP / 10; + } + + if (GB_is_cgb(gb) && gb->model < GB_MODEL_AGB && (gb->io_registers[GB_IO_RP] & 1)) { + ret += MAX_CH_AMP / 10; + } + + if (!GB_is_cgb(gb)) { + ret /= 4; + } + + ret += rand() % (MAX_CH_AMP / 12); + + return ret; +} + static void render(GB_gameboy_t *gb) { GB_sample_t output = {0, 0}; @@ -226,6 +265,17 @@ static void render(GB_gameboy_t *gb) } + + if (gb->apu_output.interference_volume) { + signed interference_bias = interference(gb); + int16_t interference_sample = (interference_bias - gb->apu_output.interference_highpass); + gb->apu_output.interference_highpass = gb->apu_output.interference_highpass * gb->apu_output.highpass_rate + + (1 - gb->apu_output.highpass_rate) * interference_sample; + interference_bias *= gb->apu_output.interference_volume; + + filtered_output.left = MAX(MIN(filtered_output.left + interference_bias, 0x7FFF), -0x8000); + filtered_output.right = MAX(MIN(filtered_output.right + interference_bias, 0x7FFF), -0x8000); + } assert(gb->apu_output.sample_callback); gb->apu_output.sample_callback(gb, &filtered_output); } @@ -1122,3 +1172,8 @@ void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb) gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */ } } + +void GB_set_interference_volume(GB_gameboy_t *gb, double volume) +{ + gb->apu_output.interference_volume = volume; +} diff --git a/Core/apu.h b/Core/apu.h index 9d5fc80..69cea16 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -154,12 +154,16 @@ typedef struct { GB_sample_callback_t sample_callback; bool rate_set_in_clocks; + double interference_volume; + double interference_highpass; } GB_apu_output_t; void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate); void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode); +void GB_set_interference_volume(GB_gameboy_t *gb, double volume); void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); + #ifdef GB_INTERNAL bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); diff --git a/SDL/gui.c b/SDL/gui.c index a21526d..0846664 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -789,7 +789,7 @@ static const struct menu_item graphics_menu[] = { {"Default Window Scale:", cycle_default_scale, current_default_scale, cycle_default_scale_backwards}, {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, - {"Ambient Light:", decrease_color_temperature, current_color_temperature, increase_color_temperature}, + {"Ambient Light Temp.:", decrease_color_temperature, current_color_temperature, increase_color_temperature}, {"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards}, {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, {"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards}, @@ -852,9 +852,33 @@ void decrease_volume(unsigned index) } } +const char *interference_volume_string(unsigned index) +{ + static char ret[5]; + sprintf(ret, "%d%%", configuration.interference_volume); + return ret; +} + +void increase_interference_volume(unsigned index) +{ + configuration.interference_volume += 5; + if (configuration.interference_volume > 100) { + configuration.interference_volume = 100; + } +} + +void decrease_interference_volume(unsigned index) +{ + configuration.interference_volume -= 5; + if (configuration.interference_volume > 100) { + configuration.interference_volume = 0; + } +} + static const struct menu_item audio_menu[] = { {"Highpass Filter:", cycle_highpass_filter, highpass_filter_string, cycle_highpass_filter_backwards}, {"Volume:", increase_volume, volume_string, decrease_volume}, + {"Interference Volume:", increase_interference_volume, interference_volume_string, decrease_interference_volume}, {"Back", return_to_root_menu}, {NULL,} }; diff --git a/SDL/gui.h b/SDL/gui.h index b507237..8d69ec3 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -115,6 +115,7 @@ typedef struct { unsigned padding; uint8_t color_temperature; char bootrom_path[4096]; + uint8_t interference_volume; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index 06cde73..63a61c9 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -121,6 +121,7 @@ static void open_menu(void) } GB_set_color_correction_mode(&gb, configuration.color_correction_mode); GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0); + GB_set_interference_volume(&gb, configuration.interference_volume / 100.0); GB_set_border_mode(&gb, configuration.border_mode); update_palette(); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); @@ -505,6 +506,7 @@ restart: GB_set_sample_rate(&gb, GB_audio_get_frequency()); GB_set_color_correction_mode(&gb, configuration.color_correction_mode); GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0); + GB_set_interference_volume(&gb, configuration.interference_volume / 100.0); update_palette(); if ((unsigned)configuration.border_mode <= GB_BORDER_ALWAYS) { GB_set_border_mode(&gb, configuration.border_mode); From b54a72d9b9a72dc243cd37216b3adb4c99511a44 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Jan 2021 14:56:45 +0200 Subject: [PATCH 063/365] Fixing a bug where where zero-shift sweep wouldn't tick --- Core/apu.c | 7 ++++--- Core/apu.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 3abca7d..4d66e24 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -394,7 +394,7 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb) /* Recalculation and overflow check only occurs after a delay */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; - + gb->apu.unshifted_sweep = !(gb->io_registers[GB_IO_NR10] & 0x7); gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } } @@ -524,7 +524,7 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.noise_channel.alignment += cycles; if (gb->apu.square_sweep_calculate_countdown && - ((gb->io_registers[GB_IO_NR10] & 7) || + (((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.unshifted_sweep) || gb->apu.square_sweep_calculate_countdown <= (gb->model > GB_MODEL_CGB_C? 3 : 1))) { // Calculation is paused if the lower bits if (gb->apu.square_sweep_calculate_countdown > cycles) { gb->apu.square_sweep_calculate_countdown -= cycles; @@ -875,6 +875,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; + gb->apu.unshifted_sweep = false; if (gb->model > GB_MODEL_CGB_C) { gb->apu.square_sweep_calculate_countdown += 2; } @@ -884,7 +885,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) else { gb->apu.sweep_length_addend = 0; } - gb->apu.channel_1_restart_hold = 4 - gb->apu.lf_div; + gb->apu.channel_1_restart_hold = 2 - gb->apu.lf_div + GB_is_cgb(gb) * 2; gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } } diff --git a/Core/apu.h b/Core/apu.h index 69cea16..eadae1b 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -66,7 +66,7 @@ typedef struct uint8_t square_sweep_calculate_countdown; // In 2 MHz uint16_t sweep_length_addend; uint16_t shadow_sweep_sample_length; - GB_PADDING(bool, sweep_enabled); + bool unshifted_sweep; GB_PADDING(bool, sweep_decreasing); struct { From a9c337264e783f234252929da3aeae089a1df8e2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Jan 2021 16:23:34 +0200 Subject: [PATCH 064/365] Fix the last remaining APU test --- Core/apu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 4d66e24..890ffe3 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -821,7 +821,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR14: case GB_IO_NR24: { unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1; - + bool was_active = gb->apu.is_active[index]; /* TODO: When the sample length changes right before being updated, the countdown should change to the old length, but the current sample should not change. Because our write timing isn't accurate to the T-cycle, we hack around it by stepping the sample index backwards. */ @@ -876,7 +876,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; gb->apu.unshifted_sweep = false; - if (gb->model > GB_MODEL_CGB_C) { + if (gb->model > GB_MODEL_CGB_C && !was_active) { gb->apu.square_sweep_calculate_countdown += 2; } gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; From ecace40fb06fa929cd27a22f5853fc2775e18f93 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 2 Jan 2021 18:27:21 +0200 Subject: [PATCH 065/365] Minor APU bug fix --- Core/apu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index 890ffe3..8ddb52b 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -846,6 +846,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } else { /* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/ + if (gb->apu.square_channels[index].sample_countdown <= 1) { + gb->apu.square_channels[index].current_sample_index++; + gb->apu.square_channels[index].current_sample_index &= 0x7; + } gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div; } gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; From f9b13c66b162766216d1c9b19f99ea38fb5625f3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 Jan 2021 13:49:36 +0200 Subject: [PATCH 066/365] Emulation of a newly discovered revision specific APU quirk --- Core/apu.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 8ddb52b..6c397a6 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -836,6 +836,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } + uint16_t old_sample_length = gb->apu.square_channels[index].sample_length; gb->apu.square_channels[index].sample_length &= 0xFF; gb->apu.square_channels[index].sample_length |= (value & 7) << 8; if (value & 0x80) { @@ -845,12 +846,21 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div; } else { - /* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/ - if (gb->apu.square_channels[index].sample_countdown <= 1) { - gb->apu.square_channels[index].current_sample_index++; - gb->apu.square_channels[index].current_sample_index &= 0x7; + unsigned extra_delay = 0; + if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */) { + if ((!(value & 4) && ((gb->io_registers[reg] & 4) || old_sample_length == 0x3FF)) || + (old_sample_length == 0x7FF && gb->apu.square_channels[index].sample_length != 0x7FF)) { + gb->apu.square_channels[index].current_sample_index++; + gb->apu.square_channels[index].current_sample_index &= 0x7; + } + else if (gb->apu.square_channels[index].sample_length == 0x7FF && + old_sample_length != 0x7FF && + (gb->apu.square_channels[index].current_sample_index & 0x80)) { + extra_delay += 2; + } } - gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div; + /* 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].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; From 29a3b18186c181399f4b99b9111ca9d8b5726886 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 3 Jan 2021 16:52:18 +0200 Subject: [PATCH 067/365] Better camera noise on frontends without camera support --- Core/camera.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Core/camera.c b/Core/camera.c index bef8489..7751f18 100644 --- a/Core/camera.c +++ b/Core/camera.c @@ -1,26 +1,26 @@ #include "gb.h" -static signed noise_seed = 0; +static uint32_t noise_seed = 0; -/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported. +/* This is not a complete emulation of the camera chip. Only the features used by the Game Boy Camera ROMs are supported. We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */ static uint8_t generate_noise(uint8_t x, uint8_t y) { - signed value = (x + y * 128 + noise_seed); - uint8_t *data = (uint8_t *) &value; - unsigned hash = 0; + uint32_t value = (x * 151 + y * 149) ^ noise_seed; + uint32_t hash = 0; - while ((signed *) data != &value + 1) { - hash ^= (*data << 8); - if (hash & 0x8000) { - hash ^= 0x8a00; - hash ^= *data; - } - data++; + while (value) { hash <<= 1; + if (hash & 0x100) { + hash ^= 0x101; + } + if (value & 0x80000000) { + hash ^= 0xA1; + } + value <<= 1; } - return (hash >> 8); + return hash; } static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) From e4c7333a1a0816a682acc7c3ae1898c87c138768 Mon Sep 17 00:00:00 2001 From: "C.W. Betts" Date: Mon, 4 Jan 2021 01:08:31 -0700 Subject: [PATCH 068/365] Fix visibility of a few functions in the QuickLook plug-in. --- QuickLook/exports.sym | 2 -- QuickLook/main.c | 12 ++++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/QuickLook/exports.sym b/QuickLook/exports.sym index f979687..2e7fdde 100644 --- a/QuickLook/exports.sym +++ b/QuickLook/exports.sym @@ -1,3 +1 @@ -_DeallocQuickLookGeneratorPluginType -_QuickLookGeneratorQueryInterface _QuickLookGeneratorPluginFactory diff --git a/QuickLook/main.c b/QuickLook/main.c index 1d1676a..4e45313 100644 --- a/QuickLook/main.c +++ b/QuickLook/main.c @@ -41,12 +41,12 @@ typedef struct __QuickLookGeneratorPluginType // Forward declaration for the IUnknown implementation. // -QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID); -void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance); -HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv); -void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID); -ULONG QuickLookGeneratorPluginAddRef(void *thisInstance); -ULONG QuickLookGeneratorPluginRelease(void *thisInstance); +static QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID); +static void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance); +static HRESULT QuickLookGeneratorQueryInterface(void *thisInstance, REFIID iid, LPVOID *ppv); +extern void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID); +static ULONG QuickLookGeneratorPluginAddRef(void *thisInstance); +static ULONG QuickLookGeneratorPluginRelease(void *thisInstance); // ----------------------------------------------------------------------------- // myInterfaceFtbl definition From c0582fd994be894adfe5049d4cf180d8cef8f9c8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 8 Jan 2021 16:43:00 +0200 Subject: [PATCH 069/365] More accurate emulation of NR10 writes --- Core/apu.c | 45 +++++++++++++++++++++++++-------------------- Core/apu.h | 3 ++- Core/gb.c | 1 + Core/mbc.c | 2 +- Core/sm83_cpu.c | 25 +++++++++++++++++++++++-- 5 files changed, 52 insertions(+), 24 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 6c397a6..2596e0f 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -394,6 +394,7 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb) /* Recalculation and overflow check only occurs after a delay */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; + gb->apu.enable_zombie_calculate_stepping = false; gb->apu.unshifted_sweep = !(gb->io_registers[GB_IO_NR10] & 0x7); gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } @@ -501,31 +502,31 @@ void GB_apu_run(GB_gameboy_t *gb) uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; + bool start_ch4 = false; - if (gb->apu.channel_4_dmg_delayed_start) { - if (gb->apu.channel_4_dmg_delayed_start == cycles) { - gb->apu.channel_4_dmg_delayed_start = 0; - start_ch4 = true; - } - else if (gb->apu.channel_4_dmg_delayed_start > cycles) { - gb->apu.channel_4_dmg_delayed_start -= cycles; - } - else { - /* Split it into two */ - cycles -= gb->apu.channel_4_dmg_delayed_start; - gb->apu.apu_cycles = gb->apu.channel_4_dmg_delayed_start * 2; - GB_apu_run(gb); - } - } - if (likely(!gb->stopped || GB_is_cgb(gb))) { + if (gb->apu.channel_4_dmg_delayed_start) { + if (gb->apu.channel_4_dmg_delayed_start == cycles) { + gb->apu.channel_4_dmg_delayed_start = 0; + start_ch4 = true; + } + else if (gb->apu.channel_4_dmg_delayed_start > cycles) { + gb->apu.channel_4_dmg_delayed_start -= cycles; + } + else { + /* Split it into two */ + cycles -= gb->apu.channel_4_dmg_delayed_start; + gb->apu.apu_cycles = gb->apu.channel_4_dmg_delayed_start * 2; + GB_apu_run(gb); + } + } /* To align the square signal to 1MHz */ gb->apu.lf_div ^= cycles & 1; gb->apu.noise_channel.alignment += cycles; if (gb->apu.square_sweep_calculate_countdown && (((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.unshifted_sweep) || - gb->apu.square_sweep_calculate_countdown <= (gb->model > GB_MODEL_CGB_C? 3 : 1))) { // Calculation is paused if the lower bits + gb->apu.square_sweep_calculate_countdown <= 3)) { // Calculation is paused if the lower bits are 0 if (gb->apu.square_sweep_calculate_countdown > cycles) { gb->apu.square_sweep_calculate_countdown -= cycles; } @@ -541,6 +542,8 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.is_active[GB_SQUARE_1] = false; update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles); } + gb->apu.channel1_completed_addend = gb->apu.sweep_length_addend; + gb->apu.square_sweep_calculate_countdown = 0; } } @@ -643,6 +646,7 @@ void GB_apu_run(GB_gameboy_t *gb) GB_apu_write(gb, GB_IO_NR44, gb->io_registers[GB_IO_NR44] | 0x80); } } + void GB_apu_init(GB_gameboy_t *gb) { memset(&gb->apu, 0, sizeof(gb->apu)); @@ -769,8 +773,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR10:{ bool old_negate = gb->io_registers[GB_IO_NR10] & 8; gb->io_registers[GB_IO_NR10] = value; - if (gb->apu.square_sweep_calculate_countdown == 0 && - gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend + old_negate > 0x7FF && + if (gb->apu.shadow_sweep_sample_length + gb->apu.channel1_completed_addend + old_negate > 0x7FF && !(value & 8)) { gb->apu.is_active[GB_SQUARE_1] = false; update_sample(gb, GB_SQUARE_1, 0, 0); @@ -886,11 +889,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (index == GB_SQUARE_1) { gb->apu.shadow_sweep_sample_length = 0; + gb->apu.channel1_completed_addend = 0; if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; + gb->apu.enable_zombie_calculate_stepping = false; gb->apu.unshifted_sweep = false; - if (gb->model > GB_MODEL_CGB_C && !was_active) { + if (!was_active) { gb->apu.square_sweep_calculate_countdown += 2; } gb->apu.sweep_length_addend = gb->apu.square_channels[GB_SQUARE_1].sample_length; diff --git a/Core/apu.h b/Core/apu.h index eadae1b..6394a55 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -67,7 +67,7 @@ typedef struct uint16_t sweep_length_addend; uint16_t shadow_sweep_sample_length; bool unshifted_sweep; - GB_PADDING(bool, sweep_decreasing); + bool enable_zombie_calculate_stepping; struct { uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks @@ -125,6 +125,7 @@ typedef struct int8_t channel_4_delta; bool channel_4_countdown_reloaded; uint8_t channel_4_dmg_delayed_start; + uint16_t channel1_completed_addend; } GB_apu_t; typedef enum { diff --git a/Core/gb.c b/Core/gb.c index 77ea144..985ed4d 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1111,6 +1111,7 @@ bool GB_serial_get_data_bit(GB_gameboy_t *gb) } 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] & 1) { diff --git a/Core/mbc.c b/Core/mbc.c index 2259681..1e91ce2 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -139,7 +139,7 @@ void GB_configure_cart(GB_gameboy_t *gb) gb->mbc_ram = malloc(gb->mbc_ram_size); } - /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */ + /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridge types? */ memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); } diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index cf73b31..042514c 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -22,6 +22,7 @@ typedef enum { GB_CONFLICT_SGB_LCDC, GB_CONFLICT_WX, GB_CONFLICT_CGB_LCDC, + GB_CONFLICT_NR10, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ @@ -33,6 +34,7 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { [GB_IO_BGP] = GB_CONFLICT_PALETTE_CGB, [GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB, [GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB, + [GB_IO_NR10] = GB_CONFLICT_NR10, /* Todo: most values not verified, and probably differ between revisions */ }; @@ -50,7 +52,8 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { [GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG, [GB_IO_WY] = GB_CONFLICT_READ_OLD, [GB_IO_WX] = GB_CONFLICT_WX, - + [GB_IO_NR10] = GB_CONFLICT_NR10, + /* Todo: these were not verified at all */ [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; @@ -68,7 +71,8 @@ static const GB_conflict_t sgb_conflict_map[0x80] = { [GB_IO_OBP1] = GB_CONFLICT_READ_NEW, [GB_IO_WY] = GB_CONFLICT_READ_OLD, [GB_IO_WX] = GB_CONFLICT_WX, - + [GB_IO_NR10] = GB_CONFLICT_NR10, + /* Todo: these were not verified at all */ [GB_IO_SCX] = GB_CONFLICT_READ_NEW, }; @@ -272,6 +276,23 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->pending_cycles = 4; } return; + + case GB_CONFLICT_NR10: + /* Hack: Due to the coupling between DIV and the APU, GB_apu_run only runs at M-cycle + resolutions, but this quirk requires 2MHz even in single speed mode. To work + around this, we specifically just step the calculate countdown if needed. */ + 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 + if (gb->apu.square_sweep_calculate_countdown > 3 && gb->apu.enable_zombie_calculate_stepping) { + gb->apu.square_sweep_calculate_countdown -= 2; + } + gb->apu.enable_zombie_calculate_stepping = true; + GB_write_memory(gb, addr, 0xFF); + } + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + return; } } From 96736fe7c51961b14a8c49764d2588c0f7c8a232 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jan 2021 00:59:12 +0200 Subject: [PATCH 070/365] Fix false positives in odd-mode detection --- Core/memory.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/memory.c b/Core/memory.c index cff31b6..58efc83 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -905,6 +905,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) { /* Sync after turning off LCD */ + gb->double_speed_alignment = 0; GB_timing_sync(gb); GB_lcd_off(gb); } From 2aa171e0ea62ab243f89ddecadf67839a41a99fe Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jan 2021 16:26:56 +0200 Subject: [PATCH 071/365] Better sample alignment on pre-CGB-D models --- Core/apu.c | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 2596e0f..d9a0440 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -394,6 +394,9 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb) /* Recalculation and overflow check only occurs after a delay */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; + if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + gb->apu.square_sweep_calculate_countdown += 2; + } gb->apu.enable_zombie_calculate_stepping = false; gb->apu.unshifted_sweep = !(gb->io_registers[GB_IO_NR10] & 0x7); gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; @@ -618,10 +621,10 @@ void GB_apu_run(GB_gameboy_t *gb) /* Step LFSR */ if (new_bit && !old_bit) { - step_lfsr(gb, cycles - cycles_left); if (cycles_left == 0 && gb->apu.samples[GB_NOISE] == 0) { gb->apu.pcm_mask[1] &= 0x0F; } + step_lfsr(gb, cycles - cycles_left); } } if (cycles_left) { @@ -823,6 +826,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR14: case GB_IO_NR24: { + /* TODO: GB_MODEL_CGB_D fails channel_1_sweep_restart_2, don't forget when adding support for this revision! */ unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1; bool was_active = gb->apu.is_active[index]; /* TODO: When the sample length changes right before being updated, the countdown should change to the @@ -847,6 +851,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) turning the APU off. */ 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_C && gb->apu.lf_div) { + gb->apu.square_channels[index].sample_countdown += 2; + } } else { unsigned extra_delay = 0; @@ -864,6 +871,9 @@ 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; + if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + gb->apu.square_channels[index].sample_countdown += 2; + } } gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; @@ -893,6 +903,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; + if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + gb->apu.square_sweep_calculate_countdown += 2; + } gb->apu.enable_zombie_calculate_stepping = false; gb->apu.unshifted_sweep = false; if (!was_active) { @@ -905,6 +918,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.sweep_length_addend = 0; } gb->apu.channel_1_restart_hold = 2 - gb->apu.lf_div + GB_is_cgb(gb) * 2; + if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + gb->apu.channel_1_restart_hold += 2; + } gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } } @@ -1080,10 +1096,23 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.channel_4_delta = 0; gb->apu.noise_channel.counter_countdown = divisor + 4; if (divisor == 2) { - gb->apu.noise_channel.counter_countdown += 1 - gb->apu.lf_div; + if (gb->model <= GB_MODEL_CGB_C) { + gb->apu.noise_channel.counter_countdown += gb->apu.lf_div; + if (!gb->cgb_double_speed) { + gb->apu.noise_channel.counter_countdown -= 1; + } + } + else { + gb->apu.noise_channel.counter_countdown += 1 - gb->apu.lf_div; + } } else { - gb->apu.noise_channel.counter_countdown += (uint8_t[]){2, 1, 0, 3}[gb->apu.noise_channel.alignment & 3]; + if (gb->model <= GB_MODEL_CGB_C) { + gb->apu.noise_channel.counter_countdown += (uint8_t[]){2, 1, 4, 3}[gb->apu.noise_channel.alignment & 3]; + } + else { + gb->apu.noise_channel.counter_countdown += (uint8_t[]){2, 1, 0, 3}[gb->apu.noise_channel.alignment & 3]; + } if (((gb->apu.noise_channel.alignment + 1) & 3) < 2) { if ((gb->io_registers[GB_IO_NR43] & 0x07) == 1) { gb->apu.noise_channel.counter_countdown -= 2; @@ -1094,7 +1123,17 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } } - + + /* TODO: These two are quite weird. Verify further */ + if (gb->model <= GB_MODEL_CGB_C && gb->cgb_double_speed) { + if (!(gb->io_registers[GB_IO_NR43] & 0xF0) && (gb->io_registers[GB_IO_NR43] & 0x07)) { + gb->apu.noise_channel.counter_countdown -= 1; + } + else if ((gb->io_registers[GB_IO_NR43] & 0xF0) && !(gb->io_registers[GB_IO_NR43] & 0x07)) { + gb->apu.noise_channel.counter_countdown += 1; + } + } + gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously From 1b3f52e8c08afd9d52cae7212fa757c8ca8a8e94 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jan 2021 21:21:22 +0200 Subject: [PATCH 072/365] Improved emulation of NR43 writes on different revisions --- Core/apu.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index d9a0440..ea187c8 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -710,6 +710,119 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; } +static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) +{ + uint16_t effective_counter = gb->apu.noise_channel.counter; + /* Ladies and gentlemen, I present you the holy grail glitch of revision detection! */ + switch (gb->model) { + /* Pre CGB revisions are assumed to be like CGB-C, A and 0 for the lack of a better guess. + TODO: It could be verified with audio based test ROMs. */ +#if 0 + case GB_MODEL_CGB_B: + if (effective_counter & 8) { + effective_counter |= 0xE; // Seems to me F under some circumstances? + } + if (effective_counter & 0x80) { + effective_counter |= 0xFF; + } + if (effective_counter & 0x100) { + effective_counter |= 0x1; + } + if (effective_counter & 0x200) { + effective_counter |= 0x2; + } + if (effective_counter & 0x400) { + effective_counter |= 0x4; + } + if (effective_counter & 0x800) { + effective_counter |= 0x408; + } + if (effective_counter & 0x1000) { + effective_counter |= 0x10; + } + if (effective_counter & 0x2000) { + effective_counter |= 0x20; + } + break; +#endif + case GB_MODEL_DMG_B: + 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: + // case GB_MODEL_CGB_0: + // case GB_MODEL_CGB_A: + case GB_MODEL_CGB_C: + if (effective_counter & 8) { + effective_counter |= 0xE; + } + if (effective_counter & 0x80) { + effective_counter |= 0xFF; + } + if (effective_counter & 0x100) { + effective_counter |= 0x1; + } + if (effective_counter & 0x200) { + effective_counter |= 0x2; + } + if (effective_counter & 0x400) { + effective_counter |= 0x4; + } + if (effective_counter & 0x800) { + effective_counter |= 0x8; + } + if (effective_counter & 0x1000) { + effective_counter |= 0x10; + } + if (effective_counter & 0x2000) { + effective_counter |= 0x20; + } + break; +#if 0 + case GB_MODEL_CGB_D: + if (effective_counter & 0x40) { + effective_counter |= 0xFF; + } + if (effective_counter & 0x100) { + effective_counter |= 0x1; + } + if (effective_counter & 0x200) { + effective_counter |= 0x2; + } + if (effective_counter & 0x400) { + effective_counter |= 0x4; + } + if (effective_counter & 0x800) { + effective_counter |= 0x8; + } + if (effective_counter & 0x1000) { + effective_counter |= 0x10; + } + break; +#endif + case GB_MODEL_CGB_E: + if (effective_counter & 0x40) { + effective_counter |= 0xFF; + } + if (effective_counter & 0x1000) { + effective_counter |= 0x10; + } + break; + case GB_MODEL_AGB: + /* 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: + 0x20 -> 0xA0 + 0x200 -> 0xA00 + 0x1000 -> 0x1010 + */ + break; + } + return effective_counter; +} + void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) || @@ -1068,9 +1181,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR43: { gb->apu.noise_channel.narrow = value & 8; - bool old_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; + uint16_t effective_counter = effective_channel4_counter(gb); + bool old_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; gb->io_registers[GB_IO_NR43] = value; - bool new_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; + bool new_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; if (gb->apu.channel_4_countdown_reloaded) { unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; if (!divisor) divisor = 2; @@ -1079,7 +1193,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.channel_4_delta = 0; } /* Step LFSR */ - if (new_bit && !old_bit) { + if (new_bit && (!old_bit || gb->model <= GB_MODEL_CGB_C)) { step_lfsr(gb, 0); } break; From 07e76a4ecf14e2968eae52005634b464375397f6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jan 2021 23:28:30 +0200 Subject: [PATCH 073/365] Oh boy, looks like my CGB-B is unique --- Core/apu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index ea187c8..3706599 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -735,7 +735,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) effective_counter |= 0x4; } if (effective_counter & 0x800) { - effective_counter |= 0x408; + effective_counter |= 0x408; // TODO: Only my CGB-B does that! Others behave like C! } if (effective_counter & 0x1000) { effective_counter |= 0x10; From e38470761555007bf5b8e579612962efb0dde402 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 10 Jan 2021 17:04:16 +0200 Subject: [PATCH 074/365] Further NR43 write glitch emulation --- Core/apu.c | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 3706599..bd00d49 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -710,7 +710,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; } -static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) +static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb, uint8_t nr43) { uint16_t effective_counter = gb->apu.noise_channel.counter; /* Ladies and gentlemen, I present you the holy grail glitch of revision detection! */ @@ -720,7 +720,14 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) #if 0 case GB_MODEL_CGB_B: if (effective_counter & 8) { - effective_counter |= 0xE; // Seems to me F under some circumstances? + // Verify this part + if (gb->apu.channel_4_countdown_reloaded && !(effective_counter & 1)) { + uint16_t lower_bits = ~(effective_counter & ~(effective_counter - 1)) & 0xF; + effective_counter = (effective_counter & ~0xF) | lower_bits; + } + else { + effective_counter |= 0xF; + } } if (effective_counter & 0x80) { effective_counter |= 0xFF; @@ -756,7 +763,16 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) // case GB_MODEL_CGB_A: case GB_MODEL_CGB_C: if (effective_counter & 8) { - effective_counter |= 0xE; + if (gb->apu.channel_4_countdown_reloaded && !(effective_counter & 1)) { + uint16_t lower_bits = ~(effective_counter & ~(effective_counter - 1)) & 0xF; + effective_counter = (effective_counter & ~0xF) | lower_bits; + if (!(nr43 >> 4)) { + effective_counter &= ~1; + } + } + else { + effective_counter |= 0xE; + } } if (effective_counter & 0x80) { effective_counter |= 0xFF; @@ -820,6 +836,9 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) */ break; } + if (gb->model <= GB_MODEL_CGB_C && gb->apu.channel_4_countdown_reloaded && (nr43 >> 4)) { + effective_counter = (effective_counter & effective_counter - 1) | (effective_counter & 1); + } return effective_counter; } @@ -1181,16 +1200,22 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR43: { gb->apu.noise_channel.narrow = value & 8; - uint16_t effective_counter = effective_channel4_counter(gb); + uint16_t effective_counter = effective_channel4_counter(gb, value); bool old_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; gb->io_registers[GB_IO_NR43] = value; bool new_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; if (gb->apu.channel_4_countdown_reloaded) { unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; if (!divisor) divisor = 2; - gb->apu.noise_channel.counter_countdown = - divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); - gb->apu.channel_4_delta = 0; + if (gb->model > GB_MODEL_CGB_C) { + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); + } + else { + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 4, 3}[(gb->apu.noise_channel.alignment) & 3]); + } + gb->apu.channel_4_delta = 0; } /* Step LFSR */ if (new_bit && (!old_bit || gb->model <= GB_MODEL_CGB_C)) { @@ -1238,15 +1263,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } - /* TODO: These two are quite weird. Verify further */ - if (gb->model <= GB_MODEL_CGB_C && gb->cgb_double_speed) { - if (!(gb->io_registers[GB_IO_NR43] & 0xF0) && (gb->io_registers[GB_IO_NR43] & 0x07)) { - gb->apu.noise_channel.counter_countdown -= 1; - } - else if ((gb->io_registers[GB_IO_NR43] & 0xF0) && !(gb->io_registers[GB_IO_NR43] & 0x07)) { - gb->apu.noise_channel.counter_countdown += 1; - } - } gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; From 6dca01ad274e46383a4a97b4b0c1eabfcd83a7d6 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Jan 2021 13:59:28 -0500 Subject: [PATCH 075/365] Annotate properties as nonatomic. --- Cocoa/AppDelegate.h | 12 +++--- Cocoa/BigSurToolbar.h | 2 +- Cocoa/Document.h | 66 ++++++++++++++++----------------- Cocoa/GBAudioClient.h | 6 +-- Cocoa/GBCheatTextFieldCell.h | 2 +- Cocoa/GBCheatWindowController.h | 19 +++++----- Cocoa/GBImageView.h | 10 ++--- Cocoa/GBMemoryByteArray.h | 4 +- Cocoa/GBOpenGLView.h | 2 +- Cocoa/GBPreferencesWindow.h | 44 +++++++++++----------- Cocoa/GBTerminalTextFieldCell.h | 2 +- Cocoa/GBView.h | 10 ++--- 12 files changed, 89 insertions(+), 90 deletions(-) diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index 8f91565..e74b191 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -2,15 +2,15 @@ @interface AppDelegate : NSObject -@property IBOutlet NSWindow *preferencesWindow; -@property (strong) IBOutlet NSView *graphicsTab; -@property (strong) IBOutlet NSView *emulationTab; -@property (strong) IBOutlet NSView *audioTab; -@property (strong) IBOutlet NSView *controlsTab; +@property (nonatomic, strong) IBOutlet NSWindow *preferencesWindow; +@property (nonatomic, strong) IBOutlet NSView *graphicsTab; +@property (nonatomic, strong) IBOutlet NSView *emulationTab; +@property (nonatomic, strong) IBOutlet NSView *audioTab; +@property (nonatomic, strong) IBOutlet NSView *controlsTab; - (IBAction)showPreferences: (id) sender; - (IBAction)toggleDeveloperMode:(id)sender; - (IBAction)switchPreferencesTab:(id)sender; -@property (weak) IBOutlet NSMenuItem *linkCableMenuItem; +@property (nonatomic, weak) IBOutlet NSMenuItem *linkCableMenuItem; @end diff --git a/Cocoa/BigSurToolbar.h b/Cocoa/BigSurToolbar.h index ea8b370..9057d34 100644 --- a/Cocoa/BigSurToolbar.h +++ b/Cocoa/BigSurToolbar.h @@ -18,7 +18,7 @@ typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) { } API_AVAILABLE(macos(11.0)); @interface NSWindow (toolbarStyle) -@property NSWindowToolbarStyle toolbarStyle API_AVAILABLE(macos(11.0)); +@property (nonatomic) NSWindowToolbarStyle toolbarStyle API_AVAILABLE(macos(11.0)); @end @interface NSImage (SFSymbols) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index bf5d9c0..b651646 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -6,39 +6,39 @@ @class GBCheatWindowController; @interface Document : NSDocument -@property (readonly) GB_gameboy_t *gb; -@property (strong) IBOutlet GBView *view; -@property (strong) IBOutlet NSTextView *consoleOutput; -@property (strong) IBOutlet NSPanel *consoleWindow; -@property (strong) IBOutlet NSTextField *consoleInput; -@property (strong) IBOutlet NSWindow *mainWindow; -@property (strong) IBOutlet NSView *memoryView; -@property (strong) IBOutlet NSPanel *memoryWindow; -@property (readonly) GB_gameboy_t *gameboy; -@property (strong) IBOutlet NSTextField *memoryBankInput; -@property (strong) IBOutlet NSToolbarItem *memoryBankItem; -@property (strong) IBOutlet GBImageView *tilesetImageView; -@property (strong) IBOutlet NSPopUpButton *tilesetPaletteButton; -@property (strong) IBOutlet GBImageView *tilemapImageView; -@property (strong) IBOutlet NSPopUpButton *tilemapPaletteButton; -@property (strong) IBOutlet NSPopUpButton *tilemapMapButton; -@property (strong) IBOutlet NSPopUpButton *TilemapSetButton; -@property (strong) IBOutlet NSButton *gridButton; -@property (strong) IBOutlet NSTabView *vramTabView; -@property (strong) IBOutlet NSPanel *vramWindow; -@property (strong) IBOutlet NSTextField *vramStatusLabel; -@property (strong) IBOutlet NSTableView *paletteTableView; -@property (strong) IBOutlet NSTableView *spritesTableView; -@property (strong) IBOutlet NSPanel *printerFeedWindow; -@property (strong) IBOutlet NSImageView *feedImageView; -@property (strong) IBOutlet NSTextView *debuggerSideViewInput; -@property (strong) IBOutlet NSTextView *debuggerSideView; -@property (strong) IBOutlet GBSplitView *debuggerSplitView; -@property (strong) IBOutlet NSBox *debuggerVerticalLine; -@property (strong) IBOutlet NSPanel *cheatsWindow; -@property (strong) IBOutlet GBCheatWindowController *cheatWindowController; -@property (readonly) Document *partner; -@property (readonly) bool isSlave; +@property (nonatomic, readonly) GB_gameboy_t *gb; +@property (nonatomic, strong) IBOutlet GBView *view; +@property (nonatomic, strong) IBOutlet NSTextView *consoleOutput; +@property (nonatomic, strong) IBOutlet NSPanel *consoleWindow; +@property (nonatomic, strong) IBOutlet NSTextField *consoleInput; +@property (nonatomic, strong) IBOutlet NSWindow *mainWindow; +@property (nonatomic, strong) IBOutlet NSView *memoryView; +@property (nonatomic, strong) IBOutlet NSPanel *memoryWindow; +@property (nonatomic, readonly) GB_gameboy_t *gameboy; +@property (nonatomic, strong) IBOutlet NSTextField *memoryBankInput; +@property (nonatomic, strong) IBOutlet NSToolbarItem *memoryBankItem; +@property (nonatomic, strong) IBOutlet GBImageView *tilesetImageView; +@property (nonatomic, strong) IBOutlet NSPopUpButton *tilesetPaletteButton; +@property (nonatomic, strong) IBOutlet GBImageView *tilemapImageView; +@property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapPaletteButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapMapButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *TilemapSetButton; +@property (nonatomic, strong) IBOutlet NSButton *gridButton; +@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 *spritesTableView; +@property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow; +@property (nonatomic, strong) IBOutlet NSImageView *feedImageView; +@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput; +@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideView; +@property (nonatomic, strong) IBOutlet GBSplitView *debuggerSplitView; +@property (nonatomic, strong) IBOutlet NSBox *debuggerVerticalLine; +@property (nonatomic, strong) IBOutlet NSPanel *cheatsWindow; +@property (nonatomic, strong) IBOutlet GBCheatWindowController *cheatWindowController; +@property (nonatomic, readonly) Document *partner; +@property (nonatomic, readonly) bool isSlave; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/GBAudioClient.h b/Cocoa/GBAudioClient.h index aa7be8c..03ed701 100644 --- a/Cocoa/GBAudioClient.h +++ b/Cocoa/GBAudioClient.h @@ -2,9 +2,9 @@ #import @interface GBAudioClient : NSObject -@property (strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer); -@property (readonly) UInt32 rate; -@property (readonly, getter=isPlaying) bool playing; +@property (nonatomic, strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer); +@property (nonatomic, readonly) UInt32 rate; +@property (nonatomic, readonly, getter=isPlaying) bool playing; -(void) start; -(void) stop; -(id) initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block diff --git a/Cocoa/GBCheatTextFieldCell.h b/Cocoa/GBCheatTextFieldCell.h index 473e0f3..e7fd917 100644 --- a/Cocoa/GBCheatTextFieldCell.h +++ b/Cocoa/GBCheatTextFieldCell.h @@ -1,5 +1,5 @@ #import @interface GBCheatTextFieldCell : NSTextFieldCell -@property bool usesAddressFormat; +@property (nonatomic) bool usesAddressFormat; @end diff --git a/Cocoa/GBCheatWindowController.h b/Cocoa/GBCheatWindowController.h index f70553e..eddebc5 100644 --- a/Cocoa/GBCheatWindowController.h +++ b/Cocoa/GBCheatWindowController.h @@ -3,15 +3,14 @@ #import "Document.h" @interface GBCheatWindowController : NSObject -@property (weak) IBOutlet NSTableView *cheatsTable; -@property (weak) IBOutlet NSTextField *addressField; -@property (weak) IBOutlet NSTextField *valueField; -@property (weak) IBOutlet NSTextField *oldValueField; -@property (weak) IBOutlet NSButton *oldValueCheckbox; -@property (weak) IBOutlet NSTextField *descriptionField; -@property (weak) IBOutlet NSTextField *importCodeField; -@property (weak) IBOutlet NSTextField *importDescriptionField; -@property (weak) IBOutlet Document *document; +@property (nonatomic, weak) IBOutlet NSTableView *cheatsTable; +@property (nonatomic, weak) IBOutlet NSTextField *addressField; +@property (nonatomic, weak) IBOutlet NSTextField *valueField; +@property (nonatomic, weak) IBOutlet NSTextField *oldValueField; +@property (nonatomic, weak) IBOutlet NSButton *oldValueCheckbox; +@property (nonatomic, weak) IBOutlet NSTextField *descriptionField; +@property (nonatomic, weak) IBOutlet NSTextField *importCodeField; +@property (nonatomic, weak) IBOutlet NSTextField *importDescriptionField; +@property (nonatomic, weak) IBOutlet Document *document; - (void)cheatsUpdated; @end - diff --git a/Cocoa/GBImageView.h b/Cocoa/GBImageView.h index d5ee534..0b93e66 100644 --- a/Cocoa/GBImageView.h +++ b/Cocoa/GBImageView.h @@ -3,17 +3,17 @@ @protocol GBImageViewDelegate; @interface GBImageViewGridConfiguration : NSObject -@property NSColor *color; -@property NSUInteger size; +@property (nonatomic, strong) NSColor *color; +@property (nonatomic) NSUInteger size; - (instancetype) initWithColor: (NSColor *) color size: (NSUInteger) size; @end @interface GBImageView : NSImageView -@property (nonatomic) NSArray *horizontalGrids; -@property (nonatomic) NSArray *verticalGrids; +@property (nonatomic, strong) NSArray *horizontalGrids; +@property (nonatomic, strong) NSArray *verticalGrids; @property (nonatomic) bool displayScrollRect; @property NSRect scrollRect; -@property (weak) IBOutlet id delegate; +@property (nonatomic, weak) IBOutlet id delegate; @end @protocol GBImageViewDelegate diff --git a/Cocoa/GBMemoryByteArray.h b/Cocoa/GBMemoryByteArray.h index e3ed71f..17abe20 100644 --- a/Cocoa/GBMemoryByteArray.h +++ b/Cocoa/GBMemoryByteArray.h @@ -12,6 +12,6 @@ typedef enum { @interface GBMemoryByteArray : HFByteArray - (instancetype) initWithDocument:(Document *)document; -@property uint16_t selectedBank; -@property GB_memory_mode_t mode; +@property (nonatomic) uint16_t selectedBank; +@property (nonatomic) GB_memory_mode_t mode; @end diff --git a/Cocoa/GBOpenGLView.h b/Cocoa/GBOpenGLView.h index 5f875ab..66001c9 100644 --- a/Cocoa/GBOpenGLView.h +++ b/Cocoa/GBOpenGLView.h @@ -2,5 +2,5 @@ #import "GBGLShader.h" @interface GBOpenGLView : NSOpenGLView -@property GBGLShader *shader; +@property (nonatomic) GBGLShader *shader; @end diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 43a8f1d..85ad6b3 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -2,27 +2,27 @@ #import @interface GBPreferencesWindow : NSWindow -@property IBOutlet NSTableView *controlsTableView; -@property IBOutlet NSPopUpButton *graphicsFilterPopupButton; -@property (strong) IBOutlet NSButton *analogControlsCheckbox; -@property (strong) IBOutlet NSButton *aspectRatioCheckbox; -@property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; -@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; -@property (strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton; -@property (strong) IBOutlet NSPopUpButton *colorPalettePopupButton; -@property (strong) IBOutlet NSPopUpButton *displayBorderPopupButton; -@property (strong) IBOutlet NSPopUpButton *rewindPopupButton; -@property (strong) IBOutlet NSButton *configureJoypadButton; -@property (strong) IBOutlet NSButton *skipButton; -@property (strong) IBOutlet NSMenuItem *bootROMsFolderItem; -@property (strong) IBOutlet NSPopUpButtonCell *bootROMsButton; -@property (strong) IBOutlet NSPopUpButton *rumbleModePopupButton; -@property (weak) IBOutlet NSSlider *temperatureSlider; -@property (weak) IBOutlet NSSlider *interferenceSlider; -@property (weak) IBOutlet NSPopUpButton *dmgPopupButton; -@property (weak) IBOutlet NSPopUpButton *sgbPopupButton; -@property (weak) IBOutlet NSPopUpButton *cgbPopupButton; -@property (weak) IBOutlet NSPopUpButton *preferredJoypadButton; -@property (weak) IBOutlet NSPopUpButton *playerListButton; +@property (nonatomic, strong) IBOutlet NSTableView *controlsTableView; +@property (nonatomic, strong) IBOutlet NSPopUpButton *graphicsFilterPopupButton; +@property (nonatomic, strong) IBOutlet NSButton *analogControlsCheckbox; +@property (nonatomic, strong) IBOutlet NSButton *aspectRatioCheckbox; +@property (nonatomic, strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *displayBorderPopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *rewindPopupButton; +@property (nonatomic, strong) IBOutlet NSButton *configureJoypadButton; +@property (nonatomic, strong) IBOutlet NSButton *skipButton; +@property (nonatomic, strong) IBOutlet NSMenuItem *bootROMsFolderItem; +@property (nonatomic, strong) IBOutlet NSPopUpButtonCell *bootROMsButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *rumbleModePopupButton; +@property (nonatomic, weak) IBOutlet NSSlider *temperatureSlider; +@property (nonatomic, weak) IBOutlet NSSlider *interferenceSlider; +@property (nonatomic, weak) IBOutlet NSPopUpButton *dmgPopupButton; +@property (nonatomic, weak) IBOutlet NSPopUpButton *sgbPopupButton; +@property (nonatomic, weak) IBOutlet NSPopUpButton *cgbPopupButton; +@property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton; +@property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton; @end diff --git a/Cocoa/GBTerminalTextFieldCell.h b/Cocoa/GBTerminalTextFieldCell.h index 484e0c3..b760336 100644 --- a/Cocoa/GBTerminalTextFieldCell.h +++ b/Cocoa/GBTerminalTextFieldCell.h @@ -2,5 +2,5 @@ #include @interface GBTerminalTextFieldCell : NSTextFieldCell -@property GB_gameboy_t *gb; +@property (nonatomic) GB_gameboy_t *gb; @end diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index 26fce14..f9aab83 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -14,12 +14,12 @@ typedef enum { @interface GBView : NSView - (void) flip; - (uint32_t *) pixels; -@property (weak) IBOutlet Document *document; -@property GB_gameboy_t *gb; +@property (nonatomic, weak) IBOutlet Document *document; +@property (nonatomic) GB_gameboy_t *gb; @property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; -@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; -@property bool isRewinding; -@property NSView *internalView; +@property (nonatomic, getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; +@property (nonatomic) bool isRewinding; +@property (nonatomic, strong) NSView *internalView; - (void) createInternalView; - (uint32_t *)currentBuffer; - (uint32_t *)previousBuffer; From 60f226321da1ad0421c76778bc556333440588da Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 13 Jan 2021 14:52:18 -0500 Subject: [PATCH 076/365] Resolve various deprecation warnings. --- Cocoa/Document.m | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 0beceab..516b6e0 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -327,7 +327,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) GB_set_pixels_output(&gb, self.view.pixels); if (self.vramWindow.isVisible) { dispatch_async(dispatch_get_main_queue(), ^{ - self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; [self reloadVRAMData: nil]; }); } @@ -408,14 +408,14 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; for (NSUserNotification *notification in [center scheduledNotifications]) { - if ([notification.identifier isEqualToString:self.fileName]) { + if ([notification.identifier isEqualToString:self.fileURL.path]) { [center removeScheduledNotification:notification]; break; } } for (NSUserNotification *notification in [center deliveredNotifications]) { - if ([notification.identifier isEqualToString:self.fileName]) { + if ([notification.identifier isEqualToString:self.fileURL.path]) { [center removeDeliveredNotification:notification]; break; } @@ -434,7 +434,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) run { - assert(!master); + assert(master == nil); running = true; [self preRun]; if (slave) { @@ -482,21 +482,21 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self.audioClient stop]; self.audioClient = nil; self.view.mouseHidingEnabled = NO; - GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); - GB_save_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); + GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]); + GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]); unsigned time_to_alarm = GB_time_to_alarm(&gb); if (time_to_alarm) { [NSUserNotificationCenter defaultUserNotificationCenter].delegate = (id)[NSApp delegate]; NSUserNotification *notification = [[NSUserNotification alloc] init]; - NSString *friendlyName = [[self.fileName lastPathComponent] stringByDeletingPathExtension]; + NSString *friendlyName = [[self.fileURL lastPathComponent] stringByDeletingPathExtension]; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil]; friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""]; friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName]; notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName]; - notification.identifier = self.fileName; + notification.identifier = self.fileURL.path; notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm]; notification.soundName = NSUserNotificationDefaultSoundName; [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification]; @@ -507,7 +507,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) start { - self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0; + self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; if (master) { [master start]; return; @@ -667,7 +667,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[self.fileURL path] lastPathComponent]]; + self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [self.fileURL.path lastPathComponent]]; self.debuggerSplitView.dividerColor = [NSColor clearColor]; if (@available(macOS 11.0, *)) { self.memoryWindow.toolbarStyle = NSWindowToolbarStyleExpanded; @@ -827,18 +827,18 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) NSString *rom_warnings = [self captureOutputForBlock:^{ GB_debugger_clear_symbols(&gb); if ([[self.fileType pathExtension] isEqualToString:@"isx"]) { - GB_load_isx(&gb, [self.fileName UTF8String]); - GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"ram"] UTF8String]); + GB_load_isx(&gb, self.fileURL.path.UTF8String); + GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); } else { - GB_load_rom(&gb, [self.fileName UTF8String]); + GB_load_rom(&gb, [self.fileURL.path UTF8String]); } - GB_load_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]); - GB_load_cheats(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"cht"] UTF8String]); + GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path.UTF8String); + GB_load_cheats(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path.UTF8String); [self.cheatWindowController cheatsUpdated]; GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]); - GB_debugger_load_symbol_file(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"] UTF8String]); + GB_debugger_load_symbol_file(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sym"].path.UTF8String); }]; if (rom_warnings && !rom_warning_issued) { rom_warning_issued = true; @@ -1144,7 +1144,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) { bool __block success = false; [self performAtomicBlock:^{ - success = GB_save_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]) == 0; + success = GB_save_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0; }]; if (!success) { @@ -1158,7 +1158,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) bool __block success = false; NSString *error = [self captureOutputForBlock:^{ - success = GB_load_state(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]] UTF8String]) == 0; + success = GB_load_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0; }]; if (!success) { @@ -1789,14 +1789,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) NSSavePanel * savePanel = [NSSavePanel savePanel]; [savePanel setAllowedFileTypes:@[@"png"]]; [savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result) { - if (result == NSFileHandlingPanelOKButton) { + if (result == NSModalResponseOK) { [savePanel orderOut:self]; CGImageRef cgRef = [self.feedImageView.image CGImageForProposedRect:NULL context:nil hints:nil]; NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; [imageRep setSize:(NSSize){160, self.feedImageView.image.size.height / 2}]; - NSData *data = [imageRep representationUsingType:NSPNGFileType properties:@{}]; + NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; [data writeToURL:savePanel.URL atomically:NO]; [self.printerFeedWindow setIsVisible:NO]; } @@ -2029,7 +2029,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (bool)isSlave { - return master; + return master != nil; } - (GB_gameboy_t *)gb From 1707c8818a75f33cf063f01ef47cb91b1bd094de Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Fri, 15 Jan 2021 00:06:21 -0500 Subject: [PATCH 077/365] Fix broken sprite rendering in the VRAM viewer due to mis-calculation of image data size. --- Cocoa/Document.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 0beceab..305eac1 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1692,7 +1692,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) switch (columnIndex) { case 0: return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image - length:64 * 4 + length:64 * 4 * 2 freeWhenDone:NO] width:8 height:oamHeight From 8f91533a9a51138b29d2b1d38d33a06dae6e2055 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Fri, 15 Jan 2021 12:49:24 -0500 Subject: [PATCH 078/365] Revert nil check changes. --- Cocoa/Document.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 516b6e0..0561074 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -434,7 +434,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) run { - assert(master == nil); + assert(!master); running = true; [self preRun]; if (slave) { @@ -2029,7 +2029,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (bool)isSlave { - return master != nil; + return master; } - (GB_gameboy_t *)gb From 557f5542708fab48b8479f005f687119482f3553 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Thu, 14 Jan 2021 22:58:19 -0500 Subject: [PATCH 079/365] [Sameboy] Add type annotations to GBImageView's grid arrays. --- Cocoa/GBImageView.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cocoa/GBImageView.h b/Cocoa/GBImageView.h index 0b93e66..c15c4e7 100644 --- a/Cocoa/GBImageView.h +++ b/Cocoa/GBImageView.h @@ -9,8 +9,8 @@ @end @interface GBImageView : NSImageView -@property (nonatomic, strong) NSArray *horizontalGrids; -@property (nonatomic, strong) NSArray *verticalGrids; +@property (nonatomic, strong) NSArray *horizontalGrids; +@property (nonatomic, strong) NSArray *verticalGrids; @property (nonatomic) bool displayScrollRect; @property NSRect scrollRect; @property (nonatomic, weak) IBOutlet id delegate; From 0056cc2d61448e9b3842f27449da4b62620f3adc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jan 2021 14:42:13 +0200 Subject: [PATCH 080/365] Revert "Further NR43 write glitch emulation" for now This reverts commit e38470761555007bf5b8e579612962efb0dde402. --- Core/apu.c | 48 ++++++++++++++++-------------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index bd00d49..3706599 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -710,7 +710,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; } -static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb, uint8_t nr43) +static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) { uint16_t effective_counter = gb->apu.noise_channel.counter; /* Ladies and gentlemen, I present you the holy grail glitch of revision detection! */ @@ -720,14 +720,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb, uint8_t nr43 #if 0 case GB_MODEL_CGB_B: if (effective_counter & 8) { - // Verify this part - if (gb->apu.channel_4_countdown_reloaded && !(effective_counter & 1)) { - uint16_t lower_bits = ~(effective_counter & ~(effective_counter - 1)) & 0xF; - effective_counter = (effective_counter & ~0xF) | lower_bits; - } - else { - effective_counter |= 0xF; - } + effective_counter |= 0xE; // Seems to me F under some circumstances? } if (effective_counter & 0x80) { effective_counter |= 0xFF; @@ -763,16 +756,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb, uint8_t nr43 // case GB_MODEL_CGB_A: case GB_MODEL_CGB_C: if (effective_counter & 8) { - if (gb->apu.channel_4_countdown_reloaded && !(effective_counter & 1)) { - uint16_t lower_bits = ~(effective_counter & ~(effective_counter - 1)) & 0xF; - effective_counter = (effective_counter & ~0xF) | lower_bits; - if (!(nr43 >> 4)) { - effective_counter &= ~1; - } - } - else { - effective_counter |= 0xE; - } + effective_counter |= 0xE; } if (effective_counter & 0x80) { effective_counter |= 0xFF; @@ -836,9 +820,6 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb, uint8_t nr43 */ break; } - if (gb->model <= GB_MODEL_CGB_C && gb->apu.channel_4_countdown_reloaded && (nr43 >> 4)) { - effective_counter = (effective_counter & effective_counter - 1) | (effective_counter & 1); - } return effective_counter; } @@ -1200,22 +1181,16 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR43: { gb->apu.noise_channel.narrow = value & 8; - uint16_t effective_counter = effective_channel4_counter(gb, value); + uint16_t effective_counter = effective_channel4_counter(gb); bool old_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; gb->io_registers[GB_IO_NR43] = value; bool new_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; if (gb->apu.channel_4_countdown_reloaded) { unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; if (!divisor) divisor = 2; - if (gb->model > GB_MODEL_CGB_C) { - gb->apu.noise_channel.counter_countdown = - divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); - } - else { - gb->apu.noise_channel.counter_countdown = - divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 4, 3}[(gb->apu.noise_channel.alignment) & 3]); - } - gb->apu.channel_4_delta = 0; + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); + gb->apu.channel_4_delta = 0; } /* Step LFSR */ if (new_bit && (!old_bit || gb->model <= GB_MODEL_CGB_C)) { @@ -1263,6 +1238,15 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } + /* TODO: These two are quite weird. Verify further */ + if (gb->model <= GB_MODEL_CGB_C && gb->cgb_double_speed) { + if (!(gb->io_registers[GB_IO_NR43] & 0xF0) && (gb->io_registers[GB_IO_NR43] & 0x07)) { + gb->apu.noise_channel.counter_countdown -= 1; + } + else if ((gb->io_registers[GB_IO_NR43] & 0xF0) && !(gb->io_registers[GB_IO_NR43] & 0x07)) { + gb->apu.noise_channel.counter_countdown += 1; + } + } gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4; From 13a1e9d332e5fce328e9850da8f94215897e7e87 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jan 2021 14:43:32 +0200 Subject: [PATCH 081/365] Timing fix --- Core/apu.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 3706599..d5dee75 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -1188,9 +1188,15 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (gb->apu.channel_4_countdown_reloaded) { unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; if (!divisor) divisor = 2; - gb->apu.noise_channel.counter_countdown = - divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); - gb->apu.channel_4_delta = 0; + if (gb->model > GB_MODEL_CGB_C) { + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 0, 3}[(gb->apu.noise_channel.alignment) & 3]); + } + else { + gb->apu.noise_channel.counter_countdown = + divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 4, 3}[(gb->apu.noise_channel.alignment) & 3]); + } + gb->apu.channel_4_delta = 0; } /* Step LFSR */ if (new_bit && (!old_bit || gb->model <= GB_MODEL_CGB_C)) { From aa421258b846273b4e21a9858daa06a51c5a55d7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jan 2021 14:51:06 +0200 Subject: [PATCH 082/365] Update the model enum so comparisons work correctly for SGB PAL and no-SFC SGBs --- Core/gb.h | 9 +++++++-- Core/save_state.c | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index c2e96db..50987a0 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -31,8 +31,13 @@ #define GB_MODEL_DMG_FAMILY 0x000 #define GB_MODEL_MGB_FAMILY 0x100 #define GB_MODEL_CGB_FAMILY 0x200 -#define GB_MODEL_PAL_BIT 0x1000 -#define GB_MODEL_NO_SFC_BIT 0x2000 +#define GB_MODEL_PAL_BIT 0x40 +#define GB_MODEL_NO_SFC_BIT 0x80 + +#ifdef GB_INTERNAL +#define GB_MODEL_PAL_BIT_OLD 0x1000 +#define GB_MODEL_NO_SFC_BIT_OLD 0x2000 +#endif #ifdef GB_INTERNAL #if __clang__ diff --git a/Core/save_state.c b/Core/save_state.c index 827cf57..2b31ba3 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -163,6 +163,16 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t } } + if (save->model & GB_MODEL_PAL_BIT_OLD) { + save->model &= ~GB_MODEL_PAL_BIT_OLD; + save->model |= GB_MODEL_PAL_BIT; + } + + if (save->model & GB_MODEL_NO_SFC_BIT_OLD) { + save->model &= ~GB_MODEL_NO_SFC_BIT_OLD; + save->model |= GB_MODEL_NO_SFC_BIT; + } + if (gb->version != save->version) { GB_log(gb, "The save state is for a different version of SameBoy.\n"); return false; From 8e1e889ce02526a164233aebc7edb7f42743a9dc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 16 Jan 2021 15:31:09 +0200 Subject: [PATCH 083/365] Add a TODO --- Core/apu.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/apu.c b/Core/apu.c index d5dee75..4e83973 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -712,6 +712,12 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) { + /* + TODO: On revisions older than the CGB-D, this behaves differently when + the counter advanced this exact T-cycle. Also, in these revisions, + it seems that "passive" changes (due to the temporary FF value NR43 + has during writes) behave slightly different from non-passive ones. + */ uint16_t effective_counter = gb->apu.noise_channel.counter; /* Ladies and gentlemen, I present you the holy grail glitch of revision detection! */ switch (gb->model) { From ef9671010b0ae181e6c65ff4c228d2a06f3ee6d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 24 Jan 2021 20:57:46 +0200 Subject: [PATCH 084/365] More NR43 obscurities --- Core/apu.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 4e83973..8f871b0 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -762,7 +762,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) // case GB_MODEL_CGB_A: case GB_MODEL_CGB_C: if (effective_counter & 8) { - effective_counter |= 0xE; + effective_counter |= 0xE; // Sometimes F on some instances } if (effective_counter & 0x80) { effective_counter |= 0xFF; @@ -777,6 +777,9 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) effective_counter |= 0x4; } if (effective_counter & 0x800) { + if ((gb->io_registers[GB_IO_NR43] & 8)) { + effective_counter |= 0x400; + } effective_counter |= 0x8; } if (effective_counter & 0x1000) { @@ -788,7 +791,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) break; #if 0 case GB_MODEL_CGB_D: - if (effective_counter & 0x40) { + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x80 : 0x40)) { // This is so weird effective_counter |= 0xFF; } if (effective_counter & 0x100) { @@ -809,7 +812,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) break; #endif case GB_MODEL_CGB_E: - if (effective_counter & 0x40) { + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x80 : 0x40)) { // This is so weird effective_counter |= 0xFF; } if (effective_counter & 0x1000) { @@ -822,7 +825,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) /* For the most part, AGS seems to do: 0x20 -> 0xA0 0x200 -> 0xA00 - 0x1000 -> 0x1010 + 0x1000 -> 0x1010, but only if wide */ break; } @@ -1250,13 +1253,18 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } - /* TODO: These two are quite weird. Verify further */ - if (gb->model <= GB_MODEL_CGB_C && gb->cgb_double_speed) { - if (!(gb->io_registers[GB_IO_NR43] & 0xF0) && (gb->io_registers[GB_IO_NR43] & 0x07)) { - gb->apu.noise_channel.counter_countdown -= 1; + /* TODO: These are quite weird. Verify further */ + if (gb->model <= GB_MODEL_CGB_C) { + if (gb->cgb_double_speed) { + if (!(gb->io_registers[GB_IO_NR43] & 0xF0) && (gb->io_registers[GB_IO_NR43] & 0x07)) { + gb->apu.noise_channel.counter_countdown -= 1; + } + else if ((gb->io_registers[GB_IO_NR43] & 0xF0) && !(gb->io_registers[GB_IO_NR43] & 0x07)) { + gb->apu.noise_channel.counter_countdown += 1; + } } - else if ((gb->io_registers[GB_IO_NR43] & 0xF0) && !(gb->io_registers[GB_IO_NR43] & 0x07)) { - gb->apu.noise_channel.counter_countdown += 1; + else { + gb->apu.noise_channel.counter_countdown -= 2; } } From bbfd16f63dbb18e5a4d70985b3c9d54e7b623189 Mon Sep 17 00:00:00 2001 From: phobos2390 Date: Mon, 25 Jan 2021 23:37:46 -0700 Subject: [PATCH 085/365] Fix for tolower extension signed char issue --- SDL/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index 63a61c9..9025403 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -524,9 +524,9 @@ restart: char extension[4] = {0,}; if (path_length > 4) { if (filename[path_length - 4] == '.') { - extension[0] = tolower(filename[path_length - 3]); - extension[1] = tolower(filename[path_length - 2]); - extension[2] = tolower(filename[path_length - 1]); + extension[0] = tolower((unsigned char)filename[path_length - 3]); + extension[1] = tolower((unsigned char)filename[path_length - 2]); + extension[2] = tolower((unsigned char)filename[path_length - 1]); } } if (strcmp(extension, "isx") == 0) { From d67580c96405c7ee07168b3dcedd7a29d4b18041 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 Jan 2021 17:16:59 +0200 Subject: [PATCH 086/365] Oops, that was reversed --- Core/apu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 8f871b0..126dd43 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -791,7 +791,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) break; #if 0 case GB_MODEL_CGB_D: - if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x80 : 0x40)) { // This is so weird + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x40 : 0x80)) { // This is so weird effective_counter |= 0xFF; } if (effective_counter & 0x100) { @@ -812,7 +812,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) break; #endif case GB_MODEL_CGB_E: - if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x80 : 0x40)) { // This is so weird + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x40 : 0x80)) { // This is so weird effective_counter |= 0xFF; } if (effective_counter & 0x1000) { From 2d766982799b03c4333b6784593c903f54d55e45 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 Jan 2021 19:17:26 +0200 Subject: [PATCH 087/365] Emulation of NR43 bit 3 glitch on CGB-C and older --- Core/apu.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 126dd43..c7345f0 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -1209,7 +1209,15 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } /* Step LFSR */ if (new_bit && (!old_bit || gb->model <= GB_MODEL_CGB_C)) { - step_lfsr(gb, 0); + if (gb->model <= GB_MODEL_CGB_C) { + bool previous_narrow = gb->apu.noise_channel.narrow; + gb->apu.noise_channel.narrow = true; + step_lfsr(gb, 0); + gb->apu.noise_channel.narrow = previous_narrow; + } + else { + step_lfsr(gb, 0); + } } break; } From 6798b1f11af54469b63afc612a0454ed7227679d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 31 Jan 2021 19:17:48 +0200 Subject: [PATCH 088/365] Use a slider for temperature in the SDL GUI --- SDL/font.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ SDL/font.h | 2 ++ SDL/gui.c | 26 ++++----------------- 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/SDL/font.c b/SDL/font.c index eb6243e..ea2c590 100644 --- a/SDL/font.c +++ b/SDL/font.c @@ -1053,6 +1053,72 @@ uint8_t font[] = { X, _, _, _, X, _, X, _, _, _, X, _, X, X, X, X, X, _, + + /* Slider */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, X, X, X, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, X, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* Slider, selected */ + _, X, X, X, _, _, + _, X, X, X, _, _, + _, X, X, X, _, _, + _, X, X, X, X, X, + _, X, X, X, _, _, + _, X, X, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + _, X, X, X, _, _, + _, X, X, X, _, _, + _, X, X, X, _, _, + X, X, X, X, X, X, + _, X, X, X, _, _, + _, X, X, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + _, X, X, X, _, _, + _, X, X, X, _, _, + _, X, X, X, _, _, + X, X, X, X, _, _, + _, X, X, X, _, _, + _, X, X, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* Slider, tick*/ + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, X, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, }; const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' '; diff --git a/SDL/font.h b/SDL/font.h index 06d7adf..f2111c3 100644 --- a/SDL/font.h +++ b/SDL/font.h @@ -14,5 +14,7 @@ extern const uint8_t font_max; #define CMD_STRING "\x84\x85" #define ELLIPSIS_STRING "\x87" #define MOJIBAKE_STRING "\x88" +#define SLIDER_STRING "\x89\x8A\x8A\x8A\x8A\x8A\x8A\x8A\x8A\x8A\x8F\x8A\x8A\x8A\x8A\x8A\x8A\x8A\x8A\x8A\x8B" +#define SELECTED_SLIDER_STRING "\x8C\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8D\x8E" #endif /* font_h */ diff --git a/SDL/gui.c b/SDL/gui.c index 0846664..92d9479 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -519,28 +519,10 @@ const char *current_color_correction_mode(unsigned index) const char *current_color_temperature(unsigned index) { - return (const char *[]){"12000K", - "11450K", - "10900K", - "10350K", - "9800K", - "9250K", - "8700K", - "8150K", - "7600K", - "7050K", - "6500K (White)", - "5950K", - "5400K", - "4850K", - "4300K", - "3750K", - "3200K", - "2650K", - "2100K", - "1550K", - "1000K"} - [configuration.color_temperature]; + static char ret[22]; + strcpy(ret, SLIDER_STRING); + ret[configuration.color_temperature] = SELECTED_SLIDER_STRING[configuration.color_temperature]; + return ret; } From 8ad08c1b35a6f03462803b2a6c12eae716b02274 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 1 Feb 2021 23:11:42 +0200 Subject: [PATCH 089/365] Fix more audio deadlocks --- Cocoa/Document.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 305eac1..d4e82d5 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -381,7 +381,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) [audioLock wait]; } - if (stopping) { + if (stopping || GB_debugger_is_stopped(&gb)) { memset(buffer, 0, nFrames * sizeof(*buffer)); [audioLock unlock]; return; @@ -1105,6 +1105,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (char *) getDebuggerInput { + [audioLock signal]; [self updateSideView]; [self log:">"]; in_sync_input = true; From 95051d1c1c6e463505a280854c9ce2aeb2f49788 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 13 Feb 2021 23:00:20 +0200 Subject: [PATCH 090/365] Improved emulation of the NRx2 write glitch (Zombie mode) on models prior to CGB-D --- Core/apu.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index c7345f0..27c060c 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -297,7 +297,7 @@ static void update_square_sample(GB_gameboy_t *gb, unsigned index) requires the PCM12 register. The behavior implemented here was verified on *my* CGB, which might behave differently from other CGB revisions, as well as from the DMG, MGB or SGB/2 */ -static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) +static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) { if (value & 8) { (*volume)++; @@ -318,6 +318,17 @@ static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) (*volume) &= 0xF; } +static void nrx2_glitch(GB_gameboy_t *gb,uint8_t *volume, uint8_t value, uint8_t old_value) +{ + if (gb->model <= GB_MODEL_CGB_C) { + _nrx2_glitch(volume, 0xFF, old_value); + _nrx2_glitch(volume, value, 0xFF); + } + else { + _nrx2_glitch(volume, value, old_value); + } +} + static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) { uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; @@ -931,7 +942,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) update_sample(gb, index, 0, 0); } else if (gb->apu.is_active[index]) { - nrx2_glitch(&gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]); + nrx2_glitch(gb, &gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]); update_square_sample(gb, index); } @@ -1179,7 +1190,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) update_sample(gb, GB_NOISE, 0, 0); } else if (gb->apu.is_active[GB_NOISE]) { - nrx2_glitch(&gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]); + nrx2_glitch(gb, &gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]); update_sample(gb, GB_NOISE, gb->apu.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, From 1a87c452b71e203d119c0577bd6cab4cb58a9a30 Mon Sep 17 00:00:00 2001 From: Johan Kotlinski Date: Sun, 14 Feb 2021 01:31:49 +0100 Subject: [PATCH 091/365] exit with error message instead of crash when a window cannot be opened --- SDL/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SDL/main.c b/SDL/main.c index 9025403..4f10ae9 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -677,6 +677,10 @@ int main(int argc, char **argv) window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 160 * configuration.default_scale, 144 * configuration.default_scale, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + if (window == NULL) { + fputs(SDL_GetError(), stderr); + exit(1); + } SDL_SetWindowMinimumSize(window, 160, 144); if (fullscreen) { From 6d2d88648e6bbe81cd986a0186dad85e80f42909 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 22 Feb 2021 01:10:14 +0200 Subject: [PATCH 092/365] Improved emulation of the volume envelope --- Core/apu.c | 85 ++++++++++++++++++++++++------------------------------ 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 27c060c..4d89e66 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -318,7 +318,7 @@ static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) (*volume) &= 0xF; } -static void nrx2_glitch(GB_gameboy_t *gb,uint8_t *volume, uint8_t value, uint8_t old_value) +static void nrx2_glitch(GB_gameboy_t *gb, uint8_t *volume, uint8_t value, uint8_t old_value) { if (gb->model <= GB_MODEL_CGB_C) { _nrx2_glitch(volume, 0xFF, old_value); @@ -332,31 +332,30 @@ static void nrx2_glitch(GB_gameboy_t *gb,uint8_t *volume, uint8_t value, uint8_t static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) { uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - - if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) { - if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { - if (gb->cgb_double_speed) { - if (index == GB_SQUARE_1) { - gb->apu.pcm_mask[0] &= gb->apu.square_channels[GB_SQUARE_1].current_volume | 0xF1; - } - else { - gb->apu.pcm_mask[0] &= (gb->apu.square_channels[GB_SQUARE_2].current_volume << 2) | 0x1F; - } + + if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { + gb->apu.square_channels[index].volume_countdown = nrx2 & 7; + if (!(nrx2 & 7)) return; + if (gb->cgb_double_speed) { + if (index == GB_SQUARE_1) { + gb->apu.pcm_mask[0] &= gb->apu.square_channels[GB_SQUARE_1].current_volume | 0xF1; } - - if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { - gb->apu.square_channels[index].current_volume++; + else { + gb->apu.pcm_mask[0] &= (gb->apu.square_channels[GB_SQUARE_2].current_volume << 2) | 0x1F; } + } + + if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { + gb->apu.square_channels[index].current_volume++; + } - else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { - gb->apu.square_channels[index].current_volume--; - } + else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { + gb->apu.square_channels[index].current_volume--; + } - gb->apu.square_channels[index].volume_countdown = nrx2 & 7; - if (gb->apu.is_active[index]) { - update_square_sample(gb, index); - } + if (gb->apu.is_active[index]) { + update_square_sample(gb, index); } } } @@ -365,27 +364,27 @@ static void tick_noise_envelope(GB_gameboy_t *gb) { uint8_t nr42 = gb->io_registers[GB_IO_NR42]; - if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) { - if (!gb->apu.noise_channel.volume_countdown || !--gb->apu.noise_channel.volume_countdown) { - if (gb->cgb_double_speed) { - gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F; - } - if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { - gb->apu.noise_channel.current_volume++; - } + if (!gb->apu.noise_channel.volume_countdown || !--gb->apu.noise_channel.volume_countdown) { + gb->apu.noise_channel.volume_countdown = nr42 & 7; + if (!(nr42 & 7)) return; - else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { - gb->apu.noise_channel.current_volume--; - } + if (gb->cgb_double_speed) { + gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F; + } + if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { + gb->apu.noise_channel.current_volume++; + } - gb->apu.noise_channel.volume_countdown = nr42 & 7; + else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { + gb->apu.noise_channel.current_volume--; + } - if (gb->apu.is_active[GB_NOISE]) { - update_sample(gb, GB_NOISE, - (gb->apu.noise_channel.lfsr & 1) ? - gb->apu.noise_channel.current_volume : 0, - 0); - } + + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.noise_channel.current_volume : 0, + 0); } } } @@ -931,10 +930,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR12: case GB_IO_NR22: { unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1; - if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { - /* Envelope disabled */ - gb->apu.square_channels[index].volume_countdown = 0; - } if ((value & 0xF8) == 0) { /* This disables the DAC */ gb->io_registers[reg] = value; @@ -1179,10 +1174,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } case GB_IO_NR42: { - if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) { - /* Envelope disabled */ - gb->apu.noise_channel.volume_countdown = 0; - } if ((value & 0xF8) == 0) { /* This disables the DAC */ gb->io_registers[reg] = value; From d50fdc52eacb8b907d0ed0c3529ff52dda2f6127 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 22 Feb 2021 01:58:43 +0200 Subject: [PATCH 093/365] Further accuracy improvements to the audio envelope --- Core/apu.c | 90 ++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 4d89e66..5ea19a7 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -333,30 +333,28 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) { uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) { - gb->apu.square_channels[index].volume_countdown = nrx2 & 7; - if (!(nrx2 & 7)) return; - if (gb->cgb_double_speed) { - if (index == GB_SQUARE_1) { - gb->apu.pcm_mask[0] &= gb->apu.square_channels[GB_SQUARE_1].current_volume | 0xF1; - } - else { - gb->apu.pcm_mask[0] &= (gb->apu.square_channels[GB_SQUARE_2].current_volume << 2) | 0x1F; - } + gb->apu.square_channels[index].volume_countdown = nrx2 & 7; + if (!(nrx2 & 7)) return; + if (gb->cgb_double_speed) { + if (index == GB_SQUARE_1) { + gb->apu.pcm_mask[0] &= gb->apu.square_channels[GB_SQUARE_1].current_volume | 0xF1; } - - if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { - gb->apu.square_channels[index].current_volume++; + else { + gb->apu.pcm_mask[0] &= (gb->apu.square_channels[GB_SQUARE_2].current_volume << 2) | 0x1F; } + } + + if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { + gb->apu.square_channels[index].current_volume++; + } - else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { - gb->apu.square_channels[index].current_volume--; - } + else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { + gb->apu.square_channels[index].current_volume--; + } - if (gb->apu.is_active[index]) { - update_square_sample(gb, index); - } + if (gb->apu.is_active[index]) { + update_square_sample(gb, index); } } @@ -364,28 +362,25 @@ static void tick_noise_envelope(GB_gameboy_t *gb) { uint8_t nr42 = gb->io_registers[GB_IO_NR42]; - if (!gb->apu.noise_channel.volume_countdown || !--gb->apu.noise_channel.volume_countdown) { - gb->apu.noise_channel.volume_countdown = nr42 & 7; - if (!(nr42 & 7)) return; + if (!(nr42 & 7)) return; - if (gb->cgb_double_speed) { - gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F; - } - if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { - gb->apu.noise_channel.current_volume++; - } + if (gb->cgb_double_speed) { + gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F; + } + if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { + gb->apu.noise_channel.current_volume++; + } - else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { - gb->apu.noise_channel.current_volume--; - } + else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { + gb->apu.noise_channel.current_volume--; + } - if (gb->apu.is_active[GB_NOISE]) { - update_sample(gb, GB_NOISE, - (gb->apu.noise_channel.lfsr & 1) ? - gb->apu.noise_channel.current_volume : 0, - 0); - } + if (gb->apu.is_active[GB_NOISE]) { + update_sample(gb, GB_NOISE, + (gb->apu.noise_channel.lfsr & 1) ? + gb->apu.noise_channel.current_volume : 0, + 0); } } @@ -428,28 +423,31 @@ void GB_apu_div_event(GB_gameboy_t *gb) } if ((gb->apu.div_divider & 1) == 0) { - for (unsigned i = GB_SQUARE_2 + 1; i--;) { + UNROLL for (unsigned i = GB_SQUARE_2 + 1; i--;) { uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0 && (nrx2 & 7)) { + if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) { tick_square_envelope(gb, i); + gb->apu.square_channels[i].volume_countdown = nrx2 & 7; } } - if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0 && (gb->io_registers[GB_IO_NR42] & 7)) { + if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0) { tick_noise_envelope(gb); + gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; } } - if ((gb->apu.div_divider & 7) == 0) { - for (unsigned i = GB_SQUARE_2 + 1; i--;) { - tick_square_envelope(gb, i); + if ((gb->apu.div_divider & 7) == 7) { + UNROLL for (unsigned i = GB_SQUARE_2 + 1; i--;) { + gb->apu.square_channels[i].volume_countdown--; + gb->apu.square_channels[i].volume_countdown &= 7; } - - tick_noise_envelope(gb); + gb->apu.noise_channel.volume_countdown--; + gb->apu.noise_channel.volume_countdown &= 7; } if ((gb->apu.div_divider & 1) == 1) { - for (unsigned i = GB_SQUARE_2 + 1; i--;) { + UNROLL for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.square_channels[i].length_enabled) { if (gb->apu.square_channels[i].pulse_length) { if (!--gb->apu.square_channels[i].pulse_length) { From 393269ae1f9be91edaaa605ce9b55ca6f4c3fb68 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 22 Feb 2021 13:48:56 +0200 Subject: [PATCH 094/365] Emulate volume envelope locking --- Core/apu.c | 48 ++++++++++++++++++++++++++++++++++-------------- Core/apu.h | 3 +++ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 5ea19a7..1003bce 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -333,7 +333,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) { uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - gb->apu.square_channels[index].volume_countdown = nrx2 & 7; + if (gb->apu.is_square_envelope_locked[index]) return; if (!(nrx2 & 7)) return; if (gb->cgb_double_speed) { if (index == GB_SQUARE_1) { @@ -344,15 +344,23 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) } } - if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) { - gb->apu.square_channels[index].current_volume++; + if (nrx2 & 8) { + if (gb->apu.square_channels[index].current_volume < 0xF) { + gb->apu.square_channels[index].current_volume++; + } + else { + gb->apu.is_square_envelope_locked[index] = true; + } } - - else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) { - gb->apu.square_channels[index].current_volume--; + else { + if (gb->apu.square_channels[index].current_volume > 0) { + gb->apu.square_channels[index].current_volume--; + } + else { + gb->apu.is_square_envelope_locked[index] = true; + } } - if (gb->apu.is_active[index]) { update_square_sample(gb, index); } @@ -362,20 +370,30 @@ static void tick_noise_envelope(GB_gameboy_t *gb) { uint8_t nr42 = gb->io_registers[GB_IO_NR42]; + if (gb->apu.is_noise_envelope_locked) return; if (!(nr42 & 7)) return; if (gb->cgb_double_speed) { gb->apu.pcm_mask[0] &= (gb->apu.noise_channel.current_volume << 2) | 0x1F; } - if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) { - gb->apu.noise_channel.current_volume++; + + if (nr42 & 8) { + if (gb->apu.noise_channel.current_volume < 0xF) { + gb->apu.noise_channel.current_volume++; + } + else { + gb->apu.is_noise_envelope_locked = true; + } } - - else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) { - gb->apu.noise_channel.current_volume--; + else { + if (gb->apu.noise_channel.current_volume > 0) { + gb->apu.noise_channel.current_volume--; + } + else { + gb->apu.is_noise_envelope_locked = true; + } } - if (gb->apu.is_active[GB_NOISE]) { update_sample(gb, GB_NOISE, (gb->apu.noise_channel.lfsr & 1) ? @@ -975,6 +993,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (value & 0x80) { /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by turning the APU off. */ + gb->apu.is_square_envelope_locked[index] = 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_C && gb->apu.lf_div) { @@ -983,7 +1002,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } else { unsigned extra_delay = 0; - if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */) { + if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */) { if ((!(value & 4) && ((gb->io_registers[reg] & 4) || old_sample_length == 0x3FF)) || (old_sample_length == 0x7FF && gb->apu.square_channels[index].sample_length != 0x7FF)) { gb->apu.square_channels[index].current_sample_index++; @@ -1224,6 +1243,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR44: { if (value & 0x80) { + gb->apu.is_noise_envelope_locked = false; if (!GB_is_cgb(gb) && (gb->apu.noise_channel.alignment & 3) != 0) { gb->apu.channel_4_dmg_delayed_start = 6; } diff --git a/Core/apu.h b/Core/apu.h index 6394a55..770f15b 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -126,6 +126,9 @@ typedef struct bool channel_4_countdown_reloaded; uint8_t channel_4_dmg_delayed_start; uint16_t channel1_completed_addend; + + bool is_square_envelope_locked[2]; + bool is_noise_envelope_locked; } GB_apu_t; typedef enum { From 71c88323b7d1a4f69cc41411d11429cf1bfa9cb1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 22 Feb 2021 14:45:30 +0200 Subject: [PATCH 095/365] Rename UNROLL to unrolled (`unrolled for`) --- Core/apu.c | 15 ++++++--------- Core/display.c | 12 ++++-------- Core/gb.h | 6 +++--- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 1003bce..2525931 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -180,8 +180,7 @@ static void render(GB_gameboy_t *gb) { GB_sample_t output = {0, 0}; - UNROLL - for (unsigned i = 0; i < GB_N_CHANNELS; i++) { + unrolled for (unsigned i = 0; i < GB_N_CHANNELS; i++) { double multiplier = CH_STEP; if (gb->model < GB_MODEL_AGB) { @@ -240,8 +239,7 @@ static void render(GB_gameboy_t *gb) unsigned mask = gb->io_registers[GB_IO_NR51]; unsigned left_volume = 0; unsigned right_volume = 0; - UNROLL - for (unsigned i = GB_N_CHANNELS; i--;) { + unrolled for (unsigned i = GB_N_CHANNELS; i--;) { if (gb->apu.is_active[i]) { if (mask & 1) { left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF; @@ -441,7 +439,7 @@ void GB_apu_div_event(GB_gameboy_t *gb) } if ((gb->apu.div_divider & 1) == 0) { - UNROLL for (unsigned i = GB_SQUARE_2 + 1; i--;) { + unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) { tick_square_envelope(gb, i); @@ -456,7 +454,7 @@ void GB_apu_div_event(GB_gameboy_t *gb) } if ((gb->apu.div_divider & 7) == 7) { - UNROLL for (unsigned i = GB_SQUARE_2 + 1; i--;) { + unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { gb->apu.square_channels[i].volume_countdown--; gb->apu.square_channels[i].volume_countdown &= 7; } @@ -465,7 +463,7 @@ void GB_apu_div_event(GB_gameboy_t *gb) } if ((gb->apu.div_divider & 1) == 1) { - UNROLL for (unsigned i = GB_SQUARE_2 + 1; i--;) { + unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.square_channels[i].length_enabled) { if (gb->apu.square_channels[i].pulse_length) { if (!--gb->apu.square_channels[i].pulse_length) { @@ -586,8 +584,7 @@ void GB_apu_run(GB_gameboy_t *gb) } } - UNROLL - for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { + unrolled for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { if (gb->apu.is_active[i]) { uint8_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) { diff --git a/Core/display.c b/Core/display.c index 6ac6be8..72e89c8 100644 --- a/Core/display.c +++ b/Core/display.c @@ -28,8 +28,7 @@ static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo) 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) { if (!flip_x) { - UNROLL - for (unsigned i = 8; i--;) { + unrolled for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower >> 7) | ((upper >> 7) << 1), palette, @@ -44,8 +43,7 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint } } else { - UNROLL - for (unsigned i = 8; i--;) { + unrolled for (unsigned i = 8; i--;) { fifo->fifo[fifo->write_end] = (GB_fifo_item_t) { (lower & 1) | ((upper & 1) << 1), palette, @@ -71,8 +69,7 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe uint8_t flip_xor = flip_x? 0: 0x7; - UNROLL - for (unsigned i = 8; i--;) { + unrolled for (unsigned i = 8; i--;) { uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1); GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)]; if (pixel != 0 && (target->pixel == 0 || target->priority > priority)) { @@ -1551,8 +1548,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } for (unsigned y = 0; y < *sprite_height; y++) { - UNROLL - for (unsigned x = 0; x < 8; x++) { + unrolled for (unsigned x = 0; x < 8; x++) { uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); diff --git a/Core/gb.h b/Core/gb.h index 50987a0..2c0f11a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -41,11 +41,11 @@ #ifdef GB_INTERNAL #if __clang__ -#define UNROLL _Pragma("unroll") +#define unrolled _Pragma("unroll") #elif __GNUC__ >= 8 -#define UNROLL _Pragma("GCC unroll 8") +#define unrolled _Pragma("GCC unroll 8") #else -#define UNROLL +#define unrolled #endif #endif From 8809d8ac2f16d160d3989d9ac6394afe2e39ba17 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 22 Feb 2021 15:27:17 +0200 Subject: [PATCH 096/365] More correct emulation of manual clocking of channels 1 and 2 --- Core/apu.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 2525931..c42e6df 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -1000,16 +1000,17 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) else { unsigned extra_delay = 0; if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */) { - if ((!(value & 4) && ((gb->io_registers[reg] & 4) || old_sample_length == 0x3FF)) || - (old_sample_length == 0x7FF && gb->apu.square_channels[index].sample_length != 0x7FF)) { + if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1) / 2) & 0x400)) { gb->apu.square_channels[index].current_sample_index++; gb->apu.square_channels[index].current_sample_index &= 0x7; - } - else if (gb->apu.square_channels[index].sample_length == 0x7FF && - old_sample_length != 0x7FF && - (gb->apu.square_channels[index].current_sample_index & 0x80)) { - extra_delay += 2; - } + gb->apu.is_active[index] = true; + } + /* Todo: verify with the schematics what's going on in here */ + else if (gb->apu.square_channels[index].sample_length == 0x7FF && + old_sample_length != 0x7FF && + (gb->apu.square_channels[index].current_sample_index & 0x80)) { + extra_delay += 2; + } } /* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/ gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div + extra_delay; From 4c05ebcea60368ffda2bbe96104182031f297f7b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Feb 2021 15:43:38 +0200 Subject: [PATCH 097/365] Redo the volume envelope with better timings, locking emulation and zombie mode edge cases. Fixes #344 --- Core/apu.c | 154 ++++++++++++++++++++++++++++++++++---------------- Core/apu.h | 12 +++- Core/timing.c | 14 ++++- 3 files changed, 127 insertions(+), 53 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index c42e6df..53c98a3 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -295,35 +295,73 @@ static void update_square_sample(GB_gameboy_t *gb, unsigned index) requires the PCM12 register. The behavior implemented here was verified on *my* CGB, which might behave differently from other CGB revisions, as well as from the DMG, MGB or SGB/2 */ -static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value) +static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value, uint8_t *countdown, GB_envelope_clock_t *lock) { - if (value & 8) { - (*volume)++; + if (lock->clock) { + *countdown = value & 7; } - - if (((value ^ old_value) & 8)) { - *volume = 0x10 - *volume; + bool should_tick = (value & 7) && !(old_value & 7) && !lock->locked; + bool should_invert = (value & 8) ^ (old_value & 8); + + if ((value & 0xF) == 8 && (old_value & 0xF) == 8 && !lock->locked) { + should_tick = true; } - - if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) { - (*volume)--; + + if (should_invert) { + // The weird way and over-the-top way clocks for this counter are connected cause + // some weird ways for it to invert + if (value & 8) { + if (!(old_value & 7) && !lock->locked) { + *volume ^= 0xF; + } + else { + *volume = 0xE - *volume; + *volume &= 0xF; + } + should_tick = false; // Somehow prevents ticking? + } + else { + *volume = 0x10 - *volume; + *volume &= 0xF; + } } - - if ((old_value & 7) && (value & 8)) { - (*volume)--; + if (should_tick) { + if (value & 8) { + (*volume)++; + } + else { + (*volume)--; + } + *volume &= 0xF; + } + else if (!(value & 7) && lock->clock) { + // *lock->locked = false; // Excepted from the schematics, but doesn't actually happen on any model? + if (!should_invert) { + if (*volume == 0xF && (value & 8)) { + lock->locked = true; + } + else if (*volume == 0 && !(value & 8)) { + lock->locked = true; + } + } + else if (*volume == 1 && !(value & 8)) { + lock->locked = true; + } + else if (*volume == 0xE && (value & 8)) { + lock->locked = true; + } + lock->clock = false; } - - (*volume) &= 0xF; } -static void nrx2_glitch(GB_gameboy_t *gb, uint8_t *volume, uint8_t value, uint8_t old_value) +static void nrx2_glitch(GB_gameboy_t *gb, uint8_t *volume, uint8_t value, uint8_t old_value, uint8_t *countdown, GB_envelope_clock_t *lock) { if (gb->model <= GB_MODEL_CGB_C) { - _nrx2_glitch(volume, 0xFF, old_value); - _nrx2_glitch(volume, value, 0xFF); + _nrx2_glitch(volume, 0xFF, old_value, countdown, lock); + _nrx2_glitch(volume, value, 0xFF, countdown, lock); } else { - _nrx2_glitch(volume, value, old_value); + _nrx2_glitch(volume, value, old_value, countdown, lock); } } @@ -331,7 +369,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) { uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - if (gb->apu.is_square_envelope_locked[index]) return; + if (gb->apu.square_envelope_clock[index].locked) return; if (!(nrx2 & 7)) return; if (gb->cgb_double_speed) { if (index == GB_SQUARE_1) { @@ -347,7 +385,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) gb->apu.square_channels[index].current_volume++; } else { - gb->apu.is_square_envelope_locked[index] = true; + gb->apu.square_envelope_clock[index].locked = true; } } else { @@ -355,7 +393,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) gb->apu.square_channels[index].current_volume--; } else { - gb->apu.is_square_envelope_locked[index] = true; + gb->apu.square_envelope_clock[index].locked = true; } } @@ -368,7 +406,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb) { uint8_t nr42 = gb->io_registers[GB_IO_NR42]; - if (gb->apu.is_noise_envelope_locked) return; + if (gb->apu.noise_envelope_clock.locked) return; if (!(nr42 & 7)) return; if (gb->cgb_double_speed) { @@ -380,7 +418,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb) gb->apu.noise_channel.current_volume++; } else { - gb->apu.is_noise_envelope_locked = true; + gb->apu.noise_envelope_clock.locked = true; } } else { @@ -388,7 +426,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb) gb->apu.noise_channel.current_volume--; } else { - gb->apu.is_noise_envelope_locked = true; + gb->apu.noise_envelope_clock.locked = true; } } @@ -438,30 +476,31 @@ void GB_apu_div_event(GB_gameboy_t *gb) gb->apu.div_divider++; } - if ((gb->apu.div_divider & 1) == 0) { - unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { - uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) { - tick_square_envelope(gb, i); - gb->apu.square_channels[i].volume_countdown = nrx2 & 7; - } - } - - if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0) { - tick_noise_envelope(gb); - gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; - } - } - if ((gb->apu.div_divider & 7) == 7) { unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { - gb->apu.square_channels[i].volume_countdown--; - gb->apu.square_channels[i].volume_countdown &= 7; + if (!gb->apu.square_envelope_clock[i].clock) { + gb->apu.square_channels[i].volume_countdown--; + gb->apu.square_channels[i].volume_countdown &= 7; + } + } + if (!gb->apu.noise_envelope_clock.clock) { + gb->apu.noise_channel.volume_countdown--; + gb->apu.noise_channel.volume_countdown &= 7; } - gb->apu.noise_channel.volume_countdown--; - gb->apu.noise_channel.volume_countdown &= 7; } + unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { + if (gb->apu.square_envelope_clock[i].clock) { + tick_square_envelope(gb, i); + gb->apu.square_envelope_clock[i].clock = false; + } + } + + if (gb->apu.noise_envelope_clock.clock) { + tick_noise_envelope(gb); + gb->apu.noise_envelope_clock.clock = false; + } + if ((gb->apu.div_divider & 1) == 1) { unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { if (gb->apu.square_channels[i].length_enabled) { @@ -500,6 +539,20 @@ void GB_apu_div_event(GB_gameboy_t *gb) } } +void GB_apu_div_secondary_event(GB_gameboy_t *gb) +{ + unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { + uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; + if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) { + gb->apu.square_envelope_clock[i].clock = gb->apu.square_channels[i].volume_countdown = nrx2 & 7; + } + } + + if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0) { + gb->apu.noise_envelope_clock.clock = gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; + } +} + static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset) { unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000; @@ -950,7 +1003,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) update_sample(gb, index, 0, 0); } else if (gb->apu.is_active[index]) { - nrx2_glitch(gb, &gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]); + nrx2_glitch(gb, &gb->apu.square_channels[index].current_volume, + value, gb->io_registers[reg], &gb->apu.square_channels[index].volume_countdown, + &gb->apu.square_envelope_clock[index]); update_square_sample(gb, index); } @@ -990,7 +1045,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (value & 0x80) { /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by turning the APU off. */ - gb->apu.is_square_envelope_locked[index] = false; + gb->apu.square_envelope_clock[index].locked = false; + gb->apu.square_envelope_clock[index].clock = 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_C && gb->apu.lf_div) { @@ -1019,7 +1075,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } } gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4; - /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously started sound). The playback itself is not instant which is why we don't update the sample for other cases. */ @@ -1196,7 +1251,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) update_sample(gb, GB_NOISE, 0, 0); } else if (gb->apu.is_active[GB_NOISE]) { - nrx2_glitch(gb, &gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]); + nrx2_glitch(gb, &gb->apu.noise_channel.current_volume, + value, gb->io_registers[reg], &gb->apu.noise_channel.volume_countdown, + &gb->apu.noise_envelope_clock); update_sample(gb, GB_NOISE, gb->apu.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, @@ -1241,7 +1298,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR44: { if (value & 0x80) { - gb->apu.is_noise_envelope_locked = false; + gb->apu.noise_envelope_clock.locked = false; + gb->apu.noise_envelope_clock.clock = false; if (!GB_is_cgb(gb) && (gb->apu.noise_channel.alignment & 3) != 0) { gb->apu.channel_4_dmg_delayed_start = 6; } diff --git a/Core/apu.h b/Core/apu.h index 770f15b..e44b0b6 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -46,6 +46,13 @@ enum GB_CHANNELS { GB_N_CHANNELS }; +typedef struct +{ + bool locked:1; + bool clock:1; // Represents FOSY on channel 4 + unsigned padding:6; +} GB_envelope_clock_t; + typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample); typedef struct @@ -127,8 +134,8 @@ typedef struct uint8_t channel_4_dmg_delayed_start; uint16_t channel1_completed_addend; - bool is_square_envelope_locked[2]; - bool is_noise_envelope_locked; + GB_envelope_clock_t square_envelope_clock[2]; + GB_envelope_clock_t noise_envelope_clock; } GB_apu_t; typedef enum { @@ -173,6 +180,7 @@ bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); void GB_apu_div_event(GB_gameboy_t *gb); +void GB_apu_div_secondary_event(GB_gameboy_t *gb); void GB_apu_init(GB_gameboy_t *gb); void GB_apu_run(GB_gameboy_t *gb); void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); diff --git a/Core/timing.c b/Core/timing.c index 7009d7b..2522982 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -137,20 +137,28 @@ static void increase_tima(GB_gameboy_t *gb) } } -static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) +static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) { /* TIMA increases when a specific high-bit becomes a low-bit. */ value &= INTERNAL_DIV_CYCLES - 1; - uint32_t triggers = gb->div_counter & ~value; + uint16_t triggers = gb->div_counter & ~value; if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); } /* TODO: Can switching to double speed mode trigger an event? */ - if (triggers & (gb->cgb_double_speed? 0x2000 : 0x1000)) { + uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000; + if (triggers & apu_bit) { GB_apu_run(gb); GB_apu_div_event(gb); } + else { + uint16_t secondary_triggers = ~gb->div_counter & value; + if (secondary_triggers & apu_bit) { + GB_apu_run(gb); + GB_apu_div_secondary_event(gb); + } + } gb->div_counter = value; } From fa5420136e9c68680925f36e6c08978862647e1d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Feb 2021 15:43:52 +0200 Subject: [PATCH 098/365] I hate the audio thread --- Cocoa/Document.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index d8b48e9..fa589a6 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1105,7 +1105,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (char *) getDebuggerInput { + [audioLock lock]; [audioLock signal]; + [audioLock unlock]; [self updateSideView]; [self log:">"]; in_sync_input = true; From 6ec4583aa0e018459b9bd18a1f95c1d7e6875b85 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Feb 2021 15:52:48 +0200 Subject: [PATCH 099/365] Tell GCC to calm down --- Core/apu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 53c98a3..5c12443 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -544,12 +544,12 @@ void GB_apu_div_secondary_event(GB_gameboy_t *gb) unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) { - gb->apu.square_envelope_clock[i].clock = gb->apu.square_channels[i].volume_countdown = nrx2 & 7; + gb->apu.square_envelope_clock[i].clock = (gb->apu.square_channels[i].volume_countdown = nrx2 & 7); } } if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0) { - gb->apu.noise_envelope_clock.clock = gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; + gb->apu.noise_envelope_clock.clock = (gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7); } } From 9fa564f97c7d443987738142585d288f58a26315 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Feb 2021 17:12:01 +0200 Subject: [PATCH 100/365] Fix #336 --- Cocoa/AppDelegate.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 282105e..aee2111 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -120,8 +120,15 @@ - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender { + /* Bring an existing panel to the foreground */ + for (NSWindow *window in [[NSApplication sharedApplication] windows]) { + if ([window isKindOfClass:[NSOpenPanel class]]) { + [(NSOpenPanel *)window makeKeyAndOrderFront:nil]; + return true; + } + } [[NSDocumentController sharedDocumentController] openDocument:self]; - return YES; + return true; } - (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification From 807712b9c266e75a47e7ffb3f907758a9971b244 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Feb 2021 18:04:52 +0200 Subject: [PATCH 101/365] Allow creating sav files from the tester (Fixes #311) --- Tester/main.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Tester/main.c b/Tester/main.c index 6faab3b..423a246 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -22,6 +22,7 @@ static bool running = false; static char *filename; static char *bmp_filename; static char *log_filename; +static char *sav_filename; static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, @@ -160,6 +161,9 @@ static void vblank(GB_gameboy_t *gb) if (is_screen_blank) { GB_log(gb, "Game probably stuck with blank screen. \n"); } + if (sav_filename) { + GB_save_battery(gb, sav_filename); + } running = false; } } @@ -259,7 +263,7 @@ int main(int argc, char **argv) fprintf(stderr, "SameBoy Tester v" xstr(VERSION) "\n"); if (argc == 1) { - fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds] [--boot path to boot ROM]" + fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds] [--sav] [--boot path to boot ROM]" #ifndef _WIN32 " [--jobs number of tests to run simultaneously]" #endif @@ -273,6 +277,7 @@ int main(int argc, char **argv) #endif bool dmg = false; + bool sav = false; const char *boot_rom_path = NULL; GB_random_set_enabled(false); @@ -308,6 +313,12 @@ int main(int argc, char **argv) continue; } + if (strcmp(argv[i], "--sav") == 0) { + fprintf(stderr, "Saving a battery save\n"); + sav = true; + continue; + } + #ifndef _WIN32 if (strcmp(argv[i], "--jobs") == 0 && i != argc - 1) { max_forks = atoi(argv[++i]); @@ -340,6 +351,12 @@ int main(int argc, char **argv) replace_extension(filename, path_length, log_path, ".log"); log_filename = &log_path[0]; + char sav_path[path_length + 5]; + if (sav) { + replace_extension(filename, path_length, sav_path, ".sav"); + sav_filename = &sav_path[0]; + } + fprintf(stderr, "Testing ROM %s\n", filename); if (dmg) { From e08df2a089c9fe3984ab52c8e86df9a16a591636 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Feb 2021 22:12:14 +0200 Subject: [PATCH 102/365] Add accurate RTC emulation mode --- Cocoa/Document.m | 14 ++ Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 21 +++ Cocoa/Preferences.xib | 357 +++++++++++++++++++----------------- Core/gb.c | 37 ++-- Core/gb.h | 11 ++ Core/timing.c | 114 +++++++----- Core/timing.h | 1 - 8 files changed, 330 insertions(+), 226 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index fa589a6..a9b3101 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -295,6 +295,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) GB_set_camera_update_request_callback(&gb, cameraRequestUpdate); GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]); GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]); + GB_set_rtc_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); GB_apu_set_sample_callback(&gb, audioCallback); GB_set_rumble_callback(&gb, rumbleCallback); GB_set_infrared_callback(&gb, infraredStateChanged); @@ -726,6 +727,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) name:@"GBRewindLengthChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateRTCMode) + name:@"GBRTCModeChanged" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dmgModelChanged) name:@"GBDMGModelChanged" @@ -1878,6 +1885,13 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) }]; } +- (void) updateRTCMode +{ + if (GB_is_inited(&gb)) { + GB_set_rtc_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]); + } +} + - (void)dmgModelChanged { modelsChanging = true; diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 85ad6b3..b25e476 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -12,6 +12,7 @@ @property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton; @property (nonatomic, strong) IBOutlet NSPopUpButton *displayBorderPopupButton; @property (nonatomic, strong) IBOutlet NSPopUpButton *rewindPopupButton; +@property (nonatomic, strong) IBOutlet NSPopUpButton *rtcPopupButton; @property (nonatomic, strong) IBOutlet NSButton *configureJoypadButton; @property (nonatomic, strong) IBOutlet NSButton *skipButton; @property (nonatomic, strong) IBOutlet NSMenuItem *bootROMsFolderItem; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index bd3a4a8..54d190f 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -19,6 +19,7 @@ NSPopUpButton *_colorPalettePopupButton; NSPopUpButton *_displayBorderPopupButton; NSPopUpButton *_rewindPopupButton; + NSPopUpButton *_rtcPopupButton; NSButton *_aspectRatioCheckbox; NSButton *_analogControlsCheckbox; NSEventModifierFlags previousModifiers; @@ -181,6 +182,18 @@ return _rewindPopupButton; } +- (NSPopUpButton *)rtcPopupButton +{ + return _rtcPopupButton; +} + +- (void)setRtcPopupButton:(NSPopUpButton *)rtcPopupButton +{ + _rtcPopupButton = rtcPopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]; + [_rtcPopupButton selectItemAtIndex:mode]; +} + - (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton { _highpassFilterPopupButton = highpassFilterPopupButton; @@ -360,6 +373,14 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil]; } +- (IBAction)rtcModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBRTCMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBRTCModeChanged" object:nil]; + +} + - (IBAction) configureJoypad:(id)sender { [self.configureJoypadButton setEnabled:NO]; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 248cfce..58b5ddb 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -77,6 +77,7 @@ + @@ -85,11 +86,11 @@ - + - + @@ -98,7 +99,7 @@ - + @@ -135,7 +136,7 @@ - + @@ -144,7 +145,7 @@ - + @@ -166,7 +167,7 @@ - + @@ -175,7 +176,7 @@ - + @@ -195,7 +196,7 @@ - + @@ -204,7 +205,7 @@ - + @@ -225,7 +226,7 @@ - + @@ -234,7 +235,7 @@ - + @@ -253,6 +254,23 @@ + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -321,8 +298,17 @@ + + + + + + + + + - + @@ -330,8 +316,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -354,104 +448,39 @@ - - + + - - - - - - - - - - + - + - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + @@ -471,7 +500,7 @@ - + @@ -489,7 +518,7 @@ - + @@ -497,14 +526,14 @@ - + - + @@ -522,14 +551,14 @@ - + - - + + - + @@ -548,7 +577,7 @@ - + @@ -589,7 +618,7 @@ - + @@ -609,7 +638,7 @@ - + @@ -618,7 +647,7 @@ - + @@ -637,7 +666,7 @@ - + @@ -665,19 +694,8 @@ - + diff --git a/Core/gb.c b/Core/gb.c index 985ed4d..3a0864d 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -664,9 +664,9 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; #ifdef GB_BIG_ENDIAN - rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); + rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL)); #else - rtc_save.vba64.last_rtc_second = gb->last_rtc_second; + rtc_save.vba64.last_rtc_second = time(NULL); #endif memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64)); } @@ -728,9 +728,9 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; #ifdef GB_BIG_ENDIAN - rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); + rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL)); #else - rtc_save.vba64.last_rtc_second = gb->last_rtc_second; + rtc_save.vba64.last_rtc_second = time(NULL); #endif if (fwrite(&rtc_save.vba64, 1, sizeof(rtc_save.vba64), f) != sizeof(rtc_save.vba64)) { fclose(f); @@ -976,7 +976,6 @@ uint8_t GB_run(GB_gameboy_t *gb) gb->cycles_since_run = 0; GB_cpu_run(gb); if (gb->vblank_just_occured) { - GB_rtc_run(gb); GB_debugger_handle_async_commands(gb); GB_rewind_push(gb); } @@ -1530,15 +1529,20 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) uint32_t GB_get_clock_rate(GB_gameboy_t *gb) { - if (gb->model & GB_MODEL_PAL_BIT) { - return SGB_PAL_FREQUENCY * gb->clock_multiplier; - } - if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { - return SGB_NTSC_FREQUENCY * gb->clock_multiplier; - } - return CPU_FREQUENCY * gb->clock_multiplier; + return GB_get_unmultiplied_clock_rate(gb) * gb->clock_multiplier; } + +uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb) +{ + if (gb->model & GB_MODEL_PAL_BIT) { + return SGB_PAL_FREQUENCY; + } + if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { + return SGB_NTSC_FREQUENCY; + } + return CPU_FREQUENCY; +} void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode) { if (gb->border_mode > GB_BORDER_ALWAYS) return; @@ -1623,3 +1627,12 @@ unsigned GB_time_to_alarm(GB_gameboy_t *gb) if (current_time > alarm_time) return 0; return alarm_time - current_time; } + +void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode) +{ + if (gb->rtc_mode != mode) { + gb->rtc_mode = mode; + gb->rtc_cycles = 0; + gb->last_rtc_second = time(NULL); + } +} diff --git a/Core/gb.h b/Core/gb.h index 2c0f11a..40a20a2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -254,6 +254,11 @@ typedef enum { GB_BOOT_ROM_AGB, } GB_boot_rom_t; +typedef enum { + GB_RTC_MODE_SYNC_TO_HOST, + GB_RTC_MODE_ACCURATE, +} GB_rtc_mode_t; + #ifdef GB_INTERNAL #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 @@ -481,6 +486,7 @@ struct GB_gameboy_internal_s { GB_rtc_time_t rtc_real, rtc_latched; uint64_t last_rtc_second; bool rtc_latch; + uint32_t rtc_cycles; ); /* Video Display */ @@ -588,6 +594,7 @@ struct GB_gameboy_internal_s { /* Timing */ uint64_t last_sync; uint64_t cycles_since_last_sync; // In 8MHz units + GB_rtc_mode_t rtc_mode; /* Audio */ GB_apu_output_t apu_output; @@ -798,6 +805,9 @@ void GB_disconnect_serial(GB_gameboy_t *gb); /* For cartridges with an alarm clock */ unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm +/* RTC emulation mode */ +void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode); + /* For integration with SFC/SNES emulators */ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback); @@ -805,6 +815,7 @@ void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callb void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback); uint32_t GB_get_clock_rate(GB_gameboy_t *gb); +uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb); void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); unsigned GB_get_screen_width(GB_gameboy_t *gb); diff --git a/Core/timing.c b/Core/timing.c index 2522982..a0be0e3 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -240,6 +240,70 @@ static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) } +static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc) return; + gb->rtc_cycles += cycles; + time_t current_time = 0; + + switch (gb->rtc_mode) { + case GB_RTC_MODE_SYNC_TO_HOST: + // Sync in a 1/32s resolution + if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) / 16) return; + gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) / 16; + current_time = time(NULL); + break; + case GB_RTC_MODE_ACCURATE: + if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) * 2) return; + gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) * 2; + current_time = gb->last_rtc_second + 1; + break; + } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + while (gb->last_rtc_second / 60 < current_time / 60) { + gb->last_rtc_second += 60; + gb->huc3_minutes++; + if (gb->huc3_minutes == 60 * 24) { + gb->huc3_days++; + gb->huc3_minutes = 0; + } + } + return; + } + + if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ + while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { + gb->last_rtc_second += 60 * 60 * 24; + if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + gb->rtc_real.high ^= 1; + } + } + + while (gb->last_rtc_second < current_time) { + gb->last_rtc_second++; + if (++gb->rtc_real.seconds != 60) continue; + gb->rtc_real.seconds = 0; + + if (++gb->rtc_real.minutes != 60) continue; + gb->rtc_real.minutes = 0; + + if (++gb->rtc_real.hours != 24) continue; + gb->rtc_real.hours = 0; + + if (++gb->rtc_real.days != 0) continue; + + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + } + } +} + + 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 @@ -280,6 +344,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) GB_apu_run(gb); GB_display_run(gb, cycles); GB_ir_run(gb, cycles); + GB_rtc_run(gb, cycles); } /* @@ -303,52 +368,3 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) } } } - -void GB_rtc_run(GB_gameboy_t *gb) -{ - if (gb->cartridge_type->mbc_type == GB_HUC3) { - time_t current_time = time(NULL); - while (gb->last_rtc_second / 60 < current_time / 60) { - gb->last_rtc_second += 60; - gb->huc3_minutes++; - if (gb->huc3_minutes == 60 * 24) { - gb->huc3_days++; - gb->huc3_minutes = 0; - } - } - return; - } - - if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ - time_t current_time = time(NULL); - - while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { - gb->last_rtc_second += 60 * 60 * 24; - if (++gb->rtc_real.days == 0) { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - gb->rtc_real.high ^= 1; - } - } - - while (gb->last_rtc_second < current_time) { - gb->last_rtc_second++; - if (++gb->rtc_real.seconds == 60) { - gb->rtc_real.seconds = 0; - if (++gb->rtc_real.minutes == 60) { - gb->rtc_real.minutes = 0; - if (++gb->rtc_real.hours == 24) { - gb->rtc_real.hours = 0; - if (++gb->rtc_real.days == 0) { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - gb->rtc_real.high ^= 1; - } - } - } - } - } - } -} diff --git a/Core/timing.h b/Core/timing.h index d4fa07f..07e0473 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -4,7 +4,6 @@ #ifdef GB_INTERNAL void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -void GB_rtc_run(GB_gameboy_t *gb); void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ void GB_timing_sync(GB_gameboy_t *gb); From a13469c4e2c97873be4725e18074f09ea3ab048f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Feb 2021 22:42:02 +0200 Subject: [PATCH 103/365] Fix PAL SGB in the Cocoa port --- Cocoa/Document.m | 9 +++++++-- Cocoa/Preferences.xib | 2 +- Core/gb.h | 2 -- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index a9b3101..9e52214 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -237,8 +237,13 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) case MODEL_CGB: return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]; - case MODEL_SGB: - return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]; + case MODEL_SGB: { + GB_model_t model = (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]; + if (model == (GB_MODEL_SGB | GB_MODEL_PAL_BIT_OLD)) { + model = GB_MODEL_SGB_PAL; + } + return model; + } case MODEL_AGB: return GB_MODEL_AGB; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 58b5ddb..ce3cb7c 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -396,7 +396,7 @@ - + diff --git a/Core/gb.h b/Core/gb.h index 40a20a2..7210e7a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -34,10 +34,8 @@ #define GB_MODEL_PAL_BIT 0x40 #define GB_MODEL_NO_SFC_BIT 0x80 -#ifdef GB_INTERNAL #define GB_MODEL_PAL_BIT_OLD 0x1000 #define GB_MODEL_NO_SFC_BIT_OLD 0x2000 -#endif #ifdef GB_INTERNAL #if __clang__ From 71c6fa45e0ddb386ce6848fb05c5a972d449c9f2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Feb 2021 00:40:18 +0200 Subject: [PATCH 104/365] Accurate RTC emulation --- Core/memory.c | 8 +++++++- Core/timing.c | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 58efc83..f307b7e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -192,7 +192,10 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) { /* RTC read */ - gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ + gb->rtc_latched.seconds &= 0x3F; + gb->rtc_latched.minutes &= 0x3F; + gb->rtc_latched.hours &= 0x1F; + gb->rtc_latched.high &= 0xC1; return gb->rtc_latched.data[gb->mbc_ram_bank]; } @@ -693,6 +696,9 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (gb->cartridge_type->has_rtc && gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) { + if (gb->mbc_ram_bank == 0) { + gb->rtc_cycles = 0; + } gb->rtc_latched.data[gb->mbc_ram_bank] = gb->rtc_real.data[gb->mbc_ram_bank] = value; return; } diff --git a/Core/timing.c b/Core/timing.c index a0be0e3..655629c 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -254,6 +254,10 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) current_time = time(NULL); break; case GB_RTC_MODE_ACCURATE: + if (gb->cartridge_type->mbc_type != GB_HUC3 && (gb->rtc_real.high & 0x40)) { + gb->rtc_cycles -= cycles; + return; + } if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) * 2) return; gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) * 2; current_time = gb->last_rtc_second + 1; @@ -299,6 +303,8 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ gb->rtc_real.high |= 0x80; /* Overflow bit */ } + + gb->rtc_real.high ^= 1; } } } From 72cb391612863a43cb3c876b1d210534a54a4020 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Feb 2021 00:52:18 +0200 Subject: [PATCH 105/365] Slightly improve MBC3 accuracy --- Core/memory.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index f307b7e..8aacf41 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -190,13 +190,16 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && - gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) { + gb->mbc3_rtc_mapped) { /* RTC read */ - gb->rtc_latched.seconds &= 0x3F; - gb->rtc_latched.minutes &= 0x3F; - gb->rtc_latched.hours &= 0x1F; - gb->rtc_latched.high &= 0xC1; - return gb->rtc_latched.data[gb->mbc_ram_bank]; + if (gb->mbc_ram_bank <= 4) { + gb->rtc_latched.seconds &= 0x3F; + gb->rtc_latched.minutes &= 0x3F; + gb->rtc_latched.hours &= 0x1F; + gb->rtc_latched.high &= 0xC1; + return gb->rtc_latched.data[gb->mbc_ram_bank]; + } + return 0xFF; } if (gb->camera_registers_mapped) { @@ -213,6 +216,9 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) uint8_t effective_bank = gb->mbc_ram_bank; if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) { + if (gb->cartridge_type->has_rtc) { + if (effective_bank > 3) return 0xFF; + } effective_bank &= 0x3; } uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)]; @@ -695,11 +701,13 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - if (gb->cartridge_type->has_rtc && gb->mbc3_rtc_mapped && gb->mbc_ram_bank <= 4) { - if (gb->mbc_ram_bank == 0) { - gb->rtc_cycles = 0; + if (gb->cartridge_type->has_rtc && gb->mbc3_rtc_mapped) { + if (gb->mbc_ram_bank <= 4) { + if (gb->mbc_ram_bank == 0) { + gb->rtc_cycles = 0; + } + gb->rtc_latched.data[gb->mbc_ram_bank] = gb->rtc_real.data[gb->mbc_ram_bank] = value; } - gb->rtc_latched.data[gb->mbc_ram_bank] = gb->rtc_real.data[gb->mbc_ram_bank] = value; return; } @@ -709,6 +717,9 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) uint8_t effective_bank = gb->mbc_ram_bank; if (gb->cartridge_type->mbc_type == GB_MBC3 && !gb->is_mbc30) { + if (gb->cartridge_type->has_rtc) { + if (effective_bank > 3) return; + } effective_bank &= 0x3; } From bae91cdb1d5320d42f3b637c22d026e3cff64248 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Feb 2021 01:04:24 +0200 Subject: [PATCH 106/365] Add RTC option to the SDL port, fix a bug where rewind setting didn't update --- SDL/gui.c | 15 +++++++++++++++ SDL/gui.h | 1 + SDL/main.c | 3 +++ 3 files changed, 19 insertions(+) diff --git a/SDL/gui.c b/SDL/gui.c index 92d9479..580a3f6 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -482,11 +482,26 @@ static void toggle_bootrom(unsigned index) } } +static void toggle_rtc_mode(unsigned index) +{ + configuration.rtc_mode = !configuration.rtc_mode; +} + +const char *current_rtc_mode_string(unsigned index) +{ + switch (configuration.rtc_mode) { + case GB_RTC_MODE_SYNC_TO_HOST: return "Sync to System Clock"; + case GB_RTC_MODE_ACCURATE: return "Accurate"; + } + return ""; +} + static const struct menu_item emulation_menu[] = { {"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards}, {"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards}, {"Boot ROMs Folder:", toggle_bootrom, current_bootrom_string, toggle_bootrom}, {"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards}, + {"Real Time Clock:", toggle_rtc_mode, current_rtc_mode_string, toggle_rtc_mode}, {"Back", return_to_root_menu}, {NULL,} }; diff --git a/SDL/gui.h b/SDL/gui.h index 8d69ec3..5db7aff 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -116,6 +116,7 @@ typedef struct { uint8_t color_temperature; char bootrom_path[4096]; uint8_t interference_volume; + GB_rtc_mode_t rtc_mode; } configuration_t; extern configuration_t configuration; diff --git a/SDL/main.c b/SDL/main.c index 4f10ae9..d10590d 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -125,6 +125,8 @@ static void open_menu(void) GB_set_border_mode(&gb, configuration.border_mode); update_palette(); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); + GB_set_rewind_length(&gb, configuration.rewind_length); + GB_set_rtc_mode(&gb, configuration.rtc_mode); if (previous_width != GB_get_screen_width(&gb)) { screen_size_changed(); } @@ -513,6 +515,7 @@ restart: } GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_rewind_length(&gb, configuration.rewind_length); + GB_set_rtc_mode(&gb, configuration.rtc_mode); GB_set_update_input_hint_callback(&gb, handle_events); GB_apu_set_sample_callback(&gb, gb_audio_callback); } From 34b0404ffa4adc5fbac348c9dc3db433a16e000e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Feb 2021 01:07:46 +0200 Subject: [PATCH 107/365] Add RTC setting to libretro --- libretro/libretro.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 0fb8dc5..6b531b6 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -237,6 +237,7 @@ static const struct retro_variable vars_single[] = { { "sameboy_model", "Emulated model (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, { "sameboy_border", "Display border; Super Game Boy only|always|never" }, { "sameboy_rumble", "Enable rumble; rumble-enabled games|all games|never" }, + { "sameboy_rtc", "Real Time Clock emulation; sync to system clock|accurate" }, { NULL } }; @@ -594,6 +595,17 @@ static void check_variables() GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); } } + + var.key = "sameboy_rtc"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "sync to system clock") == 0) { + GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_SYNC_TO_HOST); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_ACCURATE); + } + } var.key = "sameboy_high_pass_filter_mode"; var.value = NULL; @@ -654,6 +666,8 @@ static void check_variables() else { GB_set_border_mode(&gameboy[0], GB_BORDER_NEVER); GB_set_border_mode(&gameboy[1], GB_BORDER_NEVER); + GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_ACCURATE); + GB_set_rtc_mode(&gameboy[1], GB_RTC_MODE_ACCURATE); var.key = "sameboy_color_correction_mode_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { From cb721dae5d442fc592dea73b907228e515afc1fb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Feb 2021 01:09:40 +0200 Subject: [PATCH 108/365] Make the automation use the accurate RTC mode --- Tester/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Tester/main.c b/Tester/main.c index 423a246..d1e3370 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -380,6 +380,7 @@ int main(int argc, char **argv) GB_set_log_callback(&gb, log_callback); GB_set_async_input_callback(&gb, async_input_callback); GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_EMULATE_HARDWARE); + GB_set_rtc_mode(&gb, GB_RTC_MODE_ACCURATE); if (GB_load_rom(&gb, filename)) { perror("Failed to load ROM"); From ce44773caa26d6364c60895ae7bd3c770450d63d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Feb 2021 16:40:35 +0200 Subject: [PATCH 109/365] Make the printer not deadlock after a sudden termination --- Core/printer.c | 10 +++++++--- Core/printer.h | 3 +-- Core/timing.c | 3 +++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Core/printer.c b/Core/printer.c index f04e54d..c8514b4 100644 --- a/Core/printer.c +++ b/Core/printer.c @@ -11,7 +11,6 @@ static void handle_command(GB_gameboy_t *gb) { - switch (gb->printer.command_id) { case GB_PRINTER_INIT_COMMAND: gb->printer.status = 0; @@ -71,7 +70,7 @@ static void handle_command(GB_gameboy_t *gb) } -static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received) +static void byte_recieve_completed(GB_gameboy_t *gb, uint8_t byte_received) { gb->printer.byte_to_send = 0; switch (gb->printer.command_state) { @@ -189,11 +188,16 @@ static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received) static void serial_start(GB_gameboy_t *gb, bool bit_received) { + if (gb->printer.idle_time > GB_get_unmultiplied_clock_rate(gb)) { + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + gb->printer.bits_received = 0; + } + gb->printer.idle_time = 0; gb->printer.byte_being_received <<= 1; gb->printer.byte_being_received |= bit_received; gb->printer.bits_received++; if (gb->printer.bits_received == 8) { - byte_reieve_completed(gb, gb->printer.byte_being_received); + byte_recieve_completed(gb, gb->printer.byte_being_received); gb->printer.bits_received = 0; gb->printer.byte_being_received = 0; } diff --git a/Core/printer.h b/Core/printer.h index b29650f..f5c9b27 100644 --- a/Core/printer.h +++ b/Core/printer.h @@ -48,8 +48,7 @@ typedef struct uint8_t image[160 * 200]; uint16_t image_offset; - /* TODO: Delete me. */ - uint64_t padding; + uint64_t idle_time; uint8_t compression_run_lenth; bool compression_run_is_compressed; diff --git a/Core/timing.c b/Core/timing.c index 655629c..dedc5a0 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -198,6 +198,9 @@ main: static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) { + if (gb->printer.command_state || gb->printer.bits_received) { + gb->printer.idle_time += cycles; + } if (gb->serial_length == 0) { gb->serial_cycles += cycles; return; From 0a983b788e7d0f586ad23b2d382e2911c0498c84 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Feb 2021 04:13:31 +0200 Subject: [PATCH 110/365] Update icon --- Cocoa/AppIcon.icns | Bin 327033 -> 312085 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Cocoa/AppIcon.icns b/Cocoa/AppIcon.icns index 0fe800598b875028a395987c4c141f45b24b449c..92ad4c6516aaac4a4bc16850b06370f1d59f5005 100644 GIT binary patch literal 312085 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%0A2GhP>M=0kYX;FKZ)Gog=80 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 z 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%0DoLyMm_sNbgZ03@Q{oIeUMRK~rRm~I; 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

  • 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

    SameBoy

    MIT License

    -

    Copyright © 2015-2020 Lior Halphon

    +

    Copyright © 2015-2021 Lior Halphon

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE b/LICENSE index 17619e9..3303e0d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015-2020 Lior Halphon +Copyright (c) 2015-2021 Lior Halphon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/QuickLook/Info.plist b/QuickLook/Info.plist index b01aae1..9b369ec 100644 --- a/QuickLook/Info.plist +++ b/QuickLook/Info.plist @@ -48,7 +48,7 @@ CFPlugInUnloadFunction NSHumanReadableCopyright - Copyright © 2015-2020 Lior Halphon + Copyright © 2015-2021 Lior Halphon QLNeedsToBeRunInMainThread QLPreviewHeight diff --git a/version.mk b/version.mk index 35ae0ad..f846fc8 100644 --- a/version.mk +++ b/version.mk @@ -1 +1 @@ -VERSION := 0.13.6 +VERSION := 0.14 From 1dae345b247e5f9a6be70c69f6b402cbd30abdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sat, 27 Feb 2021 18:29:59 +0100 Subject: [PATCH 117/365] Use memmove for overlapping copy --- Core/save_state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/save_state.c b/Core/save_state.c index 9ef6ae3..827cf57 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -150,7 +150,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t { if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) { /* This is a save state with a bad printer struct from a 32-bit OS */ - memcpy(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam); + memmove(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam); } if (save->ram_size == 0) { /* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially From 0a4cb8148f6bcecd20486a6563a43eb9c074500c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sat, 27 Feb 2021 18:33:22 +0100 Subject: [PATCH 118/365] Fix DESTDIR installation Installing into a fresh DESTDIR would error out due to non-existent bin directory --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8f98132..51403a6 100644 --- a/Makefile +++ b/Makefile @@ -438,7 +438,7 @@ ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop - mkdir -p $(DESTDIR)$(PREFIX)/share/sameboy/ + mkdir -p $(DESTDIR)$(PREFIX)/share/sameboy/ $(DESTDIR)$(PREFIX)/bin/ cp -rf $(BIN)/SDL/* $(DESTDIR)$(PREFIX)/share/sameboy/ mv $(DESTDIR)$(PREFIX)/share/sameboy/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) From e8bfc4050eb976968b5f8f41ffb6293363e9e839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sat, 27 Feb 2021 19:29:06 +0100 Subject: [PATCH 119/365] Fix off-by-one in symbol search Before this commit, printing an address that's after every symbol in a bank would not show it relative to the last symbol. --- Core/symbol_hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index 75a7837..a3718b8 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -40,7 +40,7 @@ const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr) { if (!map) return NULL; size_t index = GB_map_find_symbol_index(map, addr); - if (index < map->n_symbols && map->symbols[index].addr != addr) { + if (index >= map->n_symbols || map->symbols[index].addr != addr) { index--; } if (index < map->n_symbols) { From c9665d0449ca5302dde91442351f5722d56354dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20K=C4=85dzio=C5=82ka?= Date: Sat, 27 Feb 2021 19:33:31 +0100 Subject: [PATCH 120/365] value_to_string: use snprintf Currently, value_to_string and debugger_value_to_string use an error-prone calculation to avoid overflow. This was once adjusted already, and one of the codepaths is still vulnerable. Put this in a symfile: 01:5678 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa and execute `p 1:$5679`. On Linux, the canary terminates the process. --- Core/debugger.c | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index f6b4e4f..1b1ae29 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -131,30 +131,25 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer symbol = NULL; } - /* Avoid overflow */ - if (symbol && strlen(symbol->name) >= 240) { - symbol = NULL; - } - if (!symbol) { - sprintf(output, "$%04x", value); + snprintf(output, sizeof output, "$%04x", value); } else if (symbol->addr == value) { if (prefer_name) { - sprintf(output, "%s ($%04x)", symbol->name, value); + snprintf(output, sizeof output, "%s ($%04x)", symbol->name, value); } else { - sprintf(output, "$%04x (%s)", value, symbol->name); + snprintf(output, sizeof output, "$%04x (%s)", value, symbol->name); } } else { if (prefer_name) { - sprintf(output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); + snprintf(output, sizeof output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); } else { - sprintf(output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); + snprintf(output, sizeof output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); } } return output; @@ -171,30 +166,25 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo symbol = NULL; } - /* Avoid overflow */ - if (symbol && strlen(symbol->name) >= 240) { - symbol = NULL; - } - if (!symbol) { - sprintf(output, "$%02x:$%04x", value.bank, value.value); + snprintf(output, sizeof output, "$%02x:$%04x", value.bank, value.value); } else if (symbol->addr == value.value) { if (prefer_name) { - sprintf(output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); + snprintf(output, sizeof output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); } else { - sprintf(output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); + snprintf(output, sizeof output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); } } else { if (prefer_name) { - sprintf(output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); + snprintf(output, sizeof output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); } else { - sprintf(output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); + snprintf(output, sizeof output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); } } return output; From 1fdb4f09b44731b24a21c66235d554bfd7d185e8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 03:40:58 +0200 Subject: [PATCH 121/365] Fix a sweep regression in DMG/SGB mode and CGB-C mode --- Core/apu.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 5c12443..45b5a67 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -454,7 +454,7 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb) /* Recalculation and overflow check only occurs after a delay */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { - gb->apu.square_sweep_calculate_countdown += 2; + // gb->apu.square_sweep_calculate_countdown += 2; } gb->apu.enable_zombie_calculate_stepping = false; gb->apu.unshifted_sweep = !(gb->io_registers[GB_IO_NR10] & 0x7); @@ -867,7 +867,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) break; #if 0 case GB_MODEL_CGB_D: - if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x40 : 0x80)) { // This is so weird + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8)? 0x40 : 0x80)) { // This is so weird effective_counter |= 0xFF; } if (effective_counter & 0x100) { @@ -888,7 +888,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) break; #endif case GB_MODEL_CGB_E: - if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8) ?0x40 : 0x80)) { // This is so weird + if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8)? 0x40 : 0x80)) { // This is so weird effective_counter |= 0xFF; } if (effective_counter & 0x1000) { @@ -1102,7 +1102,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ gb->apu.square_sweep_calculate_countdown = (gb->io_registers[GB_IO_NR10] & 0x7) * 2 + 5 - gb->apu.lf_div; if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { - gb->apu.square_sweep_calculate_countdown += 2; + /* TODO: I used to think this is correct, but it caused several regressions. + More research is needed to figure how calculation time is different + in models prior to CGB-D */ + // gb->apu.square_sweep_calculate_countdown += 2; } gb->apu.enable_zombie_calculate_stepping = false; gb->apu.unshifted_sweep = false; From d50514ede940ace782f2f3afbd7be1ae00bc99f3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 14:51:58 +0200 Subject: [PATCH 122/365] Fix #353 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 51403a6..211345b 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ ifneq ($(shell which xdg-open),) DESTDIR ?= PREFIX ?= /usr/local DATA_DIR ?= $(PREFIX)/share/sameboy/ -CAN_INSTALL := true +FREEDESKTOP ?= true endif default: $(DEFAULT) @@ -433,7 +433,7 @@ libretro: # Does not install mimetype icons because FreeDesktop is cursed abomination with no right to exist. # If you somehow find a reasonable way to make associate an icon with an extension in this dumpster # fire of a desktop environment, open an issue or a pull request -ifneq ($(CAN_INSTALL),) +ifneq ($(FREEDESKTOP),) ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) From 5cc2dcc864d937650f0b4ea311a25231b83e299d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 14:55:30 +0200 Subject: [PATCH 123/365] Fix #353 better --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 211345b..caf059b 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ else DEFAULT := sdl endif -ifneq ($(shell which xdg-open),) +ifneq ($(shell which xdg-open)$(FREEDESKTOP),) # Running on an FreeDesktop environment, configure for (optional) installation DESTDIR ?= PREFIX ?= /usr/local From d2eb8e0996894b3cccdf45011e667016be850fbb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 15:17:00 +0200 Subject: [PATCH 124/365] Addresses issues mentioned by #355 --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index caf059b..13747cb 100644 --- a/Makefile +++ b/Makefile @@ -438,6 +438,7 @@ ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop + -@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX)) mkdir -p $(DESTDIR)$(PREFIX)/share/sameboy/ $(DESTDIR)$(PREFIX)/bin/ cp -rf $(BIN)/SDL/* $(DESTDIR)$(PREFIX)/share/sameboy/ mv $(DESTDIR)$(PREFIX)/share/sameboy/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy @@ -446,8 +447,12 @@ ifeq ($(DESTDIR),) -xdg-desktop-menu install --novendor --mode system FreeDesktop/sameboy.desktop -xdg-icon-resource forceupdate --mode system -xdg-desktop-menu forceupdate --mode system +ifneq ($(SUDO_USER),) -su $(SUDO_USER) -c "xdg-desktop-menu forceupdate --mode system" endif +else + cp FreeDesktop/sameboy.desktop $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop +endif $(DESTDIR)$(PREFIX)/share/icons/hicolor/%/apps/sameboy.png: FreeDesktop/AppIcon/%.png -@$(MKDIR) -p $(dir $@) From 81bfea9ba246cf29a519f756da53a45497538e44 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 15:23:14 +0200 Subject: [PATCH 125/365] Coding style, ensuring string termination. --- Core/debugger.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 1b1ae29..0debf36 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -125,6 +125,7 @@ static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) { static __thread char output[256]; + output[sizeof(output) - 1] = 0; // Ensure termination const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value); if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) { @@ -132,24 +133,24 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer } if (!symbol) { - snprintf(output, sizeof output, "$%04x", value); + snprintf(output, sizeof(output) - 1, "$%04x", value); } else if (symbol->addr == value) { if (prefer_name) { - snprintf(output, sizeof output, "%s ($%04x)", symbol->name, value); + snprintf(output, sizeof(output) - 1, "%s ($%04x)", symbol->name, value); } else { - snprintf(output, sizeof output, "$%04x (%s)", value, symbol->name); + snprintf(output, sizeof(output) - 1, "$%04x (%s)", value, symbol->name); } } else { if (prefer_name) { - snprintf(output, sizeof output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); + snprintf(output, sizeof(output) - 1, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); } else { - snprintf(output, sizeof output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); + snprintf(output, sizeof(output) - 1, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); } } return output; @@ -160,6 +161,7 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); static __thread char output[256]; + output[sizeof(output) - 1] = 0; // Ensure termination const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value); if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { @@ -167,24 +169,24 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo } if (!symbol) { - snprintf(output, sizeof output, "$%02x:$%04x", value.bank, value.value); + snprintf(output, sizeof(output) - 1, "$%02x:$%04x", value.bank, value.value); } else if (symbol->addr == value.value) { if (prefer_name) { - snprintf(output, sizeof output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); + snprintf(output, sizeof(output) - 1, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); } else { - snprintf(output, sizeof output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); + snprintf(output, sizeof(output) - 1, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); } } else { if (prefer_name) { - snprintf(output, sizeof output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); + snprintf(output, sizeof(output) - 1, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); } else { - snprintf(output, sizeof output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); + snprintf(output, sizeof(output) - 1, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); } } return output; From 6a995bfe10ef172da5c7aa7e95bfd424e893459c Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sun, 28 Feb 2021 09:45:18 -0500 Subject: [PATCH 126/365] libretro: Sync updates from libretro --- libretro/Makefile | 14 ++++++++++++++ libretro/jni/Application.mk | 1 + libretro/libretro.c | 1 + 3 files changed, 16 insertions(+) diff --git a/libretro/Makefile b/libretro/Makefile index 2ed87b8..7e22126 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -119,6 +119,20 @@ else ifeq ($(platform), classic_armv7_a7) LDFLAGS += -static-libgcc -static-libstdc++ endif endif + +########################### +# Raspberry Pi 4 in 64 mode +else ifneq (,$(findstring rpi4_64,$(platform))) + EXT ?= so + TARGET := $(TARGET_NAME)_libretro.$(EXT) + fpic := -fPIC + SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined + CFLAGS += -O2 -march=armv8-a+crc+simd -mtune=cortex-a72 + CXXFLAGS += $(CFLAGS) + CPPFLAGS += $(CFLAGS) + ASFLAGS += $(CFLAGS) +########################### + ####################################### # Nintendo Switch (libtransistor) else ifeq ($(platform), switch) diff --git a/libretro/jni/Application.mk b/libretro/jni/Application.mk index a252a72..a169e74 100644 --- a/libretro/jni/Application.mk +++ b/libretro/jni/Application.mk @@ -1 +1,2 @@ +APP_STL := c++_static APP_ABI := all diff --git a/libretro/libretro.c b/libretro/libretro.c index 6b531b6..918a657 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -1128,6 +1128,7 @@ bool retro_load_game(const struct retro_game_info *info) void retro_unload_game(void) { for (int i = 0; i < emulated_devices; i++) { + log_cb(RETRO_LOG_INFO, "Unloading GB: %d\n", emulated_devices); GB_free(&gameboy[i]); } } From 57080c48bc390d7f81baa6324b6f584d8d0dd965 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 16:50:46 +0200 Subject: [PATCH 127/365] No need for -1 --- Core/debugger.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 0debf36..73bd80a 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -125,7 +125,6 @@ static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) { static __thread char output[256]; - output[sizeof(output) - 1] = 0; // Ensure termination const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value); if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) { @@ -133,24 +132,24 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer } if (!symbol) { - snprintf(output, sizeof(output) - 1, "$%04x", value); + snprintf(output, sizeof(output), "$%04x", value); } else if (symbol->addr == value) { if (prefer_name) { - snprintf(output, sizeof(output) - 1, "%s ($%04x)", symbol->name, value); + snprintf(output, sizeof(output), "%s ($%04x)", symbol->name, value); } else { - snprintf(output, sizeof(output) - 1, "$%04x (%s)", value, symbol->name); + snprintf(output, sizeof(output), "$%04x (%s)", value, symbol->name); } } else { if (prefer_name) { - snprintf(output, sizeof(output) - 1, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); + snprintf(output, sizeof(output), "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); } else { - snprintf(output, sizeof(output) - 1, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); + snprintf(output, sizeof(output), "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); } } return output; @@ -161,7 +160,6 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); static __thread char output[256]; - output[sizeof(output) - 1] = 0; // Ensure termination const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value); if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { @@ -169,24 +167,24 @@ static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, boo } if (!symbol) { - snprintf(output, sizeof(output) - 1, "$%02x:$%04x", value.bank, value.value); + snprintf(output, sizeof(output), "$%02x:$%04x", value.bank, value.value); } else if (symbol->addr == value.value) { if (prefer_name) { - snprintf(output, sizeof(output) - 1, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); + snprintf(output, sizeof(output), "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); } else { - snprintf(output, sizeof(output) - 1, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); + snprintf(output, sizeof(output), "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); } } else { if (prefer_name) { - snprintf(output, sizeof(output) - 1, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); + snprintf(output, sizeof(output), "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); } else { - snprintf(output, sizeof(output) - 1, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); + snprintf(output, sizeof(output), "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); } } return output; From 2d593a95e3561e938de795dbdf57e0f40438b8e1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 17:15:19 +0200 Subject: [PATCH 128/365] Update version to 0.14.1 --- version.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.mk b/version.mk index f846fc8..8698bb9 100644 --- a/version.mk +++ b/version.mk @@ -1 +1 @@ -VERSION := 0.14 +VERSION := 0.14.1 From 975d379d76181adfcc929ad558662288dcb82441 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sun, 28 Feb 2021 13:13:12 -0500 Subject: [PATCH 129/365] libretro: Remove empty CFLAGS --- libretro/Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/libretro/Makefile b/libretro/Makefile index 7e22126..50ab91f 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -128,9 +128,6 @@ else ifneq (,$(findstring rpi4_64,$(platform))) fpic := -fPIC SHARED := -shared -Wl,--version-script=$(CORE_DIR)/libretro/link.T -Wl,--no-undefined CFLAGS += -O2 -march=armv8-a+crc+simd -mtune=cortex-a72 - CXXFLAGS += $(CFLAGS) - CPPFLAGS += $(CFLAGS) - ASFLAGS += $(CFLAGS) ########################### ####################################### From f21fd33cc38aa7ee948f934d231b65a747eb1b14 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sun, 28 Feb 2021 13:13:40 -0500 Subject: [PATCH 130/365] libretro: Remove APP_STL --- libretro/jni/Application.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/libretro/jni/Application.mk b/libretro/jni/Application.mk index a169e74..a252a72 100644 --- a/libretro/jni/Application.mk +++ b/libretro/jni/Application.mk @@ -1,2 +1 @@ -APP_STL := c++_static APP_ABI := all From d2ed1343e5ef890f3ec9377941af25453cfe46b5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 28 Feb 2021 20:41:58 +0200 Subject: [PATCH 131/365] Add missing mkdir --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 13747cb..3d9b2cb 100644 --- a/Makefile +++ b/Makefile @@ -451,6 +451,7 @@ ifneq ($(SUDO_USER),) -su $(SUDO_USER) -c "xdg-desktop-menu forceupdate --mode system" endif else + -@$(MKDIR) -p $(DESTDIR)$(PREFIX)/share/applications/ cp FreeDesktop/sameboy.desktop $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop endif From ea97c1dc0b596204ba546583e8912ae8b7073865 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 1 Mar 2021 21:44:54 +0200 Subject: [PATCH 132/365] Fix an APU regression that caused some games in DMG mode to play in the wrong pitch --- Core/apu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/apu.c b/Core/apu.c index 45b5a67..b159b2e 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -596,7 +596,7 @@ void GB_apu_run(GB_gameboy_t *gb) else { /* Split it into two */ cycles -= gb->apu.channel_4_dmg_delayed_start; - gb->apu.apu_cycles = gb->apu.channel_4_dmg_delayed_start * 2; + gb->apu.apu_cycles = gb->apu.channel_4_dmg_delayed_start * 4; GB_apu_run(gb); } } From 4d67fa8e803b33ef0ebfe243d2c6f810e092738c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 1 Mar 2021 22:58:52 +0200 Subject: [PATCH 133/365] Close all related windows when closing a document --- Cocoa/Document.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 9e52214..c3a0010 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -865,6 +865,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"]; [self stop]; [self.consoleWindow close]; + [self.memoryWindow close]; + [self.vramWindow close]; + [self.printerFeedWindow close]; + [self.cheatsWindow close]; [super close]; } From f50d9310a7c5fed5c0cf9f5ab4b3afc457fcccb5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 1 Mar 2021 23:00:11 +0200 Subject: [PATCH 134/365] This shouldn't have been here --- FreeDesktop/x-gameboy-color-rom.xml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 FreeDesktop/x-gameboy-color-rom.xml diff --git a/FreeDesktop/x-gameboy-color-rom.xml b/FreeDesktop/x-gameboy-color-rom.xml deleted file mode 100644 index 0a42874..0000000 --- a/FreeDesktop/x-gameboy-color-rom.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - YOUR MOM - - - - - - From 5a966bba91448723f5885e084926a12ed017b3e1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 1 Mar 2021 23:21:07 +0200 Subject: [PATCH 135/365] Register ISX files on FreeDesktop --- FreeDesktop/sameboy.desktop | 2 +- FreeDesktop/sameboy.xml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/FreeDesktop/sameboy.desktop b/FreeDesktop/sameboy.desktop index d42f395..20b1b46 100644 --- a/FreeDesktop/sameboy.desktop +++ b/FreeDesktop/sameboy.desktop @@ -9,4 +9,4 @@ Keywords=game;boy;gameboy;color;emulator Terminal=false StartupNotify=false Categories=Application;Game; -MimeType=application/x-gameboy-rom;application/x-gameboy-color-rom \ No newline at end of file +MimeType=application/x-gameboy-rom;application/x-gameboy-color-rom;application/x-gameboy-isx \ No newline at end of file diff --git a/FreeDesktop/sameboy.xml b/FreeDesktop/sameboy.xml index 6c0df37..18123ed 100644 --- a/FreeDesktop/sameboy.xml +++ b/FreeDesktop/sameboy.xml @@ -14,4 +14,10 @@ + + Game Boy ISX binary + + + + From 5c1b89e82d9fe5f62bb3c5dae5f1403f3077c450 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 1 Mar 2021 23:27:40 +0200 Subject: [PATCH 136/365] Update version --- version.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.mk b/version.mk index 8698bb9..731aad9 100644 --- a/version.mk +++ b/version.mk @@ -1 +1 @@ -VERSION := 0.14.1 +VERSION := 0.14.2 From ad54dc57b06c451206cadca05d7b2b943c178d7e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 21 Mar 2021 15:15:04 +0200 Subject: [PATCH 137/365] Improved time syncing when turning the LCD on and off, fixes #193 --- Core/memory.c | 1 + Core/timing.c | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 8aacf41..b1619a6 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -919,6 +919,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) else if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; } + GB_timing_sync(gb); } else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) { /* Sync after turning off LCD */ diff --git a/Core/timing.c b/Core/timing.c index dedc5a0..18cf571 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -64,11 +64,16 @@ void GB_timing_sync(GB_gameboy_t *gb) uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ int64_t nanoseconds = get_nanoseconds(); int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds; - if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1000000000LL / GB_get_clock_rate(gb)) { + if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { // +20% to be more forgiving nsleep(time_to_sleep); gb->last_sync += target_nanoseconds; } else { + if (-time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { + // We're running a bit too slow, but the difference is small enough, + // just skip this sync and let it even out + return; + } gb->last_sync = nanoseconds; } From da1003263f750dc19b5d43ecccda81ef67836bfc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 21 Mar 2021 20:32:30 +0200 Subject: [PATCH 138/365] Redo save states to remove severe code duplication between buffers and files --- Core/save_state.c | 498 +++++++++++++++++++++------------------------- 1 file changed, 230 insertions(+), 268 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index 2b31ba3..7cea208 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -2,64 +2,78 @@ #include #include -static bool dump_section(FILE *f, const void *src, uint32_t size) +typedef struct virtual_file_s virtual_file_t; +struct virtual_file_s { - if (fwrite(&size, 1, sizeof(size), f) != sizeof(size)) { - return false; - } - - if (fwrite(src, 1, size, f) != size) { - return false; - } - - return true; + size_t (*read)(virtual_file_t *file, void *dest, size_t length); + size_t (*write)(virtual_file_t *file, const void *dest, size_t length); + void (*seek)(virtual_file_t *file, ssize_t ammount, int origin); + union { + FILE *file; + struct { + uint8_t *buffer; + size_t position; + size_t size; + }; + }; +}; + +static size_t file_read(virtual_file_t *file, void *dest, size_t length) +{ + return fread(dest, 1, length, file->file); } -#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) - -/* Todo: we need a sane and protable save state format. */ -int GB_save_state(GB_gameboy_t *gb, const char *path) +static void file_seek(virtual_file_t *file, ssize_t ammount, int origin) +{ + fseek(file->file, ammount, origin); +} + +static size_t file_write(virtual_file_t *file, const void *src, size_t length) +{ + return fwrite(src, 1, length, file->file); +} + +static size_t buffer_read(virtual_file_t *file, void *dest, size_t length) { - FILE *f = fopen(path, "wb"); - if (!f) { - GB_log(gb, "Could not open save state: %s.\n", strerror(errno)); - return errno; - } - - if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; - if (!DUMP_SECTION(gb, f, core_state)) goto error; - if (!DUMP_SECTION(gb, f, dma )) goto error; - if (!DUMP_SECTION(gb, f, mbc )) goto error; - if (!DUMP_SECTION(gb, f, hram )) goto error; - if (!DUMP_SECTION(gb, f, timing )) goto error; - if (!DUMP_SECTION(gb, f, apu )) goto error; - if (!DUMP_SECTION(gb, f, rtc )) goto error; - if (!DUMP_SECTION(gb, f, video )) goto error; - - if (GB_is_hle_sgb(gb)) { - if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; - } - - if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { - goto error; - } - - if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { - goto error; - } - - if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { - goto error; - } - errno = 0; + if (length > file->size - file->position) { + errno = EIO; + length = file->size - file->position; + } -error: - fclose(f); - return errno; + memcpy(dest, file->buffer + file->position, length); + file->position += length; + + return length; } -#undef DUMP_SECTION +static void buffer_seek(virtual_file_t *file, ssize_t ammount, int origin) +{ + switch (origin) { + case SEEK_SET: + file->position = ammount; + break; + case SEEK_CUR: + file->position += ammount; + break; + case SEEK_END: + file->position = file->size + ammount; + break; + default: + break; + } + + if (file->position > file->size) { + file->position = file->size; + } +} + +static size_t buffer_write(virtual_file_t *file, const void *src, size_t size) +{ + memcpy(file->buffer + file->position, src, size); + file->position += size; + return size; +} size_t GB_get_save_state_size(GB_gameboy_t *gb) { @@ -78,74 +92,6 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + gb->vram_size; } -/* A write-line function for memory copying */ -static void buffer_write(const void *src, size_t size, uint8_t **dest) -{ - memcpy(*dest, src, size); - *dest += size; -} - -static void buffer_dump_section(uint8_t **buffer, const void *src, uint32_t size) -{ - buffer_write(&size, sizeof(size), buffer); - buffer_write(src, size, buffer); -} - -#define DUMP_SECTION(gb, buffer, section) buffer_dump_section(&buffer, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) -void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) -{ - buffer_write(GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header), &buffer); - DUMP_SECTION(gb, buffer, core_state); - DUMP_SECTION(gb, buffer, dma ); - DUMP_SECTION(gb, buffer, mbc ); - DUMP_SECTION(gb, buffer, hram ); - DUMP_SECTION(gb, buffer, timing ); - DUMP_SECTION(gb, buffer, apu ); - DUMP_SECTION(gb, buffer, rtc ); - DUMP_SECTION(gb, buffer, video ); - - if (GB_is_hle_sgb(gb)) { - buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb)); - } - - - buffer_write(gb->mbc_ram, gb->mbc_ram_size, &buffer); - buffer_write(gb->ram, gb->ram_size, &buffer); - buffer_write(gb->vram, gb->vram_size, &buffer); -} - -/* Best-effort read function for maximum future compatibility. */ -static bool read_section(FILE *f, void *dest, uint32_t size, bool fix_broken_windows_saves) -{ - uint32_t saved_size = 0; - if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) { - return false; - } - - if (fix_broken_windows_saves) { - if (saved_size < 4) { - return false; - } - saved_size -= 4; - fseek(f, 4, SEEK_CUR); - } - - if (saved_size <= size) { - if (fread(dest, 1, saved_size, f) != saved_size) { - return false; - } - } - else { - if (fread(dest, 1, size, f) != size) { - return false; - } - fseek(f, saved_size - size, SEEK_CUR); - } - - return true; -} -#undef DUMP_SECTION - static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) { if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) { @@ -229,9 +175,116 @@ static void sanitize_state(GB_gameboy_t *gb) } } -#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves) +static bool dump_section(virtual_file_t *file, const void *src, uint32_t size) +{ + if (file->write(file, &size, sizeof(size)) != sizeof(size)) { + return false; + } + + if (file->write(file, src, size) != size) { + return false; + } + + return true; +} -int GB_load_state(GB_gameboy_t *gb, const char *path) +#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) + +static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) +{ + if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error; + if (!DUMP_SECTION(gb, file, core_state)) goto error; + if (!DUMP_SECTION(gb, file, dma )) goto error; + if (!DUMP_SECTION(gb, file, mbc )) goto error; + if (!DUMP_SECTION(gb, file, hram )) goto error; + if (!DUMP_SECTION(gb, file, timing )) goto error; + if (!DUMP_SECTION(gb, file, apu )) goto error; + if (!DUMP_SECTION(gb, file, rtc )) goto error; + if (!DUMP_SECTION(gb, file, video )) goto error; + + if (GB_is_hle_sgb(gb)) { + if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; + } + + if (file->write(file, gb->mbc_ram, gb->mbc_ram_size) != gb->mbc_ram_size) { + goto error; + } + + if (file->write(file, gb->ram, gb->ram_size) != gb->ram_size) { + goto error; + } + + if (file->write(file, gb->vram, gb->vram_size) != gb->vram_size) { + goto error; + } + + errno = 0; + +error: + return errno; +} + +int GB_save_state(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "wb"); + if (!f) { + GB_log(gb, "Could not open save state: %s.\n", strerror(errno)); + return errno; + } + virtual_file_t file = { + .write = file_write, + .seek = file_seek, + .file = f, + }; + int ret = save_state_internal(gb, &file); + fclose(f); + return ret; +} + +void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) +{ + virtual_file_t file = { + .write = buffer_write, + .seek = buffer_seek, + .buffer = (uint8_t *)buffer, + .position = 0, + }; + + save_state_internal(gb, &file); +} + + +static bool read_section(virtual_file_t *file, void *dest, uint32_t size, bool fix_broken_windows_saves) +{ + uint32_t saved_size = 0; + if (file->read(file, &saved_size, sizeof(size)) != sizeof(size)) { + return false; + } + + if (fix_broken_windows_saves) { + if (saved_size < 4) { + return false; + } + saved_size -= 4; + file->seek(file, 4, SEEK_CUR); + } + + if (saved_size <= size) { + if (file->read(file, dest, saved_size) != saved_size) { + return false; + } + } + else { + if (file->read(file, dest, size) != size) { + return false; + } + file->seek(file, saved_size - size, SEEK_CUR); + } + + return true; +} + +static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) { GB_gameboy_t save; @@ -240,184 +293,93 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) /* ...Except ram size, we use it to detect old saves with incorrect ram sizes */ save.ram_size = 0; - FILE *f = fopen(path, "rb"); - if (!f) { - GB_log(gb, "Could not open save state: %s.\n", strerror(errno)); - return errno; - } - bool fix_broken_windows_saves = false; - if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + + if (file->read(file, GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) return errno; if (save.magic == 0) { - /* Potentially legacy, broken Windows save state */ - fseek(f, 4, SEEK_SET); - if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + /* Potentially legacy, broken Windows save state*/ + + file->seek(file, 4, SEEK_SET); + if (file->read(file, GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) return errno; fix_broken_windows_saves = true; } if (gb->magic != save.magic) { GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); return false; } - if (!READ_SECTION(&save, f, core_state)) goto error; - if (!READ_SECTION(&save, f, dma )) goto error; - if (!READ_SECTION(&save, f, mbc )) goto error; - if (!READ_SECTION(&save, f, hram )) goto error; - if (!READ_SECTION(&save, f, timing )) goto error; - if (!READ_SECTION(&save, f, apu )) goto error; - if (!READ_SECTION(&save, f, rtc )) goto error; - if (!READ_SECTION(&save, f, video )) goto error; +#define READ_SECTION(gb, file, section) read_section(file, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves) + if (!READ_SECTION(&save, file, core_state)) return errno; + if (!READ_SECTION(&save, file, dma )) return errno; + if (!READ_SECTION(&save, file, mbc )) return errno; + if (!READ_SECTION(&save, file, hram )) return errno; + if (!READ_SECTION(&save, file, timing )) return errno; + if (!READ_SECTION(&save, file, apu )) return errno; + if (!READ_SECTION(&save, file, rtc )) return errno; + if (!READ_SECTION(&save, file, video )) return errno; +#undef READ_SECTION + if (!verify_and_update_state_compatibility(gb, &save)) { - errno = -1; - goto error; + return errno; } if (GB_is_hle_sgb(gb)) { - if (!read_section(f, gb->sgb, sizeof(*gb->sgb), false)) goto error; + if (!read_section(file, gb->sgb, sizeof(*gb->sgb), false)) return errno; } memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); - if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.mbc_ram_size) { - fclose(f); - return EIO; + if (file->read(file, gb->mbc_ram, save.mbc_ram_size) != save.mbc_ram_size) { + return errno; } - if (fread(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { - fclose(f); - return EIO; + if (file->read(file, gb->ram, gb->ram_size) != gb->ram_size) { + return errno; } - + /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */ - fseek(f, save.ram_size - gb->ram_size, SEEK_CUR); + file->seek(file, save.ram_size - gb->ram_size, SEEK_CUR); - if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { - fclose(f); - return EIO; + if (file->read(file, gb->vram, gb->vram_size) != gb->vram_size) { + return errno; } size_t orig_ram_size = gb->ram_size; memcpy(gb, &save, sizeof(save)); gb->ram_size = orig_ram_size; - + errno = 0; sanitize_state(gb); -error: - fclose(f); - return errno; -} - -#undef READ_SECTION - -/* An read-like function for buffer-copying */ -static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, size_t *buffer_length) -{ - if (length > *buffer_length) { - length = *buffer_length; - } - - memcpy(dest, *buffer, length); - *buffer += length; - *buffer_length -= length; - - return length; -} - -static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size, bool fix_broken_windows_saves) -{ - uint32_t saved_size = 0; - if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) { - return false; - } - - if (saved_size > *buffer_length) return false; - - if (fix_broken_windows_saves) { - if (saved_size < 4) { - return false; - } - saved_size -= 4; - *buffer += 4; - } - - if (saved_size <= size) { - if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) { - return false; - } - } - else { - if (buffer_read(dest, size, buffer, buffer_length) != size) { - return false; - } - *buffer += saved_size - size; - *buffer_length -= saved_size - size; - } - - return true; -} - -#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves) -int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length) -{ - GB_gameboy_t save; - - /* Every unread value should be kept the same. */ - memcpy(&save, gb, sizeof(save)); - bool fix_broken_windows_saves = false; - - if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1; - if (save.magic == 0) { - /* Potentially legacy, broken Windows save state*/ - buffer -= GB_SECTION_SIZE(header) - 4; - length += GB_SECTION_SIZE(header) - 4; - if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1; - fix_broken_windows_saves = true; - } - if (gb->magic != save.magic) { - GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); - return false; - } - if (!READ_SECTION(&save, buffer, length, core_state)) return -1; - if (!READ_SECTION(&save, buffer, length, dma )) return -1; - if (!READ_SECTION(&save, buffer, length, mbc )) return -1; - if (!READ_SECTION(&save, buffer, length, hram )) return -1; - if (!READ_SECTION(&save, buffer, length, timing )) return -1; - if (!READ_SECTION(&save, buffer, length, apu )) return -1; - if (!READ_SECTION(&save, buffer, length, rtc )) return -1; - if (!READ_SECTION(&save, buffer, length, video )) return -1; - - - if (!verify_and_update_state_compatibility(gb, &save)) { - return -1; - } - - if (GB_is_hle_sgb(gb)) { - if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb), false)) return -1; - } - - memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); - if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) { - return -1; - } - - if (buffer_read(gb->ram, gb->ram_size, &buffer, &length) != gb->ram_size) { - return -1; - } - - if (buffer_read(gb->vram, gb->vram_size, &buffer, &length) != gb->vram_size) { - return -1; - } - - /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */ - buffer += save.ram_size - gb->ram_size; - length -= save.ram_size - gb->ram_size; - - memcpy(gb, &save, sizeof(save)); - - sanitize_state(gb); - return 0; } -#undef READ_SECTION +int GB_load_state(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open save state: %s.\n", strerror(errno)); + return errno; + } + virtual_file_t file = { + .read = file_read, + .seek = file_seek, + .file = f, + }; + int ret = load_state_internal(gb, &file); + fclose(f); + return ret; +} + +int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length) +{ + virtual_file_t file = { + .read = buffer_read, + .seek = buffer_seek, + .buffer = (uint8_t *)buffer, + .position = 0, + .size = length, + }; + + return load_state_internal(gb, &file); +} From 925bd863c08334b54cd075b6da9e537688ecb76b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 22 Mar 2021 00:18:49 +0200 Subject: [PATCH 139/365] Better errnos --- Core/save_state.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index 7cea208..15775b2 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -308,14 +308,14 @@ static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) return false; } #define READ_SECTION(gb, file, section) read_section(file, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves) - if (!READ_SECTION(&save, file, core_state)) return errno; - if (!READ_SECTION(&save, file, dma )) return errno; - if (!READ_SECTION(&save, file, mbc )) return errno; - if (!READ_SECTION(&save, file, hram )) return errno; - if (!READ_SECTION(&save, file, timing )) return errno; - if (!READ_SECTION(&save, file, apu )) return errno; - if (!READ_SECTION(&save, file, rtc )) return errno; - if (!READ_SECTION(&save, file, video )) return errno; + if (!READ_SECTION(&save, file, core_state)) return errno ?: EIO; + if (!READ_SECTION(&save, file, dma )) return errno ?: EIO; + if (!READ_SECTION(&save, file, mbc )) return errno ?: EIO; + if (!READ_SECTION(&save, file, hram )) return errno ?: EIO; + if (!READ_SECTION(&save, file, timing )) return errno ?: EIO; + if (!READ_SECTION(&save, file, apu )) return errno ?: EIO; + if (!READ_SECTION(&save, file, rtc )) return errno ?: EIO; + if (!READ_SECTION(&save, file, video )) return errno ?: EIO; #undef READ_SECTION @@ -324,31 +324,29 @@ static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) } if (GB_is_hle_sgb(gb)) { - if (!read_section(file, gb->sgb, sizeof(*gb->sgb), false)) return errno; + if (!read_section(file, gb->sgb, sizeof(*gb->sgb), false)) return errno ?: EIO; } memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); if (file->read(file, gb->mbc_ram, save.mbc_ram_size) != save.mbc_ram_size) { - return errno; + return errno ?: EIO; } if (file->read(file, gb->ram, gb->ram_size) != gb->ram_size) { - return errno; + return errno ?: EIO; } /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */ file->seek(file, save.ram_size - gb->ram_size, SEEK_CUR); if (file->read(file, gb->vram, gb->vram_size) != gb->vram_size) { - return errno; + return errno ?: EIO; } size_t orig_ram_size = gb->ram_size; memcpy(gb, &save, sizeof(save)); gb->ram_size = orig_ram_size; - errno = 0; - sanitize_state(gb); return 0; From 75bc1e9a863b316bdb8575ce46eaf3555713e09b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 23 Mar 2021 23:33:16 +0200 Subject: [PATCH 140/365] Initial BESS support, no SGB nor RTC yet --- Cocoa/Document.m | 6 +- Core/save_state.c | 549 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 546 insertions(+), 9 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index c3a0010..e7812d4 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1181,11 +1181,11 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) }]; if (!success) { - if (error) { - [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; - } NSBeep(); } + if (error) { + [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; + } } - (IBAction)clearConsole:(id)sender diff --git a/Core/save_state.c b/Core/save_state.c index 15775b2..205fdd1 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1,6 +1,88 @@ #include "gb.h" #include #include +#include + +#define str(x) #x +#define xstr(x) str(x) +#ifdef GB_BIG_ENDIAN +#define BESS_NAME "SameBoy v" xstr(VERSION) "(Big Endian)" +#else +#define BESS_NAME "SameBoy v" xstr(VERSION) +#endif + +typedef struct __attribute__((packed)) { + uint32_t magic; + uint32_t size; +} BESS_block_t; + +typedef struct __attribute__((packed)) { + uint32_t size; + uint32_t offset; +} BESS_buffer_t; + +typedef struct __attribute__((packed)) { + uint32_t start_offset; + uint32_t magic; +} BESS_footer_t; + +typedef struct __attribute__((packed)) { + BESS_block_t header; + uint16_t major; + uint16_t minor; + union { + struct { + char family; + char model; + char revision; + char padding; + }; + uint32_t full_model; + }; + + uint16_t pc; + uint16_t af; + uint16_t bc; + uint16_t de; + uint16_t hl; + uint16_t sp; + + uint8_t ime; + uint8_t ie; + uint8_t execution_mode; // 0 = running; 1 = halted; 2 = stopped + + uint8_t io_registers[0x80]; + uint8_t hram[0x7f]; + + BESS_buffer_t ram; + BESS_buffer_t vram; + BESS_buffer_t mbc_ram; +} BESS_CORE_t; + +typedef struct __attribute__((packed)) { + BESS_block_t header; + uint8_t oam[256]; +} BESS_OAM_t; + +typedef struct __attribute__((packed)) { + BESS_block_t header; + uint8_t background_palettes[0x40]; + uint8_t sprite_palettes[0x40]; +} BESS_PALS_t; + +typedef struct __attribute__((packed)) { + uint16_t address; + uint8_t value; +} BESS_MBC_pair_t; + + +#ifdef GB_BIG_ENDIAN +#define BESS16(x) __builtin_bswap16(x) +#define BESS32(x) __builtin_bswap32(x) +#else +#define BESS16(x) (x) +#define BESS32(x) (x) +#endif typedef struct virtual_file_s virtual_file_t; struct virtual_file_s @@ -8,6 +90,7 @@ struct virtual_file_s size_t (*read)(virtual_file_t *file, void *dest, size_t length); size_t (*write)(virtual_file_t *file, const void *dest, size_t length); void (*seek)(virtual_file_t *file, ssize_t ammount, int origin); + size_t (*tell)(virtual_file_t *file); union { FILE *file; struct { @@ -33,8 +116,16 @@ static size_t file_write(virtual_file_t *file, const void *src, size_t length) return fwrite(src, 1, length, file->file); } +static size_t file_tell(virtual_file_t *file) +{ + return ftell(file->file); +} + static size_t buffer_read(virtual_file_t *file, void *dest, size_t length) { + if (length & 0x80000000) { + return 0; + } errno = 0; if (length > file->size - file->position) { errno = EIO; @@ -75,6 +166,31 @@ static size_t buffer_write(virtual_file_t *file, const void *src, size_t size) return size; } +static size_t buffer_tell(virtual_file_t *file) +{ + return file->position; +} + +static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) +{ + switch (cart->mbc_type) { + default: + case GB_NO_MBC: return 0; + case GB_MBC1: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); + case GB_MBC2: + return sizeof(BESS_block_t) + 2 * sizeof(BESS_MBC_pair_t); + case GB_MBC3: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); + case GB_MBC5: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); + case GB_HUC1: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); + case GB_HUC3: + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t); + } +} + size_t GB_get_save_state_size(GB_gameboy_t *gb) { return GB_SECTION_SIZE(header) @@ -89,11 +205,21 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + gb->mbc_ram_size + gb->ram_size - + gb->vram_size; + + gb->vram_size + // BESS + + sizeof(BESS_CORE_t) + + sizeof(BESS_block_t) // NAME + + sizeof(BESS_NAME) - 1 + + sizeof(BESS_OAM_t) + + (GB_is_cgb(gb)? sizeof(BESS_PALS_t) : 0) + + bess_size_for_cartridge(gb->cartridge_type) // MBC block + + sizeof(BESS_block_t) // END block + + sizeof(BESS_footer_t); } -static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) +static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save, bool *attempt_bess) { + *attempt_bess = false; if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) { /* This is a save state with a bad printer struct from a 32-bit OS */ memmove(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam); @@ -121,6 +247,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t if (gb->version != save->version) { GB_log(gb, "The save state is for a different version of SameBoy.\n"); + *attempt_bess = true; return false; } @@ -190,6 +317,66 @@ static bool dump_section(virtual_file_t *file, const void *src, uint32_t size) #define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) +static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) +{ + + BESS_block_t mbc_block = {htonl('MBC '), 0}; + BESS_MBC_pair_t pairs[4]; + switch (gb->cartridge_type->mbc_type) { + default: + case GB_NO_MBC: return 0; + case GB_MBC1: + pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->mbc1.bank_low}; + pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->mbc1.bank_high}; + pairs[3] = (BESS_MBC_pair_t){BESS16(0x6000), gb->mbc1.mode}; + mbc_block.size = 4 * sizeof(pairs[0]); + break; + case GB_MBC2: + pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){BESS16(0x0100), gb->mbc2.rom_bank}; + mbc_block.size = 2 * sizeof(pairs[0]); + break; + case GB_MBC3: + pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->mbc3.rom_bank}; + pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->mbc3.ram_bank | (gb->mbc3_rtc_mapped? 8 : 0)}; + pairs[3] = (BESS_MBC_pair_t){BESS16(0x6000), gb->rtc_latch}; + mbc_block.size = 4 * sizeof(pairs[0]); + break; + case GB_MBC5: + pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->mbc5.rom_bank_low}; + pairs[2] = (BESS_MBC_pair_t){BESS16(0x3000), gb->mbc5.rom_bank_high}; + pairs[3] = (BESS_MBC_pair_t){BESS16(0x4000), gb->mbc5.ram_bank}; + mbc_block.size = 4 * sizeof(pairs[0]); + break; + case GB_HUC1: + pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->huc1.ir_mode? 0xE : 0x0}; + pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->huc1.bank_low}; + pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->huc1.bank_high}; + pairs[3] = (BESS_MBC_pair_t){BESS16(0x6000), gb->huc1.mode}; + mbc_block.size = 4 * sizeof(pairs[0]); + + case GB_HUC3: + pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->huc3_mode}; + pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->huc3.rom_bank}; + pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->huc3.ram_bank}; + mbc_block.size = 3 * sizeof(pairs[0]); + break; + } + + if (file->write(file, &mbc_block, sizeof(mbc_block)) != sizeof(mbc_block)) { + return errno; + } + + if (file->write(file, &pairs, mbc_block.size) != mbc_block.size) { + return errno; + } + + return 0; +} + static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) { if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error; @@ -206,20 +393,139 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; } + BESS_CORE_t bess_core; + + bess_core.mbc_ram.offset = file->tell(file); + bess_core.mbc_ram.size = gb->mbc_ram_size; if (file->write(file, gb->mbc_ram, gb->mbc_ram_size) != gb->mbc_ram_size) { goto error; } + bess_core.ram.offset = file->tell(file); + bess_core.ram.size = gb->ram_size; if (file->write(file, gb->ram, gb->ram_size) != gb->ram_size) { goto error; } + bess_core.vram.offset = file->tell(file); + bess_core.vram.size = gb->vram_size; if (file->write(file, gb->vram, gb->vram_size) != gb->vram_size) { goto error; } - errno = 0; + BESS_footer_t bess_footer = { + .start_offset = file->tell(file), + .magic = htonl('BESS'), + }; + /* BESS CORE */ + + bess_core.header = (BESS_block_t){htonl('CORE'), BESS32(sizeof(bess_core) - sizeof(bess_core.header))}; + bess_core.major = BESS16(1); + bess_core.minor = BESS16(1); + switch (gb->model) { + + case GB_MODEL_DMG_B: bess_core.full_model = htonl('GDB '); break; + + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_NTSC_NO_SFC: + bess_core.full_model = htonl('SN '); break; + + case GB_MODEL_SGB_PAL_NO_SFC: + case GB_MODEL_SGB_PAL: + bess_core.full_model = htonl('SP '); break; + + case GB_MODEL_SGB2_NO_SFC: + case GB_MODEL_SGB2: + bess_core.full_model = htonl('S2 '); break; + + + case GB_MODEL_CGB_C: bess_core.full_model = htonl('CCC '); break; + case GB_MODEL_CGB_E: bess_core.full_model = htonl('CCE '); break; + case GB_MODEL_AGB: bess_core.full_model = htonl('CA '); break; // SameBoy doesn't emulate a specific AGB revision yet + } + + bess_core.pc = BESS16(gb->pc); + bess_core.af = BESS16(gb->af); + bess_core.bc = BESS16(gb->bc); + bess_core.de = BESS16(gb->de); + bess_core.hl = BESS16(gb->hl); + bess_core.sp = BESS16(gb->sp); + + bess_core.ime = gb->ime; + bess_core.ie = gb->interrupt_enable; + bess_core.execution_mode = 0; + if (gb->halted) { + bess_core.execution_mode = 1; + } + else if (gb->stopped) { + bess_core.execution_mode = 2; + } + + memcpy(bess_core.io_registers, gb->io_registers, sizeof(gb->io_registers)); + bess_core.io_registers[GB_IO_DIV] = gb->div_counter >> 8; + bess_core.io_registers[GB_IO_BANK] = gb->boot_rom_finished; + bess_core.io_registers[GB_IO_KEY1] |= gb->cgb_double_speed? 0x80 : 0; + memcpy(bess_core.hram, gb->hram, sizeof(gb->hram)); + + + if (file->write(file, &bess_core, sizeof(bess_core)) != sizeof(bess_core)) { + goto error; + } + + /* BESS NAME */ + + static const BESS_block_t bess_name = {htonl('NAME'), BESS32(sizeof(BESS_NAME) - 1)}; + + if (file->write(file, &bess_name, sizeof(bess_name)) != sizeof(bess_name)) { + goto error; + } + + if (file->write(file, BESS_NAME, sizeof(BESS_NAME) - 1) != sizeof(BESS_NAME) - 1) { + goto error; + } + + /* BESS OAM */ + + BESS_OAM_t bess_oam; + bess_oam.header = (BESS_block_t){htonl('OAM '), BESS32(sizeof(bess_oam) - sizeof(bess_oam.header))}; + memcpy(bess_oam.oam, gb->oam, sizeof(gb->oam)); + memcpy(bess_oam.oam + sizeof(gb->oam), gb->extra_oam, sizeof(gb->extra_oam)); + + if (file->write(file, &bess_oam, sizeof(bess_oam)) != sizeof(bess_oam)) { + goto error; + } + + save_bess_mbc_block(gb, file); + + if (GB_is_cgb(gb)) { + /* BESS PALS */ + + BESS_PALS_t bess_pals; + bess_pals.header = (BESS_block_t){htonl('PALS'), BESS32(sizeof(bess_pals) - sizeof(bess_oam.header))}; + memcpy(bess_pals.background_palettes, gb->background_palettes_data, sizeof(bess_pals.background_palettes)); + memcpy(bess_pals.sprite_palettes, gb->sprite_palettes_data, sizeof(bess_pals.sprite_palettes)); + + if (file->write(file, &bess_pals, sizeof(bess_pals)) != sizeof(bess_pals)) { + goto error; + } + } + + /* BESS END */ + + static const BESS_block_t bess_end = {htonl('END '), 0}; + + if (file->write(file, &bess_end, sizeof(bess_end)) != sizeof(bess_end)) { + goto error; + } + + /* BESS Footer */ + + if (file->write(file, &bess_footer, sizeof(bess_footer)) != sizeof(bess_footer)) { + goto error; + } + + errno = 0; error: return errno; } @@ -234,6 +540,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path) virtual_file_t file = { .write = file_write, .seek = file_seek, + .tell = file_tell, .file = f, }; int ret = save_state_internal(gb, &file); @@ -246,11 +553,13 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) virtual_file_t file = { .write = buffer_write, .seek = buffer_seek, + .tell = buffer_tell, .buffer = (uint8_t *)buffer, .position = 0, }; save_state_internal(gb, &file); + assert(file.position == GB_get_save_state_size(gb)); } @@ -284,6 +593,229 @@ static bool read_section(virtual_file_t *file, void *dest, uint32_t size, bool f return true; } +static void read_bess_buffer(const BESS_buffer_t *buffer, virtual_file_t *file, uint8_t *dest, size_t max_size) +{ + size_t pos = file->tell(file); + file->seek(file, BESS32(buffer->offset), SEEK_SET); + file->read(file, dest, MIN(BESS32(buffer->size), max_size)); + file->seek(file, pos, SEEK_SET); +} + +static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_sameboy) +{ + char emulator_name[65] = {0,}; + file->seek(file, -sizeof(BESS_footer_t), SEEK_END); + BESS_footer_t footer = {0, }; + file->read(file, &footer, sizeof(footer)); + if (footer.magic != htonl('BESS')) { + // Not a BESS file + if (!is_sameboy) { + GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); + } + return -1; + } + + GB_gameboy_t save; + GB_init(&save, gb->model); + save.cartridge_type = gb->cartridge_type; + + file->seek(file, BESS32(footer.start_offset), SEEK_SET); + bool found_core = false; + BESS_CORE_t core; + while (true) { + BESS_block_t block; + if (file->read(file, &block, sizeof(block)) != sizeof(block)) goto error; + switch (block.magic) { + case htonl('CORE'): + if (found_core) goto parse_error; + found_core = true; + if (BESS32(block.size) > sizeof(core) - sizeof(block)) { + if (file->read(file, &core.header + 1, sizeof(core) - sizeof(block)) != sizeof(core) - sizeof(block)) goto error; + file->seek(file, BESS32(block.size) - (sizeof(core) - sizeof(block)), SEEK_CUR); + } + else { + if (file->read(file, &core.header + 1, BESS32(block.size)) != BESS32(block.size)) goto error; + } + + if (core.major != BESS16(1)) { + GB_log(gb, "This save state uses an incompatible version of the BESS specification"); + GB_free(&save); + return -1; + } + + switch (core.family) { + case 'C': + if (!GB_is_cgb(&save)) goto wrong_model; + break; + case 'S': + if (!GB_is_sgb(&save)) goto wrong_model; + break; + case 'G': + if (GB_is_cgb(&save) || GB_is_sgb(&save)) goto wrong_model; + break; + default: + wrong_model: + GB_log(gb, "The save state is for a different model. Try changing the emulated model.\n"); + GB_free(&save); + return -1; + } + + + save.pc = BESS16(core.pc); + save.af = BESS16(core.af); + save.bc = BESS16(core.bc); + save.de = BESS16(core.de); + save.hl = BESS16(core.hl); + save.sp = BESS16(core.sp); + + save.ime = core.ime; + save.interrupt_enable = core.ie; + + save.halted = core.execution_mode == 1; + save.stopped = core.execution_mode == 2; + + memcpy(save.hram, core.hram, sizeof(save.hram)); + + // CPU related + + // Determines DMG mode + GB_write_memory(&save, 0xFF00 + GB_IO_KEY0, core.io_registers[GB_IO_KEY0]); + save.boot_rom_finished = core.io_registers[GB_IO_BANK]; + GB_write_memory(&save, 0xFF00 + GB_IO_KEY1, core.io_registers[GB_IO_KEY1]); + if (save.cgb_mode) { + save.cgb_double_speed = core.io_registers[GB_IO_KEY1] & 0x80; + save.object_priority = GB_OBJECT_PRIORITY_INDEX; + } + else { + save.object_priority = GB_OBJECT_PRIORITY_X; + } + + // Timers, Joypad and Serial + GB_write_memory(&save, 0xFF00 + GB_IO_JOYP, core.io_registers[GB_IO_JOYP]); + GB_write_memory(&save, 0xFF00 + GB_IO_SB, core.io_registers[GB_IO_SB]); + save.io_registers[GB_IO_SC] = core.io_registers[GB_IO_SC]; + save.div_counter = core.io_registers[GB_IO_DIV] << 8; + GB_write_memory(&save, 0xFF00 + GB_IO_TIMA, core.io_registers[GB_IO_TIMA]); + GB_write_memory(&save, 0xFF00 + GB_IO_TMA, core.io_registers[GB_IO_TMA]); + GB_write_memory(&save, 0xFF00 + GB_IO_TAC, core.io_registers[GB_IO_TAC]); + + // APU + GB_write_memory(&save, 0xFF00 + GB_IO_NR52, core.io_registers[GB_IO_NR52]); + for (unsigned i = GB_IO_NR10; i < GB_IO_NR52; i++) { + uint8_t value = core.io_registers[i]; + if (i == GB_IO_NR14 || i == GB_IO_NR24 || i == GB_IO_NR34 || i == GB_IO_NR44) { + value &= ~0x80; + } + GB_write_memory(&save, 0xFF00 + i, value); + } + + for (unsigned i = GB_IO_WAV_START; i <= GB_IO_WAV_END; i++) { + GB_write_memory(&save, 0xFF00 + i, core.io_registers[i]); + } + + // PPU + GB_write_memory(&save, 0xFF00 + GB_IO_LCDC, core.io_registers[GB_IO_LCDC]); + GB_write_memory(&save, 0xFF00 + GB_IO_STAT, core.io_registers[GB_IO_STAT]); + GB_write_memory(&save, 0xFF00 + GB_IO_SCY, core.io_registers[GB_IO_SCY]); + GB_write_memory(&save, 0xFF00 + GB_IO_SCX, core.io_registers[GB_IO_SCX]); + GB_write_memory(&save, 0xFF00 + GB_IO_LYC, core.io_registers[GB_IO_LYC]); + save.io_registers[GB_IO_DMA] = core.io_registers[GB_IO_DMA]; + GB_write_memory(&save, 0xFF00 + GB_IO_BGP, core.io_registers[GB_IO_BGP]); + GB_write_memory(&save, 0xFF00 + GB_IO_OBP0, core.io_registers[GB_IO_OBP0]); + GB_write_memory(&save, 0xFF00 + GB_IO_OBP1, core.io_registers[GB_IO_OBP1]); + GB_write_memory(&save, 0xFF00 + GB_IO_WX, core.io_registers[GB_IO_WX]); + GB_write_memory(&save, 0xFF00 + GB_IO_WY, core.io_registers[GB_IO_WY]); + + // Other registers + GB_write_memory(&save, 0xFF00 + GB_IO_VBK, core.io_registers[GB_IO_VBK]); + GB_write_memory(&save, 0xFF00 + GB_IO_HDMA1, core.io_registers[GB_IO_HDMA1]); + GB_write_memory(&save, 0xFF00 + GB_IO_HDMA2, core.io_registers[GB_IO_HDMA2]); + GB_write_memory(&save, 0xFF00 + GB_IO_HDMA3, core.io_registers[GB_IO_HDMA3]); + GB_write_memory(&save, 0xFF00 + GB_IO_HDMA4, core.io_registers[GB_IO_HDMA4]); + GB_write_memory(&save, 0xFF00 + GB_IO_RP, core.io_registers[GB_IO_RP]); + GB_write_memory(&save, 0xFF00 + GB_IO_BGPI, core.io_registers[GB_IO_BGPI]); + GB_write_memory(&save, 0xFF00 + GB_IO_OBPI, core.io_registers[GB_IO_OBPI]); + GB_write_memory(&save, 0xFF00 + GB_IO_OPRI, core.io_registers[GB_IO_OPRI]); + GB_write_memory(&save, 0xFF00 + GB_IO_SVBK, core.io_registers[GB_IO_SVBK]); + + // Interrupts + GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]); + + break; + case htonl('NAME'): + if (!found_core) goto parse_error; + if (BESS32(block.size) > sizeof(emulator_name) - 1) { + file->seek(file, BESS32(block.size), SEEK_CUR); + } + else { + file->read(file, emulator_name, BESS32(block.size)); + } + break; + case htonl('OAM '): + if (!found_core) goto parse_error; + if (BESS32(block.size) != 256 && BESS32(block.size) != 160) goto parse_error; + file->read(file, save.oam, sizeof(save.oam)); + if (BESS32(block.size) == 256) { + file->read(file, save.extra_oam, sizeof(save.extra_oam)); + } + break; + case htonl('PALS'): + if (!found_core) goto parse_error; + if (BESS32(block.size) != sizeof(BESS_PALS_t) - sizeof(block)) goto parse_error; + file->read(file, save.background_palettes_data, sizeof(save.background_palettes_data)); + file->read(file, save.sprite_palettes_data, sizeof(save.sprite_palettes_data)); + break; + case htonl('MBC '): + if (!found_core) goto parse_error; + if (BESS32(block.size) % 3 != 0) goto parse_error; + for (unsigned i = BESS32(block.size); i > 0; i -= 3) { + BESS_MBC_pair_t pair; + file->read(file, &pair, sizeof(pair)); + if (BESS16(pair.address) >= 0x8000) goto parse_error; + GB_write_memory(&save, BESS16(pair.address), pair.value); + } + break; + case htonl('END '): + if (!found_core) goto parse_error; + if (BESS32(block.size) != 0) goto parse_error; + goto done; + default: + file->seek(file, BESS32(block.size), SEEK_CUR); + break; + } + } +done: + save.mbc_ram_size = gb->mbc_ram_size; + memcpy(gb, &save, GB_SECTION_OFFSET(unsaved)); + assert(GB_get_save_state_size(gb) == GB_get_save_state_size(&save)); + GB_free(&save); + read_bess_buffer(&core.ram, file, gb->ram, gb->ram_size); + read_bess_buffer(&core.vram, file, gb->vram, gb->vram_size); + read_bess_buffer(&core.mbc_ram, file, gb->mbc_ram, gb->mbc_ram_size); + if (emulator_name[0]) { + GB_log(gb, "Save state imported from %s.\n", emulator_name); + } + else { + GB_log(gb, "Save state imported from another emulator.\n"); // SameBoy always contains a NAME block + } + for (unsigned i = 0; i < 32; i++) { + GB_palette_changed(gb, false, i * 2); + GB_palette_changed(gb, true, i * 2); + } + return 0; +parse_error: + errno = -1; +error: + if (emulator_name[0]) { + GB_log(gb, "Attempted to import a save state from %s, but the save state is invalid.\n", emulator_name); + } + else { + GB_log(gb, "Attempted to import a save state from a different emulator or incompatible version, but the save state is invalid.\n"); + } + GB_free(&save); + return errno; +} + static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) { GB_gameboy_t save; @@ -304,8 +836,7 @@ static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) fix_broken_windows_saves = true; } if (gb->magic != save.magic) { - GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); - return false; + return load_bess_save(gb, file, false); } #define READ_SECTION(gb, file, section) read_section(file, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves) if (!READ_SECTION(&save, file, core_state)) return errno ?: EIO; @@ -319,7 +850,11 @@ static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) #undef READ_SECTION - if (!verify_and_update_state_compatibility(gb, &save)) { + bool attempt_bess = false; + if (!verify_and_update_state_compatibility(gb, &save, &attempt_bess)) { + if (attempt_bess) { + return load_bess_save(gb, file, true); + } return errno; } @@ -362,6 +897,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) virtual_file_t file = { .read = file_read, .seek = file_seek, + .tell = file_tell, .file = f, }; int ret = load_state_internal(gb, &file); @@ -374,6 +910,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le virtual_file_t file = { .read = buffer_read, .seek = buffer_seek, + .tell = buffer_tell, .buffer = (uint8_t *)buffer, .position = 0, .size = length, From a52302f2f682c7713fd44a783b151433e37523b1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 24 Mar 2021 23:22:24 +0200 Subject: [PATCH 141/365] Make NAME come before CORE --- Core/save_state.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index 205fdd1..bc5ca12 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -418,6 +418,18 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) .magic = htonl('BESS'), }; + /* BESS NAME */ + + static const BESS_block_t bess_name = {htonl('NAME'), BESS32(sizeof(BESS_NAME) - 1)}; + + if (file->write(file, &bess_name, sizeof(bess_name)) != sizeof(bess_name)) { + goto error; + } + + if (file->write(file, BESS_NAME, sizeof(BESS_NAME) - 1) != sizeof(BESS_NAME) - 1) { + goto error; + } + /* BESS CORE */ bess_core.header = (BESS_block_t){htonl('CORE'), BESS32(sizeof(bess_core) - sizeof(bess_core.header))}; @@ -472,19 +484,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (file->write(file, &bess_core, sizeof(bess_core)) != sizeof(bess_core)) { goto error; } - - /* BESS NAME */ - - static const BESS_block_t bess_name = {htonl('NAME'), BESS32(sizeof(BESS_NAME) - 1)}; - - if (file->write(file, &bess_name, sizeof(bess_name)) != sizeof(bess_name)) { - goto error; - } - - if (file->write(file, BESS_NAME, sizeof(BESS_NAME) - 1) != sizeof(BESS_NAME) - 1) { - goto error; - } - + /* BESS OAM */ BESS_OAM_t bess_oam; @@ -743,7 +743,6 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo break; case htonl('NAME'): - if (!found_core) goto parse_error; if (BESS32(block.size) > sizeof(emulator_name) - 1) { file->seek(file, BESS32(block.size), SEEK_CUR); } From 659f9540288e793aad99e9592e1ace927f4ca96c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Mar 2021 00:04:47 +0200 Subject: [PATCH 142/365] RTC support --- Core/save_state.c | 63 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index bc5ca12..f407d91 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -70,6 +70,24 @@ typedef struct __attribute__((packed)) { uint8_t sprite_palettes[0x40]; } BESS_PALS_t; +/* Same RTC format as used by VBA, BGB and SameBoy in battery saves*/ +typedef struct __attribute__((packed)){ + BESS_block_t header; + struct { + uint8_t seconds; + uint8_t padding1[3]; + uint8_t minutes; + uint8_t padding2[3]; + uint8_t hours; + uint8_t padding3[3]; + uint8_t days; + uint8_t padding4[3]; + uint8_t high; + uint8_t padding5[3]; + } real, latched; + uint64_t last_rtc_second; +} BESS_RTC_t; + typedef struct __attribute__((packed)) { uint16_t address; uint8_t value; @@ -79,9 +97,11 @@ typedef struct __attribute__((packed)) { #ifdef GB_BIG_ENDIAN #define BESS16(x) __builtin_bswap16(x) #define BESS32(x) __builtin_bswap32(x) +#define BESS64(x) __builtin_bswap64(x) #else #define BESS16(x) (x) #define BESS32(x) (x) +#define BESS64(x) (x) #endif typedef struct virtual_file_s virtual_file_t; @@ -181,7 +201,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) case GB_MBC2: return sizeof(BESS_block_t) + 2 * sizeof(BESS_MBC_pair_t); case GB_MBC3: - return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); + return sizeof(BESS_block_t) + 4 * 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_HUC1: @@ -212,7 +232,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + sizeof(BESS_NAME) - 1 + sizeof(BESS_OAM_t) + (GB_is_cgb(gb)? sizeof(BESS_PALS_t) : 0) - + bess_size_for_cartridge(gb->cartridge_type) // MBC block + + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC block + sizeof(BESS_block_t) // END block + sizeof(BESS_footer_t); } @@ -497,6 +517,24 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) } save_bess_mbc_block(gb, file); + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { + BESS_RTC_t bess_rtc = {0,}; + bess_rtc.header = (BESS_block_t){htonl('RTC '), BESS32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; + bess_rtc.real.seconds = gb->rtc_real.seconds; + bess_rtc.real.minutes = gb->rtc_real.minutes; + bess_rtc.real.hours = gb->rtc_real.hours; + bess_rtc.real.days = gb->rtc_real.days; + bess_rtc.real.high = gb->rtc_real.high; + bess_rtc.latched.seconds = gb->rtc_latched.seconds; + bess_rtc.latched.minutes = gb->rtc_latched.minutes; + bess_rtc.latched.hours = gb->rtc_latched.hours; + bess_rtc.latched.days = gb->rtc_latched.days; + bess_rtc.latched.high = gb->rtc_latched.high; + bess_rtc.last_rtc_second = BESS64(gb->last_rtc_second); + if (file->write(file, &bess_rtc, sizeof(bess_rtc)) != sizeof(bess_rtc)) { + goto error; + } + } if (GB_is_cgb(gb)) { /* BESS PALS */ @@ -621,7 +659,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo file->seek(file, BESS32(footer.start_offset), SEEK_SET); bool found_core = false; - BESS_CORE_t core; + BESS_CORE_t core = {0,}; while (true) { BESS_block_t block; if (file->read(file, &block, sizeof(block)) != sizeof(block)) goto error; @@ -774,6 +812,25 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo GB_write_memory(&save, BESS16(pair.address), pair.value); } break; + case htonl('RTC '): + if (!found_core) goto parse_error; + BESS_RTC_t bess_rtc; + if (BESS32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error; + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { + if (file->read(file, &bess_rtc.header + 1, BESS32(block.size)) != BESS32(block.size)) goto error; + gb->rtc_real.seconds = bess_rtc.real.seconds; + gb->rtc_real.minutes = bess_rtc.real.minutes; + gb->rtc_real.hours = bess_rtc.real.hours; + gb->rtc_real.days = bess_rtc.real.days; + gb->rtc_real.high = bess_rtc.real.high; + gb->rtc_latched.seconds = bess_rtc.latched.seconds; + gb->rtc_latched.minutes = bess_rtc.latched.minutes; + gb->rtc_latched.hours = bess_rtc.latched.hours; + gb->rtc_latched.days = bess_rtc.latched.days; + gb->rtc_latched.high = bess_rtc.latched.high; + gb->last_rtc_second = BESS64(bess_rtc.last_rtc_second); + } + break; case htonl('END '): if (!found_core) goto parse_error; if (BESS32(block.size) != 0) goto parse_error; From aca2fd04b16b169889f223fd3ac7cce4b56ef8dc Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 25 Mar 2021 19:17:45 +0100 Subject: [PATCH 143/365] replace PREFIX/share/sameboy with DATA_DIR --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 3d9b2cb..c217102 100644 --- a/Makefile +++ b/Makefile @@ -439,9 +439,9 @@ ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop -@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX)) - mkdir -p $(DESTDIR)$(PREFIX)/share/sameboy/ $(DESTDIR)$(PREFIX)/bin/ - cp -rf $(BIN)/SDL/* $(DESTDIR)$(PREFIX)/share/sameboy/ - mv $(DESTDIR)$(PREFIX)/share/sameboy/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy + mkdir -p $(DESTDIR)$(DATA_DIR)/ $(DESTDIR)$(PREFIX)/bin/ + cp -rf $(BIN)/SDL/* $(DESTDIR)$(DATA_DIR)/ + mv $(DESTDIR)$(PREFIX)$(DATA_DIR)/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) -update-mime-database -n $(PREFIX)/share/mime -xdg-desktop-menu install --novendor --mode system FreeDesktop/sameboy.desktop From 8adaba237e760a22c42270228cc93862d0f73464 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 29 Mar 2021 02:47:57 +0300 Subject: [PATCH 144/365] SGB support in BESS, BE fixes, changes to SGB save state format on BE machines --- Core/display.c | 12 +- Core/gb.c | 9 ++ Core/gb.h | 19 +++ Core/save_state.c | 307 +++++++++++++++++++++++++++++++--------------- Core/sgb.c | 63 +++++----- Core/sgb.h | 4 + Makefile | 2 +- Tester/main.c | 7 ++ 8 files changed, 286 insertions(+), 137 deletions(-) diff --git a/Core/display.c b/Core/display.c index 72e89c8..8fa749c 100644 --- a/Core/display.c +++ b/Core/display.c @@ -158,20 +158,20 @@ static void display_vblank(GB_gameboy_t *gb) uint32_t border_colors[16 * 4]; if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) { - static uint16_t colors[] = { + 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; - gb->borrowed_border.palette[0] = colors[index]; - gb->borrowed_border.palette[10] = colors[5 + index]; - gb->borrowed_border.palette[14] = colors[10 + index]; + gb->borrowed_border.palette[0] = LE16(colors[index]); + gb->borrowed_border.palette[10] = LE16(colors[5 + index]); + gb->borrowed_border.palette[14] = LE16(colors[10 + index]); } for (unsigned i = 0; i < 16 * 4; i++) { - border_colors[i] = GB_convert_rgb15(gb, gb->borrowed_border.palette[i], true); + border_colors[i] = GB_convert_rgb15(gb, LE16(gb->borrowed_border.palette[i]), true); } for (unsigned tile_y = 0; tile_y < 28; tile_y++) { @@ -179,7 +179,7 @@ static void display_vblank(GB_gameboy_t *gb) if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) { continue; } - uint16_t tile = gb->borrowed_border.map[tile_x + tile_y * 32]; + uint16_t tile = LE16(gb->borrowed_border.map[tile_x + tile_y * 32]); uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; uint8_t palette = (tile >> 10) & 3; diff --git a/Core/gb.c b/Core/gb.c index 3a0864d..eca10ec 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -130,6 +130,15 @@ static void load_default_border(GB_gameboy_t *gb) }\ } while (false); +#ifdef GB_BIG_ENDIAN + for (unsigned i = 0; i < sizeof(gb->borrowed_border.map) / 2; i++) { + gb->borrowed_border.map[i] = LE16(gb->borrowed_border.map[i]); + } + for (unsigned i = 0; i < sizeof(gb->borrowed_border.palette) / 2; i++) { + gb->borrowed_border.palette[i] = LE16(gb->borrowed_border.palette[i]); + } +#endif + if (gb->model == GB_MODEL_AGB) { #include "graphics/agb_border.inc" LOAD_BORDER(); diff --git a/Core/gb.h b/Core/gb.h index 7210e7a..065e176 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -56,6 +56,25 @@ #error Unable to detect endianess #endif +#ifdef GB_INTERNAL +/* Todo: similar macros are everywhere, clean this up and remove direct calls to bswap */ +#ifdef GB_BIG_ENDIAN +#define LE16(x) __builtin_bswap16(x) +#define LE32(x) __builtin_bswap32(x) +#define LE64(x) __builtin_bswap64(x) +#define BE16(x) (x) +#define BE32(x) (x) +#define BE64(x) (x) +#else +#define LE16(x) (x) +#define LE32(x) (x) +#define LE64(x) (x) +#define BE16(x) __builtin_bswap16(x) +#define BE32(x) __builtin_bswap32(x) +#define BE64(x) __builtin_bswap64(x) +#endif +#endif + #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) #define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; }) #endif diff --git a/Core/save_state.c b/Core/save_state.c index f407d91..81f4dd6 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -6,7 +6,7 @@ #define str(x) #x #define xstr(x) str(x) #ifdef GB_BIG_ENDIAN -#define BESS_NAME "SameBoy v" xstr(VERSION) "(Big Endian)" +#define BESS_NAME "SameBoy v" xstr(VERSION) " (Big Endian)" #else #define BESS_NAME "SameBoy v" xstr(VERSION) #endif @@ -70,6 +70,20 @@ typedef struct __attribute__((packed)) { uint8_t sprite_palettes[0x40]; } BESS_PALS_t; +typedef struct __attribute__((packed)) { + BESS_block_t header; + BESS_buffer_t border_tiles; + BESS_buffer_t border_tilemap; + BESS_buffer_t border_palettes; + + BESS_buffer_t active_palettes; + BESS_buffer_t ram_palettes; + BESS_buffer_t attribute_map; + BESS_buffer_t attribute_files; + + uint8_t multiplayer_state; +} BESS_SGB_t; + /* Same RTC format as used by VBA, BGB and SameBoy in battery saves*/ typedef struct __attribute__((packed)){ BESS_block_t header; @@ -93,17 +107,6 @@ typedef struct __attribute__((packed)) { uint8_t value; } BESS_MBC_pair_t; - -#ifdef GB_BIG_ENDIAN -#define BESS16(x) __builtin_bswap16(x) -#define BESS32(x) __builtin_bswap32(x) -#define BESS64(x) __builtin_bswap64(x) -#else -#define BESS16(x) (x) -#define BESS32(x) (x) -#define BESS64(x) (x) -#endif - typedef struct virtual_file_s virtual_file_t; struct virtual_file_s { @@ -232,6 +235,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + sizeof(BESS_NAME) - 1 + sizeof(BESS_OAM_t) + (GB_is_cgb(gb)? sizeof(BESS_PALS_t) : 0) + + (gb->sgb? sizeof(BESS_SGB_t) : 0) + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC block + sizeof(BESS_block_t) // END block + sizeof(BESS_footer_t); @@ -320,6 +324,25 @@ static void sanitize_state(GB_gameboy_t *gb) if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; } +#ifdef GB_BIG_ENDIAN + if (gb->sgb && !gb->sgb->little_endian) { + for (unsigned i = 0; i < sizeof(gb->sgb->border.raw_data) / 2; i++) { + gb->sgb->border.raw_data[i] = LE16(gb->sgb->border.raw_data[i]); + } + + for (unsigned i = 0; i < sizeof(gb->sgb->pending_border.raw_data) / 2; i++) { + gb->sgb->pending_border.raw_data[i] = LE16(gb->sgb->pending_border.raw_data[i]); + } + + for (unsigned i = 0; i < sizeof(gb->sgb->effective_palettes) / 2; i++) { + gb->sgb->effective_palettes[i] = LE16(gb->sgb->effective_palettes[i]); + } + + for (unsigned i = 0; i < sizeof(gb->sgb->ram_palettes) / 2; i++) { + gb->sgb->ram_palettes[i] = LE16(gb->sgb->ram_palettes[i]); + } + } +#endif } static bool dump_section(virtual_file_t *file, const void *src, uint32_t size) @@ -340,57 +363,59 @@ static bool dump_section(virtual_file_t *file, const void *src, uint32_t size) static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) { - BESS_block_t mbc_block = {htonl('MBC '), 0}; + BESS_block_t mbc_block = {BE32('MBC '), 0}; BESS_MBC_pair_t pairs[4]; switch (gb->cartridge_type->mbc_type) { default: case GB_NO_MBC: return 0; case GB_MBC1: - pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; - pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->mbc1.bank_low}; - pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->mbc1.bank_high}; - pairs[3] = (BESS_MBC_pair_t){BESS16(0x6000), gb->mbc1.mode}; + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc1.bank_low}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc1.bank_high}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x6000), gb->mbc1.mode}; mbc_block.size = 4 * sizeof(pairs[0]); break; case GB_MBC2: - pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; - pairs[1] = (BESS_MBC_pair_t){BESS16(0x0100), gb->mbc2.rom_bank}; + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x0100), gb->mbc2.rom_bank}; mbc_block.size = 2 * sizeof(pairs[0]); break; case GB_MBC3: - pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; - pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->mbc3.rom_bank}; - pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->mbc3.ram_bank | (gb->mbc3_rtc_mapped? 8 : 0)}; - pairs[3] = (BESS_MBC_pair_t){BESS16(0x6000), gb->rtc_latch}; + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc3.rom_bank}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc3.ram_bank | (gb->mbc3_rtc_mapped? 8 : 0)}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x6000), gb->rtc_latch}; mbc_block.size = 4 * sizeof(pairs[0]); break; case GB_MBC5: - pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; - pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->mbc5.rom_bank_low}; - pairs[2] = (BESS_MBC_pair_t){BESS16(0x3000), gb->mbc5.rom_bank_high}; - pairs[3] = (BESS_MBC_pair_t){BESS16(0x4000), gb->mbc5.ram_bank}; + 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(0x3000), gb->mbc5.rom_bank_high}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank}; mbc_block.size = 4 * sizeof(pairs[0]); break; case GB_HUC1: - pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->huc1.ir_mode? 0xE : 0x0}; - pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->huc1.bank_low}; - pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->huc1.bank_high}; - pairs[3] = (BESS_MBC_pair_t){BESS16(0x6000), gb->huc1.mode}; + 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]); case GB_HUC3: - pairs[0] = (BESS_MBC_pair_t){BESS16(0x0000), gb->huc3_mode}; - pairs[1] = (BESS_MBC_pair_t){BESS16(0x2000), gb->huc3.rom_bank}; - pairs[2] = (BESS_MBC_pair_t){BESS16(0x4000), gb->huc3.ram_bank}; + 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; } + mbc_block.size = LE32(mbc_block.size); + if (file->write(file, &mbc_block, sizeof(mbc_block)) != sizeof(mbc_block)) { return errno; } - if (file->write(file, &pairs, mbc_block.size) != mbc_block.size) { + if (file->write(file, &pairs, LE32(mbc_block.size)) != LE32(mbc_block.size)) { return errno; } @@ -409,38 +434,42 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (!DUMP_SECTION(gb, file, rtc )) goto error; if (!DUMP_SECTION(gb, file, video )) goto error; + uint32_t sgb_offset = 0; + if (GB_is_hle_sgb(gb)) { + gb->sgb->little_endian = true; + sgb_offset = file->tell(file) + 4; if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; } BESS_CORE_t bess_core; - bess_core.mbc_ram.offset = file->tell(file); - bess_core.mbc_ram.size = gb->mbc_ram_size; + bess_core.mbc_ram.offset = LE32(file->tell(file)); + bess_core.mbc_ram.size = LE32(gb->mbc_ram_size); if (file->write(file, gb->mbc_ram, gb->mbc_ram_size) != gb->mbc_ram_size) { goto error; } - bess_core.ram.offset = file->tell(file); - bess_core.ram.size = gb->ram_size; + bess_core.ram.offset = LE32(file->tell(file)); + bess_core.ram.size = LE32(gb->ram_size); if (file->write(file, gb->ram, gb->ram_size) != gb->ram_size) { goto error; } - bess_core.vram.offset = file->tell(file); - bess_core.vram.size = gb->vram_size; + bess_core.vram.offset = LE32(file->tell(file)); + bess_core.vram.size = LE32(gb->vram_size); if (file->write(file, gb->vram, gb->vram_size) != gb->vram_size) { goto error; } BESS_footer_t bess_footer = { - .start_offset = file->tell(file), - .magic = htonl('BESS'), + .start_offset = LE32(file->tell(file)), + .magic = BE32('BESS'), }; /* BESS NAME */ - static const BESS_block_t bess_name = {htonl('NAME'), BESS32(sizeof(BESS_NAME) - 1)}; + static const BESS_block_t bess_name = {BE32('NAME'), LE32(sizeof(BESS_NAME) - 1)}; if (file->write(file, &bess_name, sizeof(bess_name)) != sizeof(bess_name)) { goto error; @@ -452,37 +481,37 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) /* BESS CORE */ - bess_core.header = (BESS_block_t){htonl('CORE'), BESS32(sizeof(bess_core) - sizeof(bess_core.header))}; - bess_core.major = BESS16(1); - bess_core.minor = BESS16(1); + bess_core.header = (BESS_block_t){BE32('CORE'), LE32(sizeof(bess_core) - sizeof(bess_core.header))}; + bess_core.major = LE16(1); + bess_core.minor = LE16(1); switch (gb->model) { - case GB_MODEL_DMG_B: bess_core.full_model = htonl('GDB '); break; + case GB_MODEL_DMG_B: bess_core.full_model = BE32('GDB '); break; case GB_MODEL_SGB_NTSC: case GB_MODEL_SGB_NTSC_NO_SFC: - bess_core.full_model = htonl('SN '); break; + bess_core.full_model = BE32('SN '); break; case GB_MODEL_SGB_PAL_NO_SFC: case GB_MODEL_SGB_PAL: - bess_core.full_model = htonl('SP '); break; + bess_core.full_model = BE32('SP '); break; case GB_MODEL_SGB2_NO_SFC: case GB_MODEL_SGB2: - bess_core.full_model = htonl('S2 '); break; + bess_core.full_model = BE32('S2 '); break; - case GB_MODEL_CGB_C: bess_core.full_model = htonl('CCC '); break; - case GB_MODEL_CGB_E: bess_core.full_model = htonl('CCE '); break; - case GB_MODEL_AGB: bess_core.full_model = htonl('CA '); break; // SameBoy doesn't emulate a specific AGB revision yet + case GB_MODEL_CGB_C: bess_core.full_model = BE32('CCC '); 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 } - bess_core.pc = BESS16(gb->pc); - bess_core.af = BESS16(gb->af); - bess_core.bc = BESS16(gb->bc); - bess_core.de = BESS16(gb->de); - bess_core.hl = BESS16(gb->hl); - bess_core.sp = BESS16(gb->sp); + bess_core.pc = LE16(gb->pc); + bess_core.af = LE16(gb->af); + bess_core.bc = LE16(gb->bc); + bess_core.de = LE16(gb->de); + bess_core.hl = LE16(gb->hl); + bess_core.sp = LE16(gb->sp); bess_core.ime = gb->ime; bess_core.ie = gb->interrupt_enable; @@ -508,7 +537,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) /* BESS OAM */ BESS_OAM_t bess_oam; - bess_oam.header = (BESS_block_t){htonl('OAM '), BESS32(sizeof(bess_oam) - sizeof(bess_oam.header))}; + bess_oam.header = (BESS_block_t){BE32('OAM '), LE32(sizeof(bess_oam) - sizeof(bess_oam.header))}; memcpy(bess_oam.oam, gb->oam, sizeof(gb->oam)); memcpy(bess_oam.oam + sizeof(gb->oam), gb->extra_oam, sizeof(gb->extra_oam)); @@ -519,7 +548,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) save_bess_mbc_block(gb, file); if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { BESS_RTC_t bess_rtc = {0,}; - bess_rtc.header = (BESS_block_t){htonl('RTC '), BESS32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; + bess_rtc.header = (BESS_block_t){BE32('RTC '), LE32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; bess_rtc.real.seconds = gb->rtc_real.seconds; bess_rtc.real.minutes = gb->rtc_real.minutes; bess_rtc.real.hours = gb->rtc_real.hours; @@ -530,7 +559,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) bess_rtc.latched.hours = gb->rtc_latched.hours; bess_rtc.latched.days = gb->rtc_latched.days; bess_rtc.latched.high = gb->rtc_latched.high; - bess_rtc.last_rtc_second = BESS64(gb->last_rtc_second); + bess_rtc.last_rtc_second = LE64(gb->last_rtc_second); if (file->write(file, &bess_rtc, sizeof(bess_rtc)) != sizeof(bess_rtc)) { goto error; } @@ -540,7 +569,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) /* BESS PALS */ BESS_PALS_t bess_pals; - bess_pals.header = (BESS_block_t){htonl('PALS'), BESS32(sizeof(bess_pals) - sizeof(bess_oam.header))}; + bess_pals.header = (BESS_block_t){BE32('PALS'), LE32(sizeof(bess_pals) - sizeof(bess_oam.header))}; memcpy(bess_pals.background_palettes, gb->background_palettes_data, sizeof(bess_pals.background_palettes)); memcpy(bess_pals.sprite_palettes, gb->sprite_palettes_data, sizeof(bess_pals.sprite_palettes)); @@ -549,14 +578,51 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) } } + bool needs_sgb_padding = false; + if (gb->sgb) { + /* BESS SGB */ + if (gb->sgb->disable_commands) { + needs_sgb_padding = true; + } + else { + BESS_SGB_t bess_sgb = {{BE32('SGB '), LE32(sizeof(bess_sgb) - sizeof(bess_sgb.header))}, }; + + bess_sgb.border_tiles = (BESS_buffer_t){LE32(sizeof(gb->sgb->pending_border.tiles)), + LE32(sgb_offset + offsetof(GB_sgb_t, pending_border.tiles))}; + bess_sgb.border_tilemap = (BESS_buffer_t){LE32(sizeof(gb->sgb->pending_border.map)), + LE32(sgb_offset + offsetof(GB_sgb_t, pending_border.map))}; + bess_sgb.border_palettes = (BESS_buffer_t){LE32(sizeof(gb->sgb->pending_border.palette)), + LE32(sgb_offset + offsetof(GB_sgb_t, pending_border.palette))}; + + bess_sgb.active_palettes = (BESS_buffer_t){LE32(sizeof(gb->sgb->effective_palettes)), + LE32(sgb_offset + offsetof(GB_sgb_t, effective_palettes))}; + bess_sgb.ram_palettes = (BESS_buffer_t){LE32(sizeof(gb->sgb->ram_palettes)), + LE32(sgb_offset + offsetof(GB_sgb_t, ram_palettes))}; + bess_sgb.attribute_map = (BESS_buffer_t){LE32(sizeof(gb->sgb->attribute_map)), + LE32(sgb_offset + offsetof(GB_sgb_t, attribute_map))}; + bess_sgb.attribute_files = (BESS_buffer_t){LE32(sizeof(gb->sgb->attribute_files)), + LE32(sgb_offset + offsetof(GB_sgb_t, attribute_files))}; + + bess_sgb.multiplayer_state = (gb->sgb->player_count << 4) | (gb->sgb->current_player & (gb->sgb->player_count - 1)); + if (file->write(file, &bess_sgb, sizeof(bess_sgb)) != sizeof(bess_sgb)) { + goto error; + } + } + } + /* BESS END */ - static const BESS_block_t bess_end = {htonl('END '), 0}; + static const BESS_block_t bess_end = {BE32('END '), 0}; if (file->write(file, &bess_end, sizeof(bess_end)) != sizeof(bess_end)) { goto error; } + if (needs_sgb_padding) { + static const uint8_t sgb_padding[sizeof(BESS_SGB_t)] = {0,}; + file->write(file, sgb_padding, sizeof(sgb_padding)); + } + /* BESS Footer */ if (file->write(file, &bess_footer, sizeof(bess_footer)) != sizeof(bess_footer)) { @@ -634,8 +700,8 @@ static bool read_section(virtual_file_t *file, void *dest, uint32_t size, bool f static void read_bess_buffer(const BESS_buffer_t *buffer, virtual_file_t *file, uint8_t *dest, size_t max_size) { size_t pos = file->tell(file); - file->seek(file, BESS32(buffer->offset), SEEK_SET); - file->read(file, dest, MIN(BESS32(buffer->size), max_size)); + file->seek(file, LE32(buffer->offset), SEEK_SET); + file->read(file, dest, MIN(LE32(buffer->size), max_size)); file->seek(file, pos, SEEK_SET); } @@ -645,7 +711,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo file->seek(file, -sizeof(BESS_footer_t), SEEK_END); BESS_footer_t footer = {0, }; file->read(file, &footer, sizeof(footer)); - if (footer.magic != htonl('BESS')) { + if (footer.magic != BE32('BESS')) { // Not a BESS file if (!is_sameboy) { GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); @@ -657,25 +723,27 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo GB_init(&save, gb->model); save.cartridge_type = gb->cartridge_type; - file->seek(file, BESS32(footer.start_offset), SEEK_SET); + file->seek(file, LE32(footer.start_offset), SEEK_SET); bool found_core = false; BESS_CORE_t core = {0,}; + bool found_sgb = false; + BESS_SGB_t sgb = {0,}; while (true) { BESS_block_t block; if (file->read(file, &block, sizeof(block)) != sizeof(block)) goto error; switch (block.magic) { - case htonl('CORE'): + case BE32('CORE'): if (found_core) goto parse_error; found_core = true; - if (BESS32(block.size) > sizeof(core) - sizeof(block)) { + if (LE32(block.size) > sizeof(core) - sizeof(block)) { if (file->read(file, &core.header + 1, sizeof(core) - sizeof(block)) != sizeof(core) - sizeof(block)) goto error; - file->seek(file, BESS32(block.size) - (sizeof(core) - sizeof(block)), SEEK_CUR); + file->seek(file, LE32(block.size) - (sizeof(core) - sizeof(block)), SEEK_CUR); } else { - if (file->read(file, &core.header + 1, BESS32(block.size)) != BESS32(block.size)) goto error; + if (file->read(file, &core.header + 1, LE32(block.size)) != LE32(block.size)) goto error; } - if (core.major != BESS16(1)) { + if (core.major != LE16(1)) { GB_log(gb, "This save state uses an incompatible version of the BESS specification"); GB_free(&save); return -1; @@ -699,12 +767,12 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo } - save.pc = BESS16(core.pc); - save.af = BESS16(core.af); - save.bc = BESS16(core.bc); - save.de = BESS16(core.de); - save.hl = BESS16(core.hl); - save.sp = BESS16(core.sp); + save.pc = LE16(core.pc); + save.af = LE16(core.af); + save.bc = LE16(core.bc); + save.de = LE16(core.de); + save.hl = LE16(core.hl); + save.sp = LE16(core.sp); save.ime = core.ime; save.interrupt_enable = core.ie; @@ -780,44 +848,44 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]); break; - case htonl('NAME'): - if (BESS32(block.size) > sizeof(emulator_name) - 1) { - file->seek(file, BESS32(block.size), SEEK_CUR); + case BE32('NAME'): + if (LE32(block.size) > sizeof(emulator_name) - 1) { + file->seek(file, LE32(block.size), SEEK_CUR); } else { - file->read(file, emulator_name, BESS32(block.size)); + file->read(file, emulator_name, LE32(block.size)); } break; - case htonl('OAM '): + case BE32('OAM '): if (!found_core) goto parse_error; - if (BESS32(block.size) != 256 && BESS32(block.size) != 160) goto parse_error; + if (LE32(block.size) != 256 && LE32(block.size) != 160) goto parse_error; file->read(file, save.oam, sizeof(save.oam)); - if (BESS32(block.size) == 256) { + if (LE32(block.size) == 256) { file->read(file, save.extra_oam, sizeof(save.extra_oam)); } break; - case htonl('PALS'): + case BE32('PALS'): if (!found_core) goto parse_error; - if (BESS32(block.size) != sizeof(BESS_PALS_t) - sizeof(block)) goto parse_error; + if (LE32(block.size) != sizeof(BESS_PALS_t) - sizeof(block)) goto parse_error; file->read(file, save.background_palettes_data, sizeof(save.background_palettes_data)); file->read(file, save.sprite_palettes_data, sizeof(save.sprite_palettes_data)); break; - case htonl('MBC '): + case BE32('MBC '): if (!found_core) goto parse_error; - if (BESS32(block.size) % 3 != 0) goto parse_error; - for (unsigned i = BESS32(block.size); i > 0; i -= 3) { + if (LE32(block.size) % 3 != 0) goto parse_error; + for (unsigned i = LE32(block.size); i > 0; i -= 3) { BESS_MBC_pair_t pair; file->read(file, &pair, sizeof(pair)); - if (BESS16(pair.address) >= 0x8000) goto parse_error; - GB_write_memory(&save, BESS16(pair.address), pair.value); + if (LE16(pair.address) >= 0x8000) goto parse_error; + GB_write_memory(&save, LE16(pair.address), pair.value); } break; - case htonl('RTC '): + case BE32('RTC '): if (!found_core) goto parse_error; BESS_RTC_t bess_rtc; - if (BESS32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error; + if (LE32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error; if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { - if (file->read(file, &bess_rtc.header + 1, BESS32(block.size)) != BESS32(block.size)) goto error; + if (file->read(file, &bess_rtc.header + 1, LE32(block.size)) != LE32(block.size)) goto error; gb->rtc_real.seconds = bess_rtc.real.seconds; gb->rtc_real.minutes = bess_rtc.real.minutes; gb->rtc_real.hours = bess_rtc.real.hours; @@ -828,15 +896,21 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo gb->rtc_latched.hours = bess_rtc.latched.hours; gb->rtc_latched.days = bess_rtc.latched.days; gb->rtc_latched.high = bess_rtc.latched.high; - gb->last_rtc_second = BESS64(bess_rtc.last_rtc_second); + gb->last_rtc_second = LE64(bess_rtc.last_rtc_second); } break; - case htonl('END '): + case BE32('SGB '): if (!found_core) goto parse_error; - if (BESS32(block.size) != 0) goto parse_error; + if (LE32(block.size) != sizeof(BESS_SGB_t) - sizeof(block)) goto parse_error; + file->read(file, &sgb.header + 1, sizeof(BESS_SGB_t) - sizeof(block)); + found_sgb = true; + break; + case BE32('END '): + if (!found_core) goto parse_error; + if (LE32(block.size) != 0) goto parse_error; goto done; default: - file->seek(file, BESS32(block.size), SEEK_CUR); + file->seek(file, LE32(block.size), SEEK_CUR); break; } } @@ -848,6 +922,37 @@ done: read_bess_buffer(&core.ram, file, gb->ram, gb->ram_size); read_bess_buffer(&core.vram, file, gb->vram, gb->vram_size); read_bess_buffer(&core.mbc_ram, file, gb->mbc_ram, gb->mbc_ram_size); + if (gb->sgb) { + memset(gb->sgb, 0, sizeof(*gb->sgb)); + GB_sgb_load_default_data(gb); + if (gb->boot_rom_finished) { + gb->sgb->intro_animation = GB_SGB_INTRO_ANIMATION_LENGTH; + if (!found_sgb) { + gb->sgb->disable_commands = true; + } + else { + read_bess_buffer(&sgb.border_tiles, file, gb->sgb->border.tiles, sizeof(gb->sgb->border.tiles)); + read_bess_buffer(&sgb.border_tilemap, file, (void *)gb->sgb->border.map, sizeof(gb->sgb->border.map)); + read_bess_buffer(&sgb.border_palettes, file, (void *)gb->sgb->border.palette, sizeof(gb->sgb->border.palette)); + + read_bess_buffer(&sgb.active_palettes, file, (void *)gb->sgb->effective_palettes, sizeof(gb->sgb->effective_palettes)); + read_bess_buffer(&sgb.ram_palettes, file, (void *)gb->sgb->ram_palettes, sizeof(gb->sgb->ram_palettes)); + read_bess_buffer(&sgb.attribute_map, file, (void *)gb->sgb->attribute_map, sizeof(gb->sgb->attribute_map)); + read_bess_buffer(&sgb.attribute_files, file, (void *)gb->sgb->attribute_files, sizeof(gb->sgb->attribute_files)); + + gb->sgb->player_count = sgb.multiplayer_state >> 4; + gb->sgb->current_player = sgb.multiplayer_state & 0xF; + if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3) { + gb->sgb->player_count = 1; + gb->sgb->current_player = 0; + } + } + } + else { + // Effectively reset if didn't finish the boot ROM + gb->pc = 0; + } + } if (emulator_name[0]) { GB_log(gb, "Save state imported from %s.\n", emulator_name); } diff --git a/Core/sgb.c b/Core/sgb.c index c77b0db..9ba4d43 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -7,8 +7,6 @@ #define M_PI 3.14159265358979323846 #endif -#define INTRO_ANIMATION_LENGTH 200 - enum { PAL01 = 0x00, PAL23 = 0x01, @@ -49,14 +47,14 @@ static inline void pal_command(GB_gameboy_t *gb, unsigned first, unsigned second { gb->sgb->effective_palettes[0] = gb->sgb->effective_palettes[4] = gb->sgb->effective_palettes[8] = gb->sgb->effective_palettes[12] = - gb->sgb->command[1] | (gb->sgb->command[2] << 8); + *(uint16_t *)&gb->sgb->command[1]; for (unsigned i = 0; i < 3; i++) { - gb->sgb->effective_palettes[first * 4 + i + 1] = gb->sgb->command[3 + i * 2] | (gb->sgb->command[4 + i * 2] << 8); + gb->sgb->effective_palettes[first * 4 + i + 1] = *(uint16_t *)&gb->sgb->command[3 + i * 2]; } for (unsigned i = 0; i < 3; i++) { - gb->sgb->effective_palettes[second * 4 + i + 1] = gb->sgb->command[9 + i * 2] | (gb->sgb->command[10 + i * 2] << 8); + gb->sgb->effective_palettes[second * 4 + i + 1] = *(uint16_t *)&gb->sgb->command[9 + i * 2]; } } @@ -172,10 +170,10 @@ static void command_ready(GB_gameboy_t *gb) gb->sgb->disable_commands = true; for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) { if (memcmp(palette_assignments[i].name, &gb->sgb->received_header[0x30], sizeof(palette_assignments[i].name)) == 0) { - gb->sgb->effective_palettes[0] = built_in_palettes[palette_assignments[i].palette_index * 4 - 4]; - gb->sgb->effective_palettes[1] = built_in_palettes[palette_assignments[i].palette_index * 4 + 1 - 4]; - gb->sgb->effective_palettes[2] = built_in_palettes[palette_assignments[i].palette_index * 4 + 2 - 4]; - gb->sgb->effective_palettes[3] = built_in_palettes[palette_assignments[i].palette_index * 4 + 3 - 4]; + gb->sgb->effective_palettes[0] = LE16(built_in_palettes[palette_assignments[i].palette_index * 4 - 4]); + gb->sgb->effective_palettes[1] = LE16(built_in_palettes[palette_assignments[i].palette_index * 4 + 1 - 4]); + gb->sgb->effective_palettes[2] = LE16(built_in_palettes[palette_assignments[i].palette_index * 4 + 2 - 4]); + gb->sgb->effective_palettes[3] = LE16(built_in_palettes[palette_assignments[i].palette_index * 4 + 3 - 4]); break; } } @@ -556,8 +554,8 @@ static void render_boot_animation (GB_gameboy_t *gb) else if (gb->sgb->intro_animation < 80) { fade_blue = 80 - gb->sgb->intro_animation; } - else if (gb->sgb->intro_animation > INTRO_ANIMATION_LENGTH - 32) { - fade_red = fade_blue = gb->sgb->intro_animation - INTRO_ANIMATION_LENGTH + 32; + else if (gb->sgb->intro_animation > GB_SGB_INTRO_ANIMATION_LENGTH - 32) { + fade_red = fade_blue = gb->sgb->intro_animation - GB_SGB_INTRO_ANIMATION_LENGTH + 32; } uint32_t colors[] = { convert_rgb15(gb, 0), @@ -607,7 +605,7 @@ void GB_sgb_render(GB_gameboy_t *gb) render_jingle(gb, gb->apu_output.sample_rate / GB_get_usual_frame_rate(gb)); } - if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; + if (gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++; if (gb->sgb->vram_transfer_countdown) { if (--gb->sgb->vram_transfer_countdown == 0) { @@ -656,9 +654,7 @@ void GB_sgb_render(GB_gameboy_t *gb) *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; } #ifdef GB_BIG_ENDIAN - if (gb->sgb->transfer_dest == TRANSFER_ATTRIBUTES) { - *data = __builtin_bswap16(*data); - } + *data = __builtin_bswap16(*data); #endif data++; } @@ -674,7 +670,7 @@ void GB_sgb_render(GB_gameboy_t *gb) uint32_t colors[4 * 4]; for (unsigned i = 0; i < 4 * 4; i++) { - colors[i] = convert_rgb15(gb, gb->sgb->effective_palettes[i]); + colors[i] = convert_rgb15(gb, LE16(gb->sgb->effective_palettes[i])); } if (gb->sgb->mask_mode != MASK_FREEZE) { @@ -683,7 +679,7 @@ void GB_sgb_render(GB_gameboy_t *gb) sizeof(gb->sgb->effective_screen_buffer)); } - if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) { + if (gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) { render_boot_animation(gb); } else { @@ -735,21 +731,21 @@ void GB_sgb_render(GB_gameboy_t *gb) } uint32_t border_colors[16 * 4]; - if (gb->sgb->border_animation == 0 || gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) { + if (gb->sgb->border_animation == 0 || gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) { for (unsigned i = 0; i < 16 * 4; i++) { - border_colors[i] = convert_rgb15(gb, gb->sgb->border.palette[i]); + border_colors[i] = convert_rgb15(gb, LE16(gb->sgb->border.palette[i])); } } else if (gb->sgb->border_animation > 32) { gb->sgb->border_animation--; for (unsigned i = 0; i < 16 * 4; i++) { - border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], 64 - gb->sgb->border_animation); + border_colors[i] = convert_rgb15_with_fade(gb, LE16(gb->sgb->border.palette[i]), 64 - gb->sgb->border_animation); } } else { gb->sgb->border_animation--; for (unsigned i = 0; i < 16 * 4; i++) { - border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], gb->sgb->border_animation); + border_colors[i] = convert_rgb15_with_fade(gb, LE16(gb->sgb->border.palette[i]), gb->sgb->border_animation); } } @@ -767,7 +763,7 @@ void GB_sgb_render(GB_gameboy_t *gb) else if (gb->border_mode == GB_BORDER_NEVER) { continue; } - uint16_t tile = gb->sgb->border.map[tile_x + tile_y * 32]; + uint16_t tile = LE16(gb->sgb->border.map[tile_x + tile_y * 32]); uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; uint8_t palette = (tile >> 10) & 3; @@ -798,9 +794,18 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb) { #include "graphics/sgb_border.inc" - + +#ifdef GB_BIG_ENDIAN + for (unsigned i = 0; i < sizeof(tilemap) / 2; i++) { + gb->sgb->border.map[i] = LE16(tilemap[i]); + } + for (unsigned i = 0; i < sizeof(palette) / 2; i++) { + gb->sgb->border.palette[i] = LE16(palette[i]); + } +#else memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap)); memcpy(gb->sgb->border.palette, palette, sizeof(palette)); +#endif /* Expand tileset */ for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) { @@ -825,10 +830,10 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb) /* Re-center */ memmove(&gb->sgb->border.map[25 * 32 + 1], &gb->sgb->border.map[25 * 32], (32 * 3 - 1) * sizeof(gb->sgb->border.map[0])); } - gb->sgb->effective_palettes[0] = built_in_palettes[0]; - gb->sgb->effective_palettes[1] = built_in_palettes[1]; - gb->sgb->effective_palettes[2] = built_in_palettes[2]; - gb->sgb->effective_palettes[3] = built_in_palettes[3]; + gb->sgb->effective_palettes[0] = LE16(built_in_palettes[0]); + gb->sgb->effective_palettes[1] = LE16(built_in_palettes[1]); + gb->sgb->effective_palettes[2] = LE16(built_in_palettes[2]); + gb->sgb->effective_palettes[3] = LE16(built_in_palettes[3]); } static double fm_synth(double phase) @@ -874,7 +879,7 @@ static void render_jingle(GB_gameboy_t *gb, size_t count) return; } - if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return; + if (gb->sgb->intro_animation >= GB_SGB_INTRO_ANIMATION_LENGTH) return; signed jingle_stage = (gb->sgb->intro_animation - 64) / 3; double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate; @@ -892,7 +897,7 @@ static void render_jingle(GB_gameboy_t *gb, size_t count) gb->sgb_intro_jingle_phases[f] += frequencies[f] / gb->apu_output.sample_rate; } if (gb->sgb->intro_animation > 100) { - sample *= pow((INTRO_ANIMATION_LENGTH - gb->sgb->intro_animation) / (INTRO_ANIMATION_LENGTH - 100.0), 3); + sample *= pow((GB_SGB_INTRO_ANIMATION_LENGTH - gb->sgb->intro_animation) / (GB_SGB_INTRO_ANIMATION_LENGTH - 100.0), 3); } if (gb->sgb->intro_animation < 120) { diff --git a/Core/sgb.h b/Core/sgb.h index aae9f75..ba12b88 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -17,6 +17,8 @@ typedef struct { } GB_sgb_border_t; #ifdef GB_INTERNAL +#define GB_SGB_INTRO_ANIMATION_LENGTH 200 + struct GB_sgb_s { uint8_t command[16 * 7]; uint16_t command_write_index; @@ -56,6 +58,8 @@ struct GB_sgb_s { /* Multiplayer (cont) */ bool mlt_lock; + + bool little_endian; // True on save states created on 0.14.3 or newer }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); diff --git a/Makefile b/Makefile index 3d9b2cb..5724e50 100644 --- a/Makefile +++ b/Makefile @@ -106,7 +106,7 @@ OPEN_DIALOG = OpenDialog/cocoa.m endif # These must come before the -Wno- flags -WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option +WARNINGS += -Werror -Wall -Wno-unknown-warning -Wno-unknown-warning-option -Wno-missing-braces WARNINGS += -Wno-nonnull -Wno-unused-result -Wno-strict-aliasing -Wno-multichar -Wno-int-in-bool-context -Wno-format-truncation # Only add this flag if the compiler supports it diff --git a/Tester/main.c b/Tester/main.c index d1e3370..b0f1b31 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -231,10 +231,17 @@ static char *executable_relative_path(const char *filename) static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { +#ifdef GB_BIG_ENDIAN + if (use_tga) { + return (r << 8) | (g << 16) | (b << 24); + } + return (r << 0) | (g << 8) | (b << 16); +#else if (use_tga) { return (r << 16) | (g << 8) | (b); } return (r << 24) | (g << 16) | (b << 8); +#endif } static void replace_extension(const char *src, size_t length, char *dest, const char *ext) From 48ec3e6413bd0ad438971cd2a3c4a4177ed59720 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 30 Mar 2021 17:29:55 +0200 Subject: [PATCH 145/365] Correct usage of PREFIX with DATA_DIR slipped through in #370 DATA_DIR is not relative to PREFIX so having it is problematic --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c217102..a739428 100644 --- a/Makefile +++ b/Makefile @@ -441,7 +441,7 @@ install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDe -@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX)) mkdir -p $(DESTDIR)$(DATA_DIR)/ $(DESTDIR)$(PREFIX)/bin/ cp -rf $(BIN)/SDL/* $(DESTDIR)$(DATA_DIR)/ - mv $(DESTDIR)$(PREFIX)$(DATA_DIR)/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy + mv $(DESTDIR)$(DATA_DIR)/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) -update-mime-database -n $(PREFIX)/share/mime -xdg-desktop-menu install --novendor --mode system FreeDesktop/sameboy.desktop From e460b0a7b4dc5161713196c8edc849bf8eb8d543 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 31 Mar 2021 00:54:55 +0300 Subject: [PATCH 146/365] Change the border format to SNES-style --- Core/display.c | 11 ++-- Core/gb.c | 14 +---- Core/save_state.c | 34 ++++++++++-- Core/sgb.c | 131 ++++++++++++++++++++++------------------------ Core/sgb.h | 7 ++- 5 files changed, 109 insertions(+), 88 deletions(-) diff --git a/Core/display.c b/Core/display.c index 8fa749c..2545e3b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -180,12 +180,17 @@ static void display_vblank(GB_gameboy_t *gb) continue; } uint16_t tile = LE16(gb->borrowed_border.map[tile_x + tile_y * 32]); - uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; - uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; + uint8_t flip_x = (tile & 0x4000)? 0:7; + uint8_t flip_y = (tile & 0x8000)? 7:0; uint8_t palette = (tile >> 10) & 3; for (unsigned y = 0; y < 8; y++) { + unsigned base = (tile & 0xFF) * 32 + (y ^ flip_y) * 2; for (unsigned x = 0; x < 8; x++) { - uint8_t color = gb->borrowed_border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; + uint8_t bit = 1 << (x ^ flip_x); + uint8_t color = ((gb->borrowed_border.tiles[base] & bit) ? 1 : 0) | + ((gb->borrowed_border.tiles[base + 1] & bit) ? 2 : 0) | + ((gb->borrowed_border.tiles[base + 16] & bit) ? 4 : 0) | + ((gb->borrowed_border.tiles[base + 17] & bit) ? 8 : 0); uint32_t *output = gb->screen + tile_x * 8 + x + (tile_y * 8 + y) * 256; if (color == 0) { *output = border_colors[0]; diff --git a/Core/gb.c b/Core/gb.c index eca10ec..9e06042 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -115,19 +115,7 @@ static void load_default_border(GB_gameboy_t *gb) #define LOAD_BORDER() do { \ memcpy(gb->borrowed_border.map, tilemap, sizeof(tilemap));\ memcpy(gb->borrowed_border.palette, palette, sizeof(palette));\ - \ - /* Expand tileset */\ - for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) {\ - for (unsigned y = 0; y < 8; y++) {\ - for (unsigned x = 0; x < 8; x++) {\ - gb->borrowed_border.tiles[tile * 8 * 8 + y * 8 + x] =\ - (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) |\ - (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) |\ - (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) |\ - (tiles[tile * 32 + y * 2 + 17] & (1 << (7 ^ x)) ? 8 : 0);\ - }\ - }\ - }\ + memcpy(gb->borrowed_border.tiles, tiles, sizeof(tiles));\ } while (false); #ifdef GB_BIG_ENDIAN diff --git a/Core/save_state.c b/Core/save_state.c index 81f4dd6..d9b91c1 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -324,8 +324,8 @@ static void sanitize_state(GB_gameboy_t *gb) if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; } + if (gb->sgb && !gb->sgb->v14_3) { #ifdef GB_BIG_ENDIAN - if (gb->sgb && !gb->sgb->little_endian) { for (unsigned i = 0; i < sizeof(gb->sgb->border.raw_data) / 2; i++) { gb->sgb->border.raw_data[i] = LE16(gb->sgb->border.raw_data[i]); } @@ -341,8 +341,36 @@ static void sanitize_state(GB_gameboy_t *gb) for (unsigned i = 0; i < sizeof(gb->sgb->ram_palettes) / 2; i++) { gb->sgb->ram_palettes[i] = LE16(gb->sgb->ram_palettes[i]); } - } #endif + uint8_t converted_tiles[sizeof(gb->sgb->border.tiles)] = {0,}; + for (unsigned tile = 0; tile < sizeof(gb->sgb->border.tiles_legacy) / 64; tile++) { + for (unsigned y = 0; y < 8; y++) { + unsigned base = tile * 32 + y * 2; + for (unsigned x = 0; x < 8; x++) { + uint8_t pixel = gb->sgb->border.tiles_legacy[tile * 8 * 8 + y * 8 + x]; + if (pixel & 1) converted_tiles[base] |= (1 << (7 ^ x)); + if (pixel & 2) converted_tiles[base + 1] |= (1 << (7 ^ x)); + if (pixel & 4) converted_tiles[base + 16] |= (1 << (7 ^ x)); + if (pixel & 8) converted_tiles[base + 17] |= (1 << (7 ^ x)); + } + } + } + memcpy(gb->sgb->border.tiles, converted_tiles, sizeof(converted_tiles)); + memset(converted_tiles, 0, sizeof(converted_tiles)); + for (unsigned tile = 0; tile < sizeof(gb->sgb->pending_border.tiles_legacy) / 64; tile++) { + for (unsigned y = 0; y < 8; y++) { + unsigned base = tile * 32 + y * 2; + for (unsigned x = 0; x < 8; x++) { + uint8_t pixel = gb->sgb->pending_border.tiles_legacy[tile * 8 * 8 + y * 8 + x]; + if (pixel & 1) converted_tiles[base] |= (1 << (7 ^ x)); + if (pixel & 2) converted_tiles[base + 1] |= (1 << (7 ^ x)); + if (pixel & 4) converted_tiles[base + 16] |= (1 << (7 ^ x)); + if (pixel & 8) converted_tiles[base + 17] |= (1 << (7 ^ x)); + } + } + } + memcpy(gb->sgb->pending_border.tiles, converted_tiles, sizeof(converted_tiles)); + } } static bool dump_section(virtual_file_t *file, const void *src, uint32_t size) @@ -437,7 +465,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) uint32_t sgb_offset = 0; if (GB_is_hle_sgb(gb)) { - gb->sgb->little_endian = true; + gb->sgb->v14_3 = true; sgb_offset = file->tell(file) + 4; if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; } diff --git a/Core/sgb.c b/Core/sgb.c index 9ba4d43..cf1b174 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -609,64 +609,67 @@ void GB_sgb_render(GB_gameboy_t *gb) if (gb->sgb->vram_transfer_countdown) { if (--gb->sgb->vram_transfer_countdown == 0) { - if (gb->sgb->transfer_dest == TRANSFER_LOW_TILES || gb->sgb->transfer_dest == TRANSFER_HIGH_TILES) { - uint8_t *base = &gb->sgb->pending_border.tiles[gb->sgb->transfer_dest == TRANSFER_HIGH_TILES ? 0x80 * 8 * 8 : 0]; - for (unsigned tile = 0; tile < 0x80; tile++) { - unsigned tile_x = (tile % 10) * 16; - unsigned tile_y = (tile / 10) * 8; - for (unsigned y = 0; y < 0x8; y++) { - for (unsigned x = 0; x < 0x8; x++) { - base[tile * 8 * 8 + y * 8 + x] = gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] + - gb->sgb->screen_buffer[(tile_x + x + 8) + (tile_y + y) * 160] * 4; - } - } - } - + unsigned size = 0; + uint16_t *data = NULL; + + switch (gb->sgb->transfer_dest) { + case TRANSFER_LOW_TILES: + size = 0x100; + data = (uint16_t *)gb->sgb->pending_border.tiles; + break; + case TRANSFER_HIGH_TILES: + size = 0x100; + data = (uint16_t *)gb->sgb->pending_border.tiles + 0x800; + break; + case TRANSFER_PALETTES: + size = 0x100; + data = gb->sgb->ram_palettes; + break; + case TRANSFER_BORDER_DATA: + size = 0x88; + data = gb->sgb->pending_border.raw_data; + break; + case TRANSFER_ATTRIBUTES: + size = 0xFE; + data = (uint16_t *)gb->sgb->attribute_files; + break; + default: + return; // Corrupt state? } - else { - unsigned size = 0; - uint16_t *data = NULL; - - switch (gb->sgb->transfer_dest) { - case TRANSFER_PALETTES: - size = 0x100; - data = gb->sgb->ram_palettes; - break; - case TRANSFER_BORDER_DATA: - size = 0x88; - data = gb->sgb->pending_border.raw_data; - break; - case TRANSFER_ATTRIBUTES: - size = 0xFE; - data = (uint16_t *)gb->sgb->attribute_files; - break; - default: - return; // Corrupt state? - } - - for (unsigned tile = 0; tile < size; tile++) { - unsigned tile_x = (tile % 20) * 8; - unsigned tile_y = (tile / 20) * 8; - for (unsigned y = 0; y < 0x8; y++) { - static const uint16_t pixel_to_bits[4] = {0x0000, 0x0080, 0x8000, 0x8080}; - *data = 0; - for (unsigned x = 0; x < 8; x++) { - *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; - } -#ifdef GB_BIG_ENDIAN - *data = __builtin_bswap16(*data); -#endif - data++; + + for (unsigned tile = 0; tile < size; tile++) { + unsigned tile_x = (tile % 20) * 8; + unsigned tile_y = (tile / 20) * 8; + for (unsigned y = 0; y < 0x8; y++) { + static const uint16_t pixel_to_bits[4] = {0x0000, 0x0080, 0x8000, 0x8080}; + *data = 0; + for (unsigned x = 0; x < 8; x++) { + *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; } +#ifdef GB_BIG_ENDIAN + *data = __builtin_bswap16(*data); +#endif + data++; } - if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) { - gb->sgb->border_animation = 64; - } + } + if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) { + gb->sgb->border_animation = 64; } } } - if (!gb->screen || !gb->rgb_encode_callback || gb->disable_rendering) return; + if (!gb->screen || !gb->rgb_encode_callback || gb->disable_rendering) { + if (gb->sgb->border_animation > 32) { + gb->sgb->border_animation--; + } + else if (gb->sgb->border_animation != 0) { + gb->sgb->border_animation--; + } + if (gb->sgb->border_animation == 32) { + memcpy(&gb->sgb->border, &gb->sgb->pending_border, sizeof(gb->sgb->border)); + } + return; + } uint32_t colors[4 * 4]; for (unsigned i = 0; i < 4 * 4; i++) { @@ -764,12 +767,18 @@ void GB_sgb_render(GB_gameboy_t *gb) continue; } uint16_t tile = LE16(gb->sgb->border.map[tile_x + tile_y * 32]); - uint8_t flip_x = (tile & 0x4000)? 0x7 : 0; - uint8_t flip_y = (tile & 0x8000)? 0x7 : 0; + uint8_t flip_x = (tile & 0x4000)? 0:7; + uint8_t flip_y = (tile & 0x8000)? 7:0; uint8_t palette = (tile >> 10) & 3; for (unsigned y = 0; y < 8; y++) { + unsigned base = (tile & 0xFF) * 32 + (y ^ flip_y) * 2; for (unsigned x = 0; x < 8; x++) { - uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF; + uint8_t bit = 1 << (x ^ flip_x); + uint8_t color = ((gb->sgb->border.tiles[base] & bit) ? 1: 0) | + ((gb->sgb->border.tiles[base + 1] & bit) ? 2: 0) | + ((gb->sgb->border.tiles[base + 16] & bit) ? 4: 0) | + ((gb->sgb->border.tiles[base + 17] & bit) ? 8: 0); + uint32_t *output = gb->screen; if (gb->border_mode == GB_BORDER_NEVER) { output += (tile_x - 6) * 8 + x + ((tile_y - 5) * 8 + y) * 160; @@ -806,19 +815,7 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb) memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap)); memcpy(gb->sgb->border.palette, palette, sizeof(palette)); #endif - - /* Expand tileset */ - for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) { - for (unsigned y = 0; y < 8; y++) { - for (unsigned x = 0; x < 8; x++) { - gb->sgb->border.tiles[tile * 8 * 8 + y * 8 + x] = - (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) | - (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) | - (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) | - (tiles[tile * 32 + y * 2 + 17] & (1 << (7 ^ x)) ? 8 : 0); - } - } - } + memcpy(gb->sgb->border.tiles, tiles, sizeof(tiles)); if (gb->model != GB_MODEL_SGB2) { /* Delete the "2" */ diff --git a/Core/sgb.h b/Core/sgb.h index ba12b88..320fb6a 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -6,7 +6,10 @@ typedef struct GB_sgb_s GB_sgb_t; typedef struct { - uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/ + union { + uint8_t tiles[0x100 * 8 * 4]; + uint8_t tiles_legacy[0x100 * 8 * 8]; /* High nibble not used; TODO: Remove when breaking save-state compatibility! */ + }; union { struct { uint16_t map[32 * 32]; @@ -59,7 +62,7 @@ struct GB_sgb_s { /* Multiplayer (cont) */ bool mlt_lock; - bool little_endian; // True on save states created on 0.14.3 or newer + bool v14_3; // True on save states created on 0.14.3 or newer; Remove when breaking save state compatibility! }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); From b7348b54782445fa2a6daa1520d0cc4c9af7432c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 1 Apr 2021 00:16:28 +0300 Subject: [PATCH 147/365] Add BESS format documentation --- BESS.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 BESS.md diff --git a/BESS.md b/BESS.md new file mode 100644 index 0000000..cfc12f3 --- /dev/null +++ b/BESS.md @@ -0,0 +1,177 @@ +# BESS – Best Effort Save State + +## Motivation + +BESS is a save state format specification designed to allow different emulators, as well as majorly different versions of the same emulator, to import save states from other BESS-compliant save states. BESS works by appending additional, implementation-agnostic information about the emulation state. This allows a single save state file to be read as both a fully-featured, implementation specific save state which includes detailed timing information; and as a portable "best effort" save state that represents a state accurately enough to be restored in casual use-cases. + +## Specification + +**Note: This specification is currently under development and will potentially change in the near future** + +Every integer used in the BESS specification is stored in Little Endian encoding. + +### BESS footer + +BESS works by appending a detectable footer at the end of an existing save state format. The footer uses the following format: + +| Offset from end of file | Content | +|-------------------------|-------------------------------------------------------| +| -8 | Offset to the first BESS Block, from the file's start | +| -4 | The ASCII string 'BESS' | + +### BESS blocks + +BESS uses a block format where each block contains the following header: + +| Offset | Content | +|--------|---------------------------------------| +| 0 | A four-letter ASCII identifier | +| 4 | Length of the block, excluding header | + +Every block is followed by another blocked, until the END block is reached. + +#### CORE block + +The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block. + +The length of the CORE block is 0x12E bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: + +| Offset | Content | +|--------|----------------------------------------| +| 0x000 | Major BESS version as a 16-bit integer | +| 0x002 | Minor BESS version as a 16-bit integer | + +Both major and minor versions should be 1. Implementations are expected to reject incompatible majors, but still attempt to read newer minor versions. + +| Offset | Content | +|--------|-----------------------------------------| +| 0x004 | A four-character ASCII model identifier | + +BESS uses a four-character string to identify Game Boy models: + + * The first letter represents mutually-incompatible families of models and is required. The allowed values are `'G'` for the original Game Boy family, `'S'` for the Super Game Boy family, and `'C'` for the Game Boy Color and Advance family. +* The second letter represents a specific model within the family, and is optional (If an implementation does not distinguish between specific models in a family, a space character may be used). The allowed values for family G are `'D'` for DMG and `'M'` for MGB; the allowed values for family S are `'N'` for NTSC, `'P'` for PAL, and `'2'` for SGB2; and the allowed values for family C are `'C'` for CGB, and `'A'` for the various GBA line models. +* The third letter represents a specific CPU revision within a model, and is optional (If an implementation does not distinguish between revisions, a space character may be used). The allowed values for model GD (DMG) are `'0'` and `'A'`, through `'C'`; the allowed values for model CC (CGB) are `'0'` and `'A'`, through `'E'`; the allowed values for model CA (AGB, AGS, GBP) are `'0'`, `'A'` and `'B'`; and for every other model this value must be a space character. +* The last character is used for padding and must be a space character. + +For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `CCE ` represent a CGB using CPU revision E. + +| Offset | Content | +|--------|--------------------------------------------------------| +| 0x008 | The value of the PC register | +| 0x00A | The value of the AF register | +| 0x00C | The value of the BC register | +| 0x00E | The value of the DE register | +| 0x010 | The value of the HL register | +| 0x012 | The value of the SP register | +| 0x014 | The value of IME (0 or 1) | +| 0x015 | The value of the IE register | +| 0x016 | Execution state (0 = running; 1 = halted; 2 = stopped) | +| 0x017 | The values of every memory-mapped register (128 bytes) | +| 0x097 | The contents of HRAM (127 bytes) | + +The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: +* Unused registers have Don't-Care values which should be ignored +* Unused register bits have Don't-Care values which should be ignored +* The value of KEY0 (FF4C) must be valid as it determines CGB mode +* Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect +* BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. +* Implementations should not start a serial transfer when writing the value of SB +* Similarly, no value of NRx4 should trigger a sound pulse on save state load +* And similarly again, implementations should not trigger DMA transfers when writing the values of DMA or HDMA5 +* The value store for DIV will be used to set the internal divisor to `DIV << 8` +* Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers) + +|Offset | Content | +|--------|--------------------------------------------------------| +| 0x116 | The size of RAM (32-bit integer) | +| 0x11A | The offset of RAM from file start (32-bit integer) | +| 0x11E | The size of VRAM (32-bit integer) | +| 0x122 | The offset of VRAM from file start (32-bit integer) | +| 0x126 | The size of MBC RAM (32-bit integer) | +| 0x12A | The offset of MBC RAM from file start (32-bit integer) | + +The contents of large RAM sizes are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. + + +#### NAME block + +The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation – it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first. + +The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII. + +#### OAM block + +The OAM block uses the `'OAM '` identifier, and is a required block that contains the data of OAM. This block length can be either 160 or 256. When 256 bytes of data are used, the addition bytes are used to set the values for the additional OAM range at `0xFEA0-0xFEFF`. Implementation that do not emulate that extra range are free to ignore the excess bytes. + +#### PALS block + +The PALS block uses the `'PALS'` identifier, and is an optional block that is only used for GBC-compatible models. The length of this block is 0x80 bytes and it contains the contents of background palette RAM (0x40 bytes) followed by the contents of object palette RAM (another 0x40 bytes). + +#### MBC block + +The MBC block uses the `'MBC '` identifier, and is an optional block that is only used when saving states of ROMs that use an MBC. The length of this block is variable and must be divisible by 3. + +This block contains an MBC-specific number of 3-byte-long pairs that represent the values of each MBC register. For example, for MBC5 the contents would look like: + +| Offset | Content | +|--------|---------------------------------------| +| 0x0 | The value 0x0000 as a 16-bit integer | +| 0x2 | 0x0A if RAM is enabled, 0 otherwise | +| 0x3 | The value 0x2000 as a 16-bit integer | +| 0x5 | The lower 8 bits of the ROM bank | +| 0x6 | The value 0x3000 as a 16-bit integer | +| 0x8 | The bit 9 of the ROM bank | +| 0x9 | The value 0x4000 as a 16-bit integer | +| 0xB | The current RAM bank | + +An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` range are not allowed. + +#### RTC block +The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. + +The length of this block is 0x30 bytes long and it follows the following structure: + +|Offset | Content | +|--------|------------------------------------------------------------------------| +| 0x00 | Current seconds (1 byte), followed by 3 bytes of padding | +| 0x04 | Current minutes (1 byte), followed by 3 bytes of padding | +| 0x08 | Current hours (1 byte), followed by 3 bytes of padding | +| 0x0C | Current days (1 byte), followed by 3 bytes of padding | +| 0x10 | Current high/overflow/running (1 byte), followed by 3 bytes of padding | +| 0x14 | Latched seconds (1 byte), followed by 3 bytes of padding | +| 0x18 | Latched minutes (1 byte), followed by 3 bytes of padding | +| 0x1C | Latched hours (1 byte), followed by 3 bytes of padding | +| 0x20 | Latched days (1 byte), followed by 3 bytes of padding | +| 0x24 | Latched high/overflow/running (1 byte), followed by 3 bytes of padding | +| 0x28 | UNIX timestamp at the time of the save state (64-bit) | + +#### SGB block + +The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing. + +The length of this block is 0x39 bytes and it follows the following structure: + +|Offset | Content | +|--------|--------------------------------------------------------------------------------------------------| +| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) | +| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) | +| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) | +| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) | +| 0x10 | The size of the border palettes (=0x80, 32-bit integer) | +| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) | +| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) | +| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) | +| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x28 | The size of the attribute map (=0x168, 32-bit integer) | +| 0x2C | The offset of the attribute map (32-bit integer) | +| 0x30 | The size of the attribute files (=0xfe0, 32-bit integer) | +| 0x34 | The offset of the attribute files (32-bit integer) | +| 0x38 | Multiplayer status (1 byte); high nibble is player count, low nibble is current player (0-based) | + +If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default. + +#### END block +The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. + From 6b8eb8063a9685427f448a75a74967d89aaca73f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 2 Apr 2021 02:54:14 +0300 Subject: [PATCH 148/365] Fix a bug where SameBoy would start in "faux turbo mode" --- Core/timing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/timing.c b/Core/timing.c index 18cf571..1da82a3 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -69,7 +69,7 @@ void GB_timing_sync(GB_gameboy_t *gb) gb->last_sync += target_nanoseconds; } else { - if (-time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { + if (time_to_sleep < 0 && -time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { // We're running a bit too slow, but the difference is small enough, // just skip this sync and let it even out return; From 39c71b40e7e6afe07df77fe21a4fc63281e861d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 2 Apr 2021 19:07:28 +0300 Subject: [PATCH 149/365] Fix memory leak --- Core/mbc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/mbc.c b/Core/mbc.c index 1e91ce2..1236a0d 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -125,6 +125,11 @@ void GB_configure_cart(GB_gameboy_t *gb) 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; + } if (gb->cartridge_type->has_ram) { if (gb->cartridge_type->mbc_type == GB_MBC2) { From 9996c7b4a21b8e5c47b9bddef64ad50305bf2e90 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 2 Apr 2021 19:08:03 +0300 Subject: [PATCH 150/365] Add GBS APIs --- Core/gb.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Core/gb.h | 29 ++++++++++++- 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index 9e06042..18774d0 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -308,6 +308,129 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) return 0; } +void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) +{ + GB_reset(gb); + GB_write_memory(gb, 0xFF00 + GB_IO_LCDC, 0x80); + GB_write_memory(gb, 0xFF00 + GB_IO_TAC, gb->gbs_header.TAC); + GB_write_memory(gb, 0xFF00 + GB_IO_TMA, gb->gbs_header.TMA); + GB_write_memory(gb, 0xFF00 + GB_IO_NR52, 0x80); + GB_write_memory(gb, 0xFF00 + GB_IO_NR51, 0xFF); + GB_write_memory(gb, 0xFF00 + GB_IO_NR50, 0x77); + memset(gb->ram, 0, gb->ram_size); + memset(gb->hram, 0, sizeof(gb->hram)); + memset(gb->oam, 0, sizeof(gb->oam)); + if (gb->gbs_header.TAC || gb->gbs_header.TMA) { + GB_write_memory(gb, 0xFFFF, 0x04); + } + else { + GB_write_memory(gb, 0xFFFF, 0x01); + } + if (gb->gbs_header.TAC & 0x80) { + gb->cgb_double_speed = true; // Might mean double speed mode on a DMG + } + gb->sp = LE16(gb->gbs_header.sp); + gb->pc = 0x100; + gb->boot_rom_finished = true; + gb->a = track; + if (gb->sgb) { + gb->sgb->intro_animation = GB_SGB_INTRO_ANIMATION_LENGTH; + gb->sgb->disable_commands = true; + } +} + +int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open GBS: %s.\n", strerror(errno)); + return errno; + } + fread(&gb->gbs_header, sizeof(gb->gbs_header), 1, f); + if (gb->gbs_header.magic != htonl('GBS\x01') || + LE16(gb->gbs_header.load_address) < 0x400 || + LE16(gb->gbs_header.load_address) >= 0x8000) { + GB_log(gb, "Not a valid GBS file: %s.\n", strerror(errno)); + fclose(f); + return -1; + } + fseek(f, 0, SEEK_END); + size_t data_size = ftell(f) - sizeof(gb->gbs_header); + gb->rom_size = (data_size + LE16(gb->gbs_header.load_address) + 0x3FFF) & ~0x3FFF; /* Round to bank */ + /* And then round to a power of two */ + while (gb->rom_size & (gb->rom_size - 1)) { + /* I promise this works. */ + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } + + if (gb->rom_size == 0) { + gb->rom_size = 0x8000; + } + fseek(f, sizeof(gb->gbs_header), SEEK_SET); + if (gb->rom) { + free(gb->rom); + } + gb->rom = malloc(gb->rom_size); + memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ + fread(gb->rom + LE16(gb->gbs_header.load_address), 1, data_size, f); + fclose(f); + + gb->cartridge_type = &GB_cart_defs[0x11]; + if (gb->mbc_ram) { + free(gb->mbc_ram); + gb->mbc_ram = NULL; + } + + if (gb->cartridge_type->has_ram) { + gb->mbc_ram_size = 0x2000; + gb->mbc_ram = malloc(gb->mbc_ram_size); + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); + } + + // Generate interrupt handlers + for (unsigned i = 0; i <= 0x38; i += 8) { + 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 = 0x40; i <= 0x60; i += 8) { + gb->rom[i] = 0xc9; // ret + } + + // Generate entry + memcpy(gb->rom + 0x100, (uint8_t[]) { + 0xCD, // Call $XXXX + LE16(gb->gbs_header.init_address), + LE16(gb->gbs_header.init_address) >> 8, + 0x76, // HALT + 0x00, // NOP + 0xAF, // XOR a + 0xE0, // LDH [$FFXX], a + GB_IO_IF, + 0xCD, // Call $XXXX + LE16(gb->gbs_header.play_address), + LE16(gb->gbs_header.play_address) >> 8, + 0x18, // JR pc ± $XX + -10 // To HALT + }, 13); + + GB_gbs_switch_track(gb, gb->gbs_header.first_track - 1); + if (info) { + memset(info, 0, sizeof(*info)); + info->first_track = gb->gbs_header.first_track - 1; + info->track_count = gb->gbs_header.track_count; + memcpy(info->title, gb->gbs_header.title, sizeof(gb->gbs_header.title)); + memcpy(info->author, gb->gbs_header.author, sizeof(gb->gbs_header.author)); + memcpy(info->copyright, gb->gbs_header.copyright, sizeof(gb->gbs_header.copyright)); + } + + gb->tried_loading_sgb_border = true; // Don't even attempt on GBS files + gb->has_sgb_border = false; + load_default_border(gb); + return 0; +} + int GB_load_isx(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "rb"); diff --git a/Core/gb.h b/Core/gb.h index 065e176..f008f03 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -325,6 +325,29 @@ typedef struct { uint8_t write_end; } GB_fifo_t; +typedef struct { + uint32_t magic; + uint8_t track_count; + uint8_t first_track; + uint16_t load_address; + uint16_t init_address; + uint16_t play_address; + uint16_t sp; + uint8_t TMA; + uint8_t TAC; + char title[32]; + char author[32]; + char copyright[32]; +} GB_gbs_header_t; + +typedef struct { + uint8_t track_count; + uint8_t first_track; + char title[33]; + char author[33]; + char copyright[33]; +} GB_gbs_info_t; + /* When state saving, each section is dumped independently of other sections. This allows adding data to the end of the section without worrying about future compatibility. Some other changes might be "safe" as well. @@ -718,6 +741,8 @@ struct GB_gameboy_internal_s { /* Temporary state */ bool wx_just_changed; bool tile_sel_glitch; + + GB_gbs_header_t gbs_header; ); }; @@ -777,7 +802,9 @@ void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, int GB_load_rom(GB_gameboy_t *gb, const char *path); void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); int GB_load_isx(GB_gameboy_t *gb, const char *path); - +int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info); +void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track); + int GB_save_battery_size(GB_gameboy_t *gb); int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size); int GB_save_battery(GB_gameboy_t *gb, const char *path); From bb3a73ff88895f72c99b7cf14a7bb708bfa8af80 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 3 Apr 2021 01:29:43 +0300 Subject: [PATCH 151/365] Cocoa GBS Player --- Cocoa/Document.h | 8 +++ Cocoa/Document.m | 84 +++++++++++++++++++++++++++++-- Cocoa/GBS.xib | 113 ++++++++++++++++++++++++++++++++++++++++++ Cocoa/Info.plist | 41 ++++++++++++++- Cocoa/Next.png | Bin 0 -> 1676 bytes Cocoa/Next@2x.png | Bin 0 -> 1787 bytes Cocoa/Pause.png | Bin 0 -> 1614 bytes Cocoa/Pause@2x.png | Bin 0 -> 4436 bytes Cocoa/Play.png | Bin 0 -> 4442 bytes Cocoa/Play@2x.png | Bin 0 -> 4527 bytes Cocoa/Previous.png | Bin 0 -> 1664 bytes Cocoa/Previous@2x.png | Bin 0 -> 4569 bytes Cocoa/Rewind.png | Bin 0 -> 1664 bytes Cocoa/Rewind@2x.png | Bin 0 -> 4515 bytes Core/display.c | 4 +- 15 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 Cocoa/GBS.xib create mode 100644 Cocoa/Next.png create mode 100644 Cocoa/Next@2x.png create mode 100644 Cocoa/Pause.png create mode 100644 Cocoa/Pause@2x.png create mode 100644 Cocoa/Play.png create mode 100644 Cocoa/Play@2x.png create mode 100644 Cocoa/Previous.png create mode 100644 Cocoa/Previous@2x.png create mode 100644 Cocoa/Rewind.png create mode 100644 Cocoa/Rewind@2x.png diff --git a/Cocoa/Document.h b/Cocoa/Document.h index b651646..e0a3aa3 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -39,6 +39,14 @@ @property (nonatomic, strong) IBOutlet GBCheatWindowController *cheatWindowController; @property (nonatomic, readonly) Document *partner; @property (nonatomic, readonly) bool isSlave; +@property (strong) IBOutlet NSView *gbsPlayerView; +@property (strong) IBOutlet NSTextField *gbsTitle; +@property (strong) IBOutlet NSTextField *gbsAuthor; +@property (strong) IBOutlet NSTextField *gbsCopyright; +@property (strong) IBOutlet NSPopUpButton *gbsTracks; +@property (strong) IBOutlet NSButton *gbsPlayPauseButton; +@property (strong) IBOutlet NSButton *gbsRewindButton; +@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index e7812d4..a5d291e 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -513,6 +513,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) start { + self.gbsPlayPauseButton.state = true; self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0; if (master) { [master start]; @@ -524,6 +525,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) stop { + self.gbsPlayPauseButton.state = false; if (master) { if (!master->running) return; GB_debugger_set_disabled(&gb, true); @@ -834,14 +836,86 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) return YES; } +- (IBAction)changeGBSTrack:(id)sender +{ + [self performAtomicBlock:^{ + GB_gbs_switch_track(&gb, self.gbsTracks.indexOfSelectedItem); + }]; +} +- (IBAction)gbsNextPrevPushed:(id)sender +{ + if (self.gbsNextPrevButton.selectedSegment == 0) { + // Previous + if (self.gbsTracks.indexOfSelectedItem == 0) { + [self.gbsTracks selectItemAtIndex:self.gbsTracks.numberOfItems - 1]; + } + else { + [self.gbsTracks selectItemAtIndex:self.gbsTracks.indexOfSelectedItem - 1]; + } + } + else { + // Next + if (self.gbsTracks.indexOfSelectedItem == self.gbsTracks.numberOfItems - 1) { + [self.gbsTracks selectItemAtIndex: 0]; + } + else { + [self.gbsTracks selectItemAtIndex:self.gbsTracks.indexOfSelectedItem + 1]; + } + } + [self changeGBSTrack:sender]; +} + +- (void)prepareGBSInterface: (GB_gbs_info_t *)info +{ + GB_set_rendering_disabled(&gb, true); + _view = nil; + for (NSView *view in _mainWindow.contentView.subviews) { + [view removeFromSuperview]; + } + [[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil]; + [_mainWindow setContentSize:self.gbsPlayerView.bounds.size]; + _mainWindow.styleMask &= ~NSWindowStyleMaskResizable; + [_mainWindow.contentView addSubview:self.gbsPlayerView]; + + self.gbsTitle.stringValue = [NSString stringWithCString:info->title encoding:NSISOLatin1StringEncoding] ?: @"GBS Player"; + self.gbsAuthor.stringValue = [NSString stringWithCString:info->author encoding:NSISOLatin1StringEncoding] ?: @"Unknown Composer"; + NSString *copyright = [NSString stringWithCString:info->copyright encoding:NSISOLatin1StringEncoding]; + if (copyright) { + copyright = [@"©" stringByAppendingString:copyright]; + } + self.gbsCopyright.stringValue = copyright ?: @"Missing copyright information"; + for (unsigned i = 0; i < info->track_count; i++) { + [self.gbsTracks addItemWithTitle:[NSString stringWithFormat:@"Track %u", i + 1]]; + } + [self.gbsTracks selectItemAtIndex:info->first_track]; + self.gbsPlayPauseButton.image.template = true; + self.gbsPlayPauseButton.alternateImage.template = true; + self.gbsRewindButton.image.template = true; + for (unsigned i = 0; i < 2; i++) { + [self.gbsNextPrevButton imageForSegment:i].template = true; + } + + if (!self.audioClient.isPlaying) { + [self.audioClient start]; + } + + if (@available(macOS 10.10, *)) { + _mainWindow.titlebarAppearsTransparent = true; + } +} + - (void) loadROM { NSString *rom_warnings = [self captureOutputForBlock:^{ GB_debugger_clear_symbols(&gb); - if ([[self.fileType pathExtension] isEqualToString:@"isx"]) { + if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"isx"]) { GB_load_isx(&gb, self.fileURL.path.UTF8String); GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); - + } + else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) { + GB_gbs_info_t info; + GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info); + [self prepareGBSInterface:&info]; } else { GB_load_rom(&gb, [self.fileURL.path UTF8String]); @@ -861,8 +935,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void)close { [self disconnectLinkCable]; - [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"]; - [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"]; + if (!self.gbsPlayerView) { + [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"]; + [[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"]; + } [self stop]; [self.consoleWindow close]; [self.memoryWindow close]; diff --git a/Cocoa/GBS.xib b/Cocoa/GBS.xib new file mode 100644 index 0000000..6d5ba01 --- /dev/null +++ b/Cocoa/GBS.xib @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index 4d42b46..5e409c9 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -51,7 +51,7 @@ CFBundleTypeExtensions - gbc + isx CFBundleTypeIconFile ColorCartridge @@ -68,6 +68,26 @@ NSDocumentClass Document + + CFBundleTypeExtensions + + gbs + + CFBundleTypeIconFile + ColorCartridge + CFBundleTypeName + Game Boy Sound File + CFBundleTypeRole + Viewer + LSItemContentTypes + + com.github.liji32.sameboy.gbs + + LSTypeIsPackage + 0 + NSDocumentClass + Document + CFBundleExecutable SameBoy @@ -156,6 +176,25 @@ + + UTTypeConformsTo + + public.data + + UTTypeDescription + Game Boy Sound File + UTTypeIconFile + ColorCartridge + UTTypeIdentifier + com.github.liji32.sameboy.gbs + UTTypeTagSpecification + + public.filename-extension + + gbs + + + NSCameraUsageDescription SameBoy needs to access your camera to emulate the Game Boy Camera diff --git a/Cocoa/Next.png b/Cocoa/Next.png new file mode 100644 index 0000000000000000000000000000000000000000..cd9a4c318c6b74cd95443dafd7d2a0c0eb1efd60 GIT binary patch literal 1676 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Pause.png b/Cocoa/Pause.png new file mode 100644 index 0000000000000000000000000000000000000000..2bb380b7b72d8838d8e4410daa05397ad16f9439 GIT binary patch literal 1614 zcmcIkO^@3|7XvM(pu{zWApUmOhi#R(6Deg8|e zGY^3vmztq@J}v5S9@z`t5Z30H*k}RKCy~8t9HQgo5cO3hk>BcI?sz`x1}&%4_JZIG z~unphOUcNmkr)3k%mX{gOxAXMfODNG9+X+o3&=`ma4|Y34(x7m$)tl z1g_5u&E~kMx7Q4@ta+7{I#UHQjBKP%b!3CmWe9oM&=&P7O^`4$XC-l3)P%GKCduW{ zJa5po-a7*?E4}X|u7~HPBuBDVrxllbXR^@SYiEKtw&iIxT|_E4kyDuitOj9DWnj9MYW$y~bful)ND1FE8!HuQY@v!fE5(mQX z;I!LKnQ*1k73rGoQI)~(!=`Vu8dU-q`N-wcO)2iO)WM#i2!f7}-GDoO&}D&0JsSxg zrmD&rG)tDVu_R?Nw$U3keKl28q6Fl;ZCV=GA&UL1hWeeaev-%Xr`2kjp+)C~jGKm! zY;GzoA^PQU_KxgrrgLdOn@AwNr1(o%tx`Q>mE0XerZ?*_^1cXac6Pb`&8q)@>t7|9 zPgpUQFh_}PA|htuEXzawTe-!zx2Dq?4kOPjn^_B+W?IVvs4JMo|Ni;v8O-rJgLrp% x{RjT?59{hjPi`K(y0(4u%5(G&x)s+u*2ACgB|8s({nkwU!QMgq%U3@>eGN7g@ofMA literal 0 HcmV?d00001 diff --git a/Cocoa/Pause@2x.png b/Cocoa/Pause@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..36b6da0188ab9c00f2c0b86f132af5b22ada1b5a GIT binary patch literal 4436 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Play.png b/Cocoa/Play.png new file mode 100644 index 0000000000000000000000000000000000000000..3f8709217dcecc464c72a8133fb273e784d1feba GIT binary patch literal 4442 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 DDNu2x literal 0 HcmV?d00001 diff --git a/Cocoa/Play@2x.png b/Cocoa/Play@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0de055300ff748dace38a9ccfb5522df06367a11 GIT binary patch literal 4527 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Previous.png b/Cocoa/Previous.png new file mode 100644 index 0000000000000000000000000000000000000000..cc91221dd816c5226aa36517ec7f09cc88c490e7 GIT binary patch literal 1664 zcmcIl&5zqe6!%Ji*p&)bbS&*AzuL*J?Hn;Jyy$+LKSf_1o`${uFMjN%MN9DCaK}=Su5f>vu(Y{Kv3;m>&jj zcuYnW%4Jtn#!&)jMY(i&oKSWoa?}<5F!t5^x9_MZ*6(5(G-#&`RaDz5FG~ls4i21>`GN*x@Du9 zThXhg<+`sRgXj(>4%SUguX+~o3M^rwI~<$27ru zg%lwzfI)CJG>S@emhYT^m$}}D5@+p6BJhF8yifWG=ITd_)}Ob{S3L)l)%pnYN`IE(*23U_teIQ5Y9p3@8iZg8HRG*4#=o zXfAx!Acn3HOCx5R5YIL}!+DvQ9w9Sm0L{Q@PL~5q9`+{7K!d<*#aT{cCRz<&1yPkS zcjQ5O>Q&0=4zJFt!kF73ylh4 zXtvvRiDQ|-RZ|gXUQebGg=PsO+7~#9`zrcZOCM${3E$0 z-kGn4A)0ocM_JMEzRHS93(z}z9X=#`%julij|Kur&nW&3mdRc|qN&*KL#CJOF#M

    T_cTYtRp@vRS5)_>W0>#6Ivob~%Fci(H? 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 Nvazr=FWv3M{};>KtZx7S literal 0 HcmV?d00001 diff --git a/Cocoa/Rewind.png b/Cocoa/Rewind.png new file mode 100644 index 0000000000000000000000000000000000000000..999f358fa38d23d07f289eda3ff3197fe925c51e GIT binary patch literal 1664 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 W565(3Hhfstopped && gb->io_registers[GB_IO_LCDC] & 0x80; - if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) { + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (!GB_is_sgb(gb)) { uint32_t color = 0; @@ -153,7 +153,7 @@ static void display_vblank(GB_gameboy_t *gb) } } - if (gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) { + if (!gb->disable_rendering && gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) { GB_borrow_sgb_border(gb); uint32_t border_colors[16 * 4]; From f67d3947d6b753d5c25828637cead4d6448ca3b8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 5 Apr 2021 23:08:43 +0300 Subject: [PATCH 152/365] UI Updates --- Cocoa/Document.m | 6 ++++-- Core/gb.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index a5d291e..9d2d7f7 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -770,7 +770,6 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self.view screenSizeChanged]; [self loadROM]; [self reset:nil]; - } - (void) initMemoryView @@ -875,6 +874,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [[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 + [_mainWindow standardWindowButton:NSWindowZoomButton].enabled = false; + }); [_mainWindow.contentView addSubview:self.gbsPlayerView]; self.gbsTitle.stringValue = [NSString stringWithCString:info->title encoding:NSISOLatin1StringEncoding] ?: @"GBS Player"; @@ -913,7 +915,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); } else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) { - GB_gbs_info_t info; + __block GB_gbs_info_t info; GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info); [self prepareGBSInterface:&info]; } diff --git a/Core/gb.c b/Core/gb.c index 18774d0..fe75b26 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -412,7 +412,7 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) LE16(gb->gbs_header.play_address), LE16(gb->gbs_header.play_address) >> 8, 0x18, // JR pc ± $XX - -10 // To HALT + -10 // To HALT }, 13); GB_gbs_switch_track(gb, gb->gbs_header.first_track - 1); From e6fa2336dae971a02fbc3c50bcf44f5961ff6e12 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 5 Apr 2021 23:09:32 +0300 Subject: [PATCH 153/365] Fix a potential crash/corruption when modifying cheats --- Core/cheats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/cheats.c b/Core/cheats.c index 451dddb..8defc6c 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -200,7 +200,7 @@ void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *des GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; for (unsigned i = 0; i < (*hash)->size; i++) { if ((*hash)->cheats[i] == cheat) { - (*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; + (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size]; if ((*hash)->size == 0) { free(*hash); *hash = NULL; From 1c31812ffd687e6157aa86c388285b4938ec3380 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 6 Apr 2021 01:02:49 +0300 Subject: [PATCH 154/365] BESS format updates --- Core/save_state.c | 83 ++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index d9b91c1..8d00416 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -52,23 +52,20 @@ typedef struct __attribute__((packed)) { uint8_t execution_mode; // 0 = running; 1 = halted; 2 = stopped uint8_t io_registers[0x80]; - uint8_t hram[0x7f]; BESS_buffer_t ram; BESS_buffer_t vram; BESS_buffer_t mbc_ram; + BESS_buffer_t oam; + BESS_buffer_t hram; + BESS_buffer_t background_palettes; + BESS_buffer_t sprite_palettes; } BESS_CORE_t; typedef struct __attribute__((packed)) { BESS_block_t header; - uint8_t oam[256]; -} BESS_OAM_t; - -typedef struct __attribute__((packed)) { - BESS_block_t header; - uint8_t background_palettes[0x40]; - uint8_t sprite_palettes[0x40]; -} BESS_PALS_t; + uint8_t extra_oam[96]; +} BESS_XOAM_t; typedef struct __attribute__((packed)) { BESS_block_t header; @@ -233,8 +230,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + sizeof(BESS_CORE_t) + sizeof(BESS_block_t) // NAME + sizeof(BESS_NAME) - 1 - + sizeof(BESS_OAM_t) - + (GB_is_cgb(gb)? sizeof(BESS_PALS_t) : 0) + + sizeof(BESS_XOAM_t) + (gb->sgb? sizeof(BESS_SGB_t) : 0) + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC block + sizeof(BESS_block_t) // END block @@ -456,10 +452,12 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (!DUMP_SECTION(gb, file, core_state)) goto error; if (!DUMP_SECTION(gb, file, dma )) goto error; if (!DUMP_SECTION(gb, file, mbc )) goto error; + uint32_t hram_offset = file->tell(file) + 4; if (!DUMP_SECTION(gb, file, hram )) goto error; if (!DUMP_SECTION(gb, file, timing )) goto error; if (!DUMP_SECTION(gb, file, apu )) goto error; if (!DUMP_SECTION(gb, file, rtc )) goto error; + uint32_t video_offset = file->tell(file) + 4; if (!DUMP_SECTION(gb, file, video )) goto error; uint32_t sgb_offset = 0; @@ -470,7 +468,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; } - BESS_CORE_t bess_core; + BESS_CORE_t bess_core = {0,}; bess_core.mbc_ram.offset = LE32(file->tell(file)); bess_core.mbc_ram.size = LE32(gb->mbc_ram_size); @@ -555,21 +553,30 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) bess_core.io_registers[GB_IO_DIV] = gb->div_counter >> 8; bess_core.io_registers[GB_IO_BANK] = gb->boot_rom_finished; bess_core.io_registers[GB_IO_KEY1] |= gb->cgb_double_speed? 0x80 : 0; - memcpy(bess_core.hram, gb->hram, sizeof(gb->hram)); - + bess_core.hram.size = LE32(sizeof(gb->hram)); + bess_core.hram.offset = LE32(hram_offset + offsetof(GB_gameboy_t, hram) - GB_SECTION_OFFSET(hram)); + bess_core.oam.size = LE32(sizeof(gb->oam)); + bess_core.oam.offset = LE32(video_offset + offsetof(GB_gameboy_t, oam) - GB_SECTION_OFFSET(video)); + if (GB_is_cgb(gb)) { + bess_core.background_palettes.size = LE32(sizeof(gb->background_palettes_data)); + bess_core.background_palettes.offset = LE32(video_offset + offsetof(GB_gameboy_t, background_palettes_data) - GB_SECTION_OFFSET(video)); + bess_core.sprite_palettes.size = LE32(sizeof(gb->sprite_palettes_data)); + bess_core.sprite_palettes.offset = LE32(video_offset + offsetof(GB_gameboy_t, sprite_palettes_data) - GB_SECTION_OFFSET(video)); + } if (file->write(file, &bess_core, sizeof(bess_core)) != sizeof(bess_core)) { goto error; } - /* BESS OAM */ + /* BESS XOAM */ - BESS_OAM_t bess_oam; - bess_oam.header = (BESS_block_t){BE32('OAM '), LE32(sizeof(bess_oam) - sizeof(bess_oam.header))}; - memcpy(bess_oam.oam, gb->oam, sizeof(gb->oam)); - memcpy(bess_oam.oam + sizeof(gb->oam), gb->extra_oam, sizeof(gb->extra_oam)); + BESS_XOAM_t bess_xoam = {0,}; + bess_xoam.header = (BESS_block_t){BE32('XOAM'), LE32(sizeof(bess_xoam) - sizeof(bess_xoam.header))}; + if (GB_is_cgb(gb)) { + memcpy(bess_xoam.extra_oam, gb->extra_oam, sizeof(bess_xoam.extra_oam)); + } - if (file->write(file, &bess_oam, sizeof(bess_oam)) != sizeof(bess_oam)) { + if (file->write(file, &bess_xoam, sizeof(bess_xoam)) != sizeof(bess_xoam)) { goto error; } @@ -593,19 +600,6 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) } } - if (GB_is_cgb(gb)) { - /* BESS PALS */ - - BESS_PALS_t bess_pals; - bess_pals.header = (BESS_block_t){BE32('PALS'), LE32(sizeof(bess_pals) - sizeof(bess_oam.header))}; - memcpy(bess_pals.background_palettes, gb->background_palettes_data, sizeof(bess_pals.background_palettes)); - memcpy(bess_pals.sprite_palettes, gb->sprite_palettes_data, sizeof(bess_pals.sprite_palettes)); - - if (file->write(file, &bess_pals, sizeof(bess_pals)) != sizeof(bess_pals)) { - goto error; - } - } - bool needs_sgb_padding = false; if (gb->sgb) { /* BESS SGB */ @@ -807,9 +801,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo save.halted = core.execution_mode == 1; save.stopped = core.execution_mode == 2; - - memcpy(save.hram, core.hram, sizeof(save.hram)); - + // CPU related // Determines DMG mode @@ -884,19 +876,10 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo file->read(file, emulator_name, LE32(block.size)); } break; - case BE32('OAM '): + case BE32('XOAM'): if (!found_core) goto parse_error; - if (LE32(block.size) != 256 && LE32(block.size) != 160) goto parse_error; - file->read(file, save.oam, sizeof(save.oam)); - if (LE32(block.size) == 256) { - file->read(file, save.extra_oam, sizeof(save.extra_oam)); - } - break; - case BE32('PALS'): - if (!found_core) goto parse_error; - if (LE32(block.size) != sizeof(BESS_PALS_t) - sizeof(block)) goto parse_error; - file->read(file, save.background_palettes_data, sizeof(save.background_palettes_data)); - file->read(file, save.sprite_palettes_data, sizeof(save.sprite_palettes_data)); + if (LE32(block.size) != 96) goto parse_error; + file->read(file, save.extra_oam, sizeof(save.extra_oam)); break; case BE32('MBC '): if (!found_core) goto parse_error; @@ -950,6 +933,10 @@ done: read_bess_buffer(&core.ram, file, gb->ram, gb->ram_size); read_bess_buffer(&core.vram, file, gb->vram, gb->vram_size); read_bess_buffer(&core.mbc_ram, file, gb->mbc_ram, gb->mbc_ram_size); + read_bess_buffer(&core.oam, file, gb->oam, sizeof(gb->oam)); + read_bess_buffer(&core.hram, file, gb->hram, sizeof(gb->hram)); + read_bess_buffer(&core.background_palettes, file, gb->background_palettes_data, sizeof(gb->background_palettes_data)); + read_bess_buffer(&core.sprite_palettes, file, gb->sprite_palettes_data, sizeof(gb->sprite_palettes_data)); if (gb->sgb) { memset(gb->sgb, 0, sizeof(*gb->sgb)); GB_sgb_load_default_data(gb); From 20ffa27dd4d98632e4ad9e7448641a60d28f9293 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 7 Apr 2021 21:45:43 +0300 Subject: [PATCH 155/365] Forgot to commit the document update --- BESS.md | 74 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/BESS.md b/BESS.md index cfc12f3..da601f3 100644 --- a/BESS.md +++ b/BESS.md @@ -34,18 +34,18 @@ Every block is followed by another blocked, until the END block is reached. The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block. -The length of the CORE block is 0x12E bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: +The length of the CORE block is 0xCF bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: -| Offset | Content | -|--------|----------------------------------------| -| 0x000 | Major BESS version as a 16-bit integer | -| 0x002 | Minor BESS version as a 16-bit integer | +| Offset | Content | +|--------|---------------------------------------| +| 0x00 | Major BESS version as a 16-bit integer | +| 0x02 | Minor BESS version as a 16-bit integer | Both major and minor versions should be 1. Implementations are expected to reject incompatible majors, but still attempt to read newer minor versions. -| Offset | Content | -|--------|-----------------------------------------| -| 0x004 | A four-character ASCII model identifier | +| Offset | Content | +|--------|----------------------------------------| +| 0x04 | A four-character ASCII model identifier | BESS uses a four-character string to identify Game Boy models: @@ -56,19 +56,18 @@ BESS uses a four-character string to identify Game Boy models: For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `CCE ` represent a CGB using CPU revision E. -| Offset | Content | -|--------|--------------------------------------------------------| -| 0x008 | The value of the PC register | -| 0x00A | The value of the AF register | -| 0x00C | The value of the BC register | -| 0x00E | The value of the DE register | -| 0x010 | The value of the HL register | -| 0x012 | The value of the SP register | -| 0x014 | The value of IME (0 or 1) | -| 0x015 | The value of the IE register | -| 0x016 | Execution state (0 = running; 1 = halted; 2 = stopped) | -| 0x017 | The values of every memory-mapped register (128 bytes) | -| 0x097 | The contents of HRAM (127 bytes) | +| Offset | Content | +|--------|-------------------------------------------------------| +| 0x08 | The value of the PC register | +| 0x0A | The value of the AF register | +| 0x0C | The value of the BC register | +| 0x0E | The value of the DE register | +| 0x10 | The value of the HL register | +| 0x12 | The value of the SP register | +| 0x14 | The value of IME (0 or 1) | +| 0x15 | The value of the IE register | +| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | +| 0x17 | The values of every memory-mapped register (128 bytes) | The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: * Unused registers have Don't-Care values which should be ignored @@ -82,16 +81,24 @@ The values of memory-mapped registers should be written 'as-is' to memory as if * The value store for DIV will be used to set the internal divisor to `DIV << 8` * Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers) -|Offset | Content | -|--------|--------------------------------------------------------| -| 0x116 | The size of RAM (32-bit integer) | -| 0x11A | The offset of RAM from file start (32-bit integer) | -| 0x11E | The size of VRAM (32-bit integer) | -| 0x122 | The offset of VRAM from file start (32-bit integer) | -| 0x126 | The size of MBC RAM (32-bit integer) | -| 0x12A | The offset of MBC RAM from file start (32-bit integer) | +|Offset | Content | +|-------|--------------------------------------------------------------------| +| 0x97 | The size of RAM (32-bit integer) | +| 0x9B | The offset of RAM from file start (32-bit integer) | +| 0x9F | The size of VRAM (32-bit integer) | +| 0xA3 | The offset of VRAM from file start (32-bit integer) | +| 0xA7 | The size of MBC RAM (32-bit integer) | +| 0xAB | The offset of MBC RAM from file start (32-bit integer) | +| 0xAF | The size of OAM (=0xA0, 32-bit integer) | +| 0xB3 | The offset of OAM from file start (32-bit integer) | +| 0xB7 | The size of HRAM (=0x7F, 32-bit integer) | +| 0xBB | The offset of HRAM from file start (32-bit integer) | +| 0xBF | The size of background palettes (=0x40 or 0, 32-bit integer) | +| 0xC3 | The offset of background palettes from file start (32-bit integer) | +| 0xC7 | The size of object palettes (=0x40 or 0, 32-bit integer) | +| 0xCB | The offset of object palettes from file start (32-bit integer) | -The contents of large RAM sizes are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. +The contents of large buffers are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. Background and object palette sizes must be 0 for models prior to Game Boy Color. #### NAME block @@ -100,13 +107,10 @@ The NAME block uses the `'NAME'` identifier, and is an optional block that conta The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII. -#### OAM block +#### XOAM block -The OAM block uses the `'OAM '` identifier, and is a required block that contains the data of OAM. This block length can be either 160 or 256. When 256 bytes of data are used, the addition bytes are used to set the values for the additional OAM range at `0xFEA0-0xFEFF`. Implementation that do not emulate that extra range are free to ignore the excess bytes. +The XOAM block uses the `'XOAM'` identifier, and is an optional block that contains the data of extra OAM (addresses `0xFEA0-0xFEFF`). This block length must be `0x60`. Implementations that do not emulate this extra range are free to ignore the excess bytes, and to not create this block. -#### PALS block - -The PALS block uses the `'PALS'` identifier, and is an optional block that is only used for GBC-compatible models. The length of this block is 0x80 bytes and it contains the contents of background palette RAM (0x40 bytes) followed by the contents of object palette RAM (another 0x40 bytes). #### MBC block From 6ddfcc9725603f7a6ab7469d39240b585e7efbac Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Apr 2021 16:10:10 +0300 Subject: [PATCH 156/365] Added visualizer to the GBS player, various GBS UI improvements --- Cocoa/Document.h | 2 + Cocoa/Document.m | 15 ++++++- Cocoa/GBS.xib | 65 ++++++++++++++++----------- Cocoa/GBViewMetal.m | 4 +- Cocoa/GBVisualizerView.h | 14 ++++++ Cocoa/GBVisualizerView.m | 95 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+), 28 deletions(-) create mode 100644 Cocoa/GBVisualizerView.h create mode 100644 Cocoa/GBVisualizerView.m diff --git a/Cocoa/Document.h b/Cocoa/Document.h index e0a3aa3..297f508 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -2,6 +2,7 @@ #include "GBView.h" #include "GBImageView.h" #include "GBSplitView.h" +#include "GBVisualizerView.h" @class GBCheatWindowController; @@ -47,6 +48,7 @@ @property (strong) IBOutlet NSButton *gbsPlayPauseButton; @property (strong) IBOutlet NSButton *gbsRewindButton; @property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton; +@property (strong) IBOutlet GBVisualizerView *gbsVisualizer; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 9d2d7f7..0e7009a 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -318,6 +318,11 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) - (void) vblank { + if (_gbsVisualizer) { + dispatch_async(dispatch_get_main_queue(), ^{ + [_gbsVisualizer setNeedsDisplay:YES]; + }); + } [self.view flip]; if (borderModeChanged) { dispatch_sync(dispatch_get_main_queue(), ^{ @@ -344,6 +349,9 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) - (void)gotNewSample:(GB_sample_t *)sample { + if (_gbsVisualizer) { + [_gbsVisualizer addSample:sample]; + } [audioLock lock]; if (self.audioClient.isPlaying) { if (audioBufferPosition == audioBufferSize) { @@ -837,6 +845,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (IBAction)changeGBSTrack:(id)sender { + if (!running) { + [self start]; + } [self performAtomicBlock:^{ GB_gbs_switch_track(&gb, self.gbsTracks.indexOfSelectedItem); }]; @@ -878,7 +889,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [_mainWindow standardWindowButton:NSWindowZoomButton].enabled = false; }); [_mainWindow.contentView addSubview:self.gbsPlayerView]; - + _mainWindow.movableByWindowBackground = true; + [_mainWindow setContentBorderThickness:24 forEdge:NSRectEdgeMinY]; + self.gbsTitle.stringValue = [NSString stringWithCString:info->title encoding:NSISOLatin1StringEncoding] ?: @"GBS Player"; self.gbsAuthor.stringValue = [NSString stringWithCString:info->author encoding:NSISOLatin1StringEncoding] ?: @"Unknown Composer"; NSString *copyright = [NSString stringWithCString:info->copyright encoding:NSISOLatin1StringEncoding]; diff --git a/Cocoa/GBS.xib b/Cocoa/GBS.xib index 6d5ba01..534ff55 100644 --- a/Cocoa/GBS.xib +++ b/Cocoa/GBS.xib @@ -16,44 +16,36 @@ + - + - - - + + + - - - - - - - - - - - - + + + - - + + @@ -86,8 +78,8 @@ - - + + @@ -99,8 +91,31 @@ + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 9a1c78b..580db2c 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -123,7 +123,7 @@ static const vector_float2 rect[] = command_queue = [device newCommandQueue]; } -- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size +- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size { output_resolution = (vector_float2){size.width, size.height}; dispatch_async(dispatch_get_main_queue(), ^{ @@ -131,7 +131,7 @@ static const vector_float2 rect[] = }); } -- (void)drawInMTKView:(nonnull MTKView *)view +- (void)drawInMTKView:(MTKView *)view { if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return; if (!self.gb) return; diff --git a/Cocoa/GBVisualizerView.h b/Cocoa/GBVisualizerView.h new file mode 100644 index 0000000..43cda4b --- /dev/null +++ b/Cocoa/GBVisualizerView.h @@ -0,0 +1,14 @@ +// +// GBVisualizerView.h +// SameBoySDL +// +// Created by Lior Halphon on 7/4/21. +// Copyright © 2021 Lior Halphon. All rights reserved. +// + +#import +#include + +@interface GBVisualizerView : NSView +- (void)addSample:(GB_sample_t *)sample; +@end diff --git a/Cocoa/GBVisualizerView.m b/Cocoa/GBVisualizerView.m new file mode 100644 index 0000000..c09cfe1 --- /dev/null +++ b/Cocoa/GBVisualizerView.m @@ -0,0 +1,95 @@ +// +// GBVisualizerView.m +// SameBoySDL +// +// Created by Lior Halphon on 7/4/21. +// Copyright © 2021 Lior Halphon. All rights reserved. +// + +#import "GBVisualizerView.h" +#include + +#define SAMPLE_COUNT 1024 + +static NSColor *color_to_effect_color(typeof(GB_PALETTE_DMG.colors[0]) color) +{ + if (@available(macOS 10.10, *)) { + double tint = MAX(color.r, MAX(color.g, color.b)) + 64; + + return [NSColor colorWithRed:color.r / tint + green:color.g / tint + blue:color.b / tint + alpha:tint/(255 + 64)]; + + } + return [NSColor colorWithRed:color.r / 255.0 + green:color.g / 255.0 + blue:color.b / 255.0 + alpha:1.0]; +} + +@implementation GBVisualizerView +{ + GB_sample_t _samples[SAMPLE_COUNT]; + size_t _position; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + const GB_palette_t *palette; + switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]) { + case 1: + palette = &GB_PALETTE_DMG; + break; + + case 2: + palette = &GB_PALETTE_MGB; + break; + + case 3: + palette = &GB_PALETTE_GBL; + break; + + default: + palette = &GB_PALETTE_GREY; + break; + } + NSSize size = self.bounds.size; + + [color_to_effect_color(palette->colors[0]) setFill]; + NSRectFill(self.bounds); + + NSBezierPath *line = [NSBezierPath bezierPath]; + [line moveToPoint:NSMakePoint(0, size.height / 2)]; + + for (unsigned i = 0; i < SAMPLE_COUNT; i++) { + GB_sample_t *sample = _samples + ((i + _position) % SAMPLE_COUNT); + double volume = ((signed)sample->left + (signed)sample->right) / 32768.0; + [line lineToPoint:NSMakePoint(size.width * (i + 0.5) / SAMPLE_COUNT, + (volume + 1) * size.height / 2)]; + } + + [line lineToPoint:NSMakePoint(size.width, size.height / 2)]; + [line setLineWidth:1.0]; + + [color_to_effect_color(palette->colors[2]) setFill]; + [line fill]; + + [color_to_effect_color(palette->colors[1]) setFill]; + NSRectFill(NSMakeRect(0, size.height / 2 - 0.5, size.width, 1)); + + [color_to_effect_color(palette->colors[3]) setStroke]; + [line stroke]; + + [super drawRect:dirtyRect]; +} + +- (void)addSample:(GB_sample_t *)sample +{ + _samples[_position++] = *sample; + if (_position == SAMPLE_COUNT) { + _position = 0; + } +} + +@end From d0bbf383d6c8c251d379e4082645f1d38f7e94cf Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Apr 2021 16:10:23 +0300 Subject: [PATCH 157/365] Another cheat bugfix --- Core/cheats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/cheats.c b/Core/cheats.c index 8defc6c..c7c43fe 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -109,7 +109,7 @@ void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat) GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)]; for (unsigned i = 0; i < (*hash)->size; i++) { if ((*hash)->cheats[i] == cheat) { - (*hash)->cheats[i] = (*hash)->cheats[(*hash)->size--]; + (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size]; if ((*hash)->size == 0) { free(*hash); *hash = NULL; From ad05eb6d0a02a8fac351eb75bd94c5675a9b305f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Apr 2021 16:15:40 +0300 Subject: [PATCH 158/365] GCC build fix --- Core/gb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index fe75b26..2db749a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -347,7 +347,7 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) return errno; } fread(&gb->gbs_header, sizeof(gb->gbs_header), 1, f); - if (gb->gbs_header.magic != htonl('GBS\x01') || + if (gb->gbs_header.magic != BE32('GBS\x01') || LE16(gb->gbs_header.load_address) < 0x400 || LE16(gb->gbs_header.load_address) >= 0x8000) { GB_log(gb, "Not a valid GBS file: %s.\n", strerror(errno)); From 44c75ae7be199d7267921fdb988a53b4d562698b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Apr 2021 18:43:24 +0300 Subject: [PATCH 159/365] Remove commented out code --- Core/display.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 72e89c8..6979a0a 100644 --- a/Core/display.c +++ b/Core/display.c @@ -574,7 +574,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) else if (gb->model & GB_MODEL_NO_SFC_BIT) { if (gb->icd_pixel_callback) { icd_pixel = pixel; - //gb->icd_pixel_callback(gb, pixel); } } else if (gb->cgb_palettes_ppu_blocked) { From f24489b9834781ef4c3fff86fa58da3f7560c151 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Apr 2021 23:56:41 +0300 Subject: [PATCH 160/365] TPP1 support --- Core/gb.h | 7 +++- Core/mbc.c | 17 ++++++++ Core/mbc.h | 1 + Core/memory.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++--- Core/timing.c | 47 +++++++++++++++------- 5 files changed, 156 insertions(+), 21 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 7210e7a..b09a506 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -441,10 +441,10 @@ struct GB_gameboy_internal_s { uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ bool camera_registers_mapped; uint8_t camera_registers[0x36]; - bool rumble_state; + uint8_t rumble_strength; bool cart_ir; - // TODO: move to huc3/mbc3 struct when breaking save compat + // TODO: move to huc3/mbc3/tpp1 struct when breaking save compat uint8_t huc3_mode; uint8_t huc3_access_index; uint16_t huc3_minutes, huc3_days; @@ -453,6 +453,9 @@ struct GB_gameboy_internal_s { uint8_t huc3_read; uint8_t huc3_access_flags; bool mbc3_rtc_mapped; + uint16_t tpp1_rom_bank; + uint8_t tpp1_ram_bank; + uint8_t tpp1_mode; ); diff --git a/Core/mbc.c b/Core/mbc.c index 1e91ce2..ffb8d9d 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -111,12 +111,24 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_rom_bank = gb->huc3.rom_bank; gb->mbc_ram_bank = gb->huc3.ram_bank; break; + case GB_TPP1: + gb->mbc_rom_bank = gb->tpp1_rom_bank; + gb->mbc_ram_bank = gb->tpp1_ram_bank; + gb->mbc_ram_enable = (gb->tpp1_mode == 2) || (gb->tpp1_mode == 3); + break; } } 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}; + 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"); @@ -130,6 +142,11 @@ void GB_configure_cart(GB_gameboy_t *gb) if (gb->cartridge_type->mbc_type == GB_MBC2) { gb->mbc_ram_size = 0x200; } + else if (gb->cartridge_type->mbc_type == GB_TPP1) { + if (gb->rom[0x152] >= 1 && gb->rom[0x152] <= 9) { + gb->mbc_ram_size = 0x2000 << (gb->rom[0x152] - 1); + } + } else { static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; diff --git a/Core/mbc.h b/Core/mbc.h index 6a23300..3bbe782 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -12,6 +12,7 @@ typedef struct { GB_MBC5, GB_HUC1, GB_HUC3, + GB_TPP1, } mbc_type; enum { GB_STANDARD_MBC, diff --git a/Core/memory.c b/Core/memory.c index b1619a6..3e7d4a4 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -178,7 +178,31 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } } - if ((!gb->mbc_ram_enable) && + if (gb->cartridge_type->mbc_type == GB_TPP1) { + switch (gb->tpp1_mode) { + case 0: + switch (addr & 3) { + case 0: return gb->tpp1_rom_bank; + case 1: return gb->tpp1_rom_bank >> 8; + case 2: return gb->tpp1_ram_bank; + case 3: return gb->rumble_strength | (((gb->rtc_real.high & 0xC0) ^ 0x40) >> 4); + } + case 2: + case 3: + break; // Read RAM + case 5: + switch (addr & 3) { + case 0: return (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) / 7; // Week count + case 1: return gb->rtc_latched.hours | + (((((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) % 7) << 5); // Hours and weekday + case 2: return gb->rtc_latched.minutes; + case 3: return gb->rtc_latched.seconds; + } + default: + return 0xFF; + } + } + else if ((!gb->mbc_ram_enable) && gb->cartridge_type->mbc_subtype != GB_CAMERA && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) { @@ -335,9 +359,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF00) { - return 0; - } if (addr < 0xFF80) { @@ -539,8 +561,8 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x3000: gb->mbc5.rom_bank_high = value; break; case 0x4000: case 0x5000: if (gb->cartridge_type->has_rumble) { - if (!!(value & 8) != gb->rumble_state) { - gb->rumble_state = !gb->rumble_state; + if (!!(value & 8) != !!gb->rumble_strength) { + gb->rumble_strength = gb->rumble_strength? 0 : 3; } value &= 7; } @@ -567,6 +589,49 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; } break; + case GB_TPP1: + switch (addr & 3) { + case 0: + gb->tpp1_rom_bank &= 0xFF00; + gb->tpp1_rom_bank |= value; + break; + case 1: + gb->tpp1_rom_bank &= 0xFF; + gb->tpp1_rom_bank |= value << 8; + break; + case 2: + gb->tpp1_ram_bank = value; + break; + case 3: + switch (value) { + case 0: + case 2: + case 3: + case 5: + gb->tpp1_mode = value; + break; + case 0x10: + memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + break; + case 0x11: { + uint8_t flags = gb->rtc_real.high & 0xc0; + memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real)); + gb->rtc_real.high &= ~0xc0; + gb->rtc_real.high |= flags; + break; + } + case 0x14: + gb->rtc_real.high &= ~0x80; + break; + case 0x18: + gb->rtc_real.high |= 0x40; + break; + case 0x19: + gb->rtc_real.high &= ~0x40; + break; + } + } + break; } GB_update_mbc_mappings(gb); } @@ -688,6 +753,36 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } + if (gb->cartridge_type->mbc_type == GB_TPP1) { + switch (gb->tpp1_mode) { + case 3: + break; + case 5: + switch (addr & 3) { + case 0: { + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + total_days = total_days % 7 + value * 7; + gb->rtc_latched.days = total_days; + gb->rtc_latched.high = total_days >> 8; + return; + } + case 1: { + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + total_days = total_days / 7 * 7 + (value >> 5); + gb->rtc_latched.hours = value & 0x1F; + gb->rtc_latched.days = total_days; + gb->rtc_latched.high = total_days >> 8; + return; + } + case 2: gb->rtc_latched.minutes = value; return; + case 3: gb->rtc_latched.seconds = value; return; + } + return; + default: + return; + } + } + if ((!gb->mbc_ram_enable) && gb->cartridge_type->mbc_type != GB_HUC1) return; diff --git a/Core/timing.c b/Core/timing.c index 1da82a3..d240525 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -288,10 +288,22 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { gb->last_rtc_second += 60 * 60 * 24; if (++gb->rtc_real.days == 0) { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/ + gb->rtc_real.high &= 0x40; + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + else { + gb->rtc_real.high++; + } + } + else { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_real.high ^= 1; } - gb->rtc_real.high ^= 1; } } @@ -308,11 +320,22 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) if (++gb->rtc_real.days != 0) continue; - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/ + gb->rtc_real.high &= 0x40; + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + else { + gb->rtc_real.high++; + } + } + else { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_real.high ^= 1; } - - gb->rtc_real.high ^= 1; } } } @@ -344,13 +367,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_since_last_sync += cycles; gb->cycles_since_run += cycles; - if (gb->rumble_state) { - gb->rumble_on_cycles++; - } - else { - gb->rumble_off_cycles++; - } - + gb->rumble_on_cycles += gb->rumble_strength & 3; + gb->rumble_off_cycles += (gb->rumble_strength & 3) ^ 3; + if (!gb->stopped) { // TODO: Verify what happens in STOP mode GB_dma_run(gb); GB_hdma_run(gb); From 0c5e15b49dd8a8167dffc11a2fadecc74464bd33 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 02:38:58 +0300 Subject: [PATCH 161/365] Correct emulation of count overflow in ATTR_CHR, fixes #372 --- Core/sgb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/sgb.c b/Core/sgb.c index c77b0db..f22eb3e 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -269,7 +269,8 @@ static void command_ready(GB_gameboy_t *gb) #endif uint8_t x = command->x; uint8_t y = command->y; - if (x >= 20 || y >= 18 || (count + 3) / 4 > sizeof(gb->sgb->command) - sizeof(*command) - 1) { + count = MIN(count, 20 * 18); + if (x >= 20 || y >= 18) { /* TODO: Verify with the SFC BIOS */ break; } From 42471095e41e6d062cee9cf408360fbbbfa33540 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 22:38:25 +0300 Subject: [PATCH 162/365] Normalize invalid weekdays only after a $11 command --- Core/memory.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 3e7d4a4..20aaac9 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -192,9 +192,20 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) break; // Read RAM case 5: switch (addr & 3) { - case 0: return (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) / 7; // Week count - case 1: return gb->rtc_latched.hours | - (((((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) % 7) << 5); // Hours and weekday + case 0: { // Week count + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + if (gb->rtc_latched.high & 0x20) { + return total_days / 7 - 1; + } + return total_days / 7; + } + case 1: { // Week count + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + if (gb->rtc_latched.high & 0x20) { + return gb->rtc_latched.hours | 0xe0; // Hours and weekday + } + return gb->rtc_latched.hours | ((total_days % 7) << 5); // Hours and weekday + } case 2: return gb->rtc_latched.minutes; case 3: return gb->rtc_latched.seconds; } @@ -616,7 +627,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x11: { uint8_t flags = gb->rtc_real.high & 0xc0; memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real)); - gb->rtc_real.high &= ~0xc0; + gb->rtc_real.high &= ~0xe0; gb->rtc_real.high |= flags; break; } @@ -762,8 +773,12 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0: { unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); total_days = total_days % 7 + value * 7; + bool had_illegal_weekday = gb->rtc_latched.high & 0x20; gb->rtc_latched.days = total_days; gb->rtc_latched.high = total_days >> 8; + if (had_illegal_weekday) { + gb->rtc_latched.high |= 0x20; + } return; } case 1: { @@ -772,6 +787,9 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->rtc_latched.hours = value & 0x1F; gb->rtc_latched.days = total_days; gb->rtc_latched.high = total_days >> 8; + if ((value & 0xE0) == 0xE0) { // Illegal weekday + gb->rtc_latched.high |= 0x20; + } return; } case 2: gb->rtc_latched.minutes = value; return; From 763de9d2e0c8846a4dc82f67971392f88e028a75 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 22:52:34 +0300 Subject: [PATCH 163/365] Fix Rumble support in TPP1 --- Core/memory.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index 20aaac9..0082131 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -640,6 +640,13 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x19: gb->rtc_real.high &= ~0x40; break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + gb->rumble_strength = value & 3; + break; } } break; From 80f422d0cad6580f81028c1bdfb91b6aadf609f7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 23:16:31 +0300 Subject: [PATCH 164/365] Respect TPP1 feature flags for rumble and RTC --- Core/debugger.c | 7 +++++-- Core/gb.c | 4 ++++ Core/rumble.c | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 73bd80a..be80fd0 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1523,7 +1523,9 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg const GB_cartridge_t *cartridge = gb->cartridge_type; if (cartridge->has_ram) { - GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); + bool has_battery = gb->cartridge_type->has_battery && + (gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 8)); + GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", has_battery? " battery-backed": "", gb->mbc_ram_size); } else { GB_log(gb, "No cartridge RAM\n"); @@ -1565,7 +1567,8 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "No MBC\n"); } - if (cartridge->has_rumble) { + if (gb->cartridge_type->has_rumble && + (gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) { GB_log(gb, "Cart contains a Rumble Pak\n"); } diff --git a/Core/gb.c b/Core/gb.c index 3a0864d..e6747c6 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -609,6 +609,8 @@ typedef union { int GB_save_battery_size(GB_gameboy_t *gb) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ if (gb->cartridge_type->mbc_type == GB_HUC3) { @@ -621,6 +623,7 @@ int GB_save_battery_size(GB_gameboy_t *gb) int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save. if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ if (size < GB_save_battery_size(gb)) return EIO; @@ -678,6 +681,7 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) int GB_save_battery(GB_gameboy_t *gb, const char *path) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save. if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ FILE *f = fopen(path, "wb"); if (!f) { diff --git a/Core/rumble.c b/Core/rumble.c index 87eb870..5f83c47 100644 --- a/Core/rumble.c +++ b/Core/rumble.c @@ -15,7 +15,8 @@ void GB_handle_rumble(GB_gameboy_t *gb) if (gb->rumble_mode == GB_RUMBLE_DISABLED) { return; } - if (gb->cartridge_type->has_rumble) { + if (gb->cartridge_type->has_rumble && + (gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) { if (gb->rumble_on_cycles + gb->rumble_off_cycles) { gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles)); gb->rumble_on_cycles = gb->rumble_off_cycles = 0; From 251dd15ff91cf7638a93d8386f4eee3af3b4fcdd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 23:36:42 +0300 Subject: [PATCH 165/365] Fixed a bug where the screen would not redraw when certain controllers are rumbling in specific strengths in the Cocoa port --- JoyKit/JOYController.m | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index ca2d1b1..8ec1279 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -166,6 +166,7 @@ typedef union { double _sentRumbleAmp; unsigned _rumbleCounter; bool _deviceCantSendReports; + dispatch_queue_t _rumbleQueue; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks @@ -490,9 +491,11 @@ typedef union { {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0}, } }; - } + _rumbleQueue = dispatch_queue_create([NSString stringWithFormat:@"Rumble Queue for %@", self.deviceName].UTF8String, + NULL); + return self; } @@ -564,7 +567,9 @@ typedef union { } } } - [self updateRumble]; + dispatch_async(_rumbleQueue, ^{ + [self updateRumble]; + }); } - (void)elementChanged:(IOHIDElementRef)element @@ -699,7 +704,9 @@ typedef union { _physicallyConnected = false; [exposedControllers removeObject:self]; [self setRumbleAmplitude:0]; - [self updateRumble]; + dispatch_sync(_rumbleQueue, ^{ + [self updateRumble]; + }); _device = nil; } From 9a1f962281864f53ee9336fe385d6e5560b77d9f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 22:39:13 +0300 Subject: [PATCH 166/365] Spec update --- BESS.md | 40 +++++++++++++++++++++------------------- Core/save_state.c | 5 +++++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/BESS.md b/BESS.md index da601f3..ab4f3ea 100644 --- a/BESS.md +++ b/BESS.md @@ -34,7 +34,7 @@ Every block is followed by another blocked, until the END block is reached. The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block. -The length of the CORE block is 0xCF bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: +The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: | Offset | Content | |--------|---------------------------------------| @@ -54,7 +54,7 @@ BESS uses a four-character string to identify Game Boy models: * The third letter represents a specific CPU revision within a model, and is optional (If an implementation does not distinguish between revisions, a space character may be used). The allowed values for model GD (DMG) are `'0'` and `'A'`, through `'C'`; the allowed values for model CC (CGB) are `'0'` and `'A'`, through `'E'`; the allowed values for model CA (AGB, AGS, GBP) are `'0'`, `'A'` and `'B'`; and for every other model this value must be a space character. * The last character is used for padding and must be a space character. -For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `CCE ` represent a CGB using CPU revision E. +For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E. | Offset | Content | |--------|-------------------------------------------------------| @@ -67,7 +67,8 @@ For example; `'GD '` represents a DMG of an unspecified revision, `'S '` repr | 0x14 | The value of IME (0 or 1) | | 0x15 | The value of the IE register | | 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | -| 0x17 | The values of every memory-mapped register (128 bytes) | +| 0x17 | Reserved, must be 0 | +| 0x18 | The values of every memory-mapped register (128 bytes) | The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: * Unused registers have Don't-Care values which should be ignored @@ -81,25 +82,26 @@ The values of memory-mapped registers should be written 'as-is' to memory as if * The value store for DIV will be used to set the internal divisor to `DIV << 8` * Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers) -|Offset | Content | -|-------|--------------------------------------------------------------------| -| 0x97 | The size of RAM (32-bit integer) | -| 0x9B | The offset of RAM from file start (32-bit integer) | -| 0x9F | The size of VRAM (32-bit integer) | -| 0xA3 | The offset of VRAM from file start (32-bit integer) | -| 0xA7 | The size of MBC RAM (32-bit integer) | -| 0xAB | The offset of MBC RAM from file start (32-bit integer) | -| 0xAF | The size of OAM (=0xA0, 32-bit integer) | -| 0xB3 | The offset of OAM from file start (32-bit integer) | -| 0xB7 | The size of HRAM (=0x7F, 32-bit integer) | -| 0xBB | The offset of HRAM from file start (32-bit integer) | -| 0xBF | The size of background palettes (=0x40 or 0, 32-bit integer) | -| 0xC3 | The offset of background palettes from file start (32-bit integer) | -| 0xC7 | The size of object palettes (=0x40 or 0, 32-bit integer) | -| 0xCB | The offset of object palettes from file start (32-bit integer) | +| Offset | Content | +|--------|--------------------------------------------------------------------| +| 0x98 | The size of RAM (32-bit integer) | +| 0x9C | The offset of RAM from file start (32-bit integer) | +| 0xA0 | The size of VRAM (32-bit integer) | +| 0xA4 | The offset of VRAM from file start (32-bit integer) | +| 0xA9 | The size of MBC RAM (32-bit integer) | +| 0xAC | The offset of MBC RAM from file start (32-bit integer) | +| 0xB0 | The size of OAM (=0xA0, 32-bit integer) | +| 0xB4 | The offset of OAM from file start (32-bit integer) | +| 0xB9 | The size of HRAM (=0x7F, 32-bit integer) | +| 0xBC | The offset of HRAM from file start (32-bit integer) | +| 0xC0 | The size of background palettes (=0x40 or 0, 32-bit integer) | +| 0xC4 | The offset of background palettes from file start (32-bit integer) | +| 0xC8 | The size of object palettes (=0x40 or 0, 32-bit integer) | +| 0xCC | The offset of object palettes from file start (32-bit integer) | The contents of large buffers are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. Background and object palette sizes must be 0 for models prior to Game Boy Color. +An implementation needs handle size mismatches gracefully. For example, if too large MBC RAM size is specified, the superfluous data should be ignored. On the other hand, if a too small VRAM size is specified (For example, if it's a save state from an emulator emulating a CGB in DMG mode, and it didn't save the second CGB VRAM bank), the implementation is expected to set that extra bank to all zeros. #### NAME block diff --git a/Core/save_state.c b/Core/save_state.c index 8d00416..e5df68c 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -50,6 +50,7 @@ typedef struct __attribute__((packed)) { uint8_t ime; uint8_t ie; uint8_t execution_mode; // 0 = running; 1 = halted; 2 = stopped + uint8_t _padding; uint8_t io_registers[0x80]; @@ -725,6 +726,10 @@ static void read_bess_buffer(const BESS_buffer_t *buffer, virtual_file_t *file, file->seek(file, LE32(buffer->offset), SEEK_SET); file->read(file, dest, MIN(LE32(buffer->size), max_size)); file->seek(file, pos, SEEK_SET); + + if (LE32(buffer->size) < max_size) { + memset(dest + LE32(buffer->size), 0, max_size - LE32(buffer->size)); + } } static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_sameboy) From 9c1889f4500b29daeb07631649a4f34308e78088 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 22:43:23 +0300 Subject: [PATCH 167/365] Actually update spec --- BESS.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/BESS.md b/BESS.md index ab4f3ea..d31e174 100644 --- a/BESS.md +++ b/BESS.md @@ -38,14 +38,14 @@ The length of the CORE block is 0xD0 bytes, but implementations are expected to | Offset | Content | |--------|---------------------------------------| -| 0x00 | Major BESS version as a 16-bit integer | -| 0x02 | Minor BESS version as a 16-bit integer | +| 0x00 | Major BESS version as a 16-bit integer | +| 0x02 | Minor BESS version as a 16-bit integer | Both major and minor versions should be 1. Implementations are expected to reject incompatible majors, but still attempt to read newer minor versions. | Offset | Content | |--------|----------------------------------------| -| 0x04 | A four-character ASCII model identifier | +| 0x04 | A four-character ASCII model identifier | BESS uses a four-character string to identify Game Boy models: @@ -58,23 +58,24 @@ For example; `'GD '` represents a DMG of an unspecified revision, `'S '` repr | Offset | Content | |--------|-------------------------------------------------------| -| 0x08 | The value of the PC register | -| 0x0A | The value of the AF register | -| 0x0C | The value of the BC register | -| 0x0E | The value of the DE register | -| 0x10 | The value of the HL register | -| 0x12 | The value of the SP register | -| 0x14 | The value of IME (0 or 1) | -| 0x15 | The value of the IE register | -| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | -| 0x17 | Reserved, must be 0 | -| 0x18 | The values of every memory-mapped register (128 bytes) | +| 0x08 | The value of the PC register | +| 0x0A | The value of the AF register | +| 0x0C | The value of the BC register | +| 0x0E | The value of the DE register | +| 0x10 | The value of the HL register | +| 0x12 | The value of the SP register | +| 0x14 | The value of IME (0 or 1) | +| 0x15 | The value of the IE register | +| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | +| 0x17 | Reserved, must be 0 | +| 0x18 | The values of every memory-mapped register (128 bytes) | The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: * Unused registers have Don't-Care values which should be ignored * Unused register bits have Don't-Care values which should be ignored -* The value of KEY0 (FF4C) must be valid as it determines CGB mode +* The value of KEY0 (FF4C) must be valid as it determines DMG mode when the model is CGB or newer * Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect +* If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. * BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. * Implementations should not start a serial transfer when writing the value of SB * Similarly, no value of NRx4 should trigger a sound pulse on save state load From 4346b063f595d4d058106a6cd81e11bcf4c4e660 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 22:48:05 +0300 Subject: [PATCH 168/365] Wording --- BESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BESS.md b/BESS.md index d31e174..099985c 100644 --- a/BESS.md +++ b/BESS.md @@ -73,7 +73,7 @@ For example; `'GD '` represents a DMG of an unspecified revision, `'S '` repr The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: * Unused registers have Don't-Care values which should be ignored * Unused register bits have Don't-Care values which should be ignored -* The value of KEY0 (FF4C) must be valid as it determines DMG mode when the model is CGB or newer +* If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode * Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect * If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. * BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. From 5b993ed7752e73a54f5b277701c89aea20aaf887 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 23:36:35 +0300 Subject: [PATCH 169/365] Add HuC3 to BESS --- BESS.md | 16 +++++++++- Core/gb.c | 10 +----- Core/gb.h | 8 +++++ Core/save_state.c | 78 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 82 insertions(+), 30 deletions(-) diff --git a/BESS.md b/BESS.md index 099985c..e935f45 100644 --- a/BESS.md +++ b/BESS.md @@ -135,7 +135,7 @@ This block contains an MBC-specific number of 3-byte-long pairs that represent t An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` range are not allowed. #### RTC block -The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. +The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. The length of this block is 0x30 bytes long and it follows the following structure: @@ -153,6 +153,20 @@ The length of this block is 0x30 bytes long and it follows the following structu | 0x24 | Latched high/overflow/running (1 byte), followed by 3 bytes of padding | | 0x28 | UNIX timestamp at the time of the save state (64-bit) | +#### HUC3 block +The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is used while emulating an HuC3 cartridge to store RTC and alarm information. The contents of this block are identical to HuC3 RTC saves from SameBoy. + +The length of this block is 0x11 bytes long and it follows the following structure: + +|Offset | Content | +|--------|-------------------------------------------------------| +| 0x00 | UNIX timestamp at the time of the save state (64-bit) | +| 0x08 | RTC minutes (16-bit) | +| 0x0A | RTC days (16-bit) | +| 0x0C | Scheduled alarm time minutes (16-bit) | +| 0x0E | Scheduled alarm time days (16-bit) | +| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) | + #### SGB block The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing. diff --git a/Core/gb.c b/Core/gb.c index 9e06042..e6a5d94 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -578,14 +578,6 @@ typedef struct { uint8_t padding5[3]; } GB_vba_rtc_time_t; -typedef struct __attribute__((packed)) { - uint64_t last_rtc_second; - uint16_t minutes; - uint16_t days; - uint16_t alarm_minutes, alarm_days; - uint8_t alarm_enabled; -} GB_huc3_rtc_time_t; - typedef union { struct __attribute__((packed)) { GB_rtc_time_t rtc_real; @@ -609,7 +601,7 @@ int GB_save_battery_size(GB_gameboy_t *gb) if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ if (gb->cartridge_type->mbc_type == GB_HUC3) { - return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); + return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); } GB_rtc_save_t rtc_save_size; return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); diff --git a/Core/gb.h b/Core/gb.h index 065e176..2cc2137 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -101,6 +101,14 @@ typedef union { uint8_t data[5]; } GB_rtc_time_t; +typedef struct __attribute__((packed)) { + uint64_t last_rtc_second; + uint16_t minutes; + uint16_t days; + uint16_t alarm_minutes, alarm_days; + uint8_t alarm_enabled; +} GB_huc3_rtc_time_t; + typedef enum { // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, diff --git a/Core/save_state.c b/Core/save_state.c index e5df68c..d38bd25 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -100,6 +100,12 @@ typedef struct __attribute__((packed)){ uint64_t last_rtc_second; } BESS_RTC_t; +/* Same HuC3 RTC format as used by SameBoy and BGB in battery saves */ +typedef struct __attribute__((packed)){ + BESS_block_t header; + GB_huc3_rtc_time_t data; +} BESS_HUC3_t; + typedef struct __attribute__((packed)) { uint16_t address; uint8_t value; @@ -208,7 +214,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) case GB_HUC1: return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); case GB_HUC3: - return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t); + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t); } } @@ -233,7 +239,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + sizeof(BESS_NAME) - 1 + sizeof(BESS_XOAM_t) + (gb->sgb? sizeof(BESS_SGB_t) : 0) - + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC block + + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3 block + sizeof(BESS_block_t) // END block + sizeof(BESS_footer_t); } @@ -582,22 +588,40 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) } save_bess_mbc_block(gb, file); - if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { - BESS_RTC_t bess_rtc = {0,}; - bess_rtc.header = (BESS_block_t){BE32('RTC '), LE32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; - bess_rtc.real.seconds = gb->rtc_real.seconds; - bess_rtc.real.minutes = gb->rtc_real.minutes; - bess_rtc.real.hours = gb->rtc_real.hours; - bess_rtc.real.days = gb->rtc_real.days; - bess_rtc.real.high = gb->rtc_real.high; - bess_rtc.latched.seconds = gb->rtc_latched.seconds; - bess_rtc.latched.minutes = gb->rtc_latched.minutes; - bess_rtc.latched.hours = gb->rtc_latched.hours; - bess_rtc.latched.days = gb->rtc_latched.days; - bess_rtc.latched.high = gb->rtc_latched.high; - bess_rtc.last_rtc_second = LE64(gb->last_rtc_second); - if (file->write(file, &bess_rtc, sizeof(bess_rtc)) != sizeof(bess_rtc)) { - goto error; + if (gb->cartridge_type->has_rtc) { + if (gb->cartridge_type ->mbc_type != GB_HUC3) { + BESS_RTC_t bess_rtc = {0,}; + bess_rtc.header = (BESS_block_t){BE32('RTC '), LE32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; + bess_rtc.real.seconds = gb->rtc_real.seconds; + bess_rtc.real.minutes = gb->rtc_real.minutes; + bess_rtc.real.hours = gb->rtc_real.hours; + bess_rtc.real.days = gb->rtc_real.days; + bess_rtc.real.high = gb->rtc_real.high; + bess_rtc.latched.seconds = gb->rtc_latched.seconds; + bess_rtc.latched.minutes = gb->rtc_latched.minutes; + bess_rtc.latched.hours = gb->rtc_latched.hours; + bess_rtc.latched.days = gb->rtc_latched.days; + bess_rtc.latched.high = gb->rtc_latched.high; + bess_rtc.last_rtc_second = LE64(gb->last_rtc_second); + if (file->write(file, &bess_rtc, sizeof(bess_rtc)) != sizeof(bess_rtc)) { + goto error; + } + } + else { + BESS_HUC3_t bess_huc3 = {0,}; + bess_huc3.header = (BESS_block_t){BE32('HUC3'), LE32(sizeof(bess_huc3) - sizeof(bess_huc3.header))}; + + bess_huc3.data = (GB_huc3_rtc_time_t) { + LE64(gb->last_rtc_second), + LE16(gb->huc3_minutes), + LE16(gb->huc3_days), + LE16(gb->huc3_alarm_minutes), + LE16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, + }; + if (file->write(file, &bess_huc3, sizeof(bess_huc3)) != sizeof(bess_huc3)) { + goto error; + } } } @@ -900,8 +924,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo if (!found_core) goto parse_error; BESS_RTC_t bess_rtc; if (LE32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error; - if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { - if (file->read(file, &bess_rtc.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (file->read(file, &bess_rtc.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3) { gb->rtc_real.seconds = bess_rtc.real.seconds; gb->rtc_real.minutes = bess_rtc.real.minutes; gb->rtc_real.hours = bess_rtc.real.hours; @@ -915,6 +939,20 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo gb->last_rtc_second = LE64(bess_rtc.last_rtc_second); } break; + case BE32('HUC3'): + if (!found_core) goto parse_error; + BESS_HUC3_t bess_huc3; + if (LE32(block.size) != sizeof(bess_huc3) - sizeof(block)) goto parse_error; + if (file->read(file, &bess_huc3.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->mbc_type == GB_HUC3) { + gb->last_rtc_second = LE64(bess_huc3.data.last_rtc_second); + gb->huc3_minutes = LE16(bess_huc3.data.minutes); + gb->huc3_days = LE16(bess_huc3.data.days); + gb->huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes); + gb->huc3_alarm_days = LE16(bess_huc3.data.alarm_days); + gb->huc3_alarm_enabled = bess_huc3.data.alarm_enabled; + } + break; case BE32('SGB '): if (!found_core) goto parse_error; if (LE32(block.size) != sizeof(BESS_SGB_t) - sizeof(block)) goto parse_error; From a3a73602fc2396d7d19b459476af96659a7490f8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 01:09:29 +0300 Subject: [PATCH 170/365] ATF is only 0xFD2 bytes, not 0xFE0 --- Core/sgb.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/sgb.h b/Core/sgb.h index 320fb6a..1e67835 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -51,7 +51,8 @@ struct GB_sgb_s { uint16_t effective_palettes[4 * 4]; uint16_t ram_palettes[4 * 512]; uint8_t attribute_map[20 * 18]; - uint8_t attribute_files[0xFE0]; + uint8_t attribute_files[0xFD2]; + uint8_t attribute_files_padding[0xFE0 - 0xFD2]; /* Intro */ int16_t intro_animation; From 6ee488688bd763a96e0b9b81c803aa3b06b174a8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 01:11:06 +0300 Subject: [PATCH 171/365] Update spec --- BESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BESS.md b/BESS.md index e935f45..9ad36ff 100644 --- a/BESS.md +++ b/BESS.md @@ -187,7 +187,7 @@ The length of this block is 0x39 bytes and it follows the following structure: | 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | | 0x28 | The size of the attribute map (=0x168, 32-bit integer) | | 0x2C | The offset of the attribute map (32-bit integer) | -| 0x30 | The size of the attribute files (=0xfe0, 32-bit integer) | +| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) | | 0x34 | The offset of the attribute files (32-bit integer) | | 0x38 | Multiplayer status (1 byte); high nibble is player count, low nibble is current player (0-based) | From dfdbff7304fead42832574bf06eb48bf67bb03d0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 16:01:44 +0300 Subject: [PATCH 172/365] Allow writes to the $a000-$bfff range in the MBC block --- BESS.md | 2 +- Core/save_state.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/BESS.md b/BESS.md index 9ad36ff..2595219 100644 --- a/BESS.md +++ b/BESS.md @@ -132,7 +132,7 @@ This block contains an MBC-specific number of 3-byte-long pairs that represent t | 0x9 | The value 0x4000 as a 16-bit integer | | 0xB | The current RAM bank | -An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` range are not allowed. +An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. #### RTC block The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. diff --git a/Core/save_state.c b/Core/save_state.c index d38bd25..f279b61 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -916,7 +916,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo for (unsigned i = LE32(block.size); i > 0; i -= 3) { BESS_MBC_pair_t pair; file->read(file, &pair, sizeof(pair)); - if (LE16(pair.address) >= 0x8000) goto parse_error; + if (LE16(pair.address) >= 0x8000 && LE16(pair.address) < 0xA000) goto parse_error; + if (LE16(pair.address) >= 0xC000) goto parse_error; GB_write_memory(&save, LE16(pair.address), pair.value); } break; From fada772cb141aeb24e71a0e5aa00086765bff3ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 20:35:07 +0300 Subject: [PATCH 173/365] Don't use BESS for internal in-memory saves --- Core/debugger.c | 20 ++++++++++---------- Core/rewind.c | 8 ++++---- Core/save_state.c | 31 ++++++++++++++++++++++++++----- Core/save_state.h | 7 +++++++ 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 73bd80a..64fc9ee 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1899,7 +1899,7 @@ static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return true; } uint16_t pc = gb->pc; - GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size(gb)); + GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size_no_bess(gb)); GB_log(gb, "Reverted a \"%s\" command.\n", gb->undo_label); if (pc != gb->pc) { GB_cpu_disassemble(gb, gb->pc, 5); @@ -2205,8 +2205,8 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) const debugger_command_t *command = find_command(command_string); if (command) { - uint8_t *old_state = malloc(GB_get_save_state_size(gb)); - GB_save_state_to_buffer(gb, old_state); + uint8_t *old_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, old_state); bool ret = command->implementation(gb, arguments, modifiers, command); if (!ret) { // Command continues, save state in any case free(gb->undo_state); @@ -2214,9 +2214,9 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) gb->undo_label = command->command; } else { - uint8_t *new_state = malloc(GB_get_save_state_size(gb)); - GB_save_state_to_buffer(gb, new_state); - if (memcmp(new_state, old_state, GB_get_save_state_size(gb)) != 0) { + uint8_t *new_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, new_state); + if (memcmp(new_state, old_state, GB_get_save_state_size_no_bess(gb)) != 0) { // State changed, save the old state as the new undo state free(gb->undo_state); gb->undo_state = old_state; @@ -2306,8 +2306,8 @@ void GB_debugger_run(GB_gameboy_t *gb) if (gb->debug_disable) return; if (!gb->undo_state) { - gb->undo_state = malloc(GB_get_save_state_size(gb)); - GB_save_state_to_buffer(gb, gb->undo_state); + gb->undo_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, gb->undo_state); } char *input = NULL; @@ -2355,9 +2355,9 @@ next_command: } else if (jump_to_result == JUMP_TO_NONTRIVIAL) { if (!gb->nontrivial_jump_state) { - gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb)); + gb->nontrivial_jump_state = malloc(GB_get_save_state_size_no_bess(gb)); } - GB_save_state_to_buffer(gb, gb->nontrivial_jump_state); + GB_save_state_to_buffer_no_bess(gb, gb->nontrivial_jump_state); gb->non_trivial_jump_breakpoint_occured = false; should_delete_state = false; } diff --git a/Core/rewind.c b/Core/rewind.c index c3900d6..d305528 100644 --- a/Core/rewind.c +++ b/Core/rewind.c @@ -108,7 +108,7 @@ static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, void GB_rewind_push(GB_gameboy_t *gb) { - const size_t save_size = GB_get_save_state_size(gb); + const size_t save_size = GB_get_save_state_size_no_bess(gb); if (!gb->rewind_sequences) { if (gb->rewind_buffer_length) { gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length); @@ -140,11 +140,11 @@ void GB_rewind_push(GB_gameboy_t *gb) if (!gb->rewind_sequences[gb->rewind_pos].key_state) { gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size); - GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state); + GB_save_state_to_buffer_no_bess(gb, gb->rewind_sequences[gb->rewind_pos].key_state); } else { uint8_t *save_state = malloc(save_size); - GB_save_state_to_buffer(gb, save_state); + GB_save_state_to_buffer_no_bess(gb, save_state); gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] = state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size); free(save_state); @@ -158,7 +158,7 @@ bool GB_rewind_pop(GB_gameboy_t *gb) return false; } - const size_t save_size = GB_get_save_state_size(gb); + const size_t save_size = GB_get_save_state_size_no_bess(gb); if (gb->rewind_sequences[gb->rewind_pos].pos == 0) { GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size); free(gb->rewind_sequences[gb->rewind_pos].key_state); diff --git a/Core/save_state.c b/Core/save_state.c index f279b61..6aa4f37 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -218,7 +218,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) } } -size_t GB_get_save_state_size(GB_gameboy_t *gb) +size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb) { return GB_SECTION_SIZE(header) + GB_SECTION_SIZE(core_state) + sizeof(uint32_t) @@ -232,7 +232,12 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + gb->mbc_ram_size + gb->ram_size - + gb->vram_size + + gb->vram_size; +} + +size_t GB_get_save_state_size(GB_gameboy_t *gb) +{ + return GB_get_save_state_size_no_bess(gb) + // BESS + sizeof(BESS_CORE_t) + sizeof(BESS_block_t) // NAME @@ -453,7 +458,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) return 0; } -static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) +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; if (!DUMP_SECTION(gb, file, core_state)) goto error; @@ -475,6 +480,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; } + BESS_CORE_t bess_core = {0,}; bess_core.mbc_ram.offset = LE32(file->tell(file)); @@ -495,6 +501,8 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) goto error; } + if (!append_bess) return 0; + BESS_footer_t bess_footer = { .start_offset = LE32(file->tell(file)), .magic = BE32('BESS'), @@ -694,7 +702,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path) .tell = file_tell, .file = f, }; - int ret = save_state_internal(gb, &file); + int ret = save_state_internal(gb, &file, true); fclose(f); return ret; } @@ -709,10 +717,23 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) .position = 0, }; - save_state_internal(gb, &file); + save_state_internal(gb, &file, true); assert(file.position == GB_get_save_state_size(gb)); } +void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer) +{ + virtual_file_t file = { + .write = buffer_write, + .seek = buffer_seek, + .tell = buffer_tell, + .buffer = (uint8_t *)buffer, + .position = 0, + }; + + save_state_internal(gb, &file, false); + assert(file.position == GB_get_save_state_size_no_bess(gb)); +} static bool read_section(virtual_file_t *file, void *dest, uint32_t size, bool fix_broken_windows_saves) { diff --git a/Core/save_state.h b/Core/save_state.h index 8e5fc4e..0c447d9 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -27,4 +27,11 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); + +#ifdef GB_INTERNAL +/* For internal in-memory save states (rewind, debugger) that do not need BESS */ +size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb); +void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); +#endif + #endif /* save_state_h */ From 24915e41eb02735771f28de424af71175234530e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 20:56:09 +0300 Subject: [PATCH 174/365] TPP1 in BESS --- Core/save_state.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Core/save_state.c b/Core/save_state.c index 6aa4f37..f97251a 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -215,6 +215,8 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) return sizeof(BESS_block_t) + 4 * 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: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_RTC_t); } } @@ -443,6 +445,14 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) 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}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x0002), gb->tpp1_rom_bank}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x0003), gb->tpp1_mode}; + mbc_block.size = 4 * sizeof(pairs[0]); + break; } mbc_block.size = LE32(mbc_block.size); From 0af4f1fa4d7632d28b91f8a0c2805617156fe06e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 21:33:13 +0300 Subject: [PATCH 175/365] Clarify SGB multiplayer, handle count = 0 --- BESS.md | 38 +++++++++++++++++++------------------- Core/save_state.c | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/BESS.md b/BESS.md index 2595219..54efe04 100644 --- a/BESS.md +++ b/BESS.md @@ -139,7 +139,7 @@ The RTC block uses the `'RTC '` identifier, and is an optional block that is use The length of this block is 0x30 bytes long and it follows the following structure: -|Offset | Content | +| Offset | Content | |--------|------------------------------------------------------------------------| | 0x00 | Current seconds (1 byte), followed by 3 bytes of padding | | 0x04 | Current minutes (1 byte), followed by 3 bytes of padding | @@ -158,7 +158,7 @@ The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is us The length of this block is 0x11 bytes long and it follows the following structure: -|Offset | Content | +| Offset | Content | |--------|-------------------------------------------------------| | 0x00 | UNIX timestamp at the time of the save state (64-bit) | | 0x08 | RTC minutes (16-bit) | @@ -173,23 +173,23 @@ The SGB block uses the `'SGB '` identifier, and is an optional block that is onl The length of this block is 0x39 bytes and it follows the following structure: -|Offset | Content | -|--------|--------------------------------------------------------------------------------------------------| -| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) | -| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) | -| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) | -| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) | -| 0x10 | The size of the border palettes (=0x80, 32-bit integer) | -| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) | -| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) | -| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) | -| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) | -| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | -| 0x28 | The size of the attribute map (=0x168, 32-bit integer) | -| 0x2C | The offset of the attribute map (32-bit integer) | -| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) | -| 0x34 | The offset of the attribute files (32-bit integer) | -| 0x38 | Multiplayer status (1 byte); high nibble is player count, low nibble is current player (0-based) | +| Offset | Content | +|--------|--------------------------------------------------------------------------------------------------------------------------| +| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) | +| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) | +| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) | +| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) | +| 0x10 | The size of the border palettes (=0x80, 32-bit integer) | +| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) | +| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) | +| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) | +| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x28 | The size of the attribute map (=0x168, 32-bit integer) | +| 0x2C | The offset of the attribute map (32-bit integer) | +| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) | +| 0x34 | The offset of the attribute files (32-bit integer) | +| 0x38 | Multiplayer status (1 byte); high nibble is player count (1, 2 or 4), low nibble is current player (Where Player 1 is 0) | If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default. diff --git a/Core/save_state.c b/Core/save_state.c index f97251a..89924ac 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1032,7 +1032,7 @@ done: gb->sgb->player_count = sgb.multiplayer_state >> 4; gb->sgb->current_player = sgb.multiplayer_state & 0xF; - if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3) { + if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3 || gb->sgb->player_count == 0) { gb->sgb->player_count = 1; gb->sgb->current_player = 0; } From 43fb86320e239537882658cc3d9b4f399ae5f7c0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 22:05:13 +0300 Subject: [PATCH 176/365] Hard fail on unexpected SGB blocks --- Core/save_state.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/save_state.c b/Core/save_state.c index 89924ac..6ef62c1 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -987,6 +987,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo break; case BE32('SGB '): if (!found_core) goto parse_error; + if (!gb->sgb) goto parse_error; if (LE32(block.size) != sizeof(BESS_SGB_t) - sizeof(block)) goto parse_error; file->read(file, &sgb.header + 1, sizeof(BESS_SGB_t) - sizeof(block)); found_sgb = true; From 79f109b463f8e0aac224efe5aba9c687626905f9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 22:08:25 +0300 Subject: [PATCH 177/365] Clarify MBC block --- BESS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BESS.md b/BESS.md index 54efe04..d4232ba 100644 --- a/BESS.md +++ b/BESS.md @@ -56,8 +56,8 @@ BESS uses a four-character string to identify Game Boy models: For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E. -| Offset | Content | -|--------|-------------------------------------------------------| +| Offset | Content | +|--------|--------------------------------------------------------| | 0x08 | The value of the PC register | | 0x0A | The value of the AF register | | 0x0C | The value of the BC register | @@ -132,7 +132,7 @@ This block contains an MBC-specific number of 3-byte-long pairs that represent t | 0x9 | The value 0x4000 as a 16-bit integer | | 0xB | The current RAM bank | -An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. +An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. Implementations must perform the writes in order (i.e. not reverse, sorted, or any other transformation on their order) #### RTC block The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. From 6f0b6407021bdaf574bf2a077f9fce4df89044c0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 22:32:45 +0300 Subject: [PATCH 178/365] More clarifications --- BESS.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/BESS.md b/BESS.md index d4232ba..59ca3db 100644 --- a/BESS.md +++ b/BESS.md @@ -28,11 +28,11 @@ BESS uses a block format where each block contains the following header: | 0 | A four-letter ASCII identifier | | 4 | Length of the block, excluding header | -Every block is followed by another blocked, until the END block is reached. +Every block is followed by another blocked, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure). #### CORE block -The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block. +The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block (But an implementation should allow unknown blocks to appear before it for future compatibility). The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: @@ -194,5 +194,15 @@ The length of this block is 0x39 bytes and it follows the following structure: If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default. #### END block -The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. +The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. The length of the END block must be 0. +## Validation and Failures + +Other than previously specified required fail conditions, an implementation is free to decide what format errors should abort the loading of a save file. Structural errors (e.g. a block with an invalid length, a file offset that is outside the file's range, or a missing END block) should be considered as irrecoverable errors. Other errors that are considered fatal by SameBoy's implementation: +* Duplicate CORE block +* A known block, other than NAME, appearing before CORE +* An invalid length for the XOAM, RTC, SGB or HUC3 blocks +* An invalid length of MBC (not a multiple of 3) +* A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block +* An SGB block on a save state targeting another model +* An END block with non-zero length \ No newline at end of file From c1509b633934eb3841f61062d263a2016e7231f3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 23:34:49 +0300 Subject: [PATCH 179/365] KEY0 info --- BESS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BESS.md b/BESS.md index 59ca3db..03cb54c 100644 --- a/BESS.md +++ b/BESS.md @@ -74,6 +74,7 @@ The values of memory-mapped registers should be written 'as-is' to memory as if * Unused registers have Don't-Care values which should be ignored * Unused register bits have Don't-Care values which should be ignored * If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode + * Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode. * Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect * If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. * BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. From 8a84a5897e5cd44b6fd0c1baa92cc48d7eddbe00 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Apr 2021 15:20:01 +0300 Subject: [PATCH 180/365] Allow drag&drop of state files --- Cocoa/Document.h | 1 + Cocoa/Document.m | 10 ++++++++-- Cocoa/GBView.m | 27 +++++++++++++++++++++++++++ Core/gb.c | 6 ------ Core/save_state.c | 33 +++++++++++++++++++++++++++++++++ Core/save_state.h | 8 +++++++- SDL/gui.c | 13 ++++++++++--- SDL/gui.h | 2 ++ SDL/main.c | 17 +++++++++++++++-- 9 files changed, 103 insertions(+), 14 deletions(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index b651646..6effe48 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -44,5 +44,6 @@ -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) performAtomicBlock: (void (^)())block; -(void) connectLinkCable:(NSMenuItem *)sender; +- (bool)loadStateFile:(const char *)path; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index e7812d4..1d9072b 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1172,12 +1172,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } -- (IBAction)loadState:(id)sender +- (bool)loadStateFile:(const char *)path { bool __block success = false; NSString *error = [self captureOutputForBlock:^{ - success = GB_load_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0; + success = GB_load_state(&gb, path) == 0; }]; if (!success) { @@ -1186,6 +1186,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) if (error) { [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; } + return success; +} + +- (IBAction)loadState:(id)sender +{ + [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String]; } - (IBAction)clearConsole:(id)sender diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 0d834c0..645544f 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -142,6 +142,8 @@ static const uint8_t workboy_vk_to_key[] = { - (void) _init { + [self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeFileURL, nil]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect @@ -626,4 +628,29 @@ static const uint8_t workboy_vk_to_key[] = { return image_buffers[(current_buffer + 2) % self.numberOfBuffers]; } +-(NSDragOperation)draggingEntered:(id)sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + + if ( [[pboard types] containsObject:NSURLPboardType] ) { + NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; + if (GB_is_stave_state(fileURL.fileSystemRepresentation)) { + return NSDragOperationGeneric; + } + } + return NSDragOperationNone; +} + +-(BOOL)performDragOperation:(id)sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + + if ( [[pboard types] containsObject:NSURLPboardType] ) { + NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; + return [_document loadStateFile:fileURL.fileSystemRepresentation]; + } + + return false; +} + @end diff --git a/Core/gb.c b/Core/gb.c index 8816552..39a265c 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -19,12 +19,6 @@ #endif -static inline uint32_t state_magic(void) -{ - if (sizeof(bool) == 1) return 'SAME'; - return 'S4ME'; -} - void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { char *string = NULL; diff --git a/Core/save_state.c b/Core/save_state.c index 6ef62c1..17932ee 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1170,3 +1170,36 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return load_state_internal(gb, &file); } + + +bool GB_is_stave_state(const char *path) +{ + bool ret = false; + FILE *f = fopen(path, "rb"); + if (!f) return false; + uint32_t magic = 0; + fread(&magic, sizeof(magic), 1, f); + if (magic == state_magic()) { + ret = true; + goto exit; + } + + // Legacy corrupted Windows save state + if (magic == 0) { + fread(&magic, sizeof(magic), 1, f); + if (magic == state_magic()) { + ret = true; + goto exit; + } + } + + fseek(f, -sizeof(magic), SEEK_END); + fread(&magic, sizeof(magic), 1, f); + if (magic == BE32('BESS')) { + ret = true; + } + +exit: + fclose(f); + return ret; +} diff --git a/Core/save_state.h b/Core/save_state.h index 0c447d9..79e8c06 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -27,8 +27,14 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); - +bool GB_is_stave_state(const char *path); #ifdef GB_INTERNAL +static inline uint32_t state_magic(void) +{ + if (sizeof(bool) == 1) return 'SAME'; + return 'S4ME'; +} + /* For internal in-memory save states (rewind, debugger) that do not need BESS */ size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb); void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); diff --git a/SDL/gui.c b/SDL/gui.c index 580a3f6..f872af5 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -18,6 +18,7 @@ SDL_Texture *texture = NULL; SDL_PixelFormat *pixel_format = NULL; enum pending_command pending_command; unsigned command_parameter; +char *dropped_state_file = NULL; #ifdef __APPLE__ #define MODIFIER_NAME " " CMD_STRING @@ -1300,9 +1301,15 @@ void run_gui(bool is_running) break; } case SDL_DROPFILE: { - set_filename(event.drop.file, SDL_free); - pending_command = GB_SDL_NEW_FILE_COMMAND; - return; + if (GB_is_stave_state(event.drop.file)) { + dropped_state_file = event.drop.file; + pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + } + else { + set_filename(event.drop.file, SDL_free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + return; + } } case SDL_JOYBUTTONDOWN: { diff --git a/SDL/gui.h b/SDL/gui.h index 5db7aff..baa6789 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -39,12 +39,14 @@ enum pending_command { GB_SDL_RESET_COMMAND, GB_SDL_NEW_FILE_COMMAND, GB_SDL_QUIT_COMMAND, + GB_SDL_LOAD_STATE_FROM_FILE_COMMAND, }; #define GB_SDL_DEFAULT_SCALE_MAX 8 extern enum pending_command pending_command; extern unsigned command_parameter; +extern char *dropped_state_file; typedef enum { JOYPAD_BUTTON_LEFT, diff --git a/SDL/main.c b/SDL/main.c index d10590d..fa74a6a 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -142,8 +142,14 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_DROPFILE: { - set_filename(event.drop.file, SDL_free); - pending_command = GB_SDL_NEW_FILE_COMMAND; + if (GB_is_stave_state(event.drop.file)) { + dropped_state_file = event.drop.file; + pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + } + else { + set_filename(event.drop.file, SDL_free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + } break; } @@ -433,6 +439,13 @@ static bool handle_pending_command(void) end_capturing_logs(true, false); return false; } + + case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: + start_capturing_logs(); + GB_load_state(&gb, dropped_state_file); + end_capturing_logs(true, false); + SDL_free(dropped_state_file); + return false; case GB_SDL_NO_COMMAND: return false; From dd860774109b50b66267106f20d99e0a8d2a6396 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Apr 2021 15:24:06 +0300 Subject: [PATCH 181/365] Use the older, more available API --- Cocoa/GBView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 645544f..5c1922c 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -142,7 +142,7 @@ static const uint8_t workboy_vk_to_key[] = { - (void) _init { - [self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeFileURL, nil]]; + [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} From b325148544e0b93c1b75d2917fe91b25ccce97fb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Apr 2021 23:37:00 +0300 Subject: [PATCH 182/365] Update and clarify specification --- BESS.md | 17 +++++++++-------- Core/save_state.c | 9 +++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/BESS.md b/BESS.md index 03cb54c..9a4a10f 100644 --- a/BESS.md +++ b/BESS.md @@ -30,9 +30,16 @@ BESS uses a block format where each block contains the following header: Every block is followed by another blocked, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure). +#### NAME block + +The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation – it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first. + +The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII. + + #### CORE block -The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block (But an implementation should allow unknown blocks to appear before it for future compatibility). +The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, unless the `NAME` block exists then it must be second. An implementation should not enforce block order on blocks unknown to it for future compatibility. The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: @@ -105,12 +112,6 @@ The contents of large buffers are stored outside of BESS structure so data from An implementation needs handle size mismatches gracefully. For example, if too large MBC RAM size is specified, the superfluous data should be ignored. On the other hand, if a too small VRAM size is specified (For example, if it's a save state from an emulator emulating a CGB in DMG mode, and it didn't save the second CGB VRAM bank), the implementation is expected to set that extra bank to all zeros. -#### NAME block - -The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation – it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first. - -The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII. - #### XOAM block The XOAM block uses the `'XOAM'` identifier, and is an optional block that contains the data of extra OAM (addresses `0xFEA0-0xFEFF`). This block length must be `0x60`. Implementations that do not emulate this extra range are free to ignore the excess bytes, and to not create this block. @@ -172,7 +173,7 @@ The length of this block is 0x11 bytes long and it follows the following structu The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing. -The length of this block is 0x39 bytes and it follows the following structure: +The length of this block is 0x39 bytes, but implementations should allow and ignore excess data in this block for extensions. The block follows the following structure: | Offset | Content | |--------|--------------------------------------------------------------------------------------------------------------------------| diff --git a/Core/save_state.c b/Core/save_state.c index 17932ee..980b791 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -988,8 +988,13 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo case BE32('SGB '): if (!found_core) goto parse_error; if (!gb->sgb) goto parse_error; - if (LE32(block.size) != sizeof(BESS_SGB_t) - sizeof(block)) goto parse_error; - file->read(file, &sgb.header + 1, sizeof(BESS_SGB_t) - sizeof(block)); + if (LE32(block.size) > sizeof(sgb) - sizeof(block)) { + if (file->read(file, &sgb.header + 1, sizeof(sgb) - sizeof(block)) != sizeof(sgb) - sizeof(block)) goto error; + file->seek(file, LE32(block.size) - (sizeof(sgb) - sizeof(block)), SEEK_CUR); + } + else { + if (file->read(file, &sgb.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + } found_sgb = true; break; case BE32('END '): From 98a39ae49adb582973f34cfdb941c3201c892ee9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Apr 2021 23:39:07 +0300 Subject: [PATCH 183/365] ATTR_CHR does not seem to wrap around screen (only lines/columns) --- Core/sgb.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/sgb.c b/Core/sgb.c index d535f21..894ae96 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -267,7 +267,6 @@ static void command_ready(GB_gameboy_t *gb) #endif uint8_t x = command->x; uint8_t y = command->y; - count = MIN(count, 20 * 18); if (x >= 20 || y >= 18) { /* TODO: Verify with the SFC BIOS */ break; @@ -282,7 +281,7 @@ static void command_ready(GB_gameboy_t *gb) x++; y = 0; if (x == 20) { - x = 0; + break; } } } @@ -292,7 +291,7 @@ static void command_ready(GB_gameboy_t *gb) y++; x = 0; if (y == 18) { - y = 0; + break; } } } From 2078c2a8fbda8e1da8ba7a3b7b852c6e35df0a97 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 15 Apr 2021 02:42:31 +0300 Subject: [PATCH 184/365] Use semantic popup icons instead of always using error --- SDL/main.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/SDL/main.c b/SDL/main.c index fa74a6a..29398b7 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -58,7 +58,7 @@ static void start_capturing_logs(void) GB_set_log_callback(&gb, log_capture_callback); } -static const char *end_capturing_logs(bool show_popup, bool should_exit) +static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags) { GB_set_log_callback(&gb, NULL); if (captured_log[0] == 0) { @@ -67,7 +67,7 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit) } else { if (show_popup) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", captured_log, window); + SDL_ShowSimpleMessageBox(popup_flags, "Error", captured_log, window); } if (should_exit) { exit(1); @@ -430,20 +430,21 @@ static bool handle_pending_command(void) replace_extension(filename, strlen(filename), save_path, save_extension); start_capturing_logs(); + bool success; if (pending_command == GB_SDL_LOAD_STATE_COMMAND) { - GB_load_state(&gb, save_path); + success = GB_load_state(&gb, save_path) == 0; } else { - GB_save_state(&gb, save_path); + success = GB_save_state(&gb, save_path) == 0; } - end_capturing_logs(true, false); + end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR); return false; } case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: start_capturing_logs(); - GB_load_state(&gb, dropped_state_file); - end_capturing_logs(true, false); + bool success = GB_load_state(&gb, dropped_state_file) == 0; + end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR); SDL_free(dropped_state_file); return false; @@ -483,7 +484,7 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) if (use_built_in) { start_capturing_logs(); GB_load_boot_rom(gb, resource_path(names[type])); - end_capturing_logs(true, false); + end_capturing_logs(true, false, SDL_MESSAGEBOX_ERROR); } } @@ -556,7 +557,7 @@ restart: else { GB_load_rom(&gb, filename); } - end_capturing_logs(true, error); + end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING); /* Configure battery */ From f0a648854652b7cce4a4c959de9dd508c72b4a62 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 15 Apr 2021 21:57:38 +0300 Subject: [PATCH 185/365] Added optional INFO block --- BESS.md | 19 ++++++++++++++----- Core/save_state.c | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/BESS.md b/BESS.md index 9a4a10f..bb49e2a 100644 --- a/BESS.md +++ b/BESS.md @@ -37,14 +37,23 @@ The NAME block uses the `'NAME'` identifier, and is an optional block that conta The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII. +#### INFO block + +The INFO block uses the `'INFO'` identifier, and is an optional block that contains information about the ROM this save state originates from. When used, this block should come before `CORE` but after `NAME`. This block is 0x12 bytes long, and it follows this structure: + +| Offset | Content | +|--------|--------------------------------------------------| +| 0x00 | Bytes 0x134-0x143 from the ROM (Title) | +| 0x10 | Bytes 0x14E-0x14F from the ROM (Global checksum) | + #### CORE block -The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, unless the `NAME` block exists then it must be second. An implementation should not enforce block order on blocks unknown to it for future compatibility. +The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, unless the `NAME` or `INFO` blocks exist then it must come directly after them. An implementation should not enforce block order on blocks unknown to it for future compatibility. The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: -| Offset | Content | -|--------|---------------------------------------| +| Offset | Content | +|--------|----------------------------------------| | 0x00 | Major BESS version as a 16-bit integer | | 0x02 | Minor BESS version as a 16-bit integer | @@ -97,11 +106,11 @@ The values of memory-mapped registers should be written 'as-is' to memory as if | 0x9C | The offset of RAM from file start (32-bit integer) | | 0xA0 | The size of VRAM (32-bit integer) | | 0xA4 | The offset of VRAM from file start (32-bit integer) | -| 0xA9 | The size of MBC RAM (32-bit integer) | +| 0xA8 | The size of MBC RAM (32-bit integer) | | 0xAC | The offset of MBC RAM from file start (32-bit integer) | | 0xB0 | The size of OAM (=0xA0, 32-bit integer) | | 0xB4 | The offset of OAM from file start (32-bit integer) | -| 0xB9 | The size of HRAM (=0x7F, 32-bit integer) | +| 0xB8 | The size of HRAM (=0x7F, 32-bit integer) | | 0xBC | The offset of HRAM from file start (32-bit integer) | | 0xC0 | The size of background palettes (=0x40 or 0, 32-bit integer) | | 0xC4 | The offset of background palettes from file start (32-bit integer) | diff --git a/Core/save_state.c b/Core/save_state.c index 980b791..4516daa 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -82,6 +82,12 @@ typedef struct __attribute__((packed)) { uint8_t multiplayer_state; } BESS_SGB_t; +typedef struct __attribute__((packed)){ + BESS_block_t header; + char title[0x10]; + uint8_t checksum[2]; +} BESS_INFO_t; + /* Same RTC format as used by VBA, BGB and SameBoy in battery saves*/ typedef struct __attribute__((packed)){ BESS_block_t header; @@ -241,9 +247,10 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) { return GB_get_save_state_size_no_bess(gb) + // BESS - + sizeof(BESS_CORE_t) + sizeof(BESS_block_t) // NAME + sizeof(BESS_NAME) - 1 + + sizeof(BESS_INFO_t) // INFO + + 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 block @@ -530,6 +537,22 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe goto error; } + /* BESS INFO */ + + static const BESS_block_t bess_info = {BE32('INFO'), LE32(sizeof(BESS_INFO_t) - sizeof(BESS_block_t))}; + + if (file->write(file, &bess_info, sizeof(bess_info)) != sizeof(bess_info)) { + goto error; + } + + if (file->write(file, gb->rom + 0x134, 0x10) != 0x10) { + goto error; + } + + if (file->write(file, gb->rom + 0x14e, 2) != 2) { + goto error; + } + /* BESS CORE */ bess_core.header = (BESS_block_t){BE32('CORE'), LE32(sizeof(bess_core) - sizeof(bess_core.header))}; @@ -936,6 +959,23 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo file->read(file, emulator_name, LE32(block.size)); } break; + case BE32('INFO'): { + 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))) { + char ascii_title[0x11] = {0,}; + for (unsigned i = 0; i < 0x10; i++) { + if (bess_info.title[i] < 0x20 || bess_info.title[i] > 0x7E) break; + ascii_title[i] = bess_info.title[i]; + } + GB_log(gb, "Save state was made on another ROM: '%s'\n", ascii_title); + } + else if (memcmp(bess_info.checksum, gb->rom + 0x14E, 2)) { + GB_log(gb, "Save state was potentially made on another revision of the same ROM.\n"); + } + break; + } case BE32('XOAM'): if (!found_core) goto parse_error; if (LE32(block.size) != 96) goto parse_error; From 87a2d486751d4a2ab6fac1a946451a6d895d9c3f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Apr 2021 00:35:54 +0300 Subject: [PATCH 186/365] Redo TPP1 saving, fix RTC and HUC3 in BESS --- Core/gb.c | 84 ++++++++++++++++++++++++++++++++++++++++++++-- Core/gb.h | 12 +++++-- Core/memory.c | 57 ++++--------------------------- Core/save_state.c | 85 +++++++++++++++++++++++++++++++++++------------ Core/timing.c | 56 ++++++++++++++++--------------- 5 files changed, 190 insertions(+), 104 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 39a265c..7226646 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -572,6 +572,15 @@ typedef struct { uint8_t padding5[3]; } GB_vba_rtc_time_t; +typedef struct __attribute__((packed)) { + uint32_t magic; + uint16_t version; + uint8_t mr4; + uint8_t reserved; + uint64_t last_rtc_second; + uint8_t rtc_data[4]; +} GB_tpp1_rtc_save_t; + typedef union { struct __attribute__((packed)) { GB_rtc_time_t rtc_real; @@ -589,6 +598,18 @@ typedef union { } vba64; } GB_rtc_save_t; +static void GB_fill_tpp1_save_data(GB_gameboy_t *gb, GB_tpp1_rtc_save_t *data) +{ + data->magic = BE32('TPP1'); + data->version = BE16(0x100); + data->mr4 = gb->tpp1_mr4; + data->reserved = 0; + data->last_rtc_second = LE64(time(NULL)); + unrolled for (unsigned i = 4; i--;) { + data->rtc_data[i] = gb->rtc_real.data[i ^ 3]; + } +} + int GB_save_battery_size(GB_gameboy_t *gb) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. @@ -599,6 +620,11 @@ int GB_save_battery_size(GB_gameboy_t *gb) if (gb->cartridge_type->mbc_type == GB_HUC3) { return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); } + + if (gb->cartridge_type->mbc_type == GB_TPP1) { + return gb->mbc_ram_size + sizeof(GB_tpp1_rtc_save_t); + } + GB_rtc_save_t rtc_save_size; return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); } @@ -613,7 +639,13 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size); - if (gb->cartridge_type->mbc_type == GB_HUC3) { + if (gb->cartridge_type->mbc_type == GB_TPP1) { + buffer += gb->mbc_ram_size; + GB_tpp1_rtc_save_t rtc_save; + GB_fill_tpp1_save_data(gb, &rtc_save); + memcpy(buffer, &rtc_save, sizeof(rtc_save)); + } + else if (gb->cartridge_type->mbc_type == GB_HUC3) { buffer += gb->mbc_ram_size; #ifdef GB_BIG_ENDIAN @@ -676,7 +708,16 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) fclose(f); return EIO; } - if (gb->cartridge_type->mbc_type == GB_HUC3) { + if (gb->cartridge_type->mbc_type == GB_TPP1) { + GB_tpp1_rtc_save_t rtc_save; + GB_fill_tpp1_save_data(gb, &rtc_save); + + if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + fclose(f); + return EIO; + } + } + else if (gb->cartridge_type->mbc_type == GB_HUC3) { #ifdef GB_BIG_ENDIAN GB_huc3_rtc_time_t rtc_save = { __builtin_bswap64(gb->last_rtc_second), @@ -731,6 +772,14 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return errno; } +static void GB_load_tpp1_save_data(GB_gameboy_t *gb, const GB_tpp1_rtc_save_t *data) +{ + gb->last_rtc_second = LE64(data->last_rtc_second); + unrolled for (unsigned i = 4; i--;) { + gb->rtc_real.data[i ^ 3] = data->rtc_data[i]; + } +} + void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) { memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size)); @@ -738,6 +787,22 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t goto reset_rtc; } + if (gb->cartridge_type->mbc_type == GB_TPP1) { + GB_tpp1_rtc_save_t rtc_save; + if (size - gb->mbc_ram_size < sizeof(rtc_save)) { + goto reset_rtc; + } + memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); + + GB_load_tpp1_save_data(gb, &rtc_save); + + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + if (gb->cartridge_type->mbc_type == GB_HUC3) { GB_huc3_rtc_time_t rtc_save; if (size - gb->mbc_ram_size < sizeof(rtc_save)) { @@ -847,6 +912,21 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) goto reset_rtc; } + if (gb->cartridge_type->mbc_type == GB_TPP1) { + GB_tpp1_rtc_save_t rtc_save; + if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + goto reset_rtc; + } + + GB_load_tpp1_save_data(gb, &rtc_save); + + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + if (gb->cartridge_type->mbc_type == GB_HUC3) { GB_huc3_rtc_time_t rtc_save; if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { diff --git a/Core/gb.h b/Core/gb.h index 4e5c4cc..c6a7f7c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -98,6 +98,13 @@ typedef union { uint8_t days; uint8_t high; }; + struct { + uint8_t seconds; + uint8_t minutes; + uint8_t hours:5; + uint8_t weekday:3; + uint8_t weeks; + } tpp1; uint8_t data[5]; } GB_rtc_time_t; @@ -515,6 +522,7 @@ struct GB_gameboy_internal_s { uint64_t last_rtc_second; bool rtc_latch; uint32_t rtc_cycles; + uint8_t tpp1_mr4; ); /* Video Display */ @@ -781,8 +789,6 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * void *GB_get_user_data(GB_gameboy_t *gb); void GB_set_user_data(GB_gameboy_t *gb, void *data); - - int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); int GB_load_rom(GB_gameboy_t *gb, const char *path); @@ -850,5 +856,5 @@ unsigned GB_get_screen_width(GB_gameboy_t *gb); unsigned GB_get_screen_height(GB_gameboy_t *gb); double GB_get_usual_frame_rate(GB_gameboy_t *gb); unsigned GB_get_player_count(GB_gameboy_t *gb); - + #endif /* GB_h */ diff --git a/Core/memory.c b/Core/memory.c index 0082131..a25b860 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -185,30 +185,13 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) case 0: return gb->tpp1_rom_bank; case 1: return gb->tpp1_rom_bank >> 8; case 2: return gb->tpp1_ram_bank; - case 3: return gb->rumble_strength | (((gb->rtc_real.high & 0xC0) ^ 0x40) >> 4); + case 3: return gb->rumble_strength | gb->tpp1_mr4; } case 2: case 3: break; // Read RAM case 5: - switch (addr & 3) { - case 0: { // Week count - unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); - if (gb->rtc_latched.high & 0x20) { - return total_days / 7 - 1; - } - return total_days / 7; - } - case 1: { // Week count - unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); - if (gb->rtc_latched.high & 0x20) { - return gb->rtc_latched.hours | 0xe0; // Hours and weekday - } - return gb->rtc_latched.hours | ((total_days % 7) << 5); // Hours and weekday - } - case 2: return gb->rtc_latched.minutes; - case 3: return gb->rtc_latched.seconds; - } + return gb->rtc_latched.data[(addr & 3) ^ 3]; default: return 0xFF; } @@ -625,20 +608,17 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); break; case 0x11: { - uint8_t flags = gb->rtc_real.high & 0xc0; memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real)); - gb->rtc_real.high &= ~0xe0; - gb->rtc_real.high |= flags; break; } case 0x14: - gb->rtc_real.high &= ~0x80; + gb->tpp1_mr4 &= ~0x8; break; case 0x18: - gb->rtc_real.high |= 0x40; + gb->tpp1_mr4 &= ~0x4; break; case 0x19: - gb->rtc_real.high &= ~0x40; + gb->tpp1_mr4 |= 0x4; break; case 0x20: @@ -776,32 +756,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 3: break; case 5: - switch (addr & 3) { - case 0: { - unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); - total_days = total_days % 7 + value * 7; - bool had_illegal_weekday = gb->rtc_latched.high & 0x20; - gb->rtc_latched.days = total_days; - gb->rtc_latched.high = total_days >> 8; - if (had_illegal_weekday) { - gb->rtc_latched.high |= 0x20; - } - return; - } - case 1: { - unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); - total_days = total_days / 7 * 7 + (value >> 5); - gb->rtc_latched.hours = value & 0x1F; - gb->rtc_latched.days = total_days; - gb->rtc_latched.high = total_days >> 8; - if ((value & 0xE0) == 0xE0) { // Illegal weekday - gb->rtc_latched.high |= 0x20; - } - return; - } - case 2: gb->rtc_latched.minutes = value; return; - case 3: gb->rtc_latched.seconds = value; return; - } + gb->rtc_latched.data[(addr & 3) ^ 3] = value; return; default: return; diff --git a/Core/save_state.c b/Core/save_state.c index 4516daa..af60203 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -112,6 +112,14 @@ typedef struct __attribute__((packed)){ GB_huc3_rtc_time_t data; } BESS_HUC3_t; +typedef struct __attribute__((packed)){ + BESS_block_t header; + uint64_t last_rtc_second; + uint8_t real_rtc_data[4]; + uint8_t latched_rtc_data[4]; + uint8_t mr4; +} BESS_TPP1_t; + typedef struct __attribute__((packed)) { uint16_t address; uint8_t value; @@ -222,7 +230,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) case GB_HUC3: return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t); case GB_TPP1: - return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_RTC_t); + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_TPP1_t); } } @@ -253,7 +261,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 block + + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1 block + sizeof(BESS_block_t) // END block + sizeof(BESS_footer_t); } @@ -630,7 +638,22 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe save_bess_mbc_block(gb, file); if (gb->cartridge_type->has_rtc) { - if (gb->cartridge_type ->mbc_type != GB_HUC3) { + if (gb->cartridge_type ->mbc_type == GB_TPP1) { + BESS_TPP1_t bess_tpp1 = {0,}; + bess_tpp1.header = (BESS_block_t){BE32('TPP1'), LE32(sizeof(bess_tpp1) - sizeof(bess_tpp1.header))}; + + bess_tpp1.last_rtc_second = LE64(gb->last_rtc_second); + unrolled for (unsigned i = 4; i--;) { + bess_tpp1.real_rtc_data[i] = gb->rtc_real.data[i ^ 3]; + bess_tpp1.latched_rtc_data[i] = gb->rtc_latched.data[i ^ 3]; + } + bess_tpp1.mr4 = gb->tpp1_mr4; + + if (file->write(file, &bess_tpp1, sizeof(bess_tpp1)) != sizeof(bess_tpp1)) { + goto error; + } + } + else if (gb->cartridge_type ->mbc_type != GB_HUC3) { BESS_RTC_t bess_rtc = {0,}; bess_rtc.header = (BESS_block_t){BE32('RTC '), LE32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; bess_rtc.real.seconds = gb->rtc_real.seconds; @@ -997,33 +1020,51 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo BESS_RTC_t bess_rtc; if (LE32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error; if (file->read(file, &bess_rtc.header + 1, LE32(block.size)) != LE32(block.size)) goto error; - if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3) { - gb->rtc_real.seconds = bess_rtc.real.seconds; - gb->rtc_real.minutes = bess_rtc.real.minutes; - gb->rtc_real.hours = bess_rtc.real.hours; - gb->rtc_real.days = bess_rtc.real.days; - gb->rtc_real.high = bess_rtc.real.high; - gb->rtc_latched.seconds = bess_rtc.latched.seconds; - gb->rtc_latched.minutes = bess_rtc.latched.minutes; - gb->rtc_latched.hours = bess_rtc.latched.hours; - gb->rtc_latched.days = bess_rtc.latched.days; - gb->rtc_latched.high = bess_rtc.latched.high; - gb->last_rtc_second = LE64(bess_rtc.last_rtc_second); + if (!gb->cartridge_type->has_rtc || gb->cartridge_type->mbc_type != GB_MBC3) break; + save.rtc_real.seconds = bess_rtc.real.seconds; + save.rtc_real.minutes = bess_rtc.real.minutes; + save.rtc_real.hours = bess_rtc.real.hours; + save.rtc_real.days = bess_rtc.real.days; + save.rtc_real.high = bess_rtc.real.high; + save.rtc_latched.seconds = bess_rtc.latched.seconds; + save.rtc_latched.minutes = bess_rtc.latched.minutes; + save.rtc_latched.hours = bess_rtc.latched.hours; + save.rtc_latched.days = bess_rtc.latched.days; + save.rtc_latched.high = bess_rtc.latched.high; + if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) { + save.last_rtc_second = MIN(LE64(bess_rtc.last_rtc_second), time(NULL)); } + break; case BE32('HUC3'): if (!found_core) goto parse_error; BESS_HUC3_t bess_huc3; if (LE32(block.size) != sizeof(bess_huc3) - sizeof(block)) goto parse_error; if (file->read(file, &bess_huc3.header + 1, LE32(block.size)) != LE32(block.size)) goto error; - if (gb->cartridge_type->mbc_type == GB_HUC3) { - gb->last_rtc_second = LE64(bess_huc3.data.last_rtc_second); - gb->huc3_minutes = LE16(bess_huc3.data.minutes); - gb->huc3_days = LE16(bess_huc3.data.days); - gb->huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes); - gb->huc3_alarm_days = LE16(bess_huc3.data.alarm_days); - gb->huc3_alarm_enabled = bess_huc3.data.alarm_enabled; + if (gb->cartridge_type->mbc_type != GB_HUC3) break; + if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) { + save.last_rtc_second = MIN(LE64(bess_huc3.data.last_rtc_second), time(NULL)); } + save.huc3_minutes = LE16(bess_huc3.data.minutes); + save.huc3_days = LE16(bess_huc3.data.days); + save.huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes); + save.huc3_alarm_days = LE16(bess_huc3.data.alarm_days); + save.huc3_alarm_enabled = bess_huc3.data.alarm_enabled; + break; + case BE32('TPP1'): + if (!found_core) goto parse_error; + BESS_TPP1_t bess_tpp1; + if (LE32(block.size) != sizeof(bess_tpp1) - sizeof(block)) goto parse_error; + if (file->read(file, &bess_tpp1.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->mbc_type != GB_TPP1) break; + if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) { + save.last_rtc_second = MIN(LE64(bess_tpp1.last_rtc_second), time(NULL)); + } + unrolled for (unsigned i = 4; i--;) { + save.rtc_real.data[i ^ 3] = bess_tpp1.real_rtc_data[i]; + save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i]; + } + save.tpp1_mr4 = bess_tpp1.mr4; break; case BE32('SGB '): if (!found_core) goto parse_error; diff --git a/Core/timing.c b/Core/timing.c index d240525..7b79b72 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -283,27 +283,31 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) } return; } + bool running = false; + if (gb->cartridge_type->mbc_type == GB_TPP1) { + running = gb->tpp1_mr4 & 0x4; + } + else { + running = (gb->rtc_real.high & 0x40) == 0; + } - if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ + if (running) { /* is timer running? */ while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { gb->last_rtc_second += 60 * 60 * 24; - if (++gb->rtc_real.days == 0) { - if (gb->cartridge_type->mbc_type == GB_TPP1) { - if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/ - gb->rtc_real.high &= 0x40; - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - else { - gb->rtc_real.high++; + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if (++gb->rtc_real.tpp1.weekday == 7) { + gb->rtc_real.tpp1.weekday = 0; + if (++gb->rtc_real.tpp1.weeks == 0) { + gb->tpp1_mr4 |= 8; /* Overflow bit */ } } - else { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - - gb->rtc_real.high ^= 1; + } + else if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ } + + gb->rtc_real.high ^= 1; } } @@ -315,21 +319,21 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) if (++gb->rtc_real.minutes != 60) continue; gb->rtc_real.minutes = 0; - if (++gb->rtc_real.hours != 24) continue; - gb->rtc_real.hours = 0; - - if (++gb->rtc_real.days != 0) continue; - if (gb->cartridge_type->mbc_type == GB_TPP1) { - if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/ - gb->rtc_real.high &= 0x40; - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - else { - gb->rtc_real.high++; + if (++gb->rtc_real.tpp1.hours != 24) continue; + gb->rtc_real.tpp1.hours = 0; + if (++gb->rtc_real.tpp1.weekday != 7) continue; + gb->rtc_real.tpp1.weekday = 0; + if (++gb->rtc_real.tpp1.weeks == 0) { + gb->tpp1_mr4 |= 8; /* Overflow bit */ } } else { + if (++gb->rtc_real.hours != 24) continue; + gb->rtc_real.hours = 0; + + if (++gb->rtc_real.days != 0) continue; + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ gb->rtc_real.high |= 0x80; /* Overflow bit */ } From 9fcdc082d24ccbec882a6c4027a409487895f6cb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Apr 2021 16:37:55 +0300 Subject: [PATCH 187/365] Fix an SDL crash, minor tweak to BESS SGB --- Core/save_state.c | 3 +++ Core/sm83_cpu.c | 4 ++-- SDL/gui.c | 10 ++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index af60203..b83790b 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1117,6 +1117,9 @@ done: read_bess_buffer(&sgb.attribute_map, file, (void *)gb->sgb->attribute_map, sizeof(gb->sgb->attribute_map)); read_bess_buffer(&sgb.attribute_files, file, (void *)gb->sgb->attribute_files, sizeof(gb->sgb->attribute_files)); + gb->sgb->effective_palettes[12] = gb->sgb->effective_palettes[8] = + gb->sgb->effective_palettes[4] = gb->sgb->effective_palettes[0]; + gb->sgb->player_count = sgb.multiplayer_state >> 4; gb->sgb->current_player = sgb.multiplayer_state & 0xF; if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3 || gb->sgb->player_count == 0) { diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 042514c..377f519 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1525,8 +1525,8 @@ static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) } static GB_opcode_t *opcodes[256] = { - /* X0 X1 X2 X3 X4 X5 X6 X7 */ - /* X8 X9 Xa Xb Xc Xd Xe Xf */ +/* X0 X1 X2 X3 X4 X5 X6 X7 */ +/* X8 X9 Xa Xb Xc Xd Xe Xf */ nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca, stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */ diff --git a/SDL/gui.c b/SDL/gui.c index f872af5..b8e69cd 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1302,8 +1302,14 @@ void run_gui(bool is_running) } case SDL_DROPFILE: { if (GB_is_stave_state(event.drop.file)) { - dropped_state_file = event.drop.file; - pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + if (GB_is_inited(&gb)) { + dropped_state_file = event.drop.file; + pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + } + else { + SDL_free(event.drop.file); + } + break; } else { set_filename(event.drop.file, SDL_free); From 939817df734f04c216de7b7debecb0f02282e17f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Apr 2021 16:59:22 +0300 Subject: [PATCH 188/365] Update version, finalize BESS 1.0 --- BESS.md | 4 +--- version.mk | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/BESS.md b/BESS.md index bb49e2a..b4d9624 100644 --- a/BESS.md +++ b/BESS.md @@ -1,4 +1,4 @@ -# BESS – Best Effort Save State +# BESS – Best Effort Save State 1.0 ## Motivation @@ -6,8 +6,6 @@ BESS is a save state format specification designed to allow different emulators, ## Specification -**Note: This specification is currently under development and will potentially change in the near future** - Every integer used in the BESS specification is stored in Little Endian encoding. ### BESS footer diff --git a/version.mk b/version.mk index 731aad9..6831f84 100644 --- a/version.mk +++ b/version.mk @@ -1 +1 @@ -VERSION := 0.14.2 +VERSION := 0.14.3 \ No newline at end of file From d9b9385eb41369e4247fe9ccaf311c50ea61efba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 17 Apr 2021 18:13:19 +0300 Subject: [PATCH 189/365] Typo fix --- BESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BESS.md b/BESS.md index b4d9624..e040d90 100644 --- a/BESS.md +++ b/BESS.md @@ -26,7 +26,7 @@ BESS uses a block format where each block contains the following header: | 0 | A four-letter ASCII identifier | | 4 | Length of the block, excluding header | -Every block is followed by another blocked, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure). +Every block is followed by another block, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure). #### NAME block From 5f2e8938289f3271241fe2304051f9dcf511458a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 19 Apr 2021 00:08:21 +0300 Subject: [PATCH 190/365] Allow GBS files with loading addresses 0x6E-0x3FF, fixes #376 --- Core/gb.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index ae2a30c..c4b1a50 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -302,6 +302,8 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) return 0; } +#define GBS_ENTRY 0x61 + void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) { GB_reset(gb); @@ -324,7 +326,7 @@ void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) gb->cgb_double_speed = true; // Might mean double speed mode on a DMG } gb->sp = LE16(gb->gbs_header.sp); - gb->pc = 0x100; + gb->pc = GBS_ENTRY; gb->boot_rom_finished = true; gb->a = track; if (gb->sgb) { @@ -342,7 +344,7 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) } fread(&gb->gbs_header, sizeof(gb->gbs_header), 1, f); if (gb->gbs_header.magic != BE32('GBS\x01') || - LE16(gb->gbs_header.load_address) < 0x400 || + LE16(gb->gbs_header.load_address) < 0x6e || LE16(gb->gbs_header.load_address) >= 0x8000) { GB_log(gb, "Not a valid GBS file: %s.\n", strerror(errno)); fclose(f); @@ -393,20 +395,20 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) } // Generate entry - memcpy(gb->rom + 0x100, (uint8_t[]) { + memcpy(gb->rom + GBS_ENTRY, (uint8_t[]) { 0xCD, // Call $XXXX - LE16(gb->gbs_header.init_address), - LE16(gb->gbs_header.init_address) >> 8, + LE16(gb->gbs_header.init_address), + LE16(gb->gbs_header.init_address) >> 8, 0x76, // HALT 0x00, // NOP 0xAF, // XOR a 0xE0, // LDH [$FFXX], a - GB_IO_IF, + GB_IO_IF, 0xCD, // Call $XXXX - LE16(gb->gbs_header.play_address), - LE16(gb->gbs_header.play_address) >> 8, + LE16(gb->gbs_header.play_address), + LE16(gb->gbs_header.play_address) >> 8, 0x18, // JR pc ± $XX - -10 // To HALT + -10 // To HALT }, 13); GB_gbs_switch_track(gb, gb->gbs_header.first_track - 1); From 2971b1770155fc396e310059269c5564362f3f61 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 19 Apr 2021 00:32:10 +0300 Subject: [PATCH 191/365] Add support for ugetab's GBS extensions, fixes #377 --- Core/gb.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index c4b1a50..cf2e86b 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -333,6 +333,9 @@ void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) gb->sgb->intro_animation = GB_SGB_INTRO_ANIMATION_LENGTH; gb->sgb->disable_commands = true; } + if (gb->gbs_header.TAC & 0x40) { + gb->interrupt_enable = true; + } } int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) @@ -384,13 +387,15 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); } + bool has_interrupts = gb->gbs_header.TAC & 0x40; + // Generate interrupt handlers - for (unsigned i = 0; i <= 0x38; i += 8) { + for (unsigned i = 0; i <= (has_interrupts? 0x50 : 0x38); i += 8) { 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 = 0x40; i <= 0x60; i += 8) { + for (unsigned i = has_interrupts? 0x58 : 0x40; i <= 0x60; i += 8) { gb->rom[i] = 0xc9; // ret } From c29edc196360fc36887b0b93c72832c15e6be9e1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 19 Apr 2021 20:57:28 +0300 Subject: [PATCH 192/365] Handle loading errors --- Cocoa/Document.m | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 2fbec7c..fd0c448 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -776,8 +776,15 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self initCommon]; self.view.gb = &gb; [self.view screenSizeChanged]; - [self loadROM]; - [self reset:nil]; + if ([self loadROM]) { + _mainWindow.alphaValue = 0; // Hack hack ugly hack + dispatch_async(dispatch_get_main_queue(), ^{ + [self close]; + }); + } + else { + [self reset:nil]; + } } - (void) initMemoryView @@ -919,21 +926,22 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } -- (void) loadROM +- (int) loadROM { + __block int ret = 0; NSString *rom_warnings = [self captureOutputForBlock:^{ GB_debugger_clear_symbols(&gb); if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"isx"]) { - GB_load_isx(&gb, self.fileURL.path.UTF8String); + ret = GB_load_isx(&gb, self.fileURL.path.UTF8String); GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String); } else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) { __block GB_gbs_info_t info; - GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info); + ret = GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info); [self prepareGBSInterface:&info]; } else { - GB_load_rom(&gb, [self.fileURL.path UTF8String]); + ret = GB_load_rom(&gb, [self.fileURL.path UTF8String]); } GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path.UTF8String); GB_load_cheats(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path.UTF8String); @@ -941,10 +949,17 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) 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); }]; - if (rom_warnings && !rom_warning_issued) { + if (ret) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:rom_warnings?: @"Could not load ROM"]; + [alert setAlertStyle:NSAlertStyleCritical]; + [alert runModal]; + } + else if (rom_warnings && !rom_warning_issued) { rom_warning_issued = true; [GBWarningPopover popoverWithContents:rom_warnings onWindow:self.mainWindow]; } + return ret; } - (void)close From a2d3b8c174c3a9719c1bae1cf58b3228e52eb629 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 19 Apr 2021 20:58:27 +0300 Subject: [PATCH 193/365] Support for non-standard GBS files with a loading address at 0 --- Core/gb.c | 81 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index cf2e86b..722b0b1 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -303,6 +303,26 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) } #define GBS_ENTRY 0x61 +#define GBS_ENTRY_SIZE 13 + +static void generate_gbs_entry(GB_gameboy_t *gb, uint8_t *data) +{ + memcpy(data, (uint8_t[]) { + 0xCD, // Call $XXXX + LE16(gb->gbs_header.init_address), + LE16(gb->gbs_header.init_address) >> 8, + 0x76, // HALT + 0x00, // NOP + 0xAF, // XOR a + 0xE0, // LDH [$FFXX], a + GB_IO_IF, + 0xCD, // Call $XXXX + LE16(gb->gbs_header.play_address), + LE16(gb->gbs_header.play_address) >> 8, + 0x18, // JR pc ± $XX + -10 // To HALT + }, GBS_ENTRY_SIZE); +} void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) { @@ -325,8 +345,19 @@ void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) if (gb->gbs_header.TAC & 0x80) { gb->cgb_double_speed = true; // Might mean double speed mode on a DMG } - gb->sp = LE16(gb->gbs_header.sp); - gb->pc = GBS_ENTRY; + if (gb->gbs_header.load_address) { + gb->sp = LE16(gb->gbs_header.sp); + gb->pc = GBS_ENTRY; + } + else { + gb->pc = gb->sp = LE16(gb->gbs_header.sp - GBS_ENTRY_SIZE); + uint8_t entry[GBS_ENTRY_SIZE]; + generate_gbs_entry(gb, entry); + for (unsigned i = 0; i < sizeof(entry); i++) { + GB_write_memory(gb, gb->pc + i, entry[i]); + } + } + gb->boot_rom_finished = true; gb->a = track; if (gb->sgb) { @@ -347,9 +378,10 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) } fread(&gb->gbs_header, sizeof(gb->gbs_header), 1, f); if (gb->gbs_header.magic != BE32('GBS\x01') || - LE16(gb->gbs_header.load_address) < 0x6e || - LE16(gb->gbs_header.load_address) >= 0x8000) { - GB_log(gb, "Not a valid GBS file: %s.\n", strerror(errno)); + ((LE16(gb->gbs_header.load_address) < GBS_ENTRY + GBS_ENTRY_SIZE || + LE16(gb->gbs_header.load_address) >= 0x8000) && + LE16(gb->gbs_header.load_address) != 0)) { + GB_log(gb, "Not a valid GBS file.\n"); fclose(f); return -1; } @@ -389,32 +421,21 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) bool has_interrupts = gb->gbs_header.TAC & 0x40; - // Generate interrupt handlers - for (unsigned i = 0; i <= (has_interrupts? 0x50 : 0x38); i += 8) { - 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; + 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 + 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 + } + + // Generate entry + generate_gbs_entry(gb, gb->rom + GBS_ENTRY); } - for (unsigned i = has_interrupts? 0x58 : 0x40; i <= 0x60; i += 8) { - gb->rom[i] = 0xc9; // ret - } - - // Generate entry - memcpy(gb->rom + GBS_ENTRY, (uint8_t[]) { - 0xCD, // Call $XXXX - LE16(gb->gbs_header.init_address), - LE16(gb->gbs_header.init_address) >> 8, - 0x76, // HALT - 0x00, // NOP - 0xAF, // XOR a - 0xE0, // LDH [$FFXX], a - GB_IO_IF, - 0xCD, // Call $XXXX - LE16(gb->gbs_header.play_address), - LE16(gb->gbs_header.play_address) >> 8, - 0x18, // JR pc ± $XX - -10 // To HALT - }, 13); + GB_gbs_switch_track(gb, gb->gbs_header.first_track - 1); if (info) { From 0e8d8effdf28d6131d7a96fe230762294ff20225 Mon Sep 17 00:00:00 2001 From: John Regan Date: Tue, 20 Apr 2021 08:38:53 -0400 Subject: [PATCH 194/365] gbs: function to load from memory buffer --- Core/gb.c | 44 ++++++++++++++++++++++++++++++++------------ Core/gb.h | 1 + 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 722b0b1..0a0c1dd 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -369,24 +369,25 @@ void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) } } -int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) +int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size, GB_gbs_info_t *info) { - FILE *f = fopen(path, "rb"); - if (!f) { - GB_log(gb, "Could not open GBS: %s.\n", strerror(errno)); - return errno; + if(size < sizeof(gb->gbs_header)) { + GB_log(gb, "Not a valid GBS file.\n"); + return -1; } - fread(&gb->gbs_header, sizeof(gb->gbs_header), 1, f); + + memcpy(&gb->gbs_header,buffer,sizeof(gb->gbs_header)); + if (gb->gbs_header.magic != BE32('GBS\x01') || ((LE16(gb->gbs_header.load_address) < GBS_ENTRY + GBS_ENTRY_SIZE || LE16(gb->gbs_header.load_address) >= 0x8000) && LE16(gb->gbs_header.load_address) != 0)) { GB_log(gb, "Not a valid GBS file.\n"); - fclose(f); return -1; } - fseek(f, 0, SEEK_END); - size_t data_size = ftell(f) - sizeof(gb->gbs_header); + + size_t data_size = size - sizeof(gb->gbs_header); + gb->rom_size = (data_size + LE16(gb->gbs_header.load_address) + 0x3FFF) & ~0x3FFF; /* Round to bank */ /* And then round to a power of two */ while (gb->rom_size & (gb->rom_size - 1)) { @@ -398,14 +399,14 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) if (gb->rom_size == 0) { gb->rom_size = 0x8000; } - fseek(f, sizeof(gb->gbs_header), SEEK_SET); + if (gb->rom) { free(gb->rom); } + gb->rom = malloc(gb->rom_size); memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ - fread(gb->rom + LE16(gb->gbs_header.load_address), 1, data_size, f); - fclose(f); + memcpy(gb->rom + LE16(gb->gbs_header.load_address), buffer + sizeof(gb->gbs_header), data_size); gb->cartridge_type = &GB_cart_defs[0x11]; if (gb->mbc_ram) { @@ -453,6 +454,25 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) return 0; } +int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open GBS: %s.\n", strerror(errno)); + return errno; + } + fseek(f, 0, SEEK_END); + size_t file_size = ftell(f); + fseek(f, 0, SEEK_SET); + uint8_t *file_data = malloc(file_size); + fread(file_data,1,file_size,f); + fclose(f); + + int r = GB_load_gbs_from_buffer(gb,file_data,file_size,info); + free(file_data); + return r; +} + int GB_load_isx(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "rb"); diff --git a/Core/gb.h b/Core/gb.h index a2e8db1..e7e0ca0 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -819,6 +819,7 @@ void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, int GB_load_rom(GB_gameboy_t *gb, const char *path); void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); int GB_load_isx(GB_gameboy_t *gb, const char *path); +int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size, GB_gbs_info_t *info); int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info); void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track); From ac5b0aca2c51768548e4ae25d86d740680ab50be Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Apr 2021 21:01:17 +0300 Subject: [PATCH 195/365] RTC accuracy fix --- Core/gb.h | 2 +- Core/memory.c | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index a2e8db1..4241a61 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -543,7 +543,7 @@ struct GB_gameboy_internal_s { GB_SECTION(rtc, GB_rtc_time_t rtc_real, rtc_latched; uint64_t last_rtc_second; - bool rtc_latch; + GB_PADDING(bool, rtc_latch); uint32_t rtc_cycles; uint8_t tpp1_mr4; ); diff --git a/Core/memory.c b/Core/memory.c index a25b860..f499e74 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -541,10 +541,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->mbc3_rtc_mapped = value & 8; break; case 0x6000: case 0x7000: - if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */ - memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); - } - gb->rtc_latch = value & 1; + memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); break; } break; @@ -781,7 +778,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->mbc_ram_bank == 0) { gb->rtc_cycles = 0; } - gb->rtc_latched.data[gb->mbc_ram_bank] = gb->rtc_real.data[gb->mbc_ram_bank] = value; + gb->rtc_real.data[gb->mbc_ram_bank] = value; } return; } From ea05a0c765b84e1674609a1cef21bab166bca64c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 23 Apr 2021 21:05:33 +0300 Subject: [PATCH 196/365] Don't save 0x6000 for MBC3 in BESS --- Core/save_state.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index b83790b..a44a4fc 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -222,7 +222,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) case GB_MBC2: return sizeof(BESS_block_t) + 2 * sizeof(BESS_MBC_pair_t); case GB_MBC3: - return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + (cart->has_rtc? sizeof(BESS_RTC_t) : 0); + 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_HUC1: @@ -437,8 +437,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc3.rom_bank}; pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc3.ram_bank | (gb->mbc3_rtc_mapped? 8 : 0)}; - pairs[3] = (BESS_MBC_pair_t){LE16(0x6000), gb->rtc_latch}; - mbc_block.size = 4 * sizeof(pairs[0]); + mbc_block.size = 3 * sizeof(pairs[0]); break; case GB_MBC5: pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; From 898ef2c981f60aecf28a5563728e1ab050cca391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Fri, 23 Apr 2021 20:43:34 +0200 Subject: [PATCH 197/365] Fix the desktop categories. --- FreeDesktop/sameboy.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FreeDesktop/sameboy.desktop b/FreeDesktop/sameboy.desktop index 20b1b46..80d0902 100644 --- a/FreeDesktop/sameboy.desktop +++ b/FreeDesktop/sameboy.desktop @@ -8,5 +8,5 @@ Comment=Game Boy and Game Boy Color emulator Keywords=game;boy;gameboy;color;emulator Terminal=false StartupNotify=false -Categories=Application;Game; -MimeType=application/x-gameboy-rom;application/x-gameboy-color-rom;application/x-gameboy-isx \ No newline at end of file +Categories=Game;Emulator; +MimeType=application/x-gameboy-rom;application/x-gameboy-color-rom;application/x-gameboy-isx From 1d0366052dbd26261265dfbbcd708e74aa2809d7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 25 Apr 2021 22:28:24 +0300 Subject: [PATCH 198/365] Updater support --- Cocoa/AppDelegate.h | 13 +- Cocoa/AppDelegate.m | 309 +++++++++++++++++++++++++++++++++++- Cocoa/Document.m | 2 +- Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 19 +++ Cocoa/Preferences.xib | 66 ++++++-- Cocoa/UpdateWindow.xib | 139 ++++++++++++++++ Makefile | 3 + 8 files changed, 537 insertions(+), 15 deletions(-) create mode 100644 Cocoa/UpdateWindow.xib diff --git a/Cocoa/AppDelegate.h b/Cocoa/AppDelegate.h index e74b191..a9b0048 100644 --- a/Cocoa/AppDelegate.h +++ b/Cocoa/AppDelegate.h @@ -1,16 +1,25 @@ #import +#import -@interface AppDelegate : NSObject +@interface AppDelegate : NSObject @property (nonatomic, strong) IBOutlet NSWindow *preferencesWindow; @property (nonatomic, strong) IBOutlet NSView *graphicsTab; @property (nonatomic, strong) IBOutlet NSView *emulationTab; @property (nonatomic, strong) IBOutlet NSView *audioTab; @property (nonatomic, strong) IBOutlet NSView *controlsTab; +@property (nonatomic, strong) IBOutlet NSView *updatesTab; - (IBAction)showPreferences: (id) sender; - (IBAction)toggleDeveloperMode:(id)sender; - (IBAction)switchPreferencesTab:(id)sender; @property (nonatomic, weak) IBOutlet NSMenuItem *linkCableMenuItem; - +@property (nonatomic, strong) IBOutlet NSWindow *updateWindow; +@property (nonatomic, strong) IBOutlet WebView *updateChanges; +@property (nonatomic, strong) IBOutlet NSProgressIndicator *updatesSpinner; +@property (strong) IBOutlet NSButton *updatesButton; +@property (strong) IBOutlet NSTextField *updateProgressLabel; +@property (strong) IBOutlet NSButton *updateProgressButton; +@property (strong) IBOutlet NSWindow *updateProgressWindow; +@property (strong) IBOutlet NSProgressIndicator *updateProgressSpinner; @end diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index aee2111..01b1527 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -4,11 +4,35 @@ #include #import #import +#import + +#define UPDATE_SERVER "https://sameboy.github.io" +#define str(x) #x +#define xstr(x) str(x) + +static uint32_t color_to_int(NSColor *color) +{ + color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + return (((unsigned)(color.redComponent * 0xFF)) << 16) | + (((unsigned)(color.greenComponent * 0xFF)) << 8) | + ((unsigned)(color.blueComponent * 0xFF)); +} @implementation AppDelegate { NSWindow *preferences_window; NSArray *preferences_tabs; + NSString *_lastVersion; + NSString *_updateURL; + NSURLSessionDownloadTask *_updateTask; + enum { + UPDATE_DOWNLOADING, + UPDATE_EXTRACTING, + UPDATE_WAIT_INSTALL, + UPDATE_INSTALLING, + UPDATE_FAILED, + } _updateState; + NSString *_downloadDirectory; } - (void) applicationDidFinishLaunching:(NSNotification *)notification @@ -54,6 +78,16 @@ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; } + + [self askAutoUpdates]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]) { + [self checkForUpdates]; + } + + if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) { + [NSApp activateIgnoringOtherApps:YES]; + } } - (IBAction)toggleDeveloperMode:(id)sender @@ -111,15 +145,19 @@ [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects]; NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject]; _preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier]; - preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab]; + preferences_tabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab, self.updatesTab]; [self switchPreferencesTab:first_toolbar_item]; [_preferencesWindow center]; +#ifndef UPDATE_SUPPORT + [_preferencesWindow.toolbar removeItemAtIndex:4]; +#endif } [_preferencesWindow makeKeyAndOrderFront:self]; } - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender { + [self askAutoUpdates]; /* Bring an existing panel to the foreground */ for (NSWindow *window in [[NSApplication sharedApplication] windows]) { if ([window isKindOfClass:[NSOpenPanel class]]) { @@ -136,6 +174,275 @@ [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; } +- (void)updateFound +{ + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/raw_changes"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + NSColor *linkColor = [NSColor colorWithRed:0.125 green:0.325 blue:1.0 alpha:1.0]; + if (@available(macOS 10.10, *)) { + linkColor = [NSColor linkColor]; + } + + NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSRange cutoffRange = [changes rangeOfString:@""]; + if (cutoffRange.location != NSNotFound) { + changes = [changes substringToIndex:cutoffRange.location]; + } + + NSString *html = [NSString stringWithFormat:@"" + "" + "%@", + color_to_int([NSColor textColor]), + color_to_int(linkColor), + changes]; + + if ([(NSHTTPURLResponse *)response statusCode] == 200) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSArray *objects; + [[NSBundle mainBundle] loadNibNamed:@"UpdateWindow" owner:self topLevelObjects:&objects]; + self.updateChanges.preferences.standardFontFamily = [NSFont systemFontOfSize:0].familyName; + self.updateChanges.preferences.fixedFontFamily = @"Menlo"; + self.updateChanges.drawsBackground = false; + [self.updateChanges.mainFrame loadHTMLString:html baseURL:nil]; + }); + } + }] resume]; +} + +- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems +{ + // Disable reload context menu + if ([defaultMenuItems count] <= 2) { + return nil; + } + return defaultMenuItems; +} + +- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sender.mainFrame.frameView.documentView.enclosingScrollView.drawsBackground = true; + sender.mainFrame.frameView.documentView.enclosingScrollView.backgroundColor = [NSColor textBackgroundColor]; + sender.policyDelegate = self; + [self.updateWindow center]; + [self.updateWindow makeKeyAndOrderFront:nil]; + }); +} + +- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener +{ + [listener ignore]; + [[NSWorkspace sharedWorkspace] openURL:[request URL]]; +} + +- (void)checkForUpdates +{ +#ifdef UPDATE_SUPPORT + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.updatesSpinner stopAnimation:nil]; + [self.updatesButton setEnabled:YES]; + }); + if ([(NSHTTPURLResponse *)response statusCode] == 200) { + NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSArray *components = [string componentsSeparatedByString:@"|"]; + if (components.count != 2) return; + _lastVersion = components[0]; + _updateURL = components[1]; + if (![@xstr(VERSION) isEqualToString:_lastVersion] && + ![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) { + [self updateFound]; + } + } + }] resume]; +#endif +} + +- (IBAction)userCheckForUpdates:(id)sender +{ + if (self.updateWindow) { + [self.updateWindow makeKeyAndOrderFront:sender]; + } + else { + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"GBSkippedVersion"]; + [self checkForUpdates]; + [sender setEnabled:false]; + [self.updatesSpinner startAnimation:sender]; + } +} + +- (void)askAutoUpdates +{ +#ifdef UPDATE_SUPPORT + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAskedAutoUpdates"]) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"Should SameBoy check for updates when launched?"; + alert.informativeText = @"SameBoy is frequently updated with new features, accuracy improvements, and bug fixes. This setting can always be changed in the preferences window."; + [alert addButtonWithTitle:@"Check on Launch"]; + [alert addButtonWithTitle:@"Don't Check on Launch"]; + + [[NSUserDefaults standardUserDefaults] setBool:[alert runModal] == NSAlertFirstButtonReturn forKey:@"GBAutoUpdatesEnabled"]; + [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBAskedAutoUpdates"]; + } +#endif +} + +- (IBAction)skipVersion:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:_lastVersion forKey:@"GBSkippedVersion"]; + [self.updateWindow performClose:sender]; +} + +- (IBAction)installUpdate:(id)sender +{ + [self.updateProgressSpinner startAnimation:nil]; + self.updateProgressButton.title = @"Cancel"; + self.updateProgressButton.enabled = true; + self.updateProgressLabel.stringValue = @"Downloading update..."; + _updateState = UPDATE_DOWNLOADING; + _updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { + _updateTask = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Extracting update..."; + _updateState = UPDATE_EXTRACTING; + }); + + _downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:[[NSBundle mainBundle] bundleURL] + create:YES + error:nil] path]; + NSTask *unzipTask; + if (!_downloadDirectory) { + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to extract update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + } + + unzipTask = [[NSTask alloc] init]; + unzipTask.launchPath = @"/usr/bin/unzip"; + unzipTask.arguments = @[location.path, @"-d", _downloadDirectory]; + [unzipTask launch]; + [unzipTask waitUntilExit]; + if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to extract update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + return; + } + + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Update ready, save your game progress and click Install."; + _updateState = UPDATE_WAIT_INSTALL; + self.updateProgressButton.title = @"Install"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + }]; + [_updateTask resume]; + + self.updateProgressWindow.preventsApplicationTerminationWhenModal = false; + [self.updateWindow beginSheet:self.updateProgressWindow completionHandler:^(NSModalResponse returnCode) { + [self.updateWindow close]; + }]; +} + +- (void)performUpgrade +{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Instaling update..."; + _updateState = UPDATE_INSTALLING; + self.updateProgressButton.enabled = false; + [self.updateProgressSpinner startAnimation:nil]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString *executablePath = [[NSBundle mainBundle] executablePath]; + NSString *contentsPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents"]; + NSString *contentsTempPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"TempContents"]; + NSString *updateContentsPath = [_downloadDirectory stringByAppendingPathComponent:@"SameBoy.app/Contents"]; + NSError *error = nil; + [[NSFileManager defaultManager] moveItemAtPath:contentsPath toPath:contentsTempPath error:&error]; + if (error) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + _downloadDirectory = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to install update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + return; + } + [[NSFileManager defaultManager] moveItemAtPath:updateContentsPath toPath:contentsPath error:&error]; + if (error) { + [[NSFileManager defaultManager] moveItemAtPath:contentsTempPath toPath:contentsPath error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + _downloadDirectory = nil; + dispatch_sync(dispatch_get_main_queue(), ^{ + self.updateProgressButton.enabled = false; + self.updateProgressLabel.stringValue = @"Failed to install update."; + _updateState = UPDATE_FAILED; + self.updateProgressButton.title = @"Close"; + self.updateProgressButton.enabled = true; + [self.updateProgressSpinner stopAnimation:nil]; + }); + return; + } + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:contentsTempPath error:nil]; + _downloadDirectory = nil; + atexit_b(^{ + execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL); + }); + + dispatch_async(dispatch_get_main_queue(), ^{ + [NSApp terminate:nil]; + }); + }); +} + +- (IBAction)updateAction:(id)sender +{ + switch (_updateState) { + case UPDATE_DOWNLOADING: + [_updateTask cancelByProducingResumeData:nil]; + _updateTask = nil; + [self.updateProgressWindow close]; + break; + case UPDATE_WAIT_INSTALL: + [self performUpgrade]; + break; + case UPDATE_EXTRACTING: + case UPDATE_INSTALLING: + break; + case UPDATE_FAILED: + [self.updateProgressWindow close]; + break; + } +} + +- (void)dealloc +{ + if (_downloadDirectory) { + [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; + } +} + - (IBAction)nop:(id)sender { } diff --git a/Cocoa/Document.m b/Cocoa/Document.m index fd0c448..8977ce9 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1586,7 +1586,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void)cameraRequestUpdate { - dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @try { if (!cameraSession) { if (@available(macOS 10.14, *)) { diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index b25e476..0c7b422 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -25,5 +25,6 @@ @property (nonatomic, weak) IBOutlet NSPopUpButton *cgbPopupButton; @property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton; @property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton; +@property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 54d190f..217c7e1 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -29,6 +29,8 @@ NSPopUpButton *_rumbleModePopupButton; NSSlider *_temperatureSlider; NSSlider *_interferenceSlider; + NSButton *_autoUpdatesCheckbox; + } + (NSArray *)filterList @@ -381,6 +383,12 @@ } +- (IBAction)changeAutoUpdates:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBAutoUpdatesEnabled"]; +} + - (IBAction) configureJoypad:(id)sender { [self.configureJoypadButton setEnabled:NO]; @@ -705,4 +713,15 @@ } [[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"]; } + +- (NSButton *)autoUpdatesCheckbox +{ + return _autoUpdatesCheckbox; +} + +- (void)setAutoUpdatesCheckbox:(NSButton *)autoUpdatesCheckbox +{ + _autoUpdatesCheckbox = autoUpdatesCheckbox; + [_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]]; +} @end diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index ce3cb7c..b1d67f7 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -13,6 +13,9 @@ + + + @@ -49,17 +52,24 @@ + + + + + + + @@ -694,17 +704,6 @@ - + + + + + + + + + + + + + + + diff --git a/Cocoa/UpdateWindow.xib b/Cocoa/UpdateWindow.xib new file mode 100644 index 0000000..e34f8f2 --- /dev/null +++ b/Cocoa/UpdateWindow.xib @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Makefile b/Makefile index fcaae75..2c75bd0 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,9 @@ endif CFLAGS += $(WARNINGS) CFLAGS += -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES +ifneq (,$(UPDATE_SUPPORT)) +CFLAGS += -DUPDATE_SUPPORT +endif ifeq (,$(PKG_CONFIG)) SDL_CFLAGS := $(shell sdl2-config --cflags) From 0dff3ef144c450d0ef0dbec0461afc8cc22223ba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 6 May 2021 00:23:46 +0300 Subject: [PATCH 199/365] A flag to disable OpenGL, better and more stable handling of no-OpenGL mode --- SDL/gui.c | 7 +++++++ SDL/main.c | 20 ++++++++++++++------ SDL/shader.c | 4 ++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index b8e69cd..ae39f7e 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -676,6 +676,7 @@ static void cycle_border_mode_backwards(unsigned index) } } +extern bool uses_gl(void); struct shader_name { const char *file_name; const char *display_name; @@ -699,6 +700,7 @@ struct shader_name { static void cycle_filter(unsigned index) { + if (!uses_gl()) return; unsigned i = 0; for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { if (strcmp(shaders[i].file_name, configuration.filter) == 0) { @@ -721,6 +723,7 @@ static void cycle_filter(unsigned index) static void cycle_filter_backwards(unsigned index) { + if (!uses_gl()) return; unsigned i = 0; for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { if (strcmp(shaders[i].file_name, configuration.filter) == 0) { @@ -742,6 +745,7 @@ static void cycle_filter_backwards(unsigned index) } const char *current_filter_name(unsigned index) { + if (!uses_gl()) return "Requires OpenGL 3.2+"; unsigned i = 0; for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { if (strcmp(shaders[i].file_name, configuration.filter) == 0) { @@ -758,6 +762,7 @@ const char *current_filter_name(unsigned index) static void cycle_blending_mode(unsigned index) { + if (!uses_gl()) return; if (configuration.blending_mode == GB_FRAME_BLENDING_MODE_ACCURATE) { configuration.blending_mode = GB_FRAME_BLENDING_MODE_DISABLED; } @@ -768,6 +773,7 @@ static void cycle_blending_mode(unsigned index) static void cycle_blending_mode_backwards(unsigned index) { + if (!uses_gl()) return; if (configuration.blending_mode == GB_FRAME_BLENDING_MODE_DISABLED) { configuration.blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE; } @@ -778,6 +784,7 @@ static void cycle_blending_mode_backwards(unsigned index) const char *blending_mode_string(unsigned index) { + if (!uses_gl()) return "Requires OpenGL 3.2+"; return (const char *[]){"Disabled", "Simple", "Accurate"} [configuration.blending_mode]; } diff --git a/SDL/main.c b/SDL/main.c index 29398b7..5afc6de 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -25,8 +25,13 @@ static double clock_mutliplier = 1.0; static char *filename = NULL; static typeof(free) *free_function = NULL; -static char *battery_save_path_ptr; +static char *battery_save_path_ptr = NULL; +static SDL_GLContext gl_context = NULL; +bool uses_gl(void) +{ + return gl_context; +} void set_filename(const char *new_filename, typeof(free) *new_free_function) { @@ -637,9 +642,10 @@ int main(int argc, char **argv) fprintf(stderr, "SameBoy v" xstr(VERSION) "\n"); bool fullscreen = get_arg_flag("--fullscreen", &argc, argv); + bool nogl = get_arg_flag("--nogl", &argc, argv); if (argc > 2) { - fprintf(stderr, "Usage: %s [--fullscreen] [rom]\n", argv[0]); + fprintf(stderr, "Usage: %s [--fullscreen] [--nogl] [rom]\n", argv[0]); exit(1); } @@ -704,13 +710,15 @@ int main(int argc, char **argv) SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } - SDL_GLContext gl_context = SDL_GL_CreateContext(window); + gl_context = nogl? NULL : SDL_GL_CreateContext(window); GLint major = 0, minor = 0; - glGetIntegerv(GL_MAJOR_VERSION, &major); - glGetIntegerv(GL_MINOR_VERSION, &minor); + if (gl_context) { + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + } - if (major * 0x100 + minor < 0x302) { + if (gl_context && major * 0x100 + minor < 0x302) { SDL_GL_DeleteContext(gl_context); gl_context = NULL; } diff --git a/SDL/shader.c b/SDL/shader.c index de2ba56..44de290 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -62,8 +62,11 @@ static GLuint create_program(const char *vsh, const char *fsh) return program; } +extern bool uses_gl(void); bool init_shader_with_name(shader_t *shader, const char *name) { + if (!uses_gl()) return false; + GLint major = 0, minor = 0; glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); @@ -187,6 +190,7 @@ void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, void free_shader(shader_t *shader) { + if (!uses_gl()) return; GLint major = 0, minor = 0; glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); From a4a8ad00d59d62eb0122c966ff79b4b4e7e3f308 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 6 May 2021 00:26:45 +0300 Subject: [PATCH 200/365] Display usage on invalid options --- SDL/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDL/main.c b/SDL/main.c index 5afc6de..97884d5 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -644,7 +644,7 @@ int main(int argc, char **argv) bool fullscreen = get_arg_flag("--fullscreen", &argc, argv); bool nogl = get_arg_flag("--nogl", &argc, argv); - if (argc > 2) { + if (argc > 2 || (argc == 2 && argv[1][0] == '-')) { fprintf(stderr, "Usage: %s [--fullscreen] [--nogl] [rom]\n", argv[0]); exit(1); } From c944142b367ecc46c4eb8f74eb9efde98db2184c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 7 May 2021 00:33:04 +0300 Subject: [PATCH 201/365] Fall back to .snX if no .sX save state found --- Cocoa/Document.h | 2 +- Cocoa/Document.m | 19 +++++++++++++------ Cocoa/GBView.m | 2 +- SDL/main.c | 31 +++++++++++++++++++++++-------- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 8847385..19228e5 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -54,6 +54,6 @@ -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) performAtomicBlock: (void (^)())block; -(void) connectLinkCable:(NSMenuItem *)sender; -- (bool)loadStateFile:(const char *)path; +-(int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 8977ce9..297547c 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1278,26 +1278,33 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } -- (bool)loadStateFile:(const char *)path +- (int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound; { - bool __block success = false; + int __block result = false; NSString *error = [self captureOutputForBlock:^{ - success = GB_load_state(&gb, path) == 0; + result = GB_load_state(&gb, path); }]; - if (!success) { + if (result == ENOENT && noErrorOnFileNotFound) { + return ENOENT; + } + + if (result) { NSBeep(); } if (error) { [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; } - return success; + return result; } - (IBAction)loadState:(id)sender { - [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String]; + int ret = [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:true]; + if (ret == ENOENT) { + [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false]; + } } - (IBAction)clearConsole:(id)sender diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 5c1922c..04b3543 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -647,7 +647,7 @@ static const uint8_t workboy_vk_to_key[] = { if ( [[pboard types] containsObject:NSURLPboardType] ) { NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; - return [_document loadStateFile:fileURL.fileSystemRepresentation]; + return [_document loadStateFile:fileURL.fileSystemRepresentation noErrorOnNotFound:false]; } return false; diff --git a/SDL/main.c b/SDL/main.c index 97884d5..221bad1 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +64,7 @@ static void start_capturing_logs(void) GB_set_log_callback(&gb, log_capture_callback); } -static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags) +static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title) { GB_set_log_callback(&gb, NULL); if (captured_log[0] == 0) { @@ -72,7 +73,7 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_ } else { if (show_popup) { - SDL_ShowSimpleMessageBox(popup_flags, "Error", captured_log, window); + SDL_ShowSimpleMessageBox(popup_flags, title, captured_log, window); } if (should_exit) { exit(1); @@ -429,7 +430,7 @@ static bool handle_pending_command(void) switch (pending_command) { case GB_SDL_LOAD_STATE_COMMAND: case GB_SDL_SAVE_STATE_COMMAND: { - char save_path[strlen(filename) + 4]; + char save_path[strlen(filename) + 5]; char save_extension[] = ".s0"; save_extension[2] += command_parameter; replace_extension(filename, strlen(filename), save_path, save_extension); @@ -437,19 +438,33 @@ static bool handle_pending_command(void) start_capturing_logs(); bool success; if (pending_command == GB_SDL_LOAD_STATE_COMMAND) { - success = GB_load_state(&gb, save_path) == 0; + int result = GB_load_state(&gb, save_path); + if (result == ENOENT) { + char save_extension[] = ".sn0"; + save_extension[3] += command_parameter; + replace_extension(filename, strlen(filename), save_path, save_extension); + start_capturing_logs(); + result = GB_load_state(&gb, save_path); + } + success = result == 0; } else { success = GB_save_state(&gb, save_path) == 0; } - end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR); + end_capturing_logs(true, + false, + success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR, + success? "Notice" : "Error"); return false; } case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: start_capturing_logs(); bool success = GB_load_state(&gb, dropped_state_file) == 0; - end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR); + end_capturing_logs(true, + false, + success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR, + success? "Notice" : "Error"); SDL_free(dropped_state_file); return false; @@ -489,7 +504,7 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) if (use_built_in) { start_capturing_logs(); GB_load_boot_rom(gb, resource_path(names[type])); - end_capturing_logs(true, false, SDL_MESSAGEBOX_ERROR); + end_capturing_logs(true, false, SDL_MESSAGEBOX_ERROR, "Error"); } } @@ -562,7 +577,7 @@ restart: else { GB_load_rom(&gb, filename); } - end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING); + end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning"); /* Configure battery */ From 9b2dfe7ae276d441583c62d582725a7cbd483bd2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 17 May 2021 16:52:55 +0300 Subject: [PATCH 202/365] Style fixes --- Core/gb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 0a0c1dd..4bf9287 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -371,12 +371,12 @@ void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track) int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size, GB_gbs_info_t *info) { - if(size < sizeof(gb->gbs_header)) { + if (size < sizeof(gb->gbs_header)) { GB_log(gb, "Not a valid GBS file.\n"); return -1; } - memcpy(&gb->gbs_header,buffer,sizeof(gb->gbs_header)); + memcpy(&gb->gbs_header, buffer, sizeof(gb->gbs_header)); if (gb->gbs_header.magic != BE32('GBS\x01') || ((LE16(gb->gbs_header.load_address) < GBS_ENTRY + GBS_ENTRY_SIZE || From 2afeb7dee388c0605efad12c96b5c019e7ed6e8a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 17 May 2021 17:11:41 +0300 Subject: [PATCH 203/365] Place a cap on the GBS file size --- Core/gb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 4bf9287..1f1acdd 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -462,13 +462,13 @@ int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info) return errno; } fseek(f, 0, SEEK_END); - size_t file_size = ftell(f); + size_t file_size = MIN(ftell(f), sizeof(GB_gbs_header_t) + 0x4000 * 0x100); // Cap with the maximum MBC3 ROM size + GBS header fseek(f, 0, SEEK_SET); uint8_t *file_data = malloc(file_size); - fread(file_data,1,file_size,f); + fread(file_data, 1, file_size, f); fclose(f); - int r = GB_load_gbs_from_buffer(gb,file_data,file_size,info); + int r = GB_load_gbs_from_buffer(gb, file_data, file_size, info); free(file_data); return r; } From fcbbecea1795430c31a309fa92dc2a0ae69f4924 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 18 May 2021 20:21:21 +0300 Subject: [PATCH 204/365] Fix #386 --- SDL/gui.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index ae39f7e..0e2ec31 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1375,7 +1375,22 @@ void run_gui(bool is_running) case SDL_KEYDOWN: - if (event_hotkey_code(&event) == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) { + if (gui_state == WAITING_FOR_KEY) { + if (current_selection >= 8) { + if (current_selection == 8) { + configuration.keys[8] = event.key.keysym.scancode; + } + else { + configuration.keys_2[current_selection - 9] = event.key.keysym.scancode; + } + } + else { + configuration.keys[current_selection] = event.key.keysym.scancode; + } + gui_state = SHOWING_MENU; + should_render = true; + } + else if (event_hotkey_code(&event) == SDL_SCANCODE_F && event.key.keysym.mod & MODIFIER) { if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == false) { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } @@ -1384,7 +1399,7 @@ void run_gui(bool is_running) } update_viewport(); } - if (event_hotkey_code(&event) == SDL_SCANCODE_O) { + else if (event_hotkey_code(&event) == SDL_SCANCODE_O) { if (event.key.keysym.mod & MODIFIER) { char *filename = do_open_rom_dialog(); if (filename) { @@ -1477,21 +1492,6 @@ void run_gui(bool is_running) } should_render = true; } - else if (gui_state == WAITING_FOR_KEY) { - if (current_selection >= 8) { - if (current_selection == 8) { - configuration.keys[8] = event.key.keysym.scancode; - } - else { - configuration.keys_2[current_selection - 9] = event.key.keysym.scancode; - } - } - else { - configuration.keys[current_selection] = event.key.keysym.scancode; - } - gui_state = SHOWING_MENU; - should_render = true; - } break; } From 75d3470d55335b5eb56cd73a296c15c56baf8682 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 19 May 2021 00:15:02 +0300 Subject: [PATCH 205/365] That code made very little sense --- SDL/gui.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/SDL/gui.c b/SDL/gui.c index 0e2ec31..3614a65 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -921,10 +921,7 @@ static const struct menu_item controls_menu[] = { static const char *key_name(unsigned index) { - if (index >= 8) { - if (index == 8) { - return SDL_GetScancodeName(configuration.keys[8]); - } + if (index > 8) { return SDL_GetScancodeName(configuration.keys_2[index - 9]); } return SDL_GetScancodeName(configuration.keys[index]); @@ -1376,13 +1373,8 @@ void run_gui(bool is_running) case SDL_KEYDOWN: if (gui_state == WAITING_FOR_KEY) { - if (current_selection >= 8) { - if (current_selection == 8) { - configuration.keys[8] = event.key.keysym.scancode; - } - else { - configuration.keys_2[current_selection - 9] = event.key.keysym.scancode; - } + if (current_selection > 8) { + configuration.keys_2[current_selection - 9] = event.key.keysym.scancode; } else { configuration.keys[current_selection] = event.key.keysym.scancode; From 033f025851622bd15c5ebff92df95963fc942ca2 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 21 May 2021 18:12:29 +0300 Subject: [PATCH 206/365] Added volume control to the Cocoa port --- Cocoa/AppDelegate.m | 2 ++ Cocoa/Document.m | 8 ++++++++ Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 19 ++++++++++++++++++- Cocoa/Preferences.xib | 26 ++++++++++++++++++++++---- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 01b1527..627a7b6 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -68,6 +68,8 @@ static uint32_t color_to_int(NSColor *color) @"GBCGBModel": @(GB_MODEL_CGB_E), @"GBSGBModel": @(GB_MODEL_SGB2), @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), + + @"GBVolume": @(1.0), }]; [JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{ diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 297547c..010cff7 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -369,6 +369,11 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) } audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize); } + double volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; + if (volume != 1) { + sample->left *= volume; + sample->right *= volume; + } audioBuffer[audioBufferPosition++] = *sample; } if (audioBufferPosition == audioBufferNeeded) { @@ -994,6 +999,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } else { [self.audioClient start]; + if ([[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] == 0) { + [GBWarningPopover popoverWithContents:@"Warning: Volume is set to to zero in the preferences panel" onWindow:self.mainWindow]; + } } [[NSUserDefaults standardUserDefaults] setBool:!self.audioClient.isPlaying forKey:@"Mute"]; } diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 0c7b422..16e36e2 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -26,5 +26,6 @@ @property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton; @property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton; @property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox; +@property (weak) IBOutlet NSSlider *volumeSlider; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 217c7e1..4398ba3 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -29,6 +29,7 @@ NSPopUpButton *_rumbleModePopupButton; NSSlider *_temperatureSlider; NSSlider *_interferenceSlider; + NSSlider *_volumeSlider; NSButton *_autoUpdatesCheckbox; } @@ -124,6 +125,17 @@ return _interferenceSlider; } +- (void)setVolumeSlider:(NSSlider *)volumeSlider +{ + _volumeSlider = volumeSlider; + [volumeSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] * 256]; +} + +- (NSSlider *)volumeSlider +{ + return _volumeSlider; +} + - (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton { _frameBlendingModePopupButton = frameBlendingModePopupButton; @@ -331,12 +343,17 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil]; } -- (IBAction)volumeTemperatureChanged:(id)sender +- (IBAction)interferenceVolumeChanged:(id)sender { [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) forKey:@"GBInterferenceVolume"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil]; +} +- (IBAction)volumeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) + forKey:@"GBVolume"]; } - (IBAction)franeBlendingModeChanged:(id)sender diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index b1d67f7..ad510c8 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -92,6 +92,7 @@ + @@ -486,9 +487,26 @@ - + + + + + + + + + + + + + + + + + + @@ -532,11 +550,11 @@ - + - + @@ -565,7 +583,7 @@ - + From 3ed18a76da22c7125d55b3e5d7edcb067725e9b5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 30 May 2021 20:55:04 +0300 Subject: [PATCH 207/365] Added optional OSD (Cocoa) --- Cocoa/AppDelegate.m | 6 +-- Cocoa/Document.h | 2 + Cocoa/Document.m | 13 +++++ Cocoa/Document.xib | 7 ++- Cocoa/GBOSDView.h | 6 +++ Cocoa/GBOSDView.m | 104 ++++++++++++++++++++++++++++++++++++ Cocoa/GBPreferencesWindow.h | 2 +- Cocoa/GBPreferencesWindow.m | 20 ++++++- Cocoa/GBView.h | 2 + Cocoa/GBView.m | 10 ++++ Cocoa/GBVisualizerView.h | 8 --- Cocoa/GBVisualizerView.m | 8 --- Cocoa/Preferences.xib | 46 ++++++++++------ Core/gb.c | 68 +++++++++++++++++++++++ Core/gb.h | 5 ++ Core/save_state.c | 6 +-- Makefile | 2 +- SDL/main.c | 6 +-- libretro/Makefile | 2 +- libretro/jni/Android.mk | 2 +- libretro/libretro.c | 4 +- 21 files changed, 276 insertions(+), 53 deletions(-) create mode 100644 Cocoa/GBOSDView.h create mode 100644 Cocoa/GBOSDView.m diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 627a7b6..108a5c8 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -7,8 +7,6 @@ #import #define UPDATE_SERVER "https://sameboy.github.io" -#define str(x) #x -#define xstr(x) str(x) static uint32_t color_to_int(NSColor *color) { @@ -186,7 +184,7 @@ static uint32_t color_to_int(NSColor *color) } NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSRange cutoffRange = [changes rangeOfString:@""]; + NSRange cutoffRange = [changes rangeOfString:@""]; if (cutoffRange.location != NSNotFound) { changes = [changes substringToIndex:cutoffRange.location]; } @@ -252,7 +250,7 @@ static uint32_t color_to_int(NSColor *color) if (components.count != 2) return; _lastVersion = components[0]; _updateURL = components[1]; - if (![@xstr(VERSION) isEqualToString:_lastVersion] && + if (![@GB_VERSION isEqualToString:_lastVersion] && ![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) { [self updateFound]; } diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 19228e5..d6f89de 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -3,6 +3,7 @@ #include "GBImageView.h" #include "GBSplitView.h" #include "GBVisualizerView.h" +#include "GBOSDView.h" @class GBCheatWindowController; @@ -49,6 +50,7 @@ @property (strong) IBOutlet NSButton *gbsRewindButton; @property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton; @property (strong) IBOutlet GBVisualizerView *gbsVisualizer; +@property (strong) IBOutlet GBOSDView *osdView; -(uint8_t) readMemory:(uint16_t) addr; -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 010cff7..c434934 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -314,6 +314,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) self.mainWindow.contentView.bounds.size.width < GB_get_screen_height(&gb)) { [self.mainWindow zoom:nil]; } + self.osdView.usesSGBScale = GB_get_screen_width(&gb) == 256; } - (void) vblank @@ -344,6 +345,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) } if (self.view.isRewinding) { rewind = true; + [self.osdView displayText:@"Rewinding..."]; } } @@ -621,6 +623,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:0]; [self hexUpdateBank:self.memoryBankInput ignoreErrors:true]; } + + char title[17]; + GB_get_rom_title(&gb, title); + [self.osdView displayText:[NSString stringWithFormat:@"SameBoy v" GB_VERSION "\n%s\n%08X", title, GB_get_rom_crc32(&gb)]]; } - (IBAction)togglePause:(id)sender @@ -780,6 +786,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self initCommon]; self.view.gb = &gb; + self.view.osdView = _osdView; [self.view screenSizeChanged]; if ([self loadROM]) { _mainWindow.alphaValue = 0; // Hack hack ugly hack @@ -1284,6 +1291,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [GBWarningPopover popoverWithContents:@"Failed to write save state." onWindow:self.mainWindow]; NSBeep(); } + else { + [self.osdView displayText:@"State saved"]; + } } - (int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound; @@ -1301,6 +1311,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) if (result) { NSBeep(); } + else { + [self.osdView displayText:@"State loaded"]; + } if (error) { [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; } diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index a2cf5ee..ec2b450 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -25,6 +25,7 @@ + @@ -65,6 +66,10 @@ + + + + @@ -978,7 +983,7 @@ - + diff --git a/Cocoa/GBOSDView.h b/Cocoa/GBOSDView.h new file mode 100644 index 0000000..4771d2f --- /dev/null +++ b/Cocoa/GBOSDView.h @@ -0,0 +1,6 @@ +#import + +@interface GBOSDView : NSView +@property bool usesSGBScale; +- (void)displayText:(NSString *)text; +@end diff --git a/Cocoa/GBOSDView.m b/Cocoa/GBOSDView.m new file mode 100644 index 0000000..710229e --- /dev/null +++ b/Cocoa/GBOSDView.m @@ -0,0 +1,104 @@ +#import "GBOSDView.h" + +@implementation GBOSDView +{ + bool _usesSGBScale; + NSString *_text; + double _animation; + NSTimer *_timer; +} + +- (void)setUsesSGBScale:(bool)usesSGBScale +{ + _usesSGBScale = usesSGBScale; + [self setNeedsDisplay:true]; +} + +- (bool)usesSGBScale +{ + return _usesSGBScale; +} + +- (void)displayText:(NSString *)text +{ + if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]) return; + dispatch_async(dispatch_get_main_queue(), ^{ + if (![_text isEqualToString:text]) { + [self setNeedsDisplay:true]; + } + _text = text; + self.alphaValue = 1.0; + _animation = 2.5; + // Longer strings should appear longer + if ([_text rangeOfString:@"\n"].location != NSNotFound) { + _animation += 4; + } + [_timer invalidate]; + self.hidden = false; + _timer = [NSTimer scheduledTimerWithTimeInterval:0.025 target:self selector:@selector(animate) userInfo:nil repeats:true]; + }); +} + +- (void)animate +{ + _animation -= 0.1; + if (_animation < 1.0) { + self.alphaValue = _animation; + }; + if (_animation == 0) { + self.hidden = true; + [_timer invalidate]; + _text = nil; + } +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [super drawRect:dirtyRect]; + if (!_text.length) return; + + double fontSize = 8; + NSSize size = self.frame.size; + if (_usesSGBScale) { + fontSize *= MIN(size.width / 256, size.height / 224); + } + else { + fontSize *= MIN(size.width / 160, size.height / 144); + } + + NSFont *font = [NSFont boldSystemFontOfSize:fontSize]; + + /* The built in stroke attribute uses an inside stroke, which is typographically terrible. + We'll use a naïve manual stroke instead which looks better. */ + + NSDictionary *attributes = @{ + NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor blackColor], + }; + + NSAttributedString *text = [[NSAttributedString alloc] initWithString:_text attributes:attributes]; + + [text drawAtPoint:NSMakePoint(fontSize + 1, fontSize + 0)]; + [text drawAtPoint:NSMakePoint(fontSize - 1, fontSize + 0)]; + [text drawAtPoint:NSMakePoint(fontSize + 0, fontSize + 1)]; + [text drawAtPoint:NSMakePoint(fontSize + 0, fontSize - 1)]; + + // The uses of sqrt(2)/2, which is more correct, results in severe ugly-looking rounding errors + if (self.window.screen.backingScaleFactor > 1) { + [text drawAtPoint:NSMakePoint(fontSize + 0.5, fontSize + 0.5)]; + [text drawAtPoint:NSMakePoint(fontSize - 0.5, fontSize + 0.5)]; + [text drawAtPoint:NSMakePoint(fontSize - 0.5, fontSize - 0.5)]; + [text drawAtPoint:NSMakePoint(fontSize + 0.5, fontSize - 0.5)]; + } + + attributes = @{ + NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor whiteColor], + }; + + text = [[NSAttributedString alloc] initWithString:_text attributes:attributes]; + + [text drawAtPoint:NSMakePoint(fontSize, fontSize)]; +} + +@end diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 16e36e2..260ebf9 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -27,5 +27,5 @@ @property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton; @property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox; @property (weak) IBOutlet NSSlider *volumeSlider; - +@property (weak) IBOutlet NSButton *OSDCheckbox; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 4398ba3..96e9c16 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -31,7 +31,7 @@ NSSlider *_interferenceSlider; NSSlider *_volumeSlider; NSButton *_autoUpdatesCheckbox; - + NSButton *_OSDCheckbox; } + (NSArray *)filterList @@ -741,4 +741,22 @@ _autoUpdatesCheckbox = autoUpdatesCheckbox; [_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]]; } + +- (NSButton *)OSDCheckbox +{ + return _OSDCheckbox; +} + +- (void)setOSDCheckbox:(NSButton *)OSDCheckbox +{ + _OSDCheckbox = OSDCheckbox; + [_OSDCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]]; +} + +- (IBAction)changeOSDEnabled:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState + forKey:@"GBOSDEnabled"]; + +} @end diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index f9aab83..cd6a539 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,6 +1,7 @@ #import #include #import +#import "GBOSDView.h" @class Document; typedef enum { @@ -20,6 +21,7 @@ typedef enum { @property (nonatomic, getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @property (nonatomic) bool isRewinding; @property (nonatomic, strong) NSView *internalView; +@property (weak) GBOSDView *osdView; - (void) createInternalView; - (uint32_t *)currentBuffer; - (uint32_t *)previousBuffer; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 04b3543..9d4ccbc 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -117,6 +117,7 @@ static const uint8_t workboy_vk_to_key[] = { NSEventModifierFlags previousModifiers; JOYController *lastController; GB_frame_blending_mode_t _frameBlendingMode; + bool _turbo; } + (instancetype)alloc @@ -283,6 +284,12 @@ static const uint8_t workboy_vk_to_key[] = { } } } + if (clockMultiplier > 1 || _turbo) { + [self.osdView displayText:@"Fast forwarding..."]; + } + else if (clockMultiplier < 1) { + [self.osdView displayText:@"Slow motion..."]; + } current_buffer = (current_buffer + 1) % self.numberOfBuffers; } @@ -329,6 +336,7 @@ static const uint8_t workboy_vk_to_key[] = { else { GB_set_turbo_mode(_gb, true, self.isRewinding); } + _turbo = true; analogClockMultiplierValid = false; break; @@ -336,6 +344,7 @@ static const uint8_t workboy_vk_to_key[] = { if (!self.document.partner) { self.isRewinding = true; GB_set_turbo_mode(_gb, false, false); + _turbo = false; } break; @@ -401,6 +410,7 @@ static const uint8_t workboy_vk_to_key[] = { else { GB_set_turbo_mode(_gb, false, false); } + _turbo = false; analogClockMultiplierValid = false; break; diff --git a/Cocoa/GBVisualizerView.h b/Cocoa/GBVisualizerView.h index 43cda4b..5ee4638 100644 --- a/Cocoa/GBVisualizerView.h +++ b/Cocoa/GBVisualizerView.h @@ -1,11 +1,3 @@ -// -// GBVisualizerView.h -// SameBoySDL -// -// Created by Lior Halphon on 7/4/21. -// Copyright © 2021 Lior Halphon. All rights reserved. -// - #import #include diff --git a/Cocoa/GBVisualizerView.m b/Cocoa/GBVisualizerView.m index c09cfe1..61688e6 100644 --- a/Cocoa/GBVisualizerView.m +++ b/Cocoa/GBVisualizerView.m @@ -1,11 +1,3 @@ -// -// GBVisualizerView.m -// SameBoySDL -// -// Created by Lior Halphon on 7/4/21. -// Copyright © 2021 Lior Halphon. All rights reserved. -// - #import "GBVisualizerView.h" #include diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index ad510c8..754a548 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -67,6 +67,7 @@ + @@ -97,11 +98,11 @@ - + - + @@ -110,7 +111,7 @@ - + @@ -147,7 +148,7 @@ - + @@ -156,7 +157,7 @@ - + @@ -178,7 +179,7 @@ - + @@ -187,7 +188,7 @@ - + @@ -207,7 +208,7 @@ - + @@ -216,7 +217,7 @@ - + @@ -237,7 +238,7 @@ - + @@ -246,7 +247,7 @@ - + @@ -266,7 +267,7 @@ - + @@ -274,7 +275,7 @@ - + @@ -283,7 +284,7 @@ + - + @@ -583,7 +595,7 @@ - + @@ -734,7 +746,7 @@

    diff --git a/Core/display.c b/Core/display.c index 8ebe803..2956cbc 100644 --- a/Core/display.c +++ b/Core/display.c @@ -292,12 +292,24 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) new_r = new_r * 7 / 8 + ( g + b) / 16; new_g = new_g * 7 / 8 + (r + b) / 16; new_b = new_b * 7 / 8 + (r + g ) / 16; - new_r = new_r * (224 - 32) / 255 + 32; new_g = new_g * (220 - 36) / 255 + 36; new_b = new_b * (216 - 40) / 255 + 40; } + else if (gb->color_correction_mode == GB_COLOR_CORRECTION_LOW_CONTRAST) { + r = new_r; + g = new_r; + b = new_r; + + new_r = new_r * 7 / 8 + ( g + b) / 16; + new_g = new_g * 7 / 8 + (r + b) / 16; + new_b = new_b * 7 / 8 + (r + g ) / 16; + + new_r = new_r * (162 - 67) / 255 + 67; + new_g = new_g * (167 - 62) / 255 + 62; + new_b = new_b * (157 - 58) / 255 + 58; + } else if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) { uint8_t old_max = MAX(r, MAX(g, b)); uint8_t new_max = MAX(new_r, MAX(new_g, new_b)); diff --git a/Core/display.h b/Core/display.h index fdaf172..c9411dc 100644 --- a/Core/display.h +++ b/Core/display.h @@ -51,6 +51,7 @@ typedef enum { GB_COLOR_CORRECTION_EMULATE_HARDWARE, GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS, GB_COLOR_CORRECTION_REDUCE_CONTRAST, + GB_COLOR_CORRECTION_LOW_CONTRAST, } GB_color_correction_mode_t; void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); diff --git a/SDL/gui.c b/SDL/gui.c index 74511a8..630e6e2 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -549,7 +549,7 @@ const char *current_default_scale(unsigned index) const char *current_color_correction_mode(unsigned index) { - return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness", "Reduce Contrast"} + return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness", "Reduce Contrast", "Harsh Reality"} [configuration.color_correction_mode]; } @@ -624,7 +624,7 @@ void cycle_default_scale_backwards(unsigned index) static void cycle_color_correction(unsigned index) { - if (configuration.color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) { + if (configuration.color_correction_mode == GB_COLOR_CORRECTION_LOW_CONTRAST) { configuration.color_correction_mode = GB_COLOR_CORRECTION_DISABLED; } else { @@ -635,7 +635,7 @@ static void cycle_color_correction(unsigned index) static void cycle_color_correction_backwards(unsigned index) { if (configuration.color_correction_mode == GB_COLOR_CORRECTION_DISABLED) { - configuration.color_correction_mode = GB_COLOR_CORRECTION_REDUCE_CONTRAST; + configuration.color_correction_mode = GB_COLOR_CORRECTION_LOW_CONTRAST; } else { configuration.color_correction_mode--; diff --git a/SDL/main.c b/SDL/main.c index 77b83e7..796eeff 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -719,7 +719,7 @@ int main(int argc, char **argv) fclose(prefs_file); /* Sanitize for stability */ - configuration.color_correction_mode %= GB_COLOR_CORRECTION_REDUCE_CONTRAST +1; + configuration.color_correction_mode %= GB_COLOR_CORRECTION_LOW_CONTRAST +1; configuration.scaling_mode %= GB_SDL_SCALING_MAX; configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1; configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1; diff --git a/libretro/libretro.c b/libretro/libretro.c index fe49d80..bf811bd 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -232,7 +232,7 @@ static retro_environment_t environ_cb; /* variables for single cart mode */ static const struct retro_variable vars_single[] = { - { "sameboy_color_correction_mode", "Color correction; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, + { "sameboy_color_correction_mode", "Color correction; emulate hardware|preserve brightness|reduce contrast|harsh reality|off|correct curves" }, { "sameboy_high_pass_filter_mode", "High-pass filter; accurate|remove dc offset|off" }, { "sameboy_model", "Emulated model (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, { "sameboy_border", "Display border; Super Game Boy only|always|never" }, @@ -249,8 +249,8 @@ static const struct retro_variable vars_dual[] = { { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, { "sameboy_model_1", "Emulated model for Game Boy #1 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, { "sameboy_model_2", "Emulated model for Game Boy #2 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, - { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; emulate hardware|preserve brightness|reduce contrast|off|correct curves" }, + { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; emulate hardware|preserve brightness|reduce contrast|harsh reality|off|correct curves" }, + { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; emulate hardware|preserve brightness|harsh reality|off|correct curves" }, { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; accurate|remove dc offset|off" }, { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; accurate|remove dc offset|off" }, { "sameboy_rumble_1", "Enable rumble for Game Boy #1; rumble-enabled games|all games|never" }, @@ -580,6 +580,9 @@ static void check_variables() else if (strcmp(var.value, "reduce contrast") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } + else if (strcmp(var.value, "harsh reality") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_LOW_CONTRAST); + } } var.key = "sameboy_rumble"; @@ -686,6 +689,9 @@ static void check_variables() else if (strcmp(var.value, "reduce contrast") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } + else if (strcmp(var.value, "harsh reality") == 0) { + GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_LOW_CONTRAST); + } } var.key = "sameboy_color_correction_mode_2"; @@ -706,6 +712,9 @@ static void check_variables() else if (strcmp(var.value, "reduce contrast") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_REDUCE_CONTRAST); } + else if (strcmp(var.value, "harsh reality") == 0) { + GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_LOW_CONTRAST); + } } From 278224299f03083c120836985bd03276935474d6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 26 Jun 2021 13:54:18 +0300 Subject: [PATCH 220/365] Fixed double->single speed switch causing misaligned CPU timing --- Core/sm83_cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 0d4ab72..ec908e7 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -373,7 +373,7 @@ static void leave_stop_mode(GB_gameboy_t *gb) for (unsigned i = 0x1FFF; i--;) { GB_advance_cycles(gb, 0x10); } - GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xF); + GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xC); GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); } From e1453f1961c62d42f9ecb642e200a4e550736a56 Mon Sep 17 00:00:00 2001 From: Ryunam Date: Sat, 26 Jun 2021 23:40:22 +0200 Subject: [PATCH 221/365] [Libretro] Upgrade Core Options to v1.3 --- libretro/libretro.c | 1033 +++++++++++++++++++------ libretro/libretro.h | 424 +++++++++- libretro/libretro_core_options.h | 701 +++++++++++++++++ libretro/libretro_core_options_intl.h | 90 +++ libretro/retro_inline.h | 39 + 5 files changed, 2019 insertions(+), 268 deletions(-) create mode 100644 libretro/libretro_core_options.h create mode 100644 libretro/libretro_core_options_intl.h create mode 100644 libretro/retro_inline.h diff --git a/libretro/libretro.c b/libretro/libretro.c index bf811bd..baca352 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -22,6 +22,7 @@ #include #include "libretro.h" +#include "libretro_core_options.h" #ifdef _WIN32 static const char slash = '\\'; @@ -45,20 +46,24 @@ char battery_save_path[512]; char symbols_path[512]; enum model { - MODEL_DMG, - MODEL_CGB, + MODEL_DMG_B, + MODEL_CGB_C, + MODEL_CGB_E, MODEL_AGB, - MODEL_SGB, + MODEL_SGB_PAL, + MODEL_SGB_NTSC, MODEL_SGB2, MODEL_AUTO }; static const GB_model_t libretro_to_internal_model[] = { - [MODEL_DMG] = GB_MODEL_DMG_B, - [MODEL_CGB] = GB_MODEL_CGB_E, + [MODEL_DMG_B] = GB_MODEL_DMG_B, + [MODEL_CGB_C] = GB_MODEL_CGB_C, + [MODEL_CGB_E] = GB_MODEL_CGB_E, [MODEL_AGB] = GB_MODEL_AGB, - [MODEL_SGB] = GB_MODEL_SGB, + [MODEL_SGB_PAL] = GB_MODEL_SGB_PAL, + [MODEL_SGB_NTSC] = GB_MODEL_SGB_NTSC, [MODEL_SGB2] = GB_MODEL_SGB2 }; @@ -73,7 +78,7 @@ enum audio_out { }; static enum model model[2]; -static enum model auto_model = MODEL_CGB; +static enum model auto_model = MODEL_CGB_E; static uint32_t *frame_buf = NULL; static uint32_t *frame_buf_copy = NULL; @@ -160,7 +165,7 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port) static void rumble_callback(GB_gameboy_t *gb, double amplitude) { if (!rumble.set_rumble_state) return; - + if (gb == &gameboy[0]) { rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535 * amplitude); } @@ -230,33 +235,77 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) static retro_environment_t environ_cb; -/* variables for single cart mode */ -static const struct retro_variable vars_single[] = { - { "sameboy_color_correction_mode", "Color correction; emulate hardware|preserve brightness|reduce contrast|harsh reality|off|correct curves" }, - { "sameboy_high_pass_filter_mode", "High-pass filter; accurate|remove dc offset|off" }, - { "sameboy_model", "Emulated model (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance|Super Game Boy|Super Game Boy 2" }, - { "sameboy_border", "Display border; Super Game Boy only|always|never" }, - { "sameboy_rumble", "Enable rumble; rumble-enabled games|all games|never" }, - { "sameboy_rtc", "Real Time Clock emulation; sync to system clock|accurate" }, - { NULL } -}; +static void set_variable_visibility(void) +{ + struct retro_core_option_display option_display_singlecart; + struct retro_core_option_display option_display_dualcart; -/* variables for dual cart dual gameboy mode */ -static const struct retro_variable vars_dual[] = { - { "sameboy_link", "Link cable emulation; enabled|disabled" }, - /*{ "sameboy_ir", "Infrared Sensor Emulation; disabled|enabled" },*/ - { "sameboy_screen_layout", "Screen layout; top-down|left-right" }, - { "sameboy_audio_output", "Audio output; Game Boy #1|Game Boy #2" }, - { "sameboy_model_1", "Emulated model for Game Boy #1 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_model_2", "Emulated model for Game Boy #2 (Restart game); Auto|Game Boy|Game Boy Color|Game Boy Advance" }, - { "sameboy_color_correction_mode_1", "Color correction for Game Boy #1; emulate hardware|preserve brightness|reduce contrast|harsh reality|off|correct curves" }, - { "sameboy_color_correction_mode_2", "Color correction for Game Boy #2; emulate hardware|preserve brightness|harsh reality|off|correct curves" }, - { "sameboy_high_pass_filter_mode_1", "High-pass filter for Game Boy #1; accurate|remove dc offset|off" }, - { "sameboy_high_pass_filter_mode_2", "High-pass filter for Game Boy #2; accurate|remove dc offset|off" }, - { "sameboy_rumble_1", "Enable rumble for Game Boy #1; rumble-enabled games|all games|never" }, - { "sameboy_rumble_2", "Enable rumble for Game Boy #2; rumble-enabled games|all games|never" }, - { NULL } -}; + size_t i; + size_t num_options = 0; + + // Show/hide options depending on the number of emulated devices + if (emulated_devices == 1) + { + option_display_singlecart.visible = true; + option_display_dualcart.visible = false; + } + else if (emulated_devices == 2) + { + option_display_singlecart.visible = false; + option_display_dualcart.visible = true; + } + + // Determine number of options + for (;;) + { + if (!option_defs_us[num_options].key) + break; + num_options++; + } + + // Copy parameters from option_defs_us array + for (i = 0; i < num_options; i++) + { + const char *key = option_defs_us[i].key; + { + if ((strcmp(key, "sameboy_model") == 0) || + (strcmp(key, "sameboy_rtc") == 0) || + (strcmp(key, "sameboy_scaling_filter") == 0) || + (strcmp(key, "sameboy_mono_palette") == 0) || + (strcmp(key, "sameboy_color_correction_mode") == 0) || + (strcmp(key, "sameboy_light_temperature") == 0) || + (strcmp(key, "sameboy_border") == 0) || + (strcmp(key, "sameboy_high_pass_filter_mode") == 0) || + (strcmp(key, "sameboy_audio_interference") == 0) || + (strcmp(key, "sameboy_rumble") == 0)) + { + option_display_singlecart.key = key; + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display_singlecart); + } + else if ((strcmp(key, "sameboy_link") == 0) || + (strcmp(key, "sameboy_screen_layout") == 0) || + (strcmp(key, "sameboy_audio_output") == 0) || + (strcmp(key, "sameboy_model_1") == 0) || + (strcmp(key, "sameboy_model_2") == 0) || + (strcmp(key, "sameboy_mono_palette_1") == 0) || + (strcmp(key, "sameboy_mono_palette_2") == 0) || + (strcmp(key, "sameboy_color_correction_mode_1") == 0) || + (strcmp(key, "sameboy_color_correction_mode_2") == 0) || + (strcmp(key, "sameboy_light_temperature_1") == 0) || + (strcmp(key, "sameboy_light_temperature_2") == 0) || + (strcmp(key, "sameboy_high_pass_filter_mode_1") == 0) || + (strcmp(key, "sameboy_high_pass_filter_mode_2") == 0) || + (strcmp(key, "sameboy_audio_interference_1") == 0) || + (strcmp(key, "sameboy_audio_interference_2") == 0) || + (strcmp(key, "sameboy_rumble_1") == 0) || + (strcmp(key, "sameboy_rumble_2") == 0)) + { + option_display_dualcart.key = key; + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display_dualcart); + } + } + } +} static const struct retro_subsystem_memory_info gb1_memory[] = { { "srm", RETRO_MEMORY_GAMEBOY_1_SRAM }, @@ -357,7 +406,7 @@ static struct retro_input_descriptor descriptors_4p[] = { static void set_link_cable_state(bool state) { - if (state && emulated_devices == 2) { + if (state && emulated_devices == 2) { GB_set_serial_transfer_bit_start_callback(&gameboy[0], serial_start1); GB_set_serial_transfer_bit_end_callback(&gameboy[0], serial_end1); GB_set_serial_transfer_bit_start_callback(&gameboy[1], serial_start2); @@ -365,7 +414,7 @@ static void set_link_cable_state(bool state) GB_set_infrared_callback(&gameboy[0], infrared_callback1); GB_set_infrared_callback(&gameboy[1], infrared_callback2); } - else if (!state) { + else if (!state) { GB_set_serial_transfer_bit_start_callback(&gameboy[0], NULL); GB_set_serial_transfer_bit_end_callback(&gameboy[0], NULL); GB_set_serial_transfer_bit_start_callback(&gameboy[1], NULL); @@ -387,7 +436,7 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) [GB_BOOT_ROM_CGB] = "cgb", [GB_BOOT_ROM_AGB] = "agb", }[type]; - + const uint8_t *boot_code = (const unsigned char *[]) { [GB_BOOT_ROM_DMG0] = dmg_boot, // dmg0 not implemented yet @@ -399,7 +448,7 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) [GB_BOOT_ROM_CGB] = cgb_boot, [GB_BOOT_ROM_AGB] = agb_boot, }[type]; - + unsigned boot_length = (unsigned []){ [GB_BOOT_ROM_DMG0] = dmg_boot_length, // dmg0 not implemented yet [GB_BOOT_ROM_DMG] = dmg_boot_length, @@ -410,7 +459,7 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) [GB_BOOT_ROM_CGB] = cgb_boot_length, [GB_BOOT_ROM_AGB] = agb_boot_length, }[type]; - + char buf[256]; snprintf(buf, sizeof(buf), "%s%c%s_boot.bin", retro_system_directory, slash, model_name); log_cb(RETRO_LOG_INFO, "Initializing as model: %s\n", model_name); @@ -527,7 +576,7 @@ static void init_for_current_model(unsigned id) } /* Let's be extremely nitpicky about how devices and descriptors are set */ - if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { + if (emulated_devices == 1 && (model[0] == MODEL_SGB_PAL || model[0] == MODEL_SGB_NTSC || model[0] == MODEL_SGB2)) { static const struct retro_controller_info ports[] = { { controllers_sgb, 1 }, { controllers_sgb, 1 }, @@ -538,7 +587,7 @@ static void init_for_current_model(unsigned id) environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_4p); } - else if (emulated_devices == 1) { + else if (emulated_devices == 1) { static const struct retro_controller_info ports[] = { { controllers, 1 }, { NULL, 0 }, @@ -546,7 +595,7 @@ static void init_for_current_model(unsigned id) environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors_1p); } - else { + else { static const struct retro_controller_info ports[] = { { controllers, 1 }, { controllers, 1 }, @@ -561,10 +610,71 @@ static void init_for_current_model(unsigned id) static void check_variables() { struct retro_variable var = {0}; - if (emulated_devices == 1) { + if (emulated_devices == 1) { + + var.key = "sameboy_model"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + enum model new_model = model[0]; + if (strcmp(var.value, "Game Boy") == 0) { + new_model = MODEL_DMG_B; + } + else if (strcmp(var.value, "Game Boy Color CPU C") == 0) { + new_model = MODEL_CGB_C; + } + else if (strcmp(var.value, "Game Boy Color") == 0) { + new_model = MODEL_CGB_E; + } + else if (strcmp(var.value, "Game Boy Advance") == 0) { + new_model = MODEL_AGB; + } + else if (strcmp(var.value, "Super Game Boy") == 0) { + new_model = MODEL_SGB_NTSC; + } + else if (strcmp(var.value, "Super Game Boy PAL") == 0) { + new_model = MODEL_SGB_PAL; + } + else if (strcmp(var.value, "Super Game Boy 2") == 0) { + new_model = MODEL_SGB2; + } + else { + new_model = MODEL_AUTO; + } + + model[0] = new_model; + } + + var.key = "sameboy_rtc"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "sync to system clock") == 0) { + GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_SYNC_TO_HOST); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_ACCURATE); + } + } + + var.key = "sameboy_mono_palette"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "greyscale") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_GREY); + } + else if (strcmp(var.value, "lime") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_DMG); + } + else if (strcmp(var.value, "olive") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_MGB); + } + else if (strcmp(var.value, "teal") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_GBL); + } + } + var.key = "sameboy_color_correction_mode"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); } @@ -584,70 +694,73 @@ static void check_variables() GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_LOW_CONTRAST); } } - - var.key = "sameboy_rumble"; + + var.key = "sameboy_light_temperature"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "never") == 0) { - GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_DISABLED); + if (strcmp(var.value, "1000K") == 0) { + GB_set_light_temperature(&gameboy[0], 1.0); } - else if (strcmp(var.value, "rumble-enabled games") == 0) { - GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_CARTRIDGE_ONLY); + else if (strcmp(var.value, "1550K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.9); } - else if (strcmp(var.value, "all games") == 0) { - GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); + else if (strcmp(var.value, "2100K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.8); } - } - - var.key = "sameboy_rtc"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "sync to system clock") == 0) { - GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_SYNC_TO_HOST); + else if (strcmp(var.value, "2650K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.7); } - else if (strcmp(var.value, "accurate") == 0) { - GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_ACCURATE); + else if (strcmp(var.value, "3200K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.6); } - } - - var.key = "sameboy_high_pass_filter_mode"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); + else if (strcmp(var.value, "3750K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.5); } - else if (strcmp(var.value, "accurate") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); + else if (strcmp(var.value, "4300K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.4); } - else if (strcmp(var.value, "remove dc offset") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + else if (strcmp(var.value, "4850K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.3); } - } - - var.key = "sameboy_model"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - enum model new_model = model[0]; - if (strcmp(var.value, "Game Boy") == 0) { - new_model = MODEL_DMG; + else if (strcmp(var.value, "5400K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.2); } - else if (strcmp(var.value, "Game Boy Color") == 0) { - new_model = MODEL_CGB; + else if (strcmp(var.value, "5950K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.1); } - else if (strcmp(var.value, "Game Boy Advance") == 0) { - new_model = MODEL_AGB; + else if (strcmp(var.value, "6500K") == 0) { + GB_set_light_temperature(&gameboy[0], 0); } - else if (strcmp(var.value, "Super Game Boy") == 0) { - new_model = MODEL_SGB; + else if (strcmp(var.value, "7050K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.1); } - else if (strcmp(var.value, "Super Game Boy 2") == 0) { - new_model = MODEL_SGB2; + else if (strcmp(var.value, "7600K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.2); } - else { - new_model = MODEL_AUTO; + else if (strcmp(var.value, "8150K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.3); + } + else if (strcmp(var.value, "8700K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.4); + } + else if (strcmp(var.value, "9250K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.5); + } + else if (strcmp(var.value, "9800K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.6); + } + else if (strcmp(var.value, "10350K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.7); + } + else if (strcmp(var.value, "10900K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.8); + } + else if (strcmp(var.value, "11450K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.9); + } + else if (strcmp(var.value, "12000K") == 0) { + GB_set_light_temperature(&gameboy[0], -1.0); } - - model[0] = new_model; } var.key = "sameboy_border"; @@ -662,18 +775,255 @@ static void check_variables() else if (strcmp(var.value, "always") == 0) { GB_set_border_mode(&gameboy[0], GB_BORDER_ALWAYS); } - geometry_updated = true; } + + var.key = "sameboy_high_pass_filter_mode"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "off") == 0) { + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); + } + else if (strcmp(var.value, "remove dc offset") == 0) { + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + } + } + + var.key = "sameboy_audio_interference"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "0") == 0) { + GB_set_interference_volume(&gameboy[0], 0.00); + } + else if (strcmp(var.value, "5") == 0) { + GB_set_interference_volume(&gameboy[0], 0.05); + } + else if (strcmp(var.value, "10") == 0) { + GB_set_interference_volume(&gameboy[0], 0.10); + } + else if (strcmp(var.value, "15") == 0) { + GB_set_interference_volume(&gameboy[0], 0.15); + } + else if (strcmp(var.value, "20") == 0) { + GB_set_interference_volume(&gameboy[0], 0.20); + } + else if (strcmp(var.value, "25") == 0) { + GB_set_interference_volume(&gameboy[0], 0.25); + } + else if (strcmp(var.value, "30") == 0) { + GB_set_interference_volume(&gameboy[0], 0.30); + } + else if (strcmp(var.value, "35") == 0) { + GB_set_interference_volume(&gameboy[0], 0.35); + } + else if (strcmp(var.value, "40") == 0) { + GB_set_interference_volume(&gameboy[0], 0.40); + } + else if (strcmp(var.value, "45") == 0) { + GB_set_interference_volume(&gameboy[0], 0.45); + } + else if (strcmp(var.value, "50") == 0) { + GB_set_interference_volume(&gameboy[0], 0.50); + } + else if (strcmp(var.value, "55") == 0) { + GB_set_interference_volume(&gameboy[0], 0.55); + } + else if (strcmp(var.value, "60") == 0) { + GB_set_interference_volume(&gameboy[0], 0.60); + } + else if (strcmp(var.value, "65") == 0) { + GB_set_interference_volume(&gameboy[0], 0.65); + } + else if (strcmp(var.value, "70") == 0) { + GB_set_interference_volume(&gameboy[0], 0.70); + } + else if (strcmp(var.value, "75") == 0) { + GB_set_interference_volume(&gameboy[0], 0.75); + } + else if (strcmp(var.value, "80") == 0) { + GB_set_interference_volume(&gameboy[0], 0.80); + } + else if (strcmp(var.value, "85") == 0) { + GB_set_interference_volume(&gameboy[0], 0.85); + } + else if (strcmp(var.value, "90") == 0) { + GB_set_interference_volume(&gameboy[0], 0.90); + } + else if (strcmp(var.value, "95") == 0) { + GB_set_interference_volume(&gameboy[0], 0.95); + } + else if (strcmp(var.value, "100") == 0) { + GB_set_interference_volume(&gameboy[0], 1.00); + } + } + + var.key = "sameboy_rumble"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "never") == 0) { + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_DISABLED); + } + else if (strcmp(var.value, "rumble-enabled games") == 0) { + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_CARTRIDGE_ONLY); + } + else if (strcmp(var.value, "all games") == 0) { + GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); + } + } + } else { GB_set_border_mode(&gameboy[0], GB_BORDER_NEVER); GB_set_border_mode(&gameboy[1], GB_BORDER_NEVER); GB_set_rtc_mode(&gameboy[0], GB_RTC_MODE_ACCURATE); GB_set_rtc_mode(&gameboy[1], GB_RTC_MODE_ACCURATE); + + var.key = "sameboy_link"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + bool tmp = link_cable_emulation; + if (strcmp(var.value, "enabled") == 0) { + link_cable_emulation = true; + } + else { + link_cable_emulation = false; + } + if (link_cable_emulation && link_cable_emulation != tmp) { + set_link_cable_state(true); + } + else if (!link_cable_emulation && link_cable_emulation != tmp) { + set_link_cable_state(false); + } + } + + var.key = "sameboy_screen_layout"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "top-down") == 0) { + screen_layout = LAYOUT_TOP_DOWN; + } + else { + screen_layout = LAYOUT_LEFT_RIGHT; + } + + geometry_updated = true; + } + + var.key = "sameboy_audio_output"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "Game Boy #1") == 0) { + audio_out = GB_1; + } + else { + audio_out = GB_2; + } + } + + var.key = "sameboy_model_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + enum model new_model = model[0]; + if (strcmp(var.value, "Game Boy") == 0) { + new_model = MODEL_DMG_B; + } + else if (strcmp(var.value, "Game Boy Color CPU C") == 0) { + new_model = MODEL_CGB_C; + } + else if (strcmp(var.value, "Game Boy Color") == 0) { + new_model = MODEL_CGB_E; + } + else if (strcmp(var.value, "Game Boy Advance") == 0) { + new_model = MODEL_AGB; + } + else if (strcmp(var.value, "Super Game Boy") == 0) { + new_model = MODEL_SGB_NTSC; + } + else if (strcmp(var.value, "Super Game Boy PAL") == 0) { + new_model = MODEL_SGB_PAL; + } + else if (strcmp(var.value, "Super Game Boy 2") == 0) { + new_model = MODEL_SGB2; + } + else { + new_model = MODEL_AUTO; + } + + model[0] = new_model; + } + + var.key = "sameboy_model_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + enum model new_model = model[1]; + if (strcmp(var.value, "Game Boy") == 0) { + new_model = MODEL_DMG_B; + } + else if (strcmp(var.value, "Game Boy Color CPU C") == 0) { + new_model = MODEL_CGB_C; + } + else if (strcmp(var.value, "Game Boy Color") == 0) { + new_model = MODEL_CGB_E; + } + else if (strcmp(var.value, "Game Boy Advance") == 0) { + new_model = MODEL_AGB; + } + else if (strcmp(var.value, "Super Game Boy") == 0) { + new_model = MODEL_SGB_NTSC; + } + else if (strcmp(var.value, "Super Game Boy PAL") == 0) { + new_model = MODEL_SGB_PAL; + } + else if (strcmp(var.value, "Super Game Boy 2") == 0) { + new_model = MODEL_SGB2; + } + else { + new_model = MODEL_AUTO; + } + + model[1] = new_model; + } + + var.key = "sameboy_mono_palette_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "greyscale") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_GREY); + } + else if (strcmp(var.value, "lime") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_DMG); + } + else if (strcmp(var.value, "olive") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_MGB); + } + else if (strcmp(var.value, "teal") == 0) { + GB_set_palette(&gameboy[0], &GB_PALETTE_GBL); + } + } + + var.key = "sameboy_mono_palette_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "greyscale") == 0) { + GB_set_palette(&gameboy[1], &GB_PALETTE_GREY); + } + else if (strcmp(var.value, "lime") == 0) { + GB_set_palette(&gameboy[1], &GB_PALETTE_DMG); + } + else if (strcmp(var.value, "olive") == 0) { + GB_set_palette(&gameboy[1], &GB_PALETTE_MGB); + } + else if (strcmp(var.value, "teal") == 0) { + GB_set_palette(&gameboy[1], &GB_PALETTE_GBL); + } + } + var.key = "sameboy_color_correction_mode_1"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) { GB_set_color_correction_mode(&gameboy[0], GB_COLOR_CORRECTION_DISABLED); } @@ -696,7 +1046,7 @@ static void check_variables() var.key = "sameboy_color_correction_mode_2"; var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "off") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_DISABLED); } @@ -715,9 +1065,308 @@ static void check_variables() else if (strcmp(var.value, "harsh reality") == 0) { GB_set_color_correction_mode(&gameboy[1], GB_COLOR_CORRECTION_LOW_CONTRAST); } - } - + + var.key = "sameboy_light_temperature_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "1000K") == 0) { + GB_set_light_temperature(&gameboy[0], 1.0); + } + else if (strcmp(var.value, "1550K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.9); + } + else if (strcmp(var.value, "2100K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.8); + } + else if (strcmp(var.value, "2650K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.7); + } + else if (strcmp(var.value, "3200K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.6); + } + else if (strcmp(var.value, "3750K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.5); + } + else if (strcmp(var.value, "4300K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.4); + } + else if (strcmp(var.value, "4850K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.3); + } + else if (strcmp(var.value, "5400K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.2); + } + else if (strcmp(var.value, "5950K") == 0) { + GB_set_light_temperature(&gameboy[0], 0.1); + } + else if (strcmp(var.value, "6500K") == 0) { + GB_set_light_temperature(&gameboy[0], 0); + } + else if (strcmp(var.value, "7050K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.1); + } + else if (strcmp(var.value, "7600K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.2); + } + else if (strcmp(var.value, "8150K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.3); + } + else if (strcmp(var.value, "8700K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.4); + } + else if (strcmp(var.value, "9250K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.5); + } + else if (strcmp(var.value, "9800K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.6); + } + else if (strcmp(var.value, "10350K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.7); + } + else if (strcmp(var.value, "10900K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.8); + } + else if (strcmp(var.value, "11450K") == 0) { + GB_set_light_temperature(&gameboy[0], -0.9); + } + else if (strcmp(var.value, "12000K") == 0) { + GB_set_light_temperature(&gameboy[0], -1.0); + } + } + + var.key = "sameboy_light_temperature_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "1000K") == 0) { + GB_set_light_temperature(&gameboy[1], 1.0); + } + else if (strcmp(var.value, "1550K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.9); + } + else if (strcmp(var.value, "2100K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.8); + } + else if (strcmp(var.value, "2650K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.7); + } + else if (strcmp(var.value, "3200K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.6); + } + else if (strcmp(var.value, "3750K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.5); + } + else if (strcmp(var.value, "4300K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.4); + } + else if (strcmp(var.value, "4850K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.3); + } + else if (strcmp(var.value, "5400K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.2); + } + else if (strcmp(var.value, "5950K") == 0) { + GB_set_light_temperature(&gameboy[1], 0.1); + } + else if (strcmp(var.value, "6500K") == 0) { + GB_set_light_temperature(&gameboy[1], 0); + } + else if (strcmp(var.value, "7050K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.1); + } + else if (strcmp(var.value, "7600K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.2); + } + else if (strcmp(var.value, "8150K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.3); + } + else if (strcmp(var.value, "8700K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.4); + } + else if (strcmp(var.value, "9250K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.5); + } + else if (strcmp(var.value, "9800K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.6); + } + else if (strcmp(var.value, "10350K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.7); + } + else if (strcmp(var.value, "10900K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.8); + } + else if (strcmp(var.value, "11450K") == 0) { + GB_set_light_temperature(&gameboy[1], -0.9); + } + else if (strcmp(var.value, "12000K") == 0) { + GB_set_light_temperature(&gameboy[1], -1.0); + } + } + + var.key = "sameboy_high_pass_filter_mode_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "off") == 0) { + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); + } + else if (strcmp(var.value, "remove dc offset") == 0) { + GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); + } + } + + var.key = "sameboy_high_pass_filter_mode_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "off") == 0) { + GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_OFF); + } + else if (strcmp(var.value, "accurate") == 0) { + GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_ACCURATE); + } + else if (strcmp(var.value, "remove dc offset") == 0) { + GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_REMOVE_DC_OFFSET); + } + } + + var.key = "sameboy_audio_interference_1"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "0") == 0) { + GB_set_interference_volume(&gameboy[0], 0.00); + } + else if (strcmp(var.value, "5") == 0) { + GB_set_interference_volume(&gameboy[0], 0.05); + } + else if (strcmp(var.value, "10") == 0) { + GB_set_interference_volume(&gameboy[0], 0.10); + } + else if (strcmp(var.value, "15") == 0) { + GB_set_interference_volume(&gameboy[0], 0.15); + } + else if (strcmp(var.value, "20") == 0) { + GB_set_interference_volume(&gameboy[0], 0.20); + } + else if (strcmp(var.value, "25") == 0) { + GB_set_interference_volume(&gameboy[0], 0.25); + } + else if (strcmp(var.value, "30") == 0) { + GB_set_interference_volume(&gameboy[0], 0.30); + } + else if (strcmp(var.value, "35") == 0) { + GB_set_interference_volume(&gameboy[0], 0.35); + } + else if (strcmp(var.value, "40") == 0) { + GB_set_interference_volume(&gameboy[0], 0.40); + } + else if (strcmp(var.value, "45") == 0) { + GB_set_interference_volume(&gameboy[0], 0.45); + } + else if (strcmp(var.value, "50") == 0) { + GB_set_interference_volume(&gameboy[0], 0.50); + } + else if (strcmp(var.value, "55") == 0) { + GB_set_interference_volume(&gameboy[0], 0.55); + } + else if (strcmp(var.value, "60") == 0) { + GB_set_interference_volume(&gameboy[0], 0.60); + } + else if (strcmp(var.value, "65") == 0) { + GB_set_interference_volume(&gameboy[0], 0.65); + } + else if (strcmp(var.value, "70") == 0) { + GB_set_interference_volume(&gameboy[0], 0.70); + } + else if (strcmp(var.value, "75") == 0) { + GB_set_interference_volume(&gameboy[0], 0.75); + } + else if (strcmp(var.value, "80") == 0) { + GB_set_interference_volume(&gameboy[0], 0.80); + } + else if (strcmp(var.value, "85") == 0) { + GB_set_interference_volume(&gameboy[0], 0.85); + } + else if (strcmp(var.value, "90") == 0) { + GB_set_interference_volume(&gameboy[0], 0.90); + } + else if (strcmp(var.value, "95") == 0) { + GB_set_interference_volume(&gameboy[0], 0.95); + } + else if (strcmp(var.value, "100") == 0) { + GB_set_interference_volume(&gameboy[0], 1.00); + } + } + + var.key = "sameboy_audio_interference_2"; + var.value = NULL; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + if (strcmp(var.value, "0") == 0) { + GB_set_interference_volume(&gameboy[1], 0.00); + } + else if (strcmp(var.value, "5") == 0) { + GB_set_interference_volume(&gameboy[1], 0.05); + } + else if (strcmp(var.value, "10") == 0) { + GB_set_interference_volume(&gameboy[1], 0.10); + } + else if (strcmp(var.value, "15") == 0) { + GB_set_interference_volume(&gameboy[1], 0.15); + } + else if (strcmp(var.value, "20") == 0) { + GB_set_interference_volume(&gameboy[1], 0.20); + } + else if (strcmp(var.value, "25") == 0) { + GB_set_interference_volume(&gameboy[1], 0.25); + } + else if (strcmp(var.value, "30") == 0) { + GB_set_interference_volume(&gameboy[1], 0.30); + } + else if (strcmp(var.value, "35") == 0) { + GB_set_interference_volume(&gameboy[1], 0.35); + } + else if (strcmp(var.value, "40") == 0) { + GB_set_interference_volume(&gameboy[1], 0.40); + } + else if (strcmp(var.value, "45") == 0) { + GB_set_interference_volume(&gameboy[1], 0.45); + } + else if (strcmp(var.value, "50") == 0) { + GB_set_interference_volume(&gameboy[1], 0.50); + } + else if (strcmp(var.value, "55") == 0) { + GB_set_interference_volume(&gameboy[1], 0.55); + } + else if (strcmp(var.value, "60") == 0) { + GB_set_interference_volume(&gameboy[1], 0.60); + } + else if (strcmp(var.value, "65") == 0) { + GB_set_interference_volume(&gameboy[1], 0.65); + } + else if (strcmp(var.value, "70") == 0) { + GB_set_interference_volume(&gameboy[1], 0.70); + } + else if (strcmp(var.value, "75") == 0) { + GB_set_interference_volume(&gameboy[1], 0.75); + } + else if (strcmp(var.value, "80") == 0) { + GB_set_interference_volume(&gameboy[1], 0.80); + } + else if (strcmp(var.value, "85") == 0) { + GB_set_interference_volume(&gameboy[1], 0.85); + } + else if (strcmp(var.value, "90") == 0) { + GB_set_interference_volume(&gameboy[1], 0.90); + } + else if (strcmp(var.value, "95") == 0) { + GB_set_interference_volume(&gameboy[1], 0.95); + } + else if (strcmp(var.value, "100") == 0) { + GB_set_interference_volume(&gameboy[1], 1.00); + } + } + var.key = "sameboy_rumble_1"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { @@ -731,7 +1380,7 @@ static void check_variables() GB_set_rumble_mode(&gameboy[0], GB_RUMBLE_ALL_GAMES); } } - + var.key = "sameboy_rumble_2"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { @@ -746,128 +1395,8 @@ static void check_variables() } } - var.key = "sameboy_high_pass_filter_mode_1"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_OFF); - } - else if (strcmp(var.value, "accurate") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_ACCURATE); - } - else if (strcmp(var.value, "remove dc offset") == 0) { - GB_set_highpass_filter_mode(&gameboy[0], GB_HIGHPASS_REMOVE_DC_OFFSET); - } - } - - var.key = "sameboy_high_pass_filter_mode_2"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "off") == 0) { - GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_OFF); - } - else if (strcmp(var.value, "accurate") == 0) { - GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_ACCURATE); - } - else if (strcmp(var.value, "remove dc offset") == 0) { - GB_set_highpass_filter_mode(&gameboy[1], GB_HIGHPASS_REMOVE_DC_OFFSET); - } - } - - var.key = "sameboy_model_1"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - enum model new_model = model[0]; - if (strcmp(var.value, "Game Boy") == 0) { - new_model = MODEL_DMG; - } - else if (strcmp(var.value, "Game Boy Color") == 0) { - new_model = MODEL_CGB; - } - else if (strcmp(var.value, "Game Boy Advance") == 0) { - new_model = MODEL_AGB; - } - else if (strcmp(var.value, "Super Game Boy") == 0) { - new_model = MODEL_SGB; - } - else if (strcmp(var.value, "Super Game Boy 2") == 0) { - new_model = MODEL_SGB2; - } - else { - new_model = MODEL_AUTO; - } - - model[0] = new_model; - } - - var.key = "sameboy_model_2"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - enum model new_model = model[1]; - if (strcmp(var.value, "Game Boy") == 0) { - new_model = MODEL_DMG; - } - else if (strcmp(var.value, "Game Boy Color") == 0) { - new_model = MODEL_CGB; - } - else if (strcmp(var.value, "Game Boy Advance") == 0) { - new_model = MODEL_AGB; - } - else if (strcmp(var.value, "Super Game Boy") == 0) { - new_model = MODEL_SGB; - } - else if (strcmp(var.value, "Super Game Boy 2") == 0) { - new_model = MODEL_SGB; - } - else { - new_model = MODEL_AUTO; - } - - model[1] = new_model; - } - - var.key = "sameboy_screen_layout"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "top-down") == 0) { - screen_layout = LAYOUT_TOP_DOWN; - } - else { - screen_layout = LAYOUT_LEFT_RIGHT; - } - - geometry_updated = true; - } - - var.key = "sameboy_link"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - bool tmp = link_cable_emulation; - if (strcmp(var.value, "enabled") == 0) { - link_cable_emulation = true; - } - else { - link_cable_emulation = false; - } - if (link_cable_emulation && link_cable_emulation != tmp) { - set_link_cable_state(true); - } - else if (!link_cable_emulation && link_cable_emulation != tmp) { - set_link_cable_state(false); - } - } - - var.key = "sameboy_audio_output"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (strcmp(var.value, "Game Boy #1") == 0) { - audio_out = GB_1; - } - else { - audio_out = GB_2; - } - } } + set_variable_visibility(); } void retro_init(void) @@ -938,7 +1467,7 @@ void retro_get_system_av_info(struct retro_system_av_info *info) struct retro_game_geometry geom; struct retro_system_timing timing = { GB_get_usual_frame_rate(&gameboy[0]), AUDIO_FREQUENCY }; - if (emulated_devices == 2) { + if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { geom.base_width = GB_get_screen_width(&gameboy[0]); geom.base_height = GB_get_screen_height(&gameboy[0]) * emulated_devices; @@ -950,7 +1479,7 @@ void retro_get_system_av_info(struct retro_system_av_info *info) geom.aspect_ratio = ((double)GB_get_screen_width(&gameboy[0]) * emulated_devices) / GB_get_screen_height(&gameboy[0]); } } - else { + else { geom.base_width = GB_get_screen_width(&gameboy[0]); geom.base_height = GB_get_screen_height(&gameboy[0]); geom.aspect_ratio = (double)GB_get_screen_width(&gameboy[0]) / GB_get_screen_height(&gameboy[0]); @@ -968,6 +1497,8 @@ void retro_set_environment(retro_environment_t cb) { environ_cb = cb; + libretro_set_core_options(environ_cb); + cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystems); } @@ -1031,11 +1562,11 @@ void retro_run(void) check_variables(); } - if (emulated_devices == 2) { + if (emulated_devices == 2) { GB_update_keys_status(&gameboy[0], 0); GB_update_keys_status(&gameboy[1], 1); } - else if (emulated_devices == 1 && (model[0] == MODEL_SGB || model[0] == MODEL_SGB2)) { + else if (emulated_devices == 1 && (model[0] == MODEL_SGB_PAL || model[0] == MODEL_SGB_NTSC || model[0] == MODEL_SGB2)) { for (unsigned i = 0; i < 4; i++) { GB_update_keys_status(&gameboy[0], i); } @@ -1046,7 +1577,7 @@ void retro_run(void) vblank1_occurred = vblank2_occurred = false; signed delta = 0; - if (emulated_devices == 2) { + if (emulated_devices == 2) { while (!vblank1_occurred || !vblank2_occurred) { if (delta >= 0) { delta -= GB_run(&gameboy[0]); @@ -1056,11 +1587,11 @@ void retro_run(void) } } } - else { + else { GB_run_frame(&gameboy[0]); } - if (emulated_devices == 2) { + if (emulated_devices == 2) { if (screen_layout == LAYOUT_TOP_DOWN) { video_cb(frame_buf, GB_get_screen_width(&gameboy[0]), @@ -1094,24 +1625,23 @@ void retro_run(void) bool retro_load_game(const struct retro_game_info *info) { - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_single); check_variables(); frame_buf = (uint32_t *)malloc(MAX_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); memset(frame_buf, 0, MAX_VIDEO_PIXELS * emulated_devices * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; - if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); return false; } - auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; + auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB_E : MODEL_DMG_B; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - for (int i = 0; i < emulated_devices; i++) { + for (int i = 0; i < emulated_devices; i++) { init_for_current_model(i); - if (GB_load_rom(&gameboy[i], info->path)) { + if (GB_load_rom(&gameboy[i], info->path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM at %s\n", info->path); return false; } @@ -1157,7 +1687,6 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, return false; /* all other types are unhandled for now */ } - environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars_dual); check_variables(); frame_buf = (uint32_t*)malloc(emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); @@ -1167,17 +1696,17 @@ bool retro_load_game_special(unsigned type, const struct retro_game_info *info, memset(frame_buf_copy, 0, emulated_devices * MAX_VIDEO_PIXELS * sizeof(uint32_t)); enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; - if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) { log_cb(RETRO_LOG_INFO, "XRGB8888 is not supported\n"); return false; } - auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB : MODEL_DMG; + auto_model = (info->path[strlen(info->path) - 1] & ~0x20) == 'C' ? MODEL_CGB_E : MODEL_DMG_B; snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path); - for (int i = 0; i < emulated_devices; i++) { + for (int i = 0; i < emulated_devices; i++) { init_for_current_model(i); - if (GB_load_rom(&gameboy[i], info[i].path)) { + if (GB_load_rom(&gameboy[i], info[i].path)) { log_cb(RETRO_LOG_INFO, "Failed to load ROM\n"); return false; } @@ -1203,21 +1732,21 @@ size_t retro_serialize_size(void) if (maximum_save_size) { return maximum_save_size * 2; } - + GB_gameboy_t temp; - + GB_init(&temp, GB_MODEL_DMG_B); maximum_save_size = GB_get_save_state_size(&temp); GB_free(&temp); - + GB_init(&temp, GB_MODEL_CGB_E); maximum_save_size = MAX(maximum_save_size, GB_get_save_state_size(&temp)); GB_free(&temp); - + GB_init(&temp, GB_MODEL_SGB2); maximum_save_size = MAX(maximum_save_size, GB_get_save_state_size(&temp)); GB_free(&temp); - + return maximum_save_size * 2; } @@ -1235,7 +1764,7 @@ bool retro_serialize(void *data, size_t size) if (state_size > size) { return false; } - + GB_save_state_to_buffer(&gameboy[i], ((uint8_t *) data) + offset); offset += state_size; size -= state_size; @@ -1246,7 +1775,7 @@ bool retro_serialize(void *data, size_t size) bool retro_unserialize(const void *data, size_t size) { - for (int i = 0; i < emulated_devices; i++) { + for (int i = 0; i < emulated_devices; i++) { size_t state_size = GB_get_save_state_size(&gameboy[i]); if (state_size > size) { return false; @@ -1255,7 +1784,7 @@ bool retro_unserialize(const void *data, size_t size) if (GB_load_state_from_buffer(&gameboy[i], data, state_size)) { return false; } - + size -= state_size; data = ((uint8_t *)data) + state_size; } @@ -1267,8 +1796,8 @@ bool retro_unserialize(const void *data, size_t size) void *retro_get_memory_data(unsigned type) { void *data = NULL; - if (emulated_devices == 1) { - switch (type) { + if (emulated_devices == 1) { + switch (type) { case RETRO_MEMORY_SYSTEM_RAM: data = gameboy[0].ram; break; @@ -1295,8 +1824,8 @@ void *retro_get_memory_data(unsigned type) break; } } - else { - switch (type) { + else { + switch (type) { case RETRO_MEMORY_GAMEBOY_1_SRAM: if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { data = gameboy[0].mbc_ram; @@ -1340,8 +1869,8 @@ void *retro_get_memory_data(unsigned type) size_t retro_get_memory_size(unsigned type) { size_t size = 0; - if (emulated_devices == 1) { - switch (type) { + if (emulated_devices == 1) { + switch (type) { case RETRO_MEMORY_SYSTEM_RAM: size = gameboy[0].ram_size; break; @@ -1368,8 +1897,8 @@ size_t retro_get_memory_size(unsigned type) break; } } - else { - switch (type) { + else { + switch (type) { case RETRO_MEMORY_GAMEBOY_1_SRAM: if (gameboy[0].cartridge_type->has_battery && gameboy[0].mbc_ram_size != 0) { size = gameboy[0].mbc_ram_size; diff --git a/libretro/libretro.h b/libretro/libretro.h index 1fd2f5b..bda0b77 100644 --- a/libretro/libretro.h +++ b/libretro/libretro.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2018 The RetroArch team +/* Copyright (C) 2010-2020 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this libretro API header (libretro.h). @@ -69,7 +69,7 @@ extern "C" { # endif # endif # else -# if defined(__GNUC__) && __GNUC__ >= 4 && !defined(__CELLOS_LV2__) +# if defined(__GNUC__) && __GNUC__ >= 4 # define RETRO_API RETRO_CALLCONV __attribute__((__visibility__("default"))) # else # define RETRO_API RETRO_CALLCONV @@ -278,6 +278,11 @@ enum retro_language RETRO_LANGUAGE_ARABIC = 16, RETRO_LANGUAGE_GREEK = 17, RETRO_LANGUAGE_TURKISH = 18, + RETRO_LANGUAGE_SLOVAK = 19, + RETRO_LANGUAGE_PERSIAN = 20, + RETRO_LANGUAGE_HEBREW = 21, + RETRO_LANGUAGE_ASTURIAN = 22, + RETRO_LANGUAGE_FINNISH = 23, RETRO_LANGUAGE_LAST, /* Ensure sizeof(enum) == sizeof(int) */ @@ -708,6 +713,9 @@ enum retro_mod * state of rumble motors in controllers. * A strong and weak motor is supported, and they can be * controlled indepedently. + * Should be called from either retro_init() or retro_load_game(). + * Should not be called from retro_set_environment(). + * Returns false if rumble functionality is unavailable. */ #define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24 /* uint64_t * -- @@ -1087,10 +1095,10 @@ enum retro_mod #define RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE (50 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* float * -- - * Float value that lets us know what target refresh rate + * Float value that lets us know what target refresh rate * is curently in use by the frontend. * - * The core can use the returned value to set an ideal + * The core can use the returned value to set an ideal * refresh rate/framerate. */ @@ -1098,7 +1106,7 @@ enum retro_mod /* bool * -- * Boolean value that indicates whether or not the frontend supports * input bitmasks being returned by retro_input_state_t. The advantage - * of this is that retro_input_state_t has to be only called once to + * of this is that retro_input_state_t has to be only called once to * grab all button states instead of multiple times. * * If it returns true, you can pass RETRO_DEVICE_ID_JOYPAD_MASK as 'id' @@ -1117,7 +1125,7 @@ enum retro_mod * This may be still be done regardless of the core options * interface version. * - * If version is 1 however, core options may instead be set by + * If version is >= 1 however, core options may instead be set by * passing an array of retro_core_option_definition structs to * RETRO_ENVIRONMENT_SET_CORE_OPTIONS, or a 2D array of * retro_core_option_definition structs to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. @@ -1132,8 +1140,8 @@ enum retro_mod * GET_VARIABLE. * This allows the frontend to present these variables to * a user dynamically. - * This should only be called if RETRO_ENVIRONMENT_GET_ENHANCED_CORE_OPTIONS - * returns an API version of 1. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 1. * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. * This should be called the first time as early as * possible (ideally in retro_set_environment). @@ -1169,8 +1177,6 @@ enum retro_mod * i.e. it should be feasible to cycle through options * without a keyboard. * - * First entry should be treated as a default. - * * Example entry: * { * "foo_option", @@ -1196,8 +1202,8 @@ enum retro_mod * GET_VARIABLE. * This allows the frontend to present these variables to * a user dynamically. - * This should only be called if RETRO_ENVIRONMENT_GET_ENHANCED_CORE_OPTIONS - * returns an API version of 1. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 1. * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. * This should be called the first time as early as * possible (ideally in retro_set_environment). @@ -1248,6 +1254,140 @@ enum retro_mod * default when calling SET_VARIABLES/SET_CORE_OPTIONS. */ +#define RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER 56 + /* unsigned * -- + * + * Allows an implementation to ask frontend preferred hardware + * context to use. Core should use this information to deal + * with what specific context to request with SET_HW_RENDER. + * + * 'data' points to an unsigned variable + */ + +#define RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION 57 + /* unsigned * -- + * Unsigned value is the API version number of the disk control + * interface supported by the frontend. If callback return false, + * API version is assumed to be 0. + * + * In legacy code, the disk control interface is defined by passing + * a struct of type retro_disk_control_callback to + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. + * This may be still be done regardless of the disk control + * interface version. + * + * If version is >= 1 however, the disk control interface may + * instead be defined by passing a struct of type + * retro_disk_control_ext_callback to + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE. + * This allows the core to provide additional information about + * disk images to the frontend and/or enables extra + * disk control functionality by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE 58 + /* const struct retro_disk_control_ext_callback * -- + * Sets an interface which frontend can use to eject and insert + * disk images, and also obtain information about individual + * disk image files registered by the core. + * This is used for games which consist of multiple images and + * must be manually swapped out by the user (e.g. PSX, floppy disk + * based systems). + */ + +#define RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION 59 + /* unsigned * -- + * Unsigned value is the API version number of the message + * interface supported by the frontend. If callback returns + * false, API version is assumed to be 0. + * + * In legacy code, messages may be displayed in an + * implementation-specific manner by passing a struct + * of type retro_message to RETRO_ENVIRONMENT_SET_MESSAGE. + * This may be still be done regardless of the message + * interface version. + * + * If version is >= 1 however, messages may instead be + * displayed by passing a struct of type retro_message_ext + * to RETRO_ENVIRONMENT_SET_MESSAGE_EXT. This allows the + * core to specify message logging level, priority and + * destination (OSD, logging interface or both). + */ + +#define RETRO_ENVIRONMENT_SET_MESSAGE_EXT 60 + /* const struct retro_message_ext * -- + * Sets a message to be displayed in an implementation-specific + * manner for a certain amount of 'frames'. Additionally allows + * the core to specify message logging level, priority and + * destination (OSD, logging interface or both). + * Should not be used for trivial messages, which should simply be + * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a + * fallback, stderr). + */ + +#define RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS 61 + /* unsigned * -- + * Unsigned value is the number of active input devices + * provided by the frontend. This may change between + * frames, but will remain constant for the duration + * of each frame. + * If callback returns true, a core need not poll any + * input device with an index greater than or equal to + * the number of active devices. + * If callback returns false, the number of active input + * devices is unknown. In this case, all input devices + * should be considered active. + */ + +#define RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK 62 + /* const struct retro_audio_buffer_status_callback * -- + * Lets the core know the occupancy level of the frontend + * audio buffer. Can be used by a core to attempt frame + * skipping in order to avoid buffer under-runs. + * A core may pass NULL to disable buffer status reporting + * in the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY 63 + /* const unsigned * -- + * Sets minimum frontend audio latency in milliseconds. + * Resultant audio latency may be larger than set value, + * or smaller if a hardware limit is encountered. A frontend + * is expected to honour requests up to 512 ms. + * + * - If value is less than current frontend + * audio latency, callback has no effect + * - If value is zero, default frontend audio + * latency is set + * + * May be used by a core to increase audio latency and + * therefore decrease the probability of buffer under-runs + * (crackling) when performing 'intensive' operations. + * A core utilising RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK + * to implement audio-buffer-based frame skipping may achieve + * optimal results by setting the audio latency to a 'high' + * (typically 6x or 8x) integer multiple of the expected + * frame time. + * + * WARNING: This can only be called from within retro_run(). + * Calling this can require a full reinitialization of audio + * drivers in the frontend, so it is important to call it very + * sparingly, and usually only with the users explicit consent. + * An eventual driver reinitialize will happen so that audio + * callbacks happening after this call within the same retro_run() + * call will target the newly initialized driver. + */ + +#define RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE 64 + /* const struct retro_fastforwarding_override * -- + * Used by a libretro core to override the current + * fastforwarding mode of the frontend. + * If NULL is passed to this function, the frontend + * will return true if fastforwarding override + * functionality is supported (no change in + * fastforwarding state will occur in this case). + */ + /* VFS functionality */ /* File paths: @@ -1924,6 +2064,10 @@ enum retro_sensor_action { RETRO_SENSOR_ACCELEROMETER_ENABLE = 0, RETRO_SENSOR_ACCELEROMETER_DISABLE, + RETRO_SENSOR_GYROSCOPE_ENABLE, + RETRO_SENSOR_GYROSCOPE_DISABLE, + RETRO_SENSOR_ILLUMINANCE_ENABLE, + RETRO_SENSOR_ILLUMINANCE_DISABLE, RETRO_SENSOR_DUMMY = INT_MAX }; @@ -1932,6 +2076,10 @@ enum retro_sensor_action #define RETRO_SENSOR_ACCELEROMETER_X 0 #define RETRO_SENSOR_ACCELEROMETER_Y 1 #define RETRO_SENSOR_ACCELEROMETER_Z 2 +#define RETRO_SENSOR_GYROSCOPE_X 3 +#define RETRO_SENSOR_GYROSCOPE_Y 4 +#define RETRO_SENSOR_GYROSCOPE_Z 5 +#define RETRO_SENSOR_ILLUMINANCE 6 typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, enum retro_sensor_action action, unsigned rate); @@ -2129,6 +2277,30 @@ struct retro_frame_time_callback retro_usec_t reference; }; +/* Notifies a libretro core of the current occupancy + * level of the frontend audio buffer. + * + * - active: 'true' if audio buffer is currently + * in use. Will be 'false' if audio is + * disabled in the frontend + * + * - occupancy: Given as a value in the range [0,100], + * corresponding to the occupancy percentage + * of the audio buffer + * + * - underrun_likely: 'true' if the frontend expects an + * audio buffer underrun during the + * next frame (indicates that a core + * should attempt frame skipping) + * + * It will be called right before retro_run() every frame. */ +typedef void (RETRO_CALLCONV *retro_audio_buffer_status_callback_t)( + bool active, unsigned occupancy, bool underrun_likely); +struct retro_audio_buffer_status_callback +{ + retro_audio_buffer_status_callback_t callback; +}; + /* Pass this to retro_video_refresh_t if rendering to hardware. * Passing NULL to retro_video_refresh_t is still a frame dupe as normal. * */ @@ -2289,7 +2461,8 @@ struct retro_keyboard_callback retro_keyboard_event_t callback; }; -/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. +/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE & + * RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE. * Should be set for implementations which can swap out multiple disk * images in runtime. * @@ -2347,6 +2520,53 @@ typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index, * with replace_image_index. */ typedef bool (RETRO_CALLCONV *retro_add_image_index_t)(void); +/* Sets initial image to insert in drive when calling + * core_load_game(). + * Since we cannot pass the initial index when loading + * content (this would require a major API change), this + * is set by the frontend *before* calling the core's + * retro_load_game()/retro_load_game_special() implementation. + * A core should therefore cache the index/path values and handle + * them inside retro_load_game()/retro_load_game_special(). + * - If 'index' is invalid (index >= get_num_images()), the + * core should ignore the set value and instead use 0 + * - 'path' is used purely for error checking - i.e. when + * content is loaded, the core should verify that the + * disk specified by 'index' has the specified file path. + * This is to guard against auto selecting the wrong image + * if (for example) the user should modify an existing M3U + * playlist. We have to let the core handle this because + * set_initial_image() must be called before loading content, + * i.e. the frontend cannot access image paths in advance + * and thus cannot perform the error check itself. + * If set path and content path do not match, the core should + * ignore the set 'index' value and instead use 0 + * Returns 'false' if index or 'path' are invalid, or core + * does not support this functionality + */ +typedef bool (RETRO_CALLCONV *retro_set_initial_image_t)(unsigned index, const char *path); + +/* Fetches the path of the specified disk image file. + * Returns 'false' if index is invalid (index >= get_num_images()) + * or path is otherwise unavailable. + */ +typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *path, size_t len); + +/* Fetches a core-provided 'label' for the specified disk + * image file. In the simplest case this may be a file name + * (without extension), but for cores with more complex + * content requirements information may be provided to + * facilitate user disk swapping - for example, a core + * running floppy-disk-based content may uniquely label + * save disks, data disks, level disks, etc. with names + * corresponding to in-game disk change prompts (so the + * frontend can provide better user guidance than a 'dumb' + * disk index value). + * Returns 'false' if index is invalid (index >= get_num_images()) + * or label is otherwise unavailable. + */ +typedef bool (RETRO_CALLCONV *retro_get_image_label_t)(unsigned index, char *label, size_t len); + struct retro_disk_control_callback { retro_set_eject_state_t set_eject_state; @@ -2360,6 +2580,27 @@ struct retro_disk_control_callback retro_add_image_index_t add_image_index; }; +struct retro_disk_control_ext_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; + + /* NOTE: Frontend will only attempt to record/restore + * last used disk index if both set_initial_image() + * and get_image_path() are implemented */ + retro_set_initial_image_t set_initial_image; /* Optional - may be NULL */ + + retro_get_image_path_t get_image_path; /* Optional - may be NULL */ + retro_get_image_label_t get_image_label; /* Optional - may be NULL */ +}; + enum retro_pixel_format { /* 0RGB1555, native endian. @@ -2390,6 +2631,104 @@ struct retro_message unsigned frames; /* Duration in frames of message. */ }; +enum retro_message_target +{ + RETRO_MESSAGE_TARGET_ALL = 0, + RETRO_MESSAGE_TARGET_OSD, + RETRO_MESSAGE_TARGET_LOG +}; + +enum retro_message_type +{ + RETRO_MESSAGE_TYPE_NOTIFICATION = 0, + RETRO_MESSAGE_TYPE_NOTIFICATION_ALT, + RETRO_MESSAGE_TYPE_STATUS, + RETRO_MESSAGE_TYPE_PROGRESS +}; + +struct retro_message_ext +{ + /* Message string to be displayed/logged */ + const char *msg; + /* Duration (in ms) of message when targeting the OSD */ + unsigned duration; + /* Message priority when targeting the OSD + * > When multiple concurrent messages are sent to + * the frontend and the frontend does not have the + * capacity to display them all, messages with the + * *highest* priority value should be shown + * > There is no upper limit to a message priority + * value (within the bounds of the unsigned data type) + * > In the reference frontend (RetroArch), the same + * priority values are used for frontend-generated + * notifications, which are typically assigned values + * between 0 and 3 depending upon importance */ + unsigned priority; + /* Message logging level (info, warn, error, etc.) */ + enum retro_log_level level; + /* Message destination: OSD, logging interface or both */ + enum retro_message_target target; + /* Message 'type' when targeting the OSD + * > RETRO_MESSAGE_TYPE_NOTIFICATION: Specifies that a + * message should be handled in identical fashion to + * a standard frontend-generated notification + * > RETRO_MESSAGE_TYPE_NOTIFICATION_ALT: Specifies that + * message is a notification that requires user attention + * or action, but that it should be displayed in a manner + * that differs from standard frontend-generated notifications. + * This would typically correspond to messages that should be + * displayed immediately (independently from any internal + * frontend message queue), and/or which should be visually + * distinguishable from frontend-generated notifications. + * For example, a core may wish to inform the user of + * information related to a disk-change event. It is + * expected that the frontend itself may provide a + * notification in this case; if the core sends a + * message of type RETRO_MESSAGE_TYPE_NOTIFICATION, an + * uncomfortable 'double-notification' may occur. A message + * of RETRO_MESSAGE_TYPE_NOTIFICATION_ALT should therefore + * be presented such that visual conflict with regular + * notifications does not occur + * > RETRO_MESSAGE_TYPE_STATUS: Indicates that message + * is not a standard notification. This typically + * corresponds to 'status' indicators, such as a core's + * internal FPS, which are intended to be displayed + * either permanently while a core is running, or in + * a manner that does not suggest user attention or action + * is required. 'Status' type messages should therefore be + * displayed in a different on-screen location and in a manner + * easily distinguishable from both standard frontend-generated + * notifications and messages of type RETRO_MESSAGE_TYPE_NOTIFICATION_ALT + * > RETRO_MESSAGE_TYPE_PROGRESS: Indicates that message reports + * the progress of an internal core task. For example, in cases + * where a core itself handles the loading of content from a file, + * this may correspond to the percentage of the file that has been + * read. Alternatively, an audio/video playback core may use a + * message of type RETRO_MESSAGE_TYPE_PROGRESS to display the current + * playback position as a percentage of the runtime. 'Progress' type + * messages should therefore be displayed as a literal progress bar, + * where: + * - 'retro_message_ext.msg' is the progress bar title/label + * - 'retro_message_ext.progress' determines the length of + * the progress bar + * NOTE: Message type is a *hint*, and may be ignored + * by the frontend. If a frontend lacks support for + * displaying messages via alternate means than standard + * frontend-generated notifications, it will treat *all* + * messages as having the type RETRO_MESSAGE_TYPE_NOTIFICATION */ + enum retro_message_type type; + /* Task progress when targeting the OSD and message is + * of type RETRO_MESSAGE_TYPE_PROGRESS + * > -1: Unmetered/indeterminate + * > 0-100: Current progress percentage + * NOTE: Since message type is a hint, a frontend may ignore + * progress values. Where relevant, a core should therefore + * include progress percentage within the message string, + * such that the message intent remains clear when displayed + * as a standard frontend-generated notification */ + int8_t progress; +}; + /* Describes how the libretro implementation maps a libretro input bind * to its internal input system through a human readable string. * This string can be used to better let a user configure input. */ @@ -2410,7 +2749,7 @@ struct retro_input_descriptor struct retro_system_info { /* All pointers are owned by libretro implementation, and pointers must - * remain valid until retro_deinit() is called. */ + * remain valid until it is unloaded. */ const char *library_name; /* Descriptive name of library. Should not * contain any version numbers, etc. */ @@ -2504,8 +2843,20 @@ struct retro_core_option_display }; /* Maximum number of values permitted for a core option - * NOTE: This may be increased on a core-by-core basis - * if required (doing so has no effect on the frontend) */ + * > Note: We have to set a maximum value due the limitations + * of the C language - i.e. it is not possible to create an + * array of structs each containing a variable sized array, + * so the retro_core_option_definition values array must + * have a fixed size. The size limit of 128 is a balancing + * act - it needs to be large enough to support all 'sane' + * core options, but setting it too large may impact low memory + * platforms. In practise, if a core option has more than + * 128 values then the implementation is likely flawed. + * To quote the above API reference: + * "The number of possible options should be very limited + * i.e. it should be feasible to cycle through options + * without a keyboard." + */ #define RETRO_NUM_CORE_OPTION_VALUES_MAX 128 struct retro_core_option_value @@ -2597,6 +2948,47 @@ struct retro_framebuffer Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */ }; +/* Used by a libretro core to override the current + * fastforwarding mode of the frontend */ +struct retro_fastforwarding_override +{ + /* Specifies the runtime speed multiplier that + * will be applied when 'fastforward' is true. + * For example, a value of 5.0 when running 60 FPS + * content will cap the fast-forward rate at 300 FPS. + * Note that the target multiplier may not be achieved + * if the host hardware has insufficient processing + * power. + * Setting a value of 0.0 (or greater than 0.0 but + * less than 1.0) will result in an uncapped + * fast-forward rate (limited only by hardware + * capacity). + * If the value is negative, it will be ignored + * (i.e. the frontend will use a runtime speed + * multiplier of its own choosing) */ + float ratio; + + /* If true, fastforwarding mode will be enabled. + * If false, fastforwarding mode will be disabled. */ + bool fastforward; + + /* If true, and if supported by the frontend, an + * on-screen notification will be displayed while + * 'fastforward' is true. + * If false, and if supported by the frontend, any + * on-screen fast-forward notifications will be + * suppressed */ + bool notification; + + /* If true, the core will have sole control over + * when fastforwarding mode is enabled/disabled; + * the frontend will not be able to change the + * state set by 'fastforward' until either + * 'inhibit_toggle' is set to false, or the core + * is unloaded */ + bool inhibit_toggle; +}; + /* Callbacks */ /* Environment callback. Gives implementations a way of performing diff --git a/libretro/libretro_core_options.h b/libretro/libretro_core_options.h new file mode 100644 index 0000000..0147074 --- /dev/null +++ b/libretro/libretro_core_options.h @@ -0,0 +1,701 @@ +#ifndef LIBRETRO_CORE_OPTIONS_H__ +#define LIBRETRO_CORE_OPTIONS_H__ + +#include +#include + +#include "libretro.h" +#include "retro_inline.h" + +#ifndef HAVE_NO_LANGEXTRA +#include "libretro_core_options_intl.h" +#endif + +/* + ******************************** + * VERSION: 1.3 + ******************************** + * + * - 1.3: Move translations to libretro_core_options_intl.h + * - libretro_core_options_intl.h includes BOM and utf-8 + * fix for MSVC 2010-2013 + * - Added HAVE_NO_LANGEXTRA flag to disable translations + * on platforms/compilers without BOM support + * - 1.2: Use core options v1 interface when + * RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION is >= 1 + * (previously required RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION == 1) + * - 1.1: Support generation of core options v0 retro_core_option_value + * arrays containing options with a single value + * - 1.0: First commit +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ******************************** + * Core Option Definitions + ******************************** +*/ + +/* RETRO_LANGUAGE_ENGLISH */ + +/* Default language: + * - All other languages must include the same keys and values + * - Will be used as a fallback in the event that frontend language + * is not available + * - Will be used as a fallback for any missing entries in + * frontend language definition */ + +struct retro_core_option_definition option_defs_us[] = { + +/* Core options used in single cart mode */ + + { + "sameboy_model", + "System - Emulated Model (Requires Restart)", + "Chooses which system model the content should be started on. Certain games may activate special in-game features when ran on specific models. This option requires a content restart in order to take effect.", + { + { "Auto", "Detect automatically" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Color CPU C", "Game Boy Color (CPU-CGB C) (Experimental)" }, + { "Game Boy Color", "Game Boy Color (CPU-CGB E)" }, + { "Game Boy Advance", NULL }, + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Auto" + }, + { + "sameboy_rtc", + "System - Real Time Clock Emulation", + "Specifies how the emulation of the real-time clock feature used in certain Game Boy and Game Boy Color games should function.", + { + { "sync to system clock", "Sync to System Clock" }, + { "accurate", "Accurate" }, + { NULL, NULL }, + }, + "sync to system clock" + }, + + { + "sameboy_mono_palette", + "Video - GB Mono Palette", + "Selects the color palette that should be used when playing Game Boy games.", + { + { "greyscale", "Greyscale" }, + { "lime", "Lime (Game Boy)" }, + { "olive", "Olive (Game Boy Pocket" }, + { "teal", "Teal (Game Boy Light)" }, + { NULL, NULL }, + }, + "greyscale" + }, + { + "sameboy_color_correction_mode", + "Video - GBC Color Correction", + "Defines which type of color correction should be applied when playing Game Boy Color games.", + { + { "emulate hardware", "Emulate Hardware" }, + { "preserve brightness", "Preserve Brightness" }, + { "reduce contrast", "Reduce Contrast" }, + { "correct curves", "Correct Color Curves" }, + { "harsh reality", "Harsh Reality (Low Contrast)" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "emulate hardware" + }, + { + "sameboy_light_temperature", + "Video - Ambient Light Temperature", + "Simulates an ambient light’s effect on non-backlit Game Boy screens, by setting a user-controlled color temperature. This option has no effect if the content is run on an original Game Boy (DMG) emulated model.", + { + { "1000K", "1000K (Warmest)" }, + { "1550K", NULL }, + { "2100K", NULL }, + { "2650K", NULL }, + { "3200K", NULL }, + { "3750K", NULL }, + { "4300K", NULL }, + { "4850K", NULL }, + { "5400K", NULL }, + { "5950K", NULL }, + { "6500K", "6500K (Neutral White)" }, + { "7050K", NULL }, + { "7600K", NULL }, + { "8150K", NULL }, + { "8700K", NULL }, + { "9250K", NULL }, + { "9800K", NULL }, + { "10350K", NULL }, + { "10900K", NULL }, + { "11450K", NULL }, + { "12000K", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "6500K" + }, + { + "sameboy_border", + "Video - Display Border", + "Defines when to display an on-screen border around the content.", + { + { "always", "Always" }, + { "Super Game Boy only", "Only for Super Game Boy" }, + { "never", "disabled" }, + { NULL, NULL }, + }, + "Super Game Boy only" + }, + { + "sameboy_high_pass_filter_mode", + "Audio - Highpass Filter", + "Applies a filter to the audio output, removing certain pop sounds caused by the DC Offset. If disabled, the sound will be rendered as output by the Game Boy APU, but popping effects will be heard when the emulator is paused or resumed. 'Accurate' will apply a global filter, masking popping sounds while also reducing lower frequencies. 'Preserve Waveform' applies the filter only to the DC Offset.", + { + { "accurate", "Accurate" }, + { "remove dc offset", "Preserve Waveform" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "accurate" + }, + { + "sameboy_audio_interference", + "Audio - Interference Volume", + "Controls the volume of the buzzing effect caused by the electrical interference between the Game Boy SoC and the system speakers.", + { + { "0", "0%" }, + { "5", "5%" }, + { "10", "10%" }, + { "15", "15%" }, + { "20", "20%" }, + { "25", "25%" }, + { "30", "30%" }, + { "35", "35%" }, + { "40", "40%" }, + { "45", "45%" }, + { "50", "50%" }, + { "55", "55%" }, + { "60", "60%" }, + { "65", "65%" }, + { "70", "70%" }, + { "75", "75%" }, + { "80", "80%" }, + { "85", "85%" }, + { "90", "90%" }, + { "95", "95%" }, + { "100", "100%" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_rumble", + "Input - Rumble Mode", + "Defines which type of content should trigger rumble effects.", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "disabled" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + +/* Core options used in dual cart mode */ + + { + "sameboy_link", + "System - Link Cable Emulation", + "Enables the emulation of the link cable, allowing communication and exchange of data between two Game Boy systems.", + { + { "enabled", NULL }, + { "disabled", NULL }, + { NULL, NULL }, + }, + "enabled" + }, + { + "sameboy_screen_layout", + "System - Screen Layout", + "When emulating two systems at once, this option defines the respective position of the two screens.", + { + { "top-down", "Top-Down" }, + { "left-right", "Left-Right" }, + { NULL, NULL }, + }, + "top-down" + }, + { + "sameboy_audio_output", + "System - Audio Output", + "Selects which of the two emulated Game Boy systems should output audio.", + { + { "Game Boy #1", NULL }, + { "Game Boy #2", NULL }, + { NULL, NULL }, + }, + "Game Boy #1" + }, + { + "sameboy_model_1", + "System - Emulated Model for Game Boy #1 (Requires Restart)", + "Chooses which system model the content should be started on for Game Boy #1. Certain games may activate special in-game features when ran on specific models. This option requires a content restart in order to take effect.", + { + { "Auto", "Detect automatically" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Color CPU C", "Game Boy Color (CPU-CGB C) (Experimental)" }, + { "Game Boy Color", "Game Boy Color (CPU-CGB E)" }, + { "Game Boy Advance", NULL }, + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Auto" + }, + { + "sameboy_model_2", + "System - Emulated Model for Game Boy #2 (Requires Restart)", + "Chooses which system model the content should be started on for Game Boy #2. Certain games may activate special in-game features when ran on specific models. This option requires a content restart in order to take effect.", + { + { "Auto", "Detect automatically" }, + { "Game Boy", "Game Boy (DMG-CPU B)" }, + { "Game Boy Color CPU C", "Game Boy Color (CPU-CGB C) (Experimental)" }, + { "Game Boy Color", "Game Boy Color (CPU-CGB E)" }, + { "Game Boy Advance", NULL }, + { "Super Game Boy", "Super Game Boy NTSC" }, + { "Super Game Boy PAL", NULL }, + { "Super Game Boy 2", NULL }, + { NULL, NULL }, + }, + "Auto" + }, + { + "sameboy_mono_palette_1", + "Video - GB Mono Palette for Game Boy #1", + "Selects the color palette that should be used when playing Game Boy games on Game Boy #1.", + { + { "greyscale", "Greyscale" }, + { "lime", "Lime (Game Boy)" }, + { "olive", "Olive (Game Boy Pocket" }, + { "teal", "Teal (Game Boy Light)" }, + { NULL, NULL }, + }, + "greyscale" + }, + { + "sameboy_mono_palette_2", + "Video - GB Mono Palette for Game Boy #2", + "Selects the color palette that should be used when playing Game Boy games on Game Boy #2.", + { + { "greyscale", "Greyscale" }, + { "lime", "Lime (Game Boy)" }, + { "olive", "Olive (Game Boy Pocket" }, + { "teal", "Teal (Game Boy Light)" }, + { NULL, NULL }, + }, + "greyscale" + }, + { + "sameboy_color_correction_mode_1", + "Video - GBC Color Correction for Game Boy #1", + "Defines which type of color correction should be applied when playing Game Boy Color games on Game Boy #1.", + { + { "emulate hardware", "Emulate Hardware" }, + { "preserve brightness", "Preserve Brightness" }, + { "reduce contrast", "Reduce Contrast" }, + { "correct curves", "Correct Color Curves" }, + { "harsh reality", "Harsh Reality (Low Contrast)" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "emulate hardware" + }, + { + "sameboy_color_correction_mode_2", + "Video - GBC Color Correction for Game Boy #2", + "Defines which type of color correction should be applied when playing Game Boy Color games on Game Boy #2.", + { + { "emulate hardware", "Emulate Hardware" }, + { "preserve brightness", "Preserve Brightness" }, + { "reduce contrast", "Reduce Contrast" }, + { "correct curves", "Correct Color Curves" }, + { "harsh reality", "Harsh Reality (Low Contrast)" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "emulate hardware" + }, + { + "sameboy_light_temperature_1", + "Video - Ambient Light Temperature for Game Boy #1", + "Simulates an ambient light’s effect on non-backlit Game Boy screens, by setting a user-controlled color temperature applied to the screen of Game Boy #1. This option has no effect if the content is run on an original Game Boy (DMG) emulated model.", + { + { "1000K", "1000K (Warmest)" }, + { "1550K", NULL }, + { "2100K", NULL }, + { "2650K", NULL }, + { "3200K", NULL }, + { "3750K", NULL }, + { "4300K", NULL }, + { "4850K", NULL }, + { "5400K", NULL }, + { "5950K", NULL }, + { "6500K", "6500K (Neutral White)" }, + { "7050K", NULL }, + { "7600K", NULL }, + { "8150K", NULL }, + { "8700K", NULL }, + { "9250K", NULL }, + { "9800K", NULL }, + { "10350K", NULL }, + { "10900K", NULL }, + { "11450K", NULL }, + { "12000K", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "6500K" + }, + { + "sameboy_light_temperature_2", + "Video - Ambient Light Temperature for Game Boy #2", + "Simulates an ambient light’s effect on non-backlit Game Boy screens, by setting a user-controlled color temperature applied to the screen of Game Boy #2. This option has no effect if the content is run on an original Game Boy (DMG) emulated model.", + { + { "1000K", "1000K (Warmest)" }, + { "1550K", NULL }, + { "2100K", NULL }, + { "2650K", NULL }, + { "3200K", NULL }, + { "3750K", NULL }, + { "4300K", NULL }, + { "4850K", NULL }, + { "5400K", NULL }, + { "5950K", NULL }, + { "6500K", "6500K (Neutral White)" }, + { "7050K", NULL }, + { "7600K", NULL }, + { "8150K", NULL }, + { "8700K", NULL }, + { "9250K", NULL }, + { "9800K", NULL }, + { "10350K", NULL }, + { "10900K", NULL }, + { "11450K", NULL }, + { "12000K", "12000K (Coolest)" }, + { NULL, NULL }, + }, + "6500K" + }, + { + "sameboy_high_pass_filter_mode_1", + "Audio - Highpass Filter for Game Boy #1", + "Applies a filter to the audio output for Game Boy #1, removing certain pop sounds caused by the DC Offset. If disabled, the sound will be rendered as output by the Game Boy APU, but popping effects will be heard when the emulator is paused or resumed. 'Accurate' will apply a global filter, masking popping sounds while also reducing lower frequencies. 'Preserve Waveform' applies the filter only to the DC Offset.", + { + { "accurate", "Accurate" }, + { "remove dc offset", "Preserve Waveform" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "accurate" + }, + { + "sameboy_high_pass_filter_mode_2", + "Audio - Highpass Filter for Game Boy #2", + "Applies a filter to the audio output for Game Boy #2, removing certain pop sounds caused by the DC Offset. If disabled, the sound will be rendered as output by the Game Boy APU, but popping effects will be heard when the emulator is paused or resumed. 'Accurate' will apply a global filter, masking popping sounds while also reducing lower frequencies. 'Preserve Waveform' applies the filter only to the DC Offset.", + { + { "accurate", "Accurate" }, + { "remove dc offset", "Preserve Waveform" }, + { "off", "disabled" }, + { NULL, NULL }, + }, + "accurate" + }, + { + "sameboy_audio_interference_1", + "Audio - Interference Volume for Game Boy #1", + "Controls the volume of the buzzing effect caused by the electrical interference between the Game Boy SoC and the system speakers on Game Boy #1.", + { + { "0", "0%" }, + { "5", "5%" }, + { "10", "10%" }, + { "15", "15%" }, + { "20", "20%" }, + { "25", "25%" }, + { "30", "30%" }, + { "35", "35%" }, + { "40", "40%" }, + { "45", "45%" }, + { "50", "50%" }, + { "55", "55%" }, + { "60", "60%" }, + { "65", "65%" }, + { "70", "70%" }, + { "75", "75%" }, + { "80", "80%" }, + { "85", "85%" }, + { "90", "90%" }, + { "95", "95%" }, + { "100", "100%" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_audio_interference_2", + "Audio - Interference Volume for Game Boy #2", + "Controls the volume of the buzzing effect caused by the electrical interference between the Game Boy SoC and the system speakers on Game Boy #2.", + { + { "0", "0%" }, + { "5", "5%" }, + { "10", "10%" }, + { "15", "15%" }, + { "20", "20%" }, + { "25", "25%" }, + { "30", "30%" }, + { "35", "35%" }, + { "40", "40%" }, + { "45", "45%" }, + { "50", "50%" }, + { "55", "55%" }, + { "60", "60%" }, + { "65", "65%" }, + { "70", "70%" }, + { "75", "75%" }, + { "80", "80%" }, + { "85", "85%" }, + { "90", "90%" }, + { "95", "95%" }, + { "100", "100%" }, + { NULL, NULL }, + }, + "0" + }, + { + "sameboy_rumble_1", + "Input - Rumble Mode for Game Boy #1", + "Defines which type of content should trigger rumble effects when played on Game Boy #1.", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "disabled" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + { + "sameboy_rumble_2", + "Input - Rumble Mode for Game Boy #2", + "Defines which type of content should trigger rumble effects when played on Game Boy #2.", + { + { "all games", "Always" }, + { "rumble-enabled games", "Only for rumble-enabled games" }, + { "never", "disabled" }, + { NULL, NULL }, + }, + "rumble-enabled games" + }, + + { NULL, NULL, NULL, {{0}}, NULL }, +}; + +/* + ******************************** + * Language Mapping + ******************************** +*/ + +#ifndef HAVE_NO_LANGEXTRA +struct retro_core_option_definition *option_defs_intl[RETRO_LANGUAGE_LAST] = { + option_defs_us, /* RETRO_LANGUAGE_ENGLISH */ + NULL, /* RETRO_LANGUAGE_JAPANESE */ + NULL, /* RETRO_LANGUAGE_FRENCH */ + NULL, /* RETRO_LANGUAGE_SPANISH */ + NULL, /* RETRO_LANGUAGE_GERMAN */ + NULL, /* RETRO_LANGUAGE_ITALIAN */ + NULL, /* RETRO_LANGUAGE_DUTCH */ + NULL, /* RETRO_LANGUAGE_PORTUGUESE_BRAZIL */ + NULL, /* RETRO_LANGUAGE_PORTUGUESE_PORTUGAL */ + NULL, /* RETRO_LANGUAGE_RUSSIAN */ + NULL, /* RETRO_LANGUAGE_KOREAN */ + NULL, /* RETRO_LANGUAGE_CHINESE_TRADITIONAL */ + NULL, /* RETRO_LANGUAGE_CHINESE_SIMPLIFIED */ + NULL, /* RETRO_LANGUAGE_ESPERANTO */ + NULL, /* RETRO_LANGUAGE_POLISH */ + NULL, /* RETRO_LANGUAGE_VIETNAMESE */ + NULL, /* RETRO_LANGUAGE_ARABIC */ + NULL, /* RETRO_LANGUAGE_GREEK */ + NULL, /* RETRO_LANGUAGE_TURKISH */ + NULL, /* RETRO_LANGUAGE_SLOVAK */ + NULL, /* RETRO_LANGUAGE_PERSIAN */ + NULL, /* RETRO_LANGUAGE_HEBREW */ + NULL, /* RETRO_LANGUAGE_ASTURIAN */ + NULL, /* RETRO_LANGUAGE_FINNISH */ + +}; +#endif + +/* + ******************************** + * Functions + ******************************** +*/ + +/* Handles configuration/setting of core options. + * Should be called as early as possible - ideally inside + * retro_set_environment(), and no later than retro_load_game() + * > We place the function body in the header to avoid the + * necessity of adding more .c files (i.e. want this to + * be as painless as possible for core devs) + */ + +static INLINE void libretro_set_core_options(retro_environment_t environ_cb) +{ + unsigned version = 0; + + if (!environ_cb) + return; + + if (environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version) && (version >= 1)) + { +#ifndef HAVE_NO_LANGEXTRA + struct retro_core_options_intl core_options_intl; + unsigned language = 0; + + core_options_intl.us = option_defs_us; + core_options_intl.local = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) && + (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH)) + core_options_intl.local = option_defs_intl[language]; + + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_intl); +#else + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, &option_defs_us); +#endif + } + else + { + size_t i; + size_t num_options = 0; + struct retro_variable *variables = NULL; + char **values_buf = NULL; + + /* Determine number of options */ + for (;;) + { + if (!option_defs_us[num_options].key) + break; + num_options++; + } + + /* Allocate arrays */ + variables = (struct retro_variable *)calloc(num_options + 1, sizeof(struct retro_variable)); + values_buf = (char **)calloc(num_options, sizeof(char *)); + + if (!variables || !values_buf) + goto error; + + /* Copy parameters from option_defs_us array */ + for (i = 0; i < num_options; i++) + { + const char *key = option_defs_us[i].key; + const char *desc = option_defs_us[i].desc; + const char *default_value = option_defs_us[i].default_value; + struct retro_core_option_value *values = option_defs_us[i].values; + size_t buf_len = 3; + size_t default_index = 0; + + values_buf[i] = NULL; + + if (desc) + { + size_t num_values = 0; + + /* Determine number of values */ + for (;;) + { + if (!values[num_values].value) + break; + + /* Check if this is the default value */ + if (default_value) + if (strcmp(values[num_values].value, default_value) == 0) + default_index = num_values; + + buf_len += strlen(values[num_values].value); + num_values++; + } + + /* Build values string */ + if (num_values > 0) + { + size_t j; + + buf_len += num_values - 1; + buf_len += strlen(desc); + + values_buf[i] = (char *)calloc(buf_len, sizeof(char)); + if (!values_buf[i]) + goto error; + + strcpy(values_buf[i], desc); + strcat(values_buf[i], "; "); + + /* Default value goes first */ + strcat(values_buf[i], values[default_index].value); + + /* Add remaining values */ + for (j = 0; j < num_values; j++) + { + if (j != default_index) + { + strcat(values_buf[i], "|"); + strcat(values_buf[i], values[j].value); + } + } + } + } + + variables[i].key = key; + variables[i].value = values_buf[i]; + } + + /* Set variables */ + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables); + +error: + + /* Clean up */ + if (values_buf) + { + for (i = 0; i < num_options; i++) + { + if (values_buf[i]) + { + free(values_buf[i]); + values_buf[i] = NULL; + } + } + + free(values_buf); + values_buf = NULL; + } + + if (variables) + { + free(variables); + variables = NULL; + } + } +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libretro/libretro_core_options_intl.h b/libretro/libretro_core_options_intl.h new file mode 100644 index 0000000..f6cf285 --- /dev/null +++ b/libretro/libretro_core_options_intl.h @@ -0,0 +1,90 @@ +#ifndef LIBRETRO_CORE_OPTIONS_INTL_H__ +#define LIBRETRO_CORE_OPTIONS_INTL_H__ + +#if defined(_MSC_VER) && (_MSC_VER >= 1500 && _MSC_VER < 1900) +/* https://support.microsoft.com/en-us/kb/980263 */ +#pragma execution_character_set("utf-8") +#pragma warning(disable:4566) +#endif + +#include "libretro.h" + +/* + ******************************** + * VERSION: 1.3 + ******************************** + * + * - 1.3: Move translations to libretro_core_options_intl.h + * - libretro_core_options_intl.h includes BOM and utf-8 + * fix for MSVC 2010-2013 + * - Added HAVE_NO_LANGEXTRA flag to disable translations + * on platforms/compilers without BOM support + * - 1.2: Use core options v1 interface when + * RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION is >= 1 + * (previously required RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION == 1) + * - 1.1: Support generation of core options v0 retro_core_option_value + * arrays containing options with a single value + * - 1.0: First commit +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ******************************** + * Core Option Definitions + ******************************** +*/ + +/* RETRO_LANGUAGE_JAPANESE */ + +/* RETRO_LANGUAGE_FRENCH */ + +/* RETRO_LANGUAGE_SPANISH */ + +/* RETRO_LANGUAGE_GERMAN */ + +/* RETRO_LANGUAGE_ITALIAN */ + +/* RETRO_LANGUAGE_DUTCH */ + +/* RETRO_LANGUAGE_PORTUGUESE_BRAZIL */ + +/* RETRO_LANGUAGE_PORTUGUESE_PORTUGAL */ + +/* RETRO_LANGUAGE_RUSSIAN */ + +/* RETRO_LANGUAGE_KOREAN */ + +/* RETRO_LANGUAGE_CHINESE_TRADITIONAL */ + +/* RETRO_LANGUAGE_CHINESE_SIMPLIFIED */ + +/* RETRO_LANGUAGE_ESPERANTO */ + +/* RETRO_LANGUAGE_POLISH */ + +/* RETRO_LANGUAGE_VIETNAMESE */ + +/* RETRO_LANGUAGE_ARABIC */ + +/* RETRO_LANGUAGE_GREEK */ + +/* RETRO_LANGUAGE_TURKISH */ + +/* RETRO_LANGUAGE_SLOVAK */ + +/* RETRO_LANGUAGE_PERSIAN */ + +/* RETRO_LANGUAGE_HEBREW */ + +/* RETRO_LANGUAGE_ASTURIAN */ + +/* RETRO_LANGUAGE_FINNISH */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libretro/retro_inline.h b/libretro/retro_inline.h new file mode 100644 index 0000000..b27d6dd --- /dev/null +++ b/libretro/retro_inline.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (retro_inline.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __LIBRETRO_SDK_INLINE_H +#define __LIBRETRO_SDK_INLINE_H + +#ifndef INLINE + +#if defined(_WIN32) || defined(__INTEL_COMPILER) +#define INLINE __inline +#elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L +#define INLINE inline +#elif defined(__GNUC__) +#define INLINE __inline__ +#else +#define INLINE +#endif + +#endif +#endif From 75ec1c033447a83644e029f81dbc2ee26c57ff3e Mon Sep 17 00:00:00 2001 From: Ryunam Date: Sun, 27 Jun 2021 11:22:27 +0200 Subject: [PATCH 222/365] [Libretro] Fix small typo in palette description --- libretro/libretro_core_options.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libretro/libretro_core_options.h b/libretro/libretro_core_options.h index 0147074..02ffdb6 100644 --- a/libretro/libretro_core_options.h +++ b/libretro/libretro_core_options.h @@ -86,10 +86,10 @@ struct retro_core_option_definition option_defs_us[] = { "Video - GB Mono Palette", "Selects the color palette that should be used when playing Game Boy games.", { - { "greyscale", "Greyscale" }, - { "lime", "Lime (Game Boy)" }, - { "olive", "Olive (Game Boy Pocket" }, - { "teal", "Teal (Game Boy Light)" }, + { "greyscale", "Greyscale" }, + { "lime", "Lime (Game Boy)" }, + { "olive", "Olive (Game Boy Pocket)" }, + { "teal", "Teal (Game Boy Light)" }, { NULL, NULL }, }, "greyscale" @@ -280,10 +280,10 @@ struct retro_core_option_definition option_defs_us[] = { "Video - GB Mono Palette for Game Boy #1", "Selects the color palette that should be used when playing Game Boy games on Game Boy #1.", { - { "greyscale", "Greyscale" }, - { "lime", "Lime (Game Boy)" }, - { "olive", "Olive (Game Boy Pocket" }, - { "teal", "Teal (Game Boy Light)" }, + { "greyscale", "Greyscale" }, + { "lime", "Lime (Game Boy)" }, + { "olive", "Olive (Game Boy Pocket)" }, + { "teal", "Teal (Game Boy Light)" }, { NULL, NULL }, }, "greyscale" @@ -293,10 +293,10 @@ struct retro_core_option_definition option_defs_us[] = { "Video - GB Mono Palette for Game Boy #2", "Selects the color palette that should be used when playing Game Boy games on Game Boy #2.", { - { "greyscale", "Greyscale" }, - { "lime", "Lime (Game Boy)" }, - { "olive", "Olive (Game Boy Pocket" }, - { "teal", "Teal (Game Boy Light)" }, + { "greyscale", "Greyscale" }, + { "lime", "Lime (Game Boy)" }, + { "olive", "Olive (Game Boy Pocket)" }, + { "teal", "Teal (Game Boy Light)" }, { NULL, NULL }, }, "greyscale" From efb644bc72ce34187fbfc11e5499159001c88271 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Jul 2021 15:02:15 +0300 Subject: [PATCH 223/365] MBC5 RAM enable is 8 bit --- Core/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index f499e74..0b08a79 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -547,7 +547,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_MBC5: switch (addr & 0xF000) { - case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xFF) == 0x0A; break; case 0x2000: gb->mbc5.rom_bank_low = value; break; case 0x3000: gb->mbc5.rom_bank_high = value; break; case 0x4000: case 0x5000: From 6f6f72dcbdea97881fd7b63e99721f3183846797 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Jul 2021 15:07:23 +0300 Subject: [PATCH 224/365] More accurate emulation of STOP --- Core/gb.h | 3 ++- Core/sm83_cpu.c | 68 +++++++++++++++++++++++++++---------------------- Core/timing.c | 18 ++++++++++++- Tester/main.c | 19 +++++++++----- 4 files changed, 70 insertions(+), 38 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 3a322c8..f29f914 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -297,7 +297,6 @@ typedef enum { #define SGB_NTSC_FREQUENCY (21477272 / 5) #define SGB_PAL_FREQUENCY (21281370 / 5) #define DIV_CYCLES (0x100) -#define INTERNAL_DIV_CYCLES (0x40000) #if !defined(MIN) #define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) @@ -532,6 +531,8 @@ struct GB_gameboy_internal_s { uint16_t serial_length; uint8_t double_speed_alignment; uint8_t serial_count; + int32_t speed_switch_halt_countdown; + uint8_t speed_switch_freeze; // Solely for realigning the PPU, should be removed when the odd modes are implemented ); /* APU */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index ec908e7..0388e4f 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -357,6 +357,9 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode) static void enter_stop_mode(GB_gameboy_t *gb) { GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); + if (!gb->ime) { // TODO: I don't trust this if, + gb->div_cycles = -4; // Emulate the CPU-side DIV-reset signal being held + } gb->stopped = true; gb->oam_ppu_blocked = !gb->oam_read_blocked; gb->vram_ppu_blocked = !gb->vram_read_blocked; @@ -369,53 +372,56 @@ static void leave_stop_mode(GB_gameboy_t *gb) gb->oam_ppu_blocked = false; gb->vram_ppu_blocked = false; gb->cgb_palettes_ppu_blocked = false; - /* The CPU takes more time to wake up then the other components */ - for (unsigned i = 0x1FFF; i--;) { - GB_advance_cycles(gb, 0x10); - } - GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xC); - GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); } static void stop(GB_gameboy_t *gb, uint8_t opcode) { - if (gb->io_registers[GB_IO_KEY1] & 0x1) { + flush_pending_cycles(gb); + bool exit_by_joyp = ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF); + bool speed_switch = (gb->io_registers[GB_IO_KEY1] & 0x1) && !exit_by_joyp; + bool immediate_exit = speed_switch || exit_by_joyp; + 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) { + enter_stop_mode(gb); + } + + if (!interrupt_pending) { + /* Todo: is PC being actually read? */ + cycle_read_inc_oam_bug(gb, gb->pc++); + } + + /* Todo: speed switching takes a fractional number of M-cycles. It make + every active component (APU, PPU) unaligned with the CPU. */ + if (speed_switch) { flush_pending_cycles(gb); - bool needs_alignment = false; - GB_advance_cycles(gb, 0x4); - /* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */ - - if (gb->double_speed_alignment & 7) { - GB_advance_cycles(gb, 0x4); - needs_alignment = true; + if (gb->io_registers[GB_IO_LCDC] & 0x80) { GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n"); + if (gb->double_speed_alignment & 7) { + gb->speed_switch_freeze = 6; + } + else { + gb->speed_switch_freeze = 4; + } } - + gb->cgb_double_speed ^= true; gb->io_registers[GB_IO_KEY1] = 0; - enter_stop_mode(gb); - leave_stop_mode(gb); - - if (!needs_alignment) { - GB_advance_cycles(gb, 0x4); - } - + gb->speed_switch_halt_countdown = 0x20008; } - else { - GB_timing_sync(gb); - if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { - /* TODO: HW Bug? When STOP is executed while a button is down, the CPU enters halt - mode instead. Fine details not confirmed yet. */ + + if (immediate_exit) { + leave_stop_mode(gb); + if (!interrupt_pending) { gb->halted = true; + gb->just_halted = true; } else { - enter_stop_mode(gb); + gb->speed_switch_halt_countdown = 0; } } - /* Todo: is PC being actually read? */ - gb->pc++; } /* Operand naming conventions for functions: @@ -1603,11 +1609,13 @@ 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; + gb->speed_switch_halt_countdown = 0; } /* Call interrupt */ else if (effective_ime && interrupt_queue) { gb->halted = false; + gb->speed_switch_halt_countdown = 0; uint16_t call_addr = gb->pc; gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); diff --git a/Core/timing.c b/Core/timing.c index 7b79b72..69b301a 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -145,7 +145,6 @@ static void increase_tima(GB_gameboy_t *gb) static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) { /* TIMA increases when a specific high-bit becomes a low-bit. */ - value &= INTERNAL_DIV_CYCLES - 1; uint16_t triggers = gb->div_counter & ~value; if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); @@ -356,7 +355,24 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode } + if (gb->speed_switch_halt_countdown) { + gb->speed_switch_halt_countdown -= cycles; + if (gb->speed_switch_halt_countdown <= 0) { + gb->speed_switch_halt_countdown = 0; + gb->halted = false; + } + } + gb->debugger_ticks += cycles; + + if (gb->speed_switch_freeze) { + if (gb->speed_switch_freeze >= cycles) { + gb->speed_switch_freeze -= cycles; + return; + } + cycles -= gb->speed_switch_freeze; + gb->speed_switch_freeze = 0; + } if (!gb->cgb_double_speed) { cycles <<= 1; diff --git a/Tester/main.c b/Tester/main.c index b0f1b31..912563c 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -27,7 +27,7 @@ static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, do_not_stop, push_a_twice, start_is_bad, allow_weird_sp_values, large_stack, push_right, - semi_random, limit_start, pointer_control; + semi_random, limit_start, pointer_control, unsafe_speed_switch; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -60,6 +60,9 @@ static char *async_input_callback(GB_gameboy_t *gb) static void handle_buttons(GB_gameboy_t *gb) { + if (!gb->cgb_double_speed && unsafe_speed_switch) { + return; + } /* Do not press any buttons during the last two seconds, this might cause a screenshot to be taken while the LCD is off if the press makes the game load graphics. */ @@ -129,7 +132,7 @@ static void vblank(GB_gameboy_t *gb) gb->registers[GB_REGISTER_SP], gb->backtrace_size); frames = test_length - 1; } - if (gb->halted && !gb->interrupt_enable) { + if (gb->halted && !gb->interrupt_enable && gb->speed_switch_halt_countdown == 0) { GB_log(gb, "The game is deadlocked.\n"); frames = test_length - 1; } @@ -265,9 +268,7 @@ static void replace_extension(const char *src, size_t length, char *dest, const int main(int argc, char **argv) { -#define str(x) #x -#define xstr(x) str(x) - fprintf(stderr, "SameBoy Tester v" xstr(VERSION) "\n"); + fprintf(stderr, "SameBoy Tester v" GB_VERSION "\n"); if (argc == 1) { fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds] [--sav] [--boot path to boot ROM]" @@ -406,7 +407,8 @@ int main(int argc, char **argv) strcmp((const char *)(gb.rom + 0x134), "ONI 5") == 0; b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0 || strcmp((const char *)(gb.rom + 0x134), "SOCCER") == 0 || - strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0; + strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0 || + strcmp((const char *)(gb.rom + 0x134), "BABE") == 0; push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0 || strcmp((const char *)(gb.rom + 0x134), "HUGO2 1/2") == 0 || strcmp((const char *)(gb.rom + 0x134), "HUGO") == 0; @@ -445,6 +447,11 @@ int main(int argc, char **argv) pointer_control = memcmp((const char *)(gb.rom + 0x134), "LEGO ATEAM BLPP", strlen("LEGO ATEAM BLPP")) == 0; push_faster |= pointer_control; + /* Games that perform an unsafe speed switch, don't input until in double speed */ + unsafe_speed_switch = strcmp((const char *)(gb.rom + 0x134), "GBVideo") == 0 || // lulz this is my fault + strcmp((const char *)(gb.rom + 0x134), "POKEMONGOLD 2") == 0; // Pokemon Adventure + + /* Run emulation */ running = true; gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true; From a5325d3374ff9b7d3901df5be213c089f3aa420a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Jul 2021 12:25:39 +0300 Subject: [PATCH 225/365] Improved ticks command, more accurate speed switch timings, better odd-mode warnings --- Core/debugger.c | 6 +++++- Core/gb.h | 2 ++ Core/sm83_cpu.c | 35 +++++++++++++++++++++++------------ Core/timing.c | 18 ++++++++++++++++++ 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 371a865..6d4deb4 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1606,8 +1606,12 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu return true; } - GB_log(gb, "Ticks: %llu. (Resetting)\n", (unsigned long long)gb->debugger_ticks); + GB_log(gb, "T-cycles: %llu\n", (unsigned long long)gb->debugger_ticks); + GB_log(gb, "M-cycles: %llu\n", (unsigned long long)gb->debugger_ticks / 4); + GB_log(gb, "Absolute 8MHz ticks: %llu\n", (unsigned long long)gb->absolute_debugger_ticks); + GB_log(gb, "Tick count reset.\n"); gb->debugger_ticks = 0; + gb->absolute_debugger_ticks = 0; return true; } diff --git a/Core/gb.h b/Core/gb.h index f29f914..80465df 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -532,6 +532,7 @@ struct GB_gameboy_internal_s { uint8_t double_speed_alignment; uint8_t serial_count; int32_t speed_switch_halt_countdown; + uint8_t speed_switch_countdown; // To compensate for the lack of pipeline emulation uint8_t speed_switch_freeze; // Solely for realigning the PPU, should be removed when the odd modes are implemented ); @@ -718,6 +719,7 @@ struct GB_gameboy_internal_s { /* Ticks command */ uint64_t debugger_ticks; + uint64_t absolute_debugger_ticks; /* Undo */ uint8_t *undo_state; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 0388e4f..20e7691 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -374,6 +374,7 @@ static void leave_stop_mode(GB_gameboy_t *gb) gb->cgb_palettes_ppu_blocked = false; } +/* TODO: Speed switch timing needs far more tests. Double to single is wrong to avoid odd mode. */ static void stop(GB_gameboy_t *gb, uint8_t opcode) { flush_pending_cycles(gb); @@ -387,29 +388,39 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) } if (!interrupt_pending) { - /* Todo: is PC being actually read? */ cycle_read_inc_oam_bug(gb, gb->pc++); } - /* Todo: speed switching takes a fractional number of M-cycles. It make - every active component (APU, PPU) unaligned with the CPU. */ + /* Todo: speed switching takes 2 extra T-cycles (so 2 PPU ticks in single->double and 1 PPU tick in double->single) */ if (speed_switch) { flush_pending_cycles(gb); - if (gb->io_registers[GB_IO_LCDC] & 0x80) { - GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n"); + if (gb->io_registers[GB_IO_LCDC] & 0x80 && gb->cgb_double_speed) { + GB_log(gb, "ROM triggered a PPU odd mode, which is currently not supported. Reverting to even-mode.\n"); if (gb->double_speed_alignment & 7) { - gb->speed_switch_freeze = 6; - } - else { - gb->speed_switch_freeze = 4; + gb->speed_switch_freeze = 2; } } + if (gb->apu.global_enable && gb->cgb_double_speed) { + GB_log(gb, "ROM triggered an APU odd mode, which is currently not tested.\n"); + } + + if (gb->cgb_double_speed) { + gb->cgb_double_speed = false; + } + else { + gb->speed_switch_countdown = 6; + gb->speed_switch_freeze = 1; + } + + if (interrupt_pending) { + } + else { + gb->speed_switch_halt_countdown = 0x20008; + gb->speed_switch_freeze = 5; + } - gb->cgb_double_speed ^= true; gb->io_registers[GB_IO_KEY1] = 0; - - gb->speed_switch_halt_countdown = 0x20008; } if (immediate_exit) { diff --git a/Core/timing.c b/Core/timing.c index 69b301a..a755ac5 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -346,6 +346,22 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { + if (gb->speed_switch_countdown) { + if (gb->speed_switch_countdown == cycles) { + gb->cgb_double_speed ^= true; + gb->speed_switch_countdown = 0; + } + else if (gb->speed_switch_countdown > cycles) { + gb->speed_switch_countdown -= cycles; + } + else { + uint8_t old_cycles = gb->speed_switch_countdown; + cycles -= old_cycles; + gb->speed_switch_countdown = 0; + GB_advance_cycles(gb, old_cycles); + gb->cgb_double_speed ^= true; + } + } 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; @@ -378,6 +394,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) cycles <<= 1; } + gb->absolute_debugger_ticks += cycles; + // Not affected by speed boost if (gb->io_registers[GB_IO_LCDC] & 0x80) { gb->double_speed_alignment += cycles; From 1d7692cff5552e296be5e1ab075c4f187f57132c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Jul 2021 23:12:46 +0300 Subject: [PATCH 226/365] Fix blurry VRAM viewer grid lines --- Cocoa/Document.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index c434934..64e3a21 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -691,7 +691,11 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) window_frame.size.height); [self.mainWindow setFrame:window_frame display:YES]; self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised; - + + NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height; + CGRect vram_window_rect = self.vramWindow.frame; + vram_window_rect.size.height = 384 + height_diff + 48; + [self.vramWindow setFrame:vram_window_rect display:YES animate:NO]; self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [self.fileURL.path lastPathComponent]]; From 4d1a28f1d1ec8ac3e9ff96f9a8e7e358a513f92c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 25 Jul 2021 16:34:34 +0300 Subject: [PATCH 227/365] Improved OAM bug accuracy in several read edge cases --- Core/memory.c | 273 ++++++++++++++++++++++++++++++++++++++---------- Core/memory.h | 1 - Core/sm83_cpu.c | 93 ++++++++--------- 3 files changed, 260 insertions(+), 107 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 0b08a79..d0af989 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -29,33 +29,80 @@ static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) return GB_BUS_INTERNAL; } -static uint8_t bitwise_glitch(uint8_t a, uint8_t b, uint8_t c) +static uint16_t bitwise_glitch(uint16_t a, uint16_t b, uint16_t c) { return ((a ^ c) & (b ^ c)) ^ c; } -static uint8_t bitwise_glitch_read(uint8_t a, uint8_t b, uint8_t c) +static uint16_t bitwise_glitch_read(uint16_t a, uint16_t b, uint16_t c) { return b | (a & c); } -static uint8_t bitwise_glitch_read_increase(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +static uint16_t bitwise_glitch_read_secondary(uint16_t a, uint16_t b, uint16_t c, uint16_t d) { return (b & (a | c | d)) | (a & c & d); } +/* + Used on the MGB in some scenarios +static uint16_t bitwise_glitch_mgb(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, bool variant) +{ + return (c & (e | d | b | (variant? 0 : a))) | (b & d & (a | e)); +} +*/ + +static uint16_t bitwise_glitch_tertiary_read_1(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e) +{ + return c | (a & b & d & e); +} + +static uint16_t bitwise_glitch_tertiary_read_2(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e) +{ + return (c & (a | b | d | e)) | (a & b & d & e); +} + +static uint16_t bitwise_glitch_tertiary_read_3(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e) +{ + return (c & (a | b | d | e)) | (b & d & e); +} + +static uint16_t bitwise_glitch_quaternary_read_dmg(uint16_t a, uint16_t b, uint16_t c, uint16_t d, + uint16_t e, uint16_t f, uint16_t g, uint16_t h) +{ + /* On my DMG, some cases are non-deterministic, while on some other DMGs they yield constant zeros. + The non deterministic cases are affected by (on the row 40 case) 34, 36, 3e and 28, and potentially + others. For my own sanity I'm going to emulate the DMGs that output zeros. */ + (void)a; + return (e & (h | g | (~d & f) | c | b)) | (c & g & h); +} + +/* + +// Oh my. +static uint16_t bitwise_glitch_quaternary_read_mgb(uint16_t a, uint16_t b, uint16_t c, uint16_t d, + uint16_t e, uint16_t f, uint16_t g, uint16_t h) +{ + return (e & (h | g | c | (a & b))) | ((c & h) & (g & (~f | b | a | ~d) | (a & b & f))); +} +*/ + +static uint16_t bitwise_glitch_quaternary_read_sgb2(uint16_t a, uint16_t b, uint16_t c, uint16_t d, + uint16_t e, uint16_t f, uint16_t g, uint16_t h) +{ + return (e & (h | g | c | (a & b))) | ((c & g & h) & (b | a | ~f)); +} + void GB_trigger_oam_bug(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) { - gb->oam[gb->accessed_oam_row] = bitwise_glitch(gb->oam[gb->accessed_oam_row], - gb->oam[gb->accessed_oam_row - 8], - gb->oam[gb->accessed_oam_row - 4]); - gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch(gb->oam[gb->accessed_oam_row + 1], - gb->oam[gb->accessed_oam_row - 7], - gb->oam[gb->accessed_oam_row - 3]); + uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row); + base[0] = bitwise_glitch(base[0], + base[-4], + base[-2]); for (unsigned i = 2; i < 8; i++) { gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i]; } @@ -63,49 +110,140 @@ void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) } } +static void oam_bug_secondary_read_corruption(GB_gameboy_t *gb) +{ + if (gb->accessed_oam_row < 0x98) { + uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row); + base[-4] = bitwise_glitch_read_secondary(base[-8], + base[-4], + base[0], + base[-2]); + for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; + } + } +} + +/* +static void oam_bug_tertiary_read_corruption_mgb(GB_gameboy_t *gb) +{ + if (gb->accessed_oam_row < 0x98) { + uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row); + uint16_t temp = bitwise_glitch_mgb( + base[0], + base[-2], + base[-4], + base[-8], + base[-16], + true); + + base[-4] = bitwise_glitch_mgb( + base[0], + base[-2], + base[-4], + base[-8], + base[-16], + false); + for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x20 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; + } + + base[-8] = temp; + base[-16] = temp; + } +} +*/ + +static void oam_bug_quaternary_read_corruption(GB_gameboy_t *gb, typeof(bitwise_glitch_quaternary_read_dmg) *bitwise_op) +{ + if (gb->accessed_oam_row < 0x98) { + uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row); + + base[-4] = bitwise_op(*(uint16_t *)gb->oam, + base[0], + base[-2], + base[-3], + base[-4], + base[-7], + base[-8], + base[-16]); + for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x20 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; + } + } +} + +static void oam_bug_tertiary_read_corruption(GB_gameboy_t *gb, typeof(bitwise_glitch_tertiary_read_1) *bitwise_op) +{ + if (gb->accessed_oam_row < 0x98) { + uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row); + + /* On some instances, the corruption row is copied to the first row for some accessed row. On my DMG it happens + for row 80, and on my MGB it happens on row 60. Some instances only copy odd or even bytes. Additionally, + for some instances on accessed rows that do not affect the first row, the last two bytes of the preceeding + row are also corrupted in a non-deterministic probability. */ + + base[-4] = bitwise_op( + base[0], + base[-2], + base[-4], + base[-8], + base[-16]); + for (unsigned i = 0; i < 8; i++) { + gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x20 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; + } + } +} + 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) { - gb->oam[gb->accessed_oam_row - 8] = - gb->oam[gb->accessed_oam_row] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row], - gb->oam[gb->accessed_oam_row - 8], - gb->oam[gb->accessed_oam_row - 4]); - gb->oam[gb->accessed_oam_row - 7] = - gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row + 1], - gb->oam[gb->accessed_oam_row - 7], - gb->oam[gb->accessed_oam_row - 3]); - for (unsigned i = 2; i < 8; i++) { + if ((gb->accessed_oam_row & 0x18) == 0x10) { + oam_bug_secondary_read_corruption(gb); + } + else if ((gb->accessed_oam_row & 0x18) == 0x00) { + /* Everything in this specific case is *extremely* revision and instance specific. */ + if (gb->accessed_oam_row == 0x40) { + oam_bug_quaternary_read_corruption(gb, + ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2)? + bitwise_glitch_quaternary_read_sgb2: + bitwise_glitch_quaternary_read_dmg); + } + else if ((gb->model & ~GB_MODEL_NO_SFC_BIT) != GB_MODEL_SGB2) { + if (gb->accessed_oam_row == 0x20) { + oam_bug_tertiary_read_corruption(gb, bitwise_glitch_tertiary_read_2); + } + else if (gb->accessed_oam_row == 0x60) { + oam_bug_tertiary_read_corruption(gb, bitwise_glitch_tertiary_read_3); + } + else { + oam_bug_tertiary_read_corruption(gb, bitwise_glitch_tertiary_read_1); + } + } + else { + oam_bug_tertiary_read_corruption(gb, bitwise_glitch_tertiary_read_2); + } + } + else { + uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row); + base[-4] = + base[0] = bitwise_glitch_read(base[0], + base[-4], + base[-2]); + } + for (unsigned i = 0; i < 8; i++) { gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i]; } + if (gb->accessed_oam_row == 0x80) { + memcpy(gb->oam, gb->oam + gb->accessed_oam_row, 8); + } } } } -void GB_trigger_oam_bug_read_increase(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 >= 0x20 && gb->accessed_oam_row < 0x98) { - gb->oam[gb->accessed_oam_row - 0x8] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x10], - gb->oam[gb->accessed_oam_row - 0x08], - gb->oam[gb->accessed_oam_row ], - gb->oam[gb->accessed_oam_row - 0x04] - ); - gb->oam[gb->accessed_oam_row - 0x7] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x0f], - gb->oam[gb->accessed_oam_row - 0x07], - gb->oam[gb->accessed_oam_row + 0x01], - gb->oam[gb->accessed_oam_row - 0x03] - ); - for (unsigned i = 0; i < 8; i++) { - gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i]; - } - } - } -} static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) { @@ -281,26 +419,53 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (gb->oam_read_blocked) { if (!GB_is_cgb(gb)) { if (addr < 0xFEA0) { + uint16_t *oam = (uint16_t *)gb->oam; if (gb->accessed_oam_row == 0) { - gb->oam[(addr & 0xf8)] = - gb->oam[0] = bitwise_glitch_read(gb->oam[0], - gb->oam[(addr & 0xf8)], - gb->oam[(addr & 0xfe)]); - gb->oam[(addr & 0xf8) + 1] = - gb->oam[1] = bitwise_glitch_read(gb->oam[1], - gb->oam[(addr & 0xf8) + 1], - gb->oam[(addr & 0xfe) | 1]); + oam[(addr & 0xf8) >> 1] = + oam[0] = bitwise_glitch_read(oam[0], + oam[(addr & 0xf8) >> 1], + oam[(addr & 0xff) >> 1]); + for (unsigned i = 2; i < 8; i++) { gb->oam[i] = gb->oam[(addr & 0xf8) + i]; } } else if (gb->accessed_oam_row == 0xa0) { - gb->oam[0x9e] = bitwise_glitch_read(gb->oam[0x9c], - gb->oam[0x9e], - gb->oam[(addr & 0xf8) | 6]); - gb->oam[0x9f] = bitwise_glitch_read(gb->oam[0x9d], - gb->oam[0x9f], - gb->oam[(addr & 0xf8) | 7]); + uint8_t target = (addr & 7) | 0x98; + uint16_t a = oam[0x9c >> 1], + b = oam[target >> 1], + c = oam[(addr & 0xf8) >> 1]; + switch (addr & 7) { + case 0: + case 1: + /* Probably instance specific */ + if ((gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_DMG_FAMILY) { + oam[target >> 1] = (a & b) | (a & c) | (b & c); + } + else { + oam[target >> 1] = bitwise_glitch_read(a, b, c); + } + break; + case 2: + case 3: { + /* Probably instance specific */ + 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); + break; + } + case 4: + case 5: + break; // No additional corruption + case 6: + case 7: + oam[target >> 1] = bitwise_glitch_read(a, b, c); + break; + + default: + break; + } for (unsigned i = 0; i < 8; i++) { gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i]; diff --git a/Core/memory.h b/Core/memory.h index f0d0390..80020f1 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -12,7 +12,6 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); void GB_dma_run(GB_gameboy_t *gb); void GB_hdma_run(GB_gameboy_t *gb); void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); -void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address); #endif #endif /* memory_h */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 20e7691..09499bc 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -87,17 +87,6 @@ static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr) return ret; } -static uint8_t cycle_read_inc_oam_bug(GB_gameboy_t *gb, uint16_t addr) -{ - if (gb->pending_cycles) { - GB_advance_cycles(gb, gb->pending_cycles); - } - GB_trigger_oam_bug_read_increase(gb, addr); /* Todo: test T-cycle timing */ - uint8_t ret = GB_read_memory(gb, addr); - gb->pending_cycles = 4; - return ret; -} - /* A special case for IF during ISR, returns the old value of IF. */ /* TODO: Verify the timing, it might be wrong in cases where, in the same M cycle, IF is both read be the CPU, modified by the ISR, and modified by an actual interrupt. @@ -388,7 +377,7 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) } if (!interrupt_pending) { - cycle_read_inc_oam_bug(gb, gb->pc++); + cycle_read(gb, gb->pc++); } /* Todo: speed switching takes 2 extra T-cycles (so 2 PPU ticks in single->double and 1 PPU tick in double->single) */ @@ -451,8 +440,8 @@ static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; uint16_t value; register_id = (opcode >> 4) + 1; - value = cycle_read_inc_oam_bug(gb, gb->pc++); - value |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + value = cycle_read(gb, gb->pc++); + value |= cycle_read(gb, gb->pc++) << 8; gb->registers[register_id] = value; } @@ -507,7 +496,7 @@ static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] &= 0xFF; - gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + gb->registers[register_id] |= cycle_read(gb, gb->pc++) << 8; } static void rlca(GB_gameboy_t *gb, uint8_t opcode) @@ -538,8 +527,8 @@ static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify order is correct */ uint16_t addr; - addr = cycle_read_inc_oam_bug(gb, gb->pc++); - addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); cycle_write(gb, addr + 1, gb->registers[GB_REGISTER_SP] >> 8); } @@ -625,7 +614,7 @@ static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = (opcode >> 4) + 1; gb->registers[register_id] &= 0xFF00; - gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++); + gb->registers[register_id] |= cycle_read(gb, gb->pc++); } static void rrca(GB_gameboy_t *gb, uint8_t opcode) @@ -655,7 +644,7 @@ static void rra(GB_gameboy_t *gb, uint8_t opcode) static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify timing */ - gb->pc += (int8_t)cycle_read_inc_oam_bug(gb, gb->pc) + 1; + gb->pc += (int8_t)cycle_read(gb, gb->pc) + 1; cycle_no_access(gb); } @@ -677,7 +666,7 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) { - int8_t offset = cycle_read_inc_oam_bug(gb, gb->pc++); + int8_t offset = cycle_read(gb, gb->pc++); if (condition_code(gb, opcode)) { gb->pc += offset; cycle_no_access(gb); @@ -752,13 +741,13 @@ static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]++) << 8; + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[GB_REGISTER_HL]++) << 8; } static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] &= 0xFF; - gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]--) << 8; + gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[GB_REGISTER_HL]--) << 8; } static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) @@ -796,7 +785,7 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t data = cycle_read_inc_oam_bug(gb, gb->pc++); + uint8_t data = cycle_read(gb, gb->pc++); cycle_write(gb, gb->registers[GB_REGISTER_HL], data); } @@ -1034,15 +1023,15 @@ static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) { uint8_t register_id; register_id = ((opcode >> 4) + 1) & 3; - gb->registers[register_id] = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++); + gb->registers[register_id] = cycle_read(gb, gb->registers[GB_REGISTER_SP]++); gb->registers[register_id] |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. } static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { - uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); - addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); + uint16_t addr = cycle_read(gb, gb->pc++); + addr |= (cycle_read(gb, gb->pc++) << 8); if (condition_code(gb, opcode)) { cycle_no_access(gb); gb->pc = addr; @@ -1051,8 +1040,8 @@ static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) { - uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc); - addr |= (cycle_read_inc_oam_bug(gb, gb->pc + 1) << 8); + uint16_t addr = cycle_read(gb, gb->pc); + addr |= (cycle_read(gb, gb->pc + 1) << 8); cycle_no_access(gb); gb->pc = addr; @@ -1061,8 +1050,8 @@ static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); - addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); + uint16_t addr = cycle_read(gb, gb->pc++); + addr |= (cycle_read(gb, gb->pc++) << 8); if (condition_code(gb, opcode)) { cycle_oam_bug(gb, GB_REGISTER_SP); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); @@ -1085,7 +1074,7 @@ static void push_rr(GB_gameboy_t *gb, uint8_t opcode) static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a + value) << 8; if ((uint8_t) (a + value) == 0) { @@ -1102,7 +1091,7 @@ static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; @@ -1121,7 +1110,7 @@ static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; if (a == value) { @@ -1138,7 +1127,7 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a, carry; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; @@ -1157,7 +1146,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; if ((a & value) == 0) { @@ -1168,7 +1157,7 @@ static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; if ((a ^ value) == 0) { @@ -1179,7 +1168,7 @@ static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] = (a | value) << 8; if ((a | value) == 0) { @@ -1190,7 +1179,7 @@ static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) { uint8_t value, a; - value = cycle_read_inc_oam_bug(gb, gb->pc++); + value = cycle_read(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; @@ -1218,7 +1207,7 @@ static void rst(GB_gameboy_t *gb, uint8_t opcode) static void ret(GB_gameboy_t *gb, uint8_t opcode) { GB_debugger_ret_hook(gb); - gb->pc = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++); + gb->pc = cycle_read(gb, gb->registers[GB_REGISTER_SP]++); gb->pc |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8; cycle_no_access(gb); } @@ -1243,8 +1232,8 @@ static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) static void call_a16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t call_addr = gb->pc - 1; - uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++); - addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8); + uint16_t addr = cycle_read(gb, gb->pc++); + addr |= (cycle_read(gb, gb->pc++) << 8); cycle_oam_bug(gb, GB_REGISTER_SP); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8); cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); @@ -1254,14 +1243,14 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) { - uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++); + uint8_t temp = cycle_read(gb, gb->pc++); cycle_write(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); } static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] &= 0xFF; - uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++); + uint8_t temp = cycle_read(gb, gb->pc++); gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + temp) << 8; } @@ -1280,7 +1269,7 @@ static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; uint16_t sp = gb->registers[GB_REGISTER_SP]; - offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++); + offset = (int8_t) cycle_read(gb, gb->pc++); cycle_no_access(gb); cycle_no_access(gb); gb->registers[GB_REGISTER_SP] += offset; @@ -1305,8 +1294,8 @@ static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; - addr = cycle_read_inc_oam_bug(gb, gb->pc++); - addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; cycle_write(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); } @@ -1314,8 +1303,8 @@ static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) { uint16_t addr; gb->registers[GB_REGISTER_AF] &= 0xFF; - addr = cycle_read_inc_oam_bug(gb, gb->pc++); - addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8; + addr = cycle_read(gb, gb->pc++); + addr |= cycle_read(gb, gb->pc++) << 8; gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8; } @@ -1338,7 +1327,7 @@ static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) { int16_t offset; gb->registers[GB_REGISTER_AF] &= 0xFF00; - offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++); + offset = (int8_t) cycle_read(gb, gb->pc++); cycle_no_access(gb); gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; @@ -1512,7 +1501,7 @@ static void bit_r(GB_gameboy_t *gb, uint8_t opcode) static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) { - opcode = cycle_read_inc_oam_bug(gb, gb->pc++); + opcode = cycle_read(gb, gb->pc++); switch (opcode >> 3) { case 0: rlc_r(gb, opcode); @@ -1629,7 +1618,7 @@ void GB_cpu_run(GB_gameboy_t *gb) gb->speed_switch_halt_countdown = 0; uint16_t call_addr = gb->pc; - gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); + gb->last_opcode_read = cycle_read(gb, gb->pc++); cycle_oam_bug_pc(gb); gb->pc--; GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */ @@ -1664,7 +1653,7 @@ void GB_cpu_run(GB_gameboy_t *gb) } /* Run mode */ else if (!gb->halted) { - gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); + gb->last_opcode_read = cycle_read(gb, gb->pc++); if (gb->halt_bug) { gb->pc--; gb->halt_bug = false; From b454ee28dbcff9d28418143ffe5d618eee003938 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 27 Jul 2021 22:18:28 +0300 Subject: [PATCH 228/365] Fix an issue where SameBoot gave DMG games the wrong palette and needlessly drew the DMG boot tilemap --- BootROMs/cgb_boot.asm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 1345915..a04949d 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -271,7 +271,7 @@ TitleChecksums: db $A2 ; STAR WARS-NOA db $49 ; db $4E ; WAVERACE - db $43 | $80 ; + db $43 ; db $68 ; LOLO2 db $E0 ; YOSHI'S COOKIE db $8B ; MYSTIC QUEST @@ -330,7 +330,7 @@ ChecksumsEnd: PalettePerChecksum: palette_index: MACRO ; palette, flags - db ((\1) * 3) | (\2) ; | $80 means game requires DMG boot tilemap + db ((\1)) | (\2) ; | $80 means game requires DMG boot tilemap ENDM palette_index 0, 0 ; Default Palette palette_index 4, 0 ; ALLEY WAY @@ -374,7 +374,7 @@ ENDM palette_index 45, 0 ; STAR WARS-NOA palette_index 36, 0 ; palette_index 38, 0 ; WAVERACE - palette_index 26, 0 ; + palette_index 26, $80 ; palette_index 42, 0 ; LOLO2 palette_index 30, 0 ; YOSHI'S COOKIE palette_index 41, 0 ; MYSTIC QUEST @@ -918,8 +918,9 @@ EmulateDMG: call GetPaletteIndex bit 7, a call nz, LoadDMGTilemap - and $7F ld b, a + add b + add b ldh a, [InputPalette] and a jr z, .nothingDown From 0ff882f3bc384ea7e1ad0bcde4d772a3e02e6f85 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 28 Jul 2021 00:47:19 +0300 Subject: [PATCH 229/365] Actually do what the previous commit claimed to do --- BootROMs/cgb_boot.asm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index a04949d..848305c 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -475,7 +475,7 @@ ENDM palette_comb 17, 4, 13 raw_palette_comb 28 * 4 - 1, 0 * 4, 14 * 4 raw_palette_comb 28 * 4 - 1, 4 * 4, 15 * 4 - palette_comb 19, 22, 9 + raw_palette_comb 19 * 4, 23 * 4 - 1, 9 * 4 palette_comb 16, 28, 10 palette_comb 4, 23, 28 palette_comb 17, 22, 2 @@ -918,9 +918,11 @@ EmulateDMG: call GetPaletteIndex bit 7, a call nz, LoadDMGTilemap + res 7, a ld b, a add b add b + ld b, a ldh a, [InputPalette] and a jr z, .nothingDown @@ -979,7 +981,7 @@ GetPaletteIndex: ; We might have a match, Do duplicate/4th letter check ld a, l - sub FirstChecksumWithDuplicate - TitleChecksums + sub FirstChecksumWithDuplicate - TitleChecksums + 1 jr c, .match ; Does not have a duplicate, must be a match! ; Has a duplicate; check 4th letter push hl From 690a263648238f8b4ec4d277329f792c671c0edb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 29 Jul 2021 22:43:55 +0300 Subject: [PATCH 230/365] Major improvements to JoyKit, fixing Xbox and 8BitDo controllers as well as analog mappings in PS controllers in some situations --- Cocoa/GBPreferencesWindow.m | 24 ++++-------- Cocoa/Preferences.xib | 4 +- JoyKit/ControllerConfiguration.inc | 8 ++-- JoyKit/JOYAxis.h | 7 ++++ JoyKit/JOYAxis.m | 45 +++++++++++++++++++++-- JoyKit/JOYButton.h | 10 +++++ JoyKit/JOYButton.m | 8 +++- JoyKit/JOYController.h | 1 - JoyKit/JOYController.m | 59 +++++++++++++++++------------- JoyKit/JOYEmulatedButton.m | 3 +- JoyKit/JOYHat.m | 2 + 11 files changed, 118 insertions(+), 53 deletions(-) diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 22c60e8..47302dd 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -491,16 +491,12 @@ [GBUnderclock] = JOYButtonUsageR1, }; - // Todo: JoyKit might need an API to match an axis to a button if (joystick_configuration_state == GBUnderclock) { + mapping[@"AnalogUnderclock"] = nil; + double max = 0; for (JOYAxis *axis in controller.axes) { - if (axis.value > 0.5 || - (axis.usage == JOYAxisUsageL1 && button.usage == JOYButtonUsageL1) || - (axis.usage == JOYAxisUsageL2 && button.usage == JOYButtonUsageL2) || - (axis.usage == JOYAxisUsageL3 && button.usage == JOYButtonUsageL3) || - (axis.usage == JOYAxisUsageR1 && button.usage == JOYButtonUsageR1) || - (axis.usage == JOYAxisUsageR2 && button.usage == JOYButtonUsageR2) || - (axis.usage == JOYAxisUsageR3 && button.usage == JOYButtonUsageR3)) { + if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) { + max = axis.value; mapping[@"AnalogUnderclock"] = @(axis.uniqueID); break; } @@ -508,16 +504,12 @@ } if (joystick_configuration_state == GBTurbo) { + mapping[@"AnalogTurbo"] = nil; + double max = 0; for (JOYAxis *axis in controller.axes) { - if (axis.value > 0.5 || - (axis.usage == JOYAxisUsageL1 && button.usage == JOYButtonUsageL1) || - (axis.usage == JOYAxisUsageL2 && button.usage == JOYButtonUsageL2) || - (axis.usage == JOYAxisUsageL3 && button.usage == JOYButtonUsageL3) || - (axis.usage == JOYAxisUsageR1 && button.usage == JOYButtonUsageR1) || - (axis.usage == JOYAxisUsageR2 && button.usage == JOYButtonUsageR2) || - (axis.usage == JOYAxisUsageR3 && button.usage == JOYButtonUsageR3)) { + if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) { + max = axis.value; mapping[@"AnalogTurbo"] = @(axis.uniqueID); - break; } } } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index b6da6ba..24e5de2 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -736,9 +736,9 @@ - + - + - - + + @@ -323,8 +323,8 @@ - - + + @@ -332,8 +332,8 @@ - - + + @@ -341,8 +341,8 @@ - - + + @@ -350,8 +350,8 @@ - - + + @@ -369,8 +369,8 @@ - - + + @@ -378,8 +378,8 @@ - - + + @@ -399,8 +399,8 @@ - - + + @@ -408,12 +408,12 @@ - - + + - - + + @@ -430,8 +430,8 @@ - - + + @@ -449,8 +449,8 @@ - - + + @@ -473,8 +473,8 @@ - - + + @@ -500,12 +500,12 @@ - + - - + + @@ -513,16 +513,16 @@ - - + + - - + + @@ -541,8 +541,8 @@ - - + + @@ -550,8 +550,8 @@ - - + + @@ -559,18 +559,18 @@ - - + + - + - + @@ -583,8 +583,8 @@ - - + + @@ -592,14 +592,14 @@ - - + + - + - + @@ -618,7 +618,7 @@ - + @@ -650,8 +650,8 @@ - - + + @@ -659,8 +659,8 @@ - - + + @@ -675,8 +675,8 @@ - - + + @@ -707,8 +707,8 @@ - - + + @@ -725,8 +725,8 @@ - + - + - - + +

    v~{qotKc(^CSL{nFjA-R#r)k#@5h+4P3H3 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 =#bdPl^da2PW@5H*+2h*Br+#2o29&8dCU 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 Date: Sat, 27 Feb 2021 04:13:50 +0200 Subject: [PATCH 111/365] Allow make install under FreeDesktop --- FreeDesktop/AppIcon/128x128.png | Bin 0 -> 26046 bytes FreeDesktop/AppIcon/16x16.png | Bin 0 -> 19956 bytes FreeDesktop/AppIcon/256x256.png | Bin 0 -> 16633 bytes FreeDesktop/AppIcon/32x32.png | Bin 0 -> 20600 bytes FreeDesktop/AppIcon/512x512.png | Bin 0 -> 36303 bytes FreeDesktop/AppIcon/64x64.png | Bin 0 -> 20732 bytes FreeDesktop/Cartridge/128x128.png | Bin 0 -> 26853 bytes FreeDesktop/Cartridge/16x16.png | Bin 0 -> 20557 bytes FreeDesktop/Cartridge/256x256.png | Bin 0 -> 35511 bytes FreeDesktop/Cartridge/32x32.png | Bin 0 -> 21299 bytes FreeDesktop/Cartridge/512x512.png | Bin 0 -> 60580 bytes FreeDesktop/Cartridge/64x64.png | Bin 0 -> 23016 bytes FreeDesktop/ColorCartridge/128x128.png | Bin 0 -> 26446 bytes FreeDesktop/ColorCartridge/16x16.png | Bin 0 -> 20504 bytes FreeDesktop/ColorCartridge/256x256.png | Bin 0 -> 34200 bytes FreeDesktop/ColorCartridge/32x32.png | Bin 0 -> 21157 bytes FreeDesktop/ColorCartridge/512x512.png | Bin 0 -> 57482 bytes FreeDesktop/ColorCartridge/64x64.png | Bin 0 -> 22767 bytes FreeDesktop/sameboy.desktop | 12 +++++++++ Makefile | 35 +++++++++++++++++++++++++ SDL/utils.c | 14 ++++++---- SDL/utils.h | 1 - 22 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 FreeDesktop/AppIcon/128x128.png create mode 100644 FreeDesktop/AppIcon/16x16.png create mode 100644 FreeDesktop/AppIcon/256x256.png create mode 100644 FreeDesktop/AppIcon/32x32.png create mode 100644 FreeDesktop/AppIcon/512x512.png create mode 100644 FreeDesktop/AppIcon/64x64.png create mode 100644 FreeDesktop/Cartridge/128x128.png create mode 100644 FreeDesktop/Cartridge/16x16.png create mode 100644 FreeDesktop/Cartridge/256x256.png create mode 100644 FreeDesktop/Cartridge/32x32.png create mode 100644 FreeDesktop/Cartridge/512x512.png create mode 100644 FreeDesktop/Cartridge/64x64.png create mode 100644 FreeDesktop/ColorCartridge/128x128.png create mode 100644 FreeDesktop/ColorCartridge/16x16.png create mode 100644 FreeDesktop/ColorCartridge/256x256.png create mode 100644 FreeDesktop/ColorCartridge/32x32.png create mode 100644 FreeDesktop/ColorCartridge/512x512.png create mode 100644 FreeDesktop/ColorCartridge/64x64.png create mode 100644 FreeDesktop/sameboy.desktop diff --git a/FreeDesktop/AppIcon/128x128.png b/FreeDesktop/AppIcon/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..6303f2373c93c75f2c29db62630022ff208aa853 GIT binary patch literal 26046 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 OtQhL<(8BoHW!WtN literal 0 HcmV?d00001 diff --git a/FreeDesktop/AppIcon/16x16.png b/FreeDesktop/AppIcon/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..6c3f81d08375d3e3869008571d3482f8e8f1b4d3 GIT binary patch literal 19956 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 literal 0 HcmV?d00001 diff --git a/FreeDesktop/AppIcon/64x64.png b/FreeDesktop/AppIcon/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..4a54e94eb23e76bee1e09ecd205e1d6c294b2df3 GIT binary patch literal 20732 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 X1U~XzwCNnx^9i}GUM_h~%hvo0$rggL literal 0 HcmV?d00001 diff --git a/FreeDesktop/Cartridge/128x128.png b/FreeDesktop/Cartridge/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..bc14d7922d2af25b6572653031447008d4b0c2a7 GIT binary patch literal 26853 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 literal 0 HcmV?d00001 diff --git a/FreeDesktop/Cartridge/16x16.png b/FreeDesktop/Cartridge/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..3cbd9ae7afc08e91da93da8401b3c54f675fd670 GIT binary patch literal 20557 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 literal 0 HcmV?d00001 diff --git a/FreeDesktop/Cartridge/512x512.png b/FreeDesktop/Cartridge/512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..71314f72bbbdd4c42fa9764598565571ed5f149e GIT binary patch literal 60580 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 F{0EYSu#o@& literal 0 HcmV?d00001 diff --git a/FreeDesktop/Cartridge/64x64.png b/FreeDesktop/Cartridge/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..8835f79c48cb2b35580f65fb95a56cf5ff3e0363 GIT binary patch literal 23016 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 literal 0 HcmV?d00001 diff --git a/FreeDesktop/ColorCartridge/128x128.png b/FreeDesktop/ColorCartridge/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..da4757e816c22938c2652d79f84ff1f8c222c7a1 GIT binary patch literal 26446 zcmeHv2{_eT_y5-{LrD@coKPgg!7 z%yW_uGA91p?dJC0_rCAnZ}^Xo^B9)>U2E;VKYQ&pd7kebZ7o%53ML8&f~eKilrDnb zemmdfq~K?REV2dsI)GC%a)BVqBRk&&Q0#9E5JZuUQB>5{w!z}DE;d*kOkGhChI7WE zG4?13^61NowAR+cF!>LAW2fXlkrEux)VeXjEg@4ol7I?Zn8Vfl zGN1{Hl(A@YIfIMN6hYi%#JE~wYwa=IN%rB7A8~7KHzA)&N^+={&n`rb({I~B&vTlV zAEKp(FwzIajzJOxe$111pI?OTkwGd!DeR}AVpb@6EO~+s%4C9~V~+UHLD85a10;}! zlgT|f$nB7y(m3vU4AdnD`8{lEeL&cbgUruNEcinRRp`qY+Q|b#oQ7UEx0*H+J)R|k zmI{3rUv3_2L-=#ujz}N5QvrZH?I&>ffcW{*ZJ9E{-U%rx0>8+U2k#I-w9v(b0=Pz9 zePQ!c^$$y@ga}~EM)_fD>L1Q+>Yj>eIzS$0Wu#R(&*}19*LwY;+W=?Ku@~H%ohPs7 zQEcPgO4)D1F4i$_!d5t^tQ${iIkU>0aK3+1A`hu6wMAjd8>D0PhKB6)T=bckS1%?t zP+{mse+|_6i&i5kWW+ppWTnLw&ShPXyTl!LzvC5h&qhr&^1M0M=tLnCcZ5l1@<`U2 zZ0&9G5$IBEwGst1Qlnj{_qy%%{d^x~b_K~GzKOoq=Dq!W5=5LQ({fK;oBrnQ2SG^+ zv2Qs`2nI9>Xht81)-XICB=K9AdVn^8uH(RCUM9U8KiaJJGgk}_C8qc-&HF~d0|K2qb4g!Y&8OTHG)^u@ZP@%%7NjRE_HXy=3;7K$`FqB+X38bf*=0 z!oRB#EmAsDNQ9awc!hs!hJ@}_Dpn^koKuW>%5Qu3jgqO(6$DkV;#6YHM|K1o-&wK5 zqc+rD3=RryPkP#=o-$wc53%sm5u|<%6}&JJ^yQ^EUnc|?EG9ft_bQJm^MGAbd7g1J zvv=^-y2CgsW=iYerCR@yF(;D&_5q3k6_bIdtoM^tcfU+|R&hbZPltj8n1 z#H=;0S@=j))BDo;9`+@ZyS1$bqd}0PiI1HnKM@nAX{hpE?Y*YWDLjqWft$B<*u$SH z7qWZNJ_%$_dSIwApe(H+t@@ec;98d1$NV`s7XO zV!D49#V4Ils87%`rDiZ%eJQOq<)uxWjceh-zN>xSl8+qEttO_mr+n4?O1eR^ab&~# zWWfDkTH`xug%{J$r7Ndjm<|>)>Q(B!uyKbp>>UGZ47xt2kN%9k`MFTg3xDO~V5 zC%iS<9xYn%!1Gpb1GCO0U3a|;+Ecoe;T~fw}NMFYqZ)DNy}@cfl?x ze-Qal!beV@O)o)T!RL56DN-xaC{jOCEK~c8^_l8!$7e%~YmAPH9fb$2(Nhj-3yu;G7mO_k2I!_-b>Bv%;;1p z-5Pi_a7mhN$ZZH$ZtZ2Xu|(B&FaO@LnE04}q1y$O1+NOsmcFk)+$`xm(d%6xH>ot) zHF=McmZY^Td7x`R_pAsqB-bu+sQx6Sx3WQ!MYvD+w#@m`kDV=ytSxtq^p-Hmrgylb4zgpUZ=@)=@m%BB*p@5n z%u04s)>O`X??^7PCxGo_<^5+V#VM01!=lb&X5U}VrQJ<-DaV5GdbwVj2`ysR$*L81va!TKDDac`~2#)!KPt%tnZhtNT$x%MWS0oo@DVX8shK27tFL# z7xfjT6b;YZa$Ver-pD463J6e?ye%r06rBHJw87=jurw_$IqPLP#KI#+W@Mq zB`v4)iiB(Tvh9q>lpSBsqwIvu*6ET6nKv?gVkVA-gZPTxA*pAp_6hF8>&65n{DbJ> zYB#P$t@**Viq(y&m6rL1xvElu((M(6p__TP{X@Tmo@0F`&F`DEl>EUxXgF^2&7v?7 z0Z~uF3)!pU%`864wHi$YDK@+hbpG?O$4>T+9wu(N-~en{K9kO6bPE z9{z2uWh!TeL!Cnr+lXx%cJRqruAaL&e)~%)`+`$a5D`S92gSlmtRMP|v6r0u?#;DD#?}i(l5^C@L6G8cjGiIh zP*X$F66?T^w8C1T_&pqOAUy-wh@1xwX=#VT!z@tN7)KfQ`HC8L7{*G5{gQ|#Tob2= zvcaf%IiqyFwDc^!>?|d$*yUs?q&*~o0tXZx3G;BUcXW~TkYWE_uOy&%h6UJRzq8=& zWY`sUCWIMkYQq$<&M24&zX+ctTu2ZmCc!T#A|fUx#0vwd1VR8VC?F`zCnzC_KuE&j zus=TRvJ`-lcD6!GUQ|-~V>9_hj7=)&C>Kj-3=WUMI>L7JA}z44co}x~or!iof9%Tvw>y!e%OBVQ z6a_qxH~~R^xWL~DSy}GN;9Q;Ue_xuFr2xtv<$!X;y8t=Czm)@!YHIF^{_S)Q4u31@ zf>(9}JNN^Uzm;^+^TeS9E}~qpuFjSyWjC-Aj=x*H3;rVNPjL633b6mJBHYhP>F?LI z`}kkNh{vG+#X@$T{7W&E2j*Xh?L7G*_Pb5}IBIEtx}>5r3W>)$>tV6>vOmoA&pHp3 zc{}C_@sNdnx)cjTapHwShEnRo65K#TY+gM>Of%;uj zlC%&JwLn=R1o?!lERcMHf?^VU7Q(`oeBy#|VYG;tsECNw?nJ*R`=^O6IAee_MB4wt zv$Gc~FqxQ`xTvVOm?fW;7}|;tA#MfdlRyii`Gnz?XmJZmq#zn?^>Z(OqvoHcQp30a z%k%ujB7t50?(TF^PX9Ri^MO6)cejN@I=i5DZnX^i&uhKw=XTxLj?C}gK@w@X- z4J>4DxA=$Ne-!-d1kwUKA;KQ>zu+ibkZ%9AgAkWM2?|?T2=iGW5JKQ`i6QtbEJfga zC=qbIM64`P!U&O{2mPNt2q7^^LGhoy=KthD{AtOSHb_Tnl$ET&&%ON|_Ki@<|8^i}4AIiXxH1g5n4ZG4Y@K`%j#mxE@HP1mTiGyK!Rw>~>us z*3k{+{2y_Gz||r_T8naakwrUW9biZt&K_fl+;OM^ZjM%eH#6>_FffBWpKuVwx z62exZ`*-vI&X~BjT=U13{15rnf0cj#f8keuWI6wsasJF=|2ae5sc}H6uJpsVsO)&e zoh0dho{)l^5|oRQzu^9}MA&OM{EPTMc+x+#gQDQyR|z5_Lc(ZaP=5%CTf+aS5`@r5 zJ_{j`F^M3>5SD24&h_4z>lbBzHy8H5S}6RO3_yz7?l2Pg1s3WO}wZ}SZ zVy#fJ!qNhNH~fpvKOcktV`~5SH>Ru-#sz1O^!!!$m#4o-?-byYpt=E>_U}PZR!~~t zpT_>mZ@5#X*t`B-g@_5k!AC%F=jR`yzdrt}sL{U{{q^x*MgNr4cf{ajh5nfBFT=k` z?Q{u#K&cBlV<=frGzx<(`me)(mC(j`pzIBmFrYql*{QPxg+TiDyU<^s{H>(Xzm)vt z$sdwGF6%E{tl!Py$0+ELf%crhZh!6!?g>=UWk3T_JYzb&puqcQ0#@+hifk= z?egrywF|{wh<&*Bg3>O}K3uy{?1k8eYcDA6^6bO43&mcDeYp05(k{!?g>=UWk3T_JYzb&puqcQ0#@+ zhifk=?egrywF|{wh<&*Bg3>O}K3uy{?1k8eYcDA6^6bO43&mcDeYp05(k{!?g>=UWk7S7sa1%yg)gE z*Ic-R_gP5R`riWY!hl(-UDSjiM=l6*$$_BnUm)nlB?!VALeLy|0UZ1*1RY8}u4y?9 zK@3akN(y=&eV>+C$t@(Rx4LIR*|AJbxo5@ctxFYG!=es68 zpBWu3n&Yk8X`zwL^`+%Qw(@1`r2N^;vOa5}-!?WvmZaN+sfYp=1lvVl zR`8qj_@j*8y?XU3FD53&x0v_LAt;Hvc!&@RGmQK$4Vlpp4|va4!9FgfFDK7PrlzIo zaMO9jPx-RdGPdL|^0w6HXBI<#uFSVxO^@aCawN2YuZB>T%NL&akrEO-&7f#<5{H<3 z-L}84j?b11J4!$!;s;H7l=BR4IrLXk+6a@|_mj5dn;X$XxaqeirPn7S);>N7?Xw`r z@CnbJsn+XLQ~JW(qyy1-MyK}5LTh+3U6J_Pye)>NrqqNUVaR9?j$T5@PL*J!d@dx~ zw-b?wVLF#WtI;_SwLshery(ZD=T+39Bfp{TE(e8i9ylcPwOx00(0y39%;aDsN`E2C zpcsPHa6mAw8q-%(OU>wl5l>d>HbpvR?z?B%oaLU=-wMs1sV{b52)J&MN8XZ86RdLh z;BB$b^Wq2bresY{fhtb|o5n{xtM6VZ&mRBO{S2-dF(p2?O7L5+4iqMjld@tUy_il% ztRT-xdmTouFY6#j`tVj%$arnBLv-q$i|My~WJ3*kRa|@s={gZQy!6P}Qy%d^vAz$- zIjtMS;vs*T==_C`i6YA5zxI&Ch?UojDu3>g3*@J9ib#Pb zOpVi=fppSRCd#5;)WEyiedi-*$?(BUv-8uC-zt)~<*ekR0N$3a&);rf zsmLcdT7;OJ6eJ(1@X`}Cz>VLTJ086^r+zBJS5R1ZDa$z9+~ru;U9uA*pIT`uj@>MO zq+wkZY0hQ4<*HE9ttm{!;l#YUZpiC~e#YQede>g`yxn`=7SYV_9X52g53vP|i^U{b)IVQU`ZrWTdFbY&^rrQ-}U2)1JY%ga{U*FVa_* z(9{i&HN-TJBx*lJCSdzT!E&e?YNh5Y6*-2?;f5qolhb2v3fXVHZDV7NLHe&b?v+i& zdw<#bHh;-sT>Rk8n>W?cIt`7DiCXgU;9YlT!Tm!%xcNaICQnpg!Vk{BHlLT2jO_Hs z=7oG-OAil*nyuv=r{z!jIOf~g+5F7X9yCi!OZd%+Ix@EB*lSD2H<%N)zNR@Xj46YC zdr>sPsSG3ix$kn*y;@)%t>L~avTkc+&?zX-LI2p>#^$4{sj2Cc8Ip^kJErkM1$sOq z3AT$j&9PH4?ICGf3RGg}rVqj~lf|a$$j7GM0dtCnfW>O_JOpPza*R=ykB$Od=3)nq za5ZyJPtOLK%aQ)(aBv^FIY<;9)8B#2ZEVPEzRzOQQ#deKcXu)S{?v7EID--(?>4f} z)ofogDaBo0+`)UZB0`w~g1YUzeElm^ye<$zO~`vtL^bTkxoy z6U?4r4_AXd(o*bk1*dR?w>%s81qwad+nMf+R1J?yN{-!4lE0r!-81RQny5`KBO|kO z-rRTbdT*SV$#3}e_8K+ko1`?}O#-g^eKB=Qe(jR<;<|LzR?fBMGjOU-ZDF#}6HQKh zXQIUP$bgk>W775d3J(Hqi#&$zFm%$jx7KhesUTIht8{Evza9oVL1m7M1N$pH?2lyN zq&+a5-PHX&$o0w)(Afig0wxyBNJ^q}O{uji$sz$4zc zn(`o=S^JB{y1uBhBJ`Pt#s=h9&ft9YqBjI-cGBEY$}KOy z+uf~Sntfg*iadf`y~ovygFc((n^T^WLoDRPIAEnr z>XbJief<;aw1cYmIq8Cw$!|cWJ$c2&D#r~gW7W3qi$8u>btfrMjq}FEbfpB2xYl}v zrma6hQ&F0=>C-2ktLXaz~gAqoSkjFPDB>=(h4`*C*oBJBL~hM#I?qohh%|K zQbwW<2Q_{f@}55t)pCL<*ubdS>umq))ri`nhYgcb24pZyHxRcWW~K1|XmhEmL#YA& z6dl3CzA)Hp9~nD4H#e6t4gybBv{57p2wUXDz(Y8fF(17KqmfV4_RF5ZBZT5w4KVcU zVb^)?Hyv?TFj3*8O&3}Qk+Il8*Zlsx*=R&&pcRL)NEdD0krEL4fp4=idJ%kFOmy~T zDL52Dh!%1B?%5#0eBK85z4C`bI&>%QvTJlE*Rc}xdL??FrVr{K{BV@+&auEE7)e^I zn$Cj&4TO4Y;PH%}t`0SdsU63KNX>JIThaKsy4tyMu8QI_3kxO8Q>=r#0d8~iru$|^ z{Pw(FU%EF}tiWA%;Dh<2p3HSRF<+h>Shgg{o;j;S2Rx6xsBt%CyN#1g4K<+?v#R$t z;dKajVcwP-N!)zF1a6rJM~=$23n!&@{l62_dODmhnRyb83Rk;zalns*C zki{H4s(kp_vuDj#LSdYD5T3wiw9h@x;>qA+q!jmA`x07SQK5QNIZ?t@_$)^{BS?vS z*5FDy$>9}Z$A%Bd!!B#wy1-;b-BA1bHC6Ch-1Pct>QHRziAP#BvNAG*?S{aK%Hx<- z%O5$ib|R+9-xb;Q!0xis-XUg58qCp5OvNN=bB{z^kiCv%z*CL8o$?tVyh;2xG3JR+ zLGxgCs+VfFVI+80p~c|2fomtQ)dJ-8#mD6wo17PCOPjr59^X5!y&(?_t$70b8j)Ld zfR_!Doz0}QiF`Yaa7bIkd{gQh5j_($FA0TNX#4us9P(p<{4z46LF!hO!yt*xVASV1;HRq|0PhX=5!bsta1agcdY>+9zg7Ty{f zG9$jJkeQVQ%zHGuVy?cvo)CmJ;2lXxNk_{0f$4gAdxyWY(&nWPSK|cNq`klYym6tI zm)GMA9`GnJgGW5%35!%J#sY)66Pcs0@1MjCJR?ZqKF-hI&YsyJ=H^sD3`o?jU0f7m zDFo@|1smlDuR}R|fY%`+B5Frar((|P>p!;;?0e!d{(|~98SmnZyktvWzmnkTpevi~ z)f?S8H^#-8#c}2h)gX>Sep_2xd~H(F(i$~f5ieth4^UH653aZjxb~Q8vbhXjUG12k zNr;l3G3eoac+TT%hicuDwAOFp9dFBy@|Cn5lKvvE+e#z*Fv~h!&o*Wldj_hs>tE|) zCV#gog6D*qCnyqPxoC-85fJdHn+w|xZw(%uXCZ)qE9vYUKkst9OU=L)+TavCPoe`+I4dlY^s&vZLj5D}?jPXW}&@jreZnS#Gb> zJoVZAS&5vqNin%yjzO)$oAtiNPgA9Cw753H?WIdc+iA`yLh|y6Dm{X?&&95Xx7ioQ zvmEjRIdr)Z)bX{@TINj7^r^42ov2iqB_)ShPIE)7LS^5DVi{?ZZ>6IilZwP8Ta|-# z6n=rZFMM^@w?DUZ_g|5?=#UpM{&32B_EEkotZn&K=kWw@SJ$E?0{K%)?~<%p1-2h; zKK^WXp|VWS!)w-}@s^g)lX`OjsM)>Vq#l>_9qjBd56PXT;CtJIrQVr~Cu$;=w61A~ z$jz}5of;Ofn+f8WbfF2OLH0PGG=qB%)=o|@%FghyCc1sSSj-GfJd!A~8H<~2!&Sci zx{yG0Wj*dL^wze~r`mZUGGL{r1mZ|YCnfLLx`WkQ;cD_8zlTp;T-fl=iBo@ev^{LN zYIWpj@oMEvNvKb1UsBf*+4lFhdQA^Oiyzv(lr{zKYyguhqIyaksAS2?C=jQWgR6{{ zX3l{}l?ESE^_$a1B`otlpBe5k%d$E)v}6|h^f?)4cJ$`s>fEXYRX!YzAHdR8BR_uS zd8MC~kWm4RlEzmrcX^w|o1bQrwd9E}Ai`}|_=aai76$8ta+_t%*yc#&tICNbUc?c2 zkj%+MLT~KK$kpDZ6&joN8!Jw)NrFh$-`+1;MeH+)#VW62BN-J6<0pDU?|I*NHg6!j zHJ3-hap!c-(TOVOWFJ`pwRaW4oe|yg93l`Frk<@D!Wa@YpC@HlY|b^5`CPDJ!M11N zRcCicRZqS>$&NL=A@fVc=;wHypz?7_{`{osdRIfkB;kQ|miLCZ6HMwnj!N3kOOB)- zY&sL}$`@>xN-CfE5?5)TPM#a+=6;)}k$1kJ>)dQ*UK9EQ8+6swtJ=&4qH7?4s8$Y4^njXATZ+t@E3d`dK!G)a7Re7#O*m z9s?C>y56T6w6*p2bJxK*_W{CzN>7*0l@D33&AB?Xh)N&Y5f$-~YjDy!F!l@ub%khU z6lWf4&+tDufJgR_KRc=%=cbW?Ie!UP@a#&Bd-ron-$!+9o6|gITTRSD>%lXW@1x`b zHa-uF@FkBog<9hAbmQaWaUr_)t|7}c!66}Y;`5-in<5(7Se^mp5cRyAcjpVXEfmuQ zU-cKyhUe?&hnhHaaqdcrs;a8t8m9yW1+RyPhw~M=`S>vF>gwv(D6=2-8Kt~p6QtjJ zdJ(-xYTw#T&aY~X zD9+Jb^P0R8UP7_f@x(VYm*z#qe02~=N}s5Pz9l?*coBra`cb&nBT0XksW(Vx=lgSV zA|n2}X9iUrKP2~=OU+yiVALVLR~{k%OFeFV{Sm=HwhQgmsYgvKCE)loC5Ojrf)J77?)7(mh!d_NiA8%(f9!Y?9UEI8BW(S&Se5;- zNjd(&@cJ9~FPlqk95EUewzd>gZ3(KJv>@s?fvue4;#&9+hus+;AD?{H9qPMjS-J4y zIONBn8UjHfY;qw{W?lz&Rt*YQCQ^AR0|QJMWf-a~{Y5}p!u^F8#wS0m4p-Q+rx{p4 z{Lkz7yFA%K+Nq;a?h$$!&ul_vRv2VAzbNud`1_Oa^75JkkjM_PCf``Te0w={xWe%Y zf0UgUOASu^UXJhg8S43(($e?q^nT-|sVYU)UvmSWT%jCsX-mQNK({r9>VO5D{pvz7 zw7qmZms0d#RXfp$L9iktsh6tX_SIzGO>ZvSc(E9bvG;kF3~X#W+XSg-y}0|FNvp?5 zUK4A>p=)1=ev@>yZ79BQ^^>mJsR#xZmaKeUfvT^x!6KCgAC`Nrs0zi{Q$VxPOz#%Y zjjUF$%J{6eTp3z?m|iq^{bn|NMp&=n- zprzMO{|4lVA*?cey$|K}^}|~xGhPrfz4OBr<<3wfL~oO(Gj>*WSI?AO(9*JScBTzi zBW?;|so)zbdcu`E3Bl%>hXfF!q5;4*SR^h zS%+*?IV&!m4p+m=f?PUNj;S=r)Y>>Q2-}oDO2phXNZdwrofH zljFp&U77~y+!pI4CeDQHH$DNMFea?=en@5#FhX8BSV!~WrN>EGb>w67P7%J<4Mi1ycHaP*I>+*hR70?OE=Z#K10NS5R z(s)zVQxSDdY`#?hvj;Sl6BTcRN)vC2J%6sChdV|CbEX`fPn?ouMy#pTcu0kx!Qhu`$p+=nJL~zaQ`d6rv`$5oWx3pH7?m+?dfy3w z6G_yz>2Js=b|^u~zaWzj3LF;)C6#WS%Iy?o9paY!c2^eOmf|k5VN74=ms83oE)s!C z0yK5e=z2Jnc!(m5#*eoJPG#9f>3fCvI#|6EvrQ~%B(%TGYiwlv~-ZJ5HhjqG}nJoI1~30f@}hXjD%x%by-x2p4@B3=T< z!<moq)MLeW4 za&@stc(TahcmVP}S~UWx=7gx}jfqP!5Q z(K#Waxg=3t@$#+fcL#|2Kt_0qg(su~2|_~9m1XxlK}ef0j4;gAw5QwwS}QD>-X?=A Ys^dS);hdOv-W#s2tfiEF*8Il*0H7Tk*8l(j literal 0 HcmV?d00001 diff --git a/FreeDesktop/ColorCartridge/16x16.png b/FreeDesktop/ColorCartridge/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..50e6b2b47bcdf081613d0faede2d91711a4ac8e8 GIT binary patch literal 20504 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 Oik-ECRhEV4rvCs;#^~e# literal 0 HcmV?d00001 diff --git a/FreeDesktop/ColorCartridge/256x256.png b/FreeDesktop/ColorCartridge/256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..186f5d30b86ab51723cb0007be7f31d66ec1c121 GIT binary patch literal 34200 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 #include #include +#include #include "utils.h" -const char *resource_folder(void) +static const char *resource_folder(void) { -#ifdef DATA_DIR - return DATA_DIR; -#else static const char *ret = NULL; if (!ret) { ret = SDL_GetBasePath(); @@ -16,13 +14,19 @@ const char *resource_folder(void) } } return ret; -#endif } char *resource_path(const char *filename) { static char path[1024]; + snprintf(path, sizeof(path), "%s%s", resource_folder(), filename); +#ifdef DATA_DIR + if (access(path, F_OK) == 0) { + return path; + } + snprintf(path, sizeof(path), "%s%s", DATA_DIR, filename); +#endif return path; } diff --git a/SDL/utils.h b/SDL/utils.h index 216e723..5c0383d 100644 --- a/SDL/utils.h +++ b/SDL/utils.h @@ -2,7 +2,6 @@ #define utils_h #include -const char *resource_folder(void); char *resource_path(const char *filename); void replace_extension(const char *src, size_t length, char *dest, const char *ext); From aebc11744c53c7ec7007c0184dab98eac708fc43 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Feb 2021 15:37:40 +0200 Subject: [PATCH 112/365] Update readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9721be9..d06ef4d 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ On Windows, SameBoy also requires: To compile, simply run `make`. The targets are `cocoa` (Default for macOS), `sdl` (Default for everything else), `libretro`, `bootroms` and `tester`. You may also specify `CONF=debug` (default), `CONF=release`, `CONF=native_release` or `CONF=fat_release` to control optimization, symbols and multi-architectures. `native_release` is faster than `release`, but is optimized to the host's CPU and therefore is not portable. `fat_release` is exclusive to macOS and builds x86-64 and ARM64 fat binaries; this requires using a recent enough `clang` and macOS SDK using `xcode-select`, or setting them explicitly with `CC=` and `SYSROOT=`, respectively. All other configurations will build to your host architecture. You may set `BOOTROMS_DIR=...` to a directory containing precompiled boot ROM files, otherwise the build system will compile and use SameBoy's own boot ROMs. -By default, the SDL port will look for resource files with a path relative to executable. If you are packaging SameBoy, you may wish to override this by setting the `DATA_DIR` variable during compilation to the target path of the directory containing all files (apart from the executable, that's not necessary) from the `build/bin/SDL` directory in the source tree. Make sure the variable ends with a `/` character. +The SDL port will look for resource files with a path relative to executable and inside the directory specified by the `DATA_DIR` variable. If you are packaging SameBoy, you may wish to override this by setting the `DATA_DIR` variable during compilation to the target path of the directory containing all files (apart from the executable, that's not necessary) from the `build/bin/SDL` directory in the source tree. Make sure the variable ends with a `/` character. On FreeDesktop environments, `DATA_DIR` will default to `/usr/local/share/sameboy/`. `PREFIX` and `DESTDIR` follow their standard usage and default to an empty string an `/usr/local`, respectively -SameBoy is compiled and tested on macOS and Ubuntu. +Linux, BSD, and other FreeDesktop users can run `sudo make install` to install SameBoy as both a GUI app and a command line tool. + +SameBoy is compiled and tested on macOS, Ubuntu and 64-bit Windows 10. From 3c0f4d458d5aa7ba897fd4d07f956104af20456b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Feb 2021 15:51:56 +0200 Subject: [PATCH 113/365] Update Windows icon --- Windows/sameboy.ico | Bin 115816 -> 115106 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Windows/sameboy.ico b/Windows/sameboy.ico index 685523e582d80562daf8b77ece60cfed025591ce..bd8e372d9b67ca5fa7527ed57831ccc8cbade7bc 100644 GIT binary patch delta 15669 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 G`F{bM-nd}^ delta 16359 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 From cd2e4b3cefef65a1f239dcefcb01ad5a85368d81 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Feb 2021 17:34:11 +0200 Subject: [PATCH 114/365] Fixes and improvements to XDG installation --- FreeDesktop/sameboy.xml | 17 +++++++++++++++++ FreeDesktop/x-gameboy-color-rom.xml | 10 ++++++++++ Makefile | 16 +++++++++++++--- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 FreeDesktop/sameboy.xml create mode 100644 FreeDesktop/x-gameboy-color-rom.xml diff --git a/FreeDesktop/sameboy.xml b/FreeDesktop/sameboy.xml new file mode 100644 index 0000000..6c0df37 --- /dev/null +++ b/FreeDesktop/sameboy.xml @@ -0,0 +1,17 @@ + + + + Game Boy ROM + + + + + + + Game Boy Color ROM + + + + + + diff --git a/FreeDesktop/x-gameboy-color-rom.xml b/FreeDesktop/x-gameboy-color-rom.xml new file mode 100644 index 0000000..0a42874 --- /dev/null +++ b/FreeDesktop/x-gameboy-color-rom.xml @@ -0,0 +1,10 @@ + + + + YOUR MOM + + + + + + diff --git a/Makefile b/Makefile index 5335f33..d065750 100644 --- a/Makefile +++ b/Makefile @@ -434,23 +434,33 @@ libretro: # If you somehow find a reasonable way to make associate an icon with an extension in this dumpster # fire of a desktop environment, open an issue or a pull request ifneq ($(CAN_INSTALL),) -ICON_NAMES := apps/sameboy +ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) -install: sdl $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop $(ICONS)(PREFIX)/share/mime/application/x-gameboy-color-rom.xml +install: sdl $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop $(ICONS) FreeDesktop/sameboy.xml mkdir -p $(DESTDIR)$(PREFIX)/share/sameboy/ cp -rf $(BIN)/SDL/* $(DESTDIR)$(PREFIX)/share/sameboy/ mv $(DESTDIR)$(PREFIX)/share/sameboy/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) + -update-mime-database -n $(PREFIX)/mime + -xdg-desktop-menu install --mode system FreeDesktop/sameboy.xml -xdg-icon-resource forceupdate --mode system -xdg-desktop-menu forceupdate --mode system + -su $(SUDO_USER) -c "xdg-desktop-menu forceupdate --mode system" endif $(DESTDIR)$(PREFIX)/share/icons/hicolor/%/apps/sameboy.png: FreeDesktop/AppIcon/%.png -@$(MKDIR) -p $(dir $@) cp -f $^ $@ - +$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/mimetypes/x-gameboy-rom.png: FreeDesktop/Cartridge/%.png + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + +$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/mimetypes/x-gameboy-color-rom.png: FreeDesktop/ColorCartridge/%.png + -@$(MKDIR) -p $(dir $@) + cp -f $^ $@ + $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop: FreeDesktop/sameboy.desktop -@$(MKDIR) -p $(dir $@) cp -f $^ $@ From a59cd856c765ad3fe221728bcf3c8778841784a9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Feb 2021 18:39:14 +0200 Subject: [PATCH 115/365] Fix make install issues --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d065750..8f98132 100644 --- a/Makefile +++ b/Makefile @@ -437,13 +437,13 @@ ifneq ($(CAN_INSTALL),) ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512 ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png)) -install: sdl $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop $(ICONS) FreeDesktop/sameboy.xml +install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop mkdir -p $(DESTDIR)$(PREFIX)/share/sameboy/ cp -rf $(BIN)/SDL/* $(DESTDIR)$(PREFIX)/share/sameboy/ mv $(DESTDIR)$(PREFIX)/share/sameboy/sameboy $(DESTDIR)$(PREFIX)/bin/sameboy ifeq ($(DESTDIR),) - -update-mime-database -n $(PREFIX)/mime - -xdg-desktop-menu install --mode system FreeDesktop/sameboy.xml + -update-mime-database -n $(PREFIX)/share/mime + -xdg-desktop-menu install --novendor --mode system FreeDesktop/sameboy.desktop -xdg-icon-resource forceupdate --mode system -xdg-desktop-menu forceupdate --mode system -su $(SUDO_USER) -c "xdg-desktop-menu forceupdate --mode system" @@ -461,7 +461,7 @@ $(DESTDIR)$(PREFIX)/share/icons/hicolor/%/mimetypes/x-gameboy-color-rom.png: Fre -@$(MKDIR) -p $(dir $@) cp -f $^ $@ -$(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop: FreeDesktop/sameboy.desktop +$(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml: FreeDesktop/sameboy.xml -@$(MKDIR) -p $(dir $@) cp -f $^ $@ endif From e57b5dd57e779acd6a17c257c7e268f750a9ed12 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 27 Feb 2021 19:08:32 +0200 Subject: [PATCH 116/365] Update version and copyright date --- Cocoa/Info.plist | 2 +- Cocoa/License.html | 2 +- LICENSE | 2 +- QuickLook/Info.plist | 2 +- version.mk | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cocoa/Info.plist b/Cocoa/Info.plist index 44a21f0..4d42b46 100644 --- a/Cocoa/Info.plist +++ b/Cocoa/Info.plist @@ -92,7 +92,7 @@ LSMinimumSystemVersion 10.9 NSHumanReadableCopyright - Copyright © 2015-2020 Lior Halphon + Copyright © 2015-2021 Lior Halphon NSMainNibFile MainMenu NSPrincipalClass diff --git a/Cocoa/License.html b/Cocoa/License.html index b21cf8d..9846514 100644 --- a/Cocoa/License.html +++ b/Cocoa/License.html @@ -30,7 +30,7 @@

  • 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

    + + + + + + + + + + + + + + + + + diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index db309e5..6d72652 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -90,6 +90,7 @@ + @@ -98,11 +99,11 @@ - + - + @@ -111,7 +112,7 @@ - + @@ -147,6 +148,17 @@
    + @@ -307,7 +319,7 @@
    - + From 5808d4485f549a7792fc38082f8b880307e08485 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Oct 2021 13:36:58 +0300 Subject: [PATCH 284/365] Drop BOOLs --- Cocoa/AppDelegate.m | 10 +++---- Cocoa/Document.m | 50 ++++++++++++++++----------------- Cocoa/GBAudioClient.m | 6 ++-- Cocoa/GBBorderView.m | 4 +-- Cocoa/GBCheatTextFieldCell.m | 2 +- Cocoa/GBCheatWindowController.m | 4 +-- Cocoa/GBColorCell.m | 2 +- Cocoa/GBOpenGLView.m | 2 +- Cocoa/GBPreferencesWindow.m | 30 ++++++++++---------- Cocoa/GBSplitView.m | 2 +- Cocoa/GBTerminalTextFieldCell.m | 4 +-- Cocoa/GBView.h | 2 +- Cocoa/GBView.m | 12 ++++---- Cocoa/GBViewGL.m | 6 ++-- Cocoa/GBViewMetal.m | 4 +-- Cocoa/GBWarningPopover.m | 4 +-- JoyKit/JOYController.m | 2 +- QuickLook/generator.m | 4 +-- 18 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 108a5c8..48514a0 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -86,7 +86,7 @@ static uint32_t color_to_int(NSColor *color) } if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) { - [NSApp activateIgnoringOtherApps:YES]; + [NSApp activateIgnoringOtherApps:true]; } } @@ -106,7 +106,7 @@ static uint32_t color_to_int(NSColor *color) NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame]; new.origin.x = old.origin.x; new.origin.y = old.origin.y + (old.size.height - new.size.height); - [_preferencesWindow setFrame:new display:YES animate:_preferencesWindow.visible]; + [_preferencesWindow setFrame:new display:true animate:_preferencesWindow.visible]; [_preferencesWindow.contentView addSubview:tab]; } @@ -171,7 +171,7 @@ static uint32_t color_to_int(NSColor *color) - (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { - [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES]; + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true]; } - (void)updateFound @@ -242,7 +242,7 @@ static uint32_t color_to_int(NSColor *color) [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ [self.updatesSpinner stopAnimation:nil]; - [self.updatesButton setEnabled:YES]; + [self.updatesButton setEnabled:true]; }); if ([(NSHTTPURLResponse *)response statusCode] == 200) { NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; @@ -312,7 +312,7 @@ static uint32_t color_to_int(NSColor *color) _downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory inDomain:NSUserDomainMask appropriateForURL:[[NSBundle mainBundle] bundleURL] - create:YES + create:true error:nil] path]; NSTask *unzipTask; if (!_downloadDirectory) { diff --git a/Cocoa/Document.m b/Cocoa/Document.m index dcd061f..2a54a13 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -321,7 +321,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) { if (_gbsVisualizer) { dispatch_async(dispatch_get_main_queue(), ^{ - [_gbsVisualizer setNeedsDisplay:YES]; + [_gbsVisualizer setNeedsDisplay:true]; }); } [self.view flip]; @@ -422,7 +422,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { [self.audioClient start]; } - hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES]; + hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:true]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; /* Clear pending alarms, don't play alarms while playing */ @@ -502,7 +502,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [audioLock unlock]; [self.audioClient stop]; self.audioClient = nil; - self.view.mouseHidingEnabled = NO; + 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]); unsigned time_to_alarm = GB_time_to_alarm(&gb); @@ -684,13 +684,13 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) window_frame.size.width); window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"], window_frame.size.height); - [self.mainWindow setFrame:window_frame display:YES]; + [self.mainWindow setFrame:window_frame display:true]; self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised; NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height; CGRect vram_window_rect = self.vramWindow.frame; vram_window_rect.size.height = 384 + height_diff + 48; - [self.vramWindow setFrame:vram_window_rect display:YES animate:NO]; + [self.vramWindow setFrame:vram_window_rect display:true animate:false]; self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [self.fileURL.path lastPathComponent]]; @@ -846,7 +846,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) + (BOOL)autosavesInPlace { - return YES; + return true; } - (NSString *)windowNibName @@ -858,7 +858,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type { - return YES; + return true; } - (IBAction)changeGBSTrack:(id)sender @@ -1061,7 +1061,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) windowWillExitFullScreen:(NSNotification *)notification { fullScreen = false; - self.view.mouseHidingEnabled = NO; + self.view.mouseHidingEnabled = false; } - (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame @@ -1156,14 +1156,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } if (![console_output_timer isValid]) { - console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 target:self selector:@selector(appendPendingOutput) userInfo:nil repeats:NO]; + console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 target:self selector:@selector(appendPendingOutput) userInfo:nil repeats:false]; [[NSRunLoop mainRunLoop] addTimer:console_output_timer forMode:NSDefaultRunLoopMode]; } [console_output_lock unlock]; /* Make sure mouse is not hidden while debugging */ - self.view.mouseHidingEnabled = NO; + self.view.mouseHidingEnabled = false; } - (IBAction)showConsoleWindow:(id)sender @@ -1390,7 +1390,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) bitmapInfo, provider, NULL, - YES, + true, renderingIntent); CGDataProviderRelease(provider); CGColorSpaceRelease(colorSpaceRef); @@ -1598,7 +1598,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } [self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]]; [hex_controller reloadData]; - [self.memoryView setNeedsDisplay:YES]; + [self.memoryView setNeedsDisplay:true]; } - (GB_gameboy_t *) gameboy @@ -1608,7 +1608,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) + (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName { - return YES; + return true; } - (void)cameraRequestUpdate @@ -1756,7 +1756,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) break; } window_rect.origin.y -= window_rect.size.height; - [self.vramWindow setFrame:window_rect display:YES animate:YES]; + [self.vramWindow setFrame:window_rect display:true animate:true]; } - (void)mouseDidLeaveImageView:(GBImageView *)view @@ -1851,7 +1851,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) case 0: return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image length:64 * 4 * 2 - freeWhenDone:NO] + freeWhenDone:false] width:8 height:oamHeight scale:16.0/oamHeight]; @@ -1894,7 +1894,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row { - return NO; + return false; } - (IBAction)showVRAMViewer:(id)sender @@ -1924,7 +1924,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) frame.size = self.feedImageView.image.size; [self.printerFeedWindow setContentMaxSize:frame.size]; frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height; - [self.printerFeedWindow setFrame:frame display:NO animate: self.printerFeedWindow.isVisible]; + [self.printerFeedWindow setFrame:frame display:false animate: self.printerFeedWindow.isVisible]; [self.printerFeedWindow orderFront:NULL]; }); @@ -1955,8 +1955,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; [imageRep setSize:(NSSize){160, self.feedImageView.image.size.height / 2}]; NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; - [data writeToURL:savePanel.URL atomically:NO]; - [self.printerFeedWindow setIsVisible:NO]; + [data writeToURL:savePanel.URL atomically:false]; + [self.printerFeedWindow setIsVisible:false]; } if (shouldResume) { [self start]; @@ -2077,9 +2077,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview; { if ([[splitView arrangedSubviews] lastObject] == subview) { - return YES; + return true; } - return NO; + return false; } - (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex @@ -2095,9 +2095,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view { if ([[splitView arrangedSubviews] lastObject] == view) { - return NO; + return false; } - return YES; + return true; } - (void)splitViewDidResizeSubviews:(NSNotification *)notification @@ -2277,7 +2277,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject; NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; - [data writeToFile:filename atomically:NO]; + [data writeToFile:filename atomically:false]; [self.osdView displayText:@"Screenshot saved"]; } @@ -2293,7 +2293,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [savePanel orderOut:self]; NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject; NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; - [data writeToURL:savePanel.URL atomically:NO]; + [data writeToURL:savePanel.URL atomically:false]; [[NSUserDefaults standardUserDefaults] setObject:savePanel.URL.path.stringByDeletingLastPathComponent forKey:@"GBScreenshotFolder"]; } diff --git a/Cocoa/GBAudioClient.m b/Cocoa/GBAudioClient.m index 7f2115d..81a51fd 100644 --- a/Cocoa/GBAudioClient.m +++ b/Cocoa/GBAudioClient.m @@ -90,7 +90,7 @@ static OSStatus render( { OSErr err = AudioOutputUnitStart(audioUnit); NSAssert1(err == noErr, @"Error starting unit: %hd", err); - _playing = YES; + _playing = true; } @@ -98,7 +98,7 @@ static OSStatus render( -(void) stop { AudioOutputUnitStop(audioUnit); - _playing = NO; + _playing = false; } -(void) dealloc @@ -108,4 +108,4 @@ static OSStatus render( AudioComponentInstanceDispose(audioUnit); } -@end \ No newline at end of file +@end diff --git a/Cocoa/GBBorderView.m b/Cocoa/GBBorderView.m index a5f5e81..d992e29 100644 --- a/Cocoa/GBBorderView.m +++ b/Cocoa/GBBorderView.m @@ -5,12 +5,12 @@ - (void)awakeFromNib { - self.wantsLayer = YES; + self.wantsLayer = true; } - (BOOL)wantsUpdateLayer { - return YES; + return true; } - (void)updateLayer diff --git a/Cocoa/GBCheatTextFieldCell.m b/Cocoa/GBCheatTextFieldCell.m index 611cade..1fdafea 100644 --- a/Cocoa/GBCheatTextFieldCell.m +++ b/Cocoa/GBCheatTextFieldCell.m @@ -114,7 +114,7 @@ return _fieldEditor; } _fieldEditor = [[GBCheatTextView alloc] initWithFrame:controlView.frame]; - _fieldEditor.fieldEditor = YES; + _fieldEditor.fieldEditor = true; _fieldEditor.usesAddressFormat = self.usesAddressFormat; return _fieldEditor; } diff --git a/Cocoa/GBCheatWindowController.m b/Cocoa/GBCheatWindowController.m index c10e2a9..5cc8f59 100644 --- a/Cocoa/GBCheatWindowController.m +++ b/Cocoa/GBCheatWindowController.m @@ -52,7 +52,7 @@ if (row >= cheatCount) { switch (columnIndex) { case 0: - return @(YES); + return @YES; case 1: return @NO; @@ -67,7 +67,7 @@ switch (columnIndex) { case 0: - return @(NO); + return @NO; case 1: return @(cheats[row]->enabled); diff --git a/Cocoa/GBColorCell.m b/Cocoa/GBColorCell.m index 0ad102a..549c3dd 100644 --- a/Cocoa/GBColorCell.m +++ b/Cocoa/GBColorCell.m @@ -42,7 +42,7 @@ static inline double scale_channel(uint8_t x) - (BOOL)drawsBackground { - return YES; + return true; } @end diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index 90ebf8d..2e4eb70 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -34,6 +34,6 @@ - (void) filterChanged { self.shader = nil; - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; } @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 92aca78..60df2dd 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -69,8 +69,8 @@ - (void)close { joystick_configuration_state = -1; - [self.configureJoypadButton setEnabled:YES]; - [self.skipButton setEnabled:NO]; + [self.configureJoypadButton setEnabled:true]; + [self.skipButton setEnabled:false]; [self.configureJoypadButton setTitle:@"Configure Controller"]; [super close]; } @@ -268,12 +268,12 @@ dispatch_async(dispatch_get_main_queue(), ^{ is_button_being_modified = true; button_being_modified = row; - tableView.enabled = NO; - self.playerListButton.enabled = NO; + tableView.enabled = false; + self.playerListButton.enabled = false; [tableView reloadData]; [self makeFirstResponder:self]; }); - return NO; + return false; } -(void)keyDown:(NSEvent *)theEvent @@ -289,8 +289,8 @@ [[NSUserDefaults standardUserDefaults] setInteger:theEvent.keyCode forKey:button_to_preference_name(button_being_modified, self.playerListButton.selectedTag)]; - self.controlsTableView.enabled = YES; - self.playerListButton.enabled = YES; + self.controlsTableView.enabled = true; + self.playerListButton.enabled = true; [self.controlsTableView reloadData]; [self makeFirstResponder:self.controlsTableView]; } @@ -410,8 +410,8 @@ - (IBAction) configureJoypad:(id)sender { - [self.configureJoypadButton setEnabled:NO]; - [self.skipButton setEnabled:YES]; + [self.configureJoypadButton setEnabled:false]; + [self.skipButton setEnabled:true]; joystick_being_configured = nil; [self advanceConfigurationStateMachine]; } @@ -432,8 +432,8 @@ } else { joystick_configuration_state = -1; - [self.configureJoypadButton setEnabled:YES]; - [self.skipButton setEnabled:NO]; + [self.configureJoypadButton setEnabled:true]; + [self.skipButton setEnabled:false]; [self.configureJoypadButton setTitle:@"Configure Joypad"]; } } @@ -565,8 +565,8 @@ - (IBAction)selectOtherBootROMFolder:(id)sender { NSOpenPanel *panel = [[NSOpenPanel alloc] init]; - [panel setCanChooseDirectories:YES]; - [panel setCanChooseFiles:NO]; + [panel setCanChooseDirectories:true]; + [panel setCanChooseFiles:false]; [panel setPrompt:@"Select"]; [panel setDirectoryURL:[[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"]]; [panel beginSheetModalForWindow:self completionHandler:^(NSModalResponse result) { @@ -590,12 +590,12 @@ [self.bootROMsFolderItem setTitle:[url lastPathComponent]]; NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[url path]]; [icon setSize:NSMakeSize(16, 16)]; - [self.bootROMsFolderItem setHidden:NO]; + [self.bootROMsFolderItem setHidden:false]; [self.bootROMsFolderItem setImage:icon]; [self.bootROMsButton selectItemAtIndex:1]; } else { - [self.bootROMsFolderItem setHidden:YES]; + [self.bootROMsFolderItem setHidden:true]; [self.bootROMsButton selectItemAtIndex:0]; } } diff --git a/Cocoa/GBSplitView.m b/Cocoa/GBSplitView.m index a56c24e..d24d580 100644 --- a/Cocoa/GBSplitView.m +++ b/Cocoa/GBSplitView.m @@ -8,7 +8,7 @@ - (void)setDividerColor:(NSColor *)color { _dividerColor = color; - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; } - (NSColor *)dividerColor diff --git a/Cocoa/GBTerminalTextFieldCell.m b/Cocoa/GBTerminalTextFieldCell.m index c1ed203..e1ba957 100644 --- a/Cocoa/GBTerminalTextFieldCell.m +++ b/Cocoa/GBTerminalTextFieldCell.m @@ -17,7 +17,7 @@ return field_editor; } field_editor = [[GBTerminalTextView alloc] init]; - [field_editor setFieldEditor:YES]; + [field_editor setFieldEditor:true]; field_editor.gb = self.gb; return field_editor; } @@ -109,7 +109,7 @@ [self updateReverseSearch]; } else { - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; reverse_search_mode = true; } diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index a140b94..01481a7 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -18,7 +18,7 @@ typedef enum { @property (nonatomic, weak) IBOutlet Document *document; @property (nonatomic) GB_gameboy_t *gb; @property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; -@property (nonatomic, getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; +@property (nonatomic, getter=isMouseHidingEnabled) bool mouseHidingEnabled; @property (nonatomic) bool isRewinding; @property (nonatomic, strong) NSView *internalView; @property (weak) GBOSDView *osdView; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index bbf1a5f..39a22e8 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -106,9 +106,9 @@ static const uint8_t workboy_vk_to_key[] = { { uint32_t *image_buffers[3]; unsigned char current_buffer; - BOOL mouse_hidden; + bool mouse_hidden; NSTrackingArea *tracking_area; - BOOL _mouseHidingEnabled; + bool _mouseHidingEnabled; bool axisActive[2]; bool underclockKeyDown; double clockMultiplier; @@ -183,7 +183,7 @@ static const uint8_t workboy_vk_to_key[] = { - (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode { _frameBlendingMode = frameBlendingMode; - [self setNeedsDisplay:YES]; + [self setNeedsDisplay:true]; } @@ -585,7 +585,7 @@ static const uint8_t workboy_vk_to_key[] = { - (BOOL)acceptsFirstResponder { - return YES; + return true; } - (void)mouseEntered:(NSEvent *)theEvent @@ -610,7 +610,7 @@ static const uint8_t workboy_vk_to_key[] = { [super mouseExited:theEvent]; } -- (void)setMouseHidingEnabled:(BOOL)mouseHidingEnabled +- (void)setMouseHidingEnabled:(bool)mouseHidingEnabled { if (mouseHidingEnabled == _mouseHidingEnabled) return; @@ -625,7 +625,7 @@ static const uint8_t workboy_vk_to_key[] = { } } -- (BOOL)isMouseHidingEnabled +- (bool)isMouseHidingEnabled { return _mouseHidingEnabled; } diff --git a/Cocoa/GBViewGL.m b/Cocoa/GBViewGL.m index b80973e..dda8584 100644 --- a/Cocoa/GBViewGL.m +++ b/Cocoa/GBViewGL.m @@ -19,7 +19,7 @@ NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; self.internalView = [[GBOpenGLView alloc] initWithFrame:self.frame pixelFormat:pf]; - ((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = YES; + ((GBOpenGLView *)self.internalView).wantsBestResolutionOpenGLSurface = true; ((GBOpenGLView *)self.internalView).openGLContext = context; } @@ -27,8 +27,8 @@ { [super flip]; dispatch_async(dispatch_get_main_queue(), ^{ - [self.internalView setNeedsDisplay:YES]; - [self setNeedsDisplay:YES]; + [self.internalView setNeedsDisplay:true]; + [self setNeedsDisplay:true]; }); } diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index 958ad37..ae7443f 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -94,7 +94,7 @@ static const vector_float2 rect[] = withString:scaler_source]; MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; - options.fastMathEnabled = YES; + options.fastMathEnabled = true; id library = [device newLibraryWithSource:shader_source options:options error:&error]; @@ -210,7 +210,7 @@ static const vector_float2 rect[] = { [super flip]; dispatch_async(dispatch_get_main_queue(), ^{ - [(MTKView *)self.internalView setNeedsDisplay:YES]; + [(MTKView *)self.internalView setNeedsDisplay:true]; }); } diff --git a/Cocoa/GBWarningPopover.m b/Cocoa/GBWarningPopover.m index 411e388..74f6444 100644 --- a/Cocoa/GBWarningPopover.m +++ b/Cocoa/GBWarningPopover.m @@ -10,7 +10,7 @@ static GBWarningPopover *lastPopover; lastPopover = [[self alloc] init]; [lastPopover setBehavior:NSPopoverBehaviorApplicationDefined]; - [lastPopover setAnimates:YES]; + [lastPopover setAnimates:true]; lastPopover.contentViewController = [[NSViewController alloc] initWithNibName:@"PopoverView" bundle:nil]; NSTextField *field = (NSTextField *)lastPopover.contentViewController.view; [field setStringValue:contents]; @@ -20,7 +20,7 @@ static GBWarningPopover *lastPopover; [lastPopover setContentSize:textSize]; if (!view.window.isVisible) { - [view.window setIsVisible:YES]; + [view.window setIsVisible:true]; } [lastPopover showRelativeToRect:view.bounds diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 9b33b00..1097ef6 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -98,7 +98,7 @@ static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportT uint32_t reportID, uint8_t *report, CFIndex reportLength) { if (reportLength) { - [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; + [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:false]]; } } diff --git a/QuickLook/generator.m b/QuickLook/generator.m index 92bb6ac..f2651d2 100644 --- a/QuickLook/generator.m +++ b/QuickLook/generator.m @@ -43,10 +43,10 @@ static OSStatus render(CGContextRef cgContext, CFURLRef url, bool showBorder) bitmapInfo, provider, NULL, - YES, + true, renderingIntent); CGContextSetInterpolationQuality(cgContext, kCGInterpolationNone); - NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:NO]; + NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:false]; [NSGraphicsContext setCurrentContext:context]; From 18007f0e5389d6a68488cd0130e9aeca321b0420 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Oct 2021 23:28:54 +0300 Subject: [PATCH 285/365] MGB support --- BootROMs/dmg_boot.asm | 4 + BootROMs/mgb_boot.asm | 2 + Cocoa/Document.m | 8 + Cocoa/MainMenu.xib | 6 + Core/apu.c | 11 +- Core/gb.c | 29 ++- Core/gb.h | 2 +- Core/graphics/mgb_border.inc | 477 +++++++++++++++++++++++++++++++++++ Core/memory.c | 11 +- Core/save_state.c | 1 + Core/sm83_cpu.c | 2 +- Makefile | 8 +- SDL/gui.c | 2 +- SDL/gui.h | 1 + SDL/main.c | 1 + 15 files changed, 551 insertions(+), 14 deletions(-) create mode 100644 BootROMs/mgb_boot.asm create mode 100644 Core/graphics/mgb_border.inc diff --git a/BootROMs/dmg_boot.asm b/BootROMs/dmg_boot.asm index 97a12e7..5517683 100644 --- a/BootROMs/dmg_boot.asm +++ b/BootROMs/dmg_boot.asm @@ -115,7 +115,11 @@ Start: call WaitBFrames ; Set registers to match the original DMG boot +IF DEF(MGB) + ld hl, $FFB0 +ELSE ld hl, $01B0 +ENDC push hl pop af ld hl, $014D diff --git a/BootROMs/mgb_boot.asm b/BootROMs/mgb_boot.asm new file mode 100644 index 0000000..3a98aef --- /dev/null +++ b/BootROMs/mgb_boot.asm @@ -0,0 +1,2 @@ +MGB EQU 1 +include "dmg_boot.asm" \ No newline at end of file diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 2a54a13..74fc98e 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -20,6 +20,7 @@ enum model { MODEL_CGB, MODEL_AGB, MODEL_SGB, + MODEL_MGB, }; @interface Document () @@ -245,6 +246,9 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) return model; } + case MODEL_MGB: + return GB_MODEL_MGB; + case MODEL_AGB: return GB_MODEL_AGB; } @@ -606,6 +610,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_DMG forKey:@"EmulateDMG"]; [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_SGB forKey:@"EmulateSGB"]; [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_AGB forKey:@"EmulateAGB"]; + [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_MGB forKey:@"EmulateMGB"]; } /* Reload the ROM, SAV and SYM files */ @@ -779,6 +784,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateSGB"]) { current_model = MODEL_SGB; } + else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateMGB"]) { + current_model = MODEL_MGB; + } else { current_model = [[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]? MODEL_AGB : MODEL_CGB; } diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 348e960..ca15de4 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -339,6 +339,12 @@ + + + + + + diff --git a/Core/apu.c b/Core/apu.c index 537ae01..c40ce61 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -303,11 +303,6 @@ static inline void update_wave_sample(GB_gameboy_t *gb, unsigned cycles) } } -/* the effects of NRX2 writes on current volume are not well documented and differ - between models and variants. The exact behavior can only be verified on CGB as it - requires the PCM12 register. The behavior implemented here was verified on *my* - CGB, which might behave differently from other CGB revisions, as well as from the - DMG, MGB or SGB/2 */ static void _nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value, uint8_t *countdown, GB_envelope_clock_t *lock) { if (lock->clock) { @@ -868,6 +863,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) break; #endif 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: @@ -1232,10 +1228,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) DMG-B: Most of them behave as emulated. A few behave differently. SGB: As far as I know, all tested instances behave as emulated. MGB, SGB2: Most instances behave non-deterministically, a few behave as emulated. + + For DMG-B emulation I emulate the most common behavior, which blargg's tests expect (not my own DMG-B, which fails it) + For MGB emulation, I emulate my Game Boy Light, which happens to be deterministic. Additionally, I believe DMGs, including those we behave differently than emulated, are all deterministic. */ - if (offset < 4) { + if (offset < 4 && gb->model != GB_MODEL_MGB) { gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset]; } else { diff --git a/Core/gb.c b/Core/gb.c index a845797..6bd1f50 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -125,6 +125,17 @@ static void load_default_border(GB_gameboy_t *gb) #include "graphics/agb_border.inc" LOAD_BORDER(); } + else if (gb->model == GB_MODEL_MGB) { + #include "graphics/mgb_border.inc" + LOAD_BORDER(); + if (gb->dmg_palette && + gb->dmg_palette->colors[4].b > gb->dmg_palette->colors[4].r) { + for (unsigned i = 0; i < 7; i++) { + gb->borrowed_border.map[13 + 24 * 32 + i] = i + 1; + gb->borrowed_border.map[13 + 25 * 32 + i] = i + 8; + } + } + } else if (GB_is_cgb(gb)) { #include "graphics/cgb_border.inc" LOAD_BORDER(); @@ -1420,6 +1431,7 @@ void GB_set_user_data(GB_gameboy_t *gb, void *data) 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 */ for (unsigned i = 0; i < gb->ram_size; i++) { @@ -1475,6 +1487,7 @@ static void reset_ram(GB_gameboy_t *gb) break; case GB_MODEL_DMG_B: + case GB_MODEL_MGB: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ @@ -1501,6 +1514,7 @@ static void reset_ram(GB_gameboy_t *gb) break; case GB_MODEL_DMG_B: + case GB_MODEL_MGB: case GB_MODEL_SGB_NTSC: /* Unverified */ case GB_MODEL_SGB_PAL: /* Unverified */ case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */ @@ -1528,7 +1542,17 @@ static void reset_ram(GB_gameboy_t *gb) case GB_MODEL_AGB: /* Initialized by CGB-A and newer, 0s in CGB-0*/ break; - + case GB_MODEL_MGB: { + for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { + if (i & 1) { + gb->io_registers[GB_IO_WAV_START + i] = GB_random() & GB_random(); + } + else { + gb->io_registers[GB_IO_WAV_START + i] = GB_random() | GB_random(); + } + } + break; + } case GB_MODEL_DMG_B: case GB_MODEL_SGB_NTSC: /* Unverified*/ case GB_MODEL_SGB_PAL: /* Unverified */ @@ -1572,6 +1596,9 @@ static void request_boot_rom(GB_gameboy_t *gb) case GB_MODEL_DMG_B: type = GB_BOOT_ROM_DMG; break; + case GB_MODEL_MGB: + type = GB_BOOT_ROM_MGB; + break; case GB_MODEL_SGB_NTSC: case GB_MODEL_SGB_PAL: case GB_MODEL_SGB_NTSC_NO_SFC: diff --git a/Core/gb.h b/Core/gb.h index ea4fe7d..0a1b358 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -127,7 +127,7 @@ typedef enum { GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC, GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT, - // GB_MODEL_MGB = 0x100, + GB_MODEL_MGB = 0x100, GB_MODEL_SGB2 = 0x101, GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, // GB_MODEL_CGB_0 = 0x200, diff --git a/Core/graphics/mgb_border.inc b/Core/graphics/mgb_border.inc new file mode 100644 index 0000000..f19ed8a --- /dev/null +++ b/Core/graphics/mgb_border.inc @@ -0,0 +1,477 @@ +static const uint16_t palette[] = { + 0x0000, 0x0000, 0x0011, 0x001A, 0x39CE, 0x6B5A, 0x739C, 0x5265, + 0x3DC5, 0x2924, 0x18A4, 0x20E6, 0x2D49, 0x1484, 0x5694, 0x20EC, +}; + + +static const uint16_t tilemap[] = { + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x0010, 0x0011, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, + 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, + 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, + 0x0012, 0x0012, 0x0012, 0x0012, 0x0012, 0x4011, 0x4010, 0x000F, + 0x000F, 0x0013, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, + 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, + 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, + 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x4013, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0016, 0x0017, 0x0017, + 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, + 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, 0x0017, + 0x0017, 0x0017, 0x4016, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0019, 0x001A, 0x4019, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x8019, 0x001B, 0xC019, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x0018, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x4018, 0x0014, 0x0014, 0x0014, 0x4015, 0x000F, + 0x000F, 0x0015, 0x0014, 0x0014, 0x0014, 0x001C, 0x001D, 0x001D, + 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, + 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, 0x001D, + 0x001D, 0x001D, 0x401C, 0x0014, 0x0014, 0x0014, 0x001E, 0x000F, + 0x000F, 0x0015, 0x0014, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023, + 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, + 0x002C, 0x002D, 0x002E, 0x002F, 0x0014, 0x0014, 0x0014, 0x0014, + 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0030, 0x0031, 0x000F, + 0x000F, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, + 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, + 0x0041, 0x0042, 0x0043, 0x0044, 0x0014, 0x0014, 0x0014, 0x0014, + 0x0014, 0x0014, 0x0014, 0x0014, 0x0045, 0x0046, 0x000F, 0x000F, + 0x000F, 0x0047, 0x0048, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, + 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, + 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, 0x0049, + 0x0049, 0x0049, 0x004A, 0x004B, 0x004C, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, + 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, 0x000F, +}; + + + +const uint8_t tiles[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x3D, + 0x42, 0x7F, 0x81, 0xFF, 0x01, 0xFD, 0x01, + 0xFD, 0x01, 0xFF, 0x03, 0xFF, 0x03, 0xFF, + 0xFF, 0xBC, 0x7F, 0xFD, 0xFE, 0xFD, 0xFE, + 0xFD, 0xFE, 0xFD, 0xFE, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0x00, 0xBF, 0x41, 0xFE, 0xC0, + 0xBF, 0xC1, 0xFF, 0x81, 0x7D, 0x03, 0x7F, + 0x01, 0x7F, 0x01, 0xFF, 0xFF, 0x3E, 0xFF, + 0xBE, 0x7F, 0xBF, 0x7E, 0xFF, 0x7E, 0x7D, + 0xFE, 0x7D, 0xFE, 0x7D, 0xFE, 0xFF, 0x00, + 0xFF, 0x00, 0xBF, 0x83, 0xBF, 0x87, 0xFC, + 0x8D, 0xED, 0x8E, 0xDB, 0xF8, 0xBF, 0xD8, + 0xFF, 0xFF, 0x3E, 0xFF, 0xBB, 0x7C, 0xB7, + 0x78, 0xAC, 0x73, 0xAD, 0x73, 0x9B, 0x67, + 0x9B, 0x67, 0xFF, 0x00, 0xB7, 0x08, 0xFF, + 0xF8, 0x3F, 0x38, 0xFF, 0x08, 0xFE, 0x01, + 0x87, 0x00, 0xFB, 0x78, 0xFF, 0xFF, 0x07, + 0xFF, 0xFB, 0x07, 0x3B, 0xC7, 0xE7, 0xFF, + 0xFE, 0xFF, 0x82, 0xFF, 0xFA, 0x87, 0xFF, + 0x00, 0xFE, 0x81, 0x5F, 0x40, 0xDE, 0xC0, + 0xFE, 0xC0, 0xE0, 0xDE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x1E, 0xFF, 0x5E, 0xBF, + 0xDE, 0x3F, 0xDE, 0x3F, 0xC0, 0x3F, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xCF, 0x30, + 0xDF, 0xEF, 0xFF, 0xCF, 0xFF, 0xC1, 0xBD, + 0x81, 0xBD, 0x81, 0xFF, 0x83, 0xFF, 0xFF, + 0x00, 0xFF, 0xCF, 0x30, 0xEF, 0x30, 0xFD, + 0x3E, 0xBD, 0x7E, 0xBD, 0x7E, 0xBF, 0x7C, + 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0xF0, 0xFF, + 0xF0, 0xBF, 0xC0, 0xFF, 0x80, 0x7F, 0x00, + 0x7F, 0x00, 0xFF, 0xFF, 0x07, 0xFF, 0xF7, + 0x0F, 0xF7, 0x0F, 0xBF, 0x7F, 0xFF, 0x7F, + 0x7F, 0xFF, 0x7F, 0xFF, 0xFB, 0x07, 0xFF, + 0x03, 0xFF, 0x03, 0xFB, 0x03, 0xFB, 0x03, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, + 0xFC, 0xFB, 0xFC, 0xFB, 0xFC, 0xFB, 0xFC, + 0xFB, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFD, 0x01, 0xFD, 0x81, 0xFF, 0x0B, + 0xF7, 0xF3, 0xFB, 0xF7, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x7D, 0xFE, 0x7D, 0xFE, + 0x07, 0xFC, 0xF7, 0x0C, 0xF3, 0x0C, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, + 0x7B, 0x38, 0x7C, 0x1D, 0xFF, 0x0F, 0xFB, + 0x0B, 0xFD, 0x03, 0xFF, 0x00, 0xFF, 0x00, + 0xDB, 0x67, 0x5B, 0xE7, 0x7C, 0xE3, 0x6F, + 0xF0, 0x73, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x9E, 0x18, 0xFE, 0x3C, 0x5A, + 0xDC, 0xFF, 0xF9, 0xED, 0xE3, 0xBF, 0xC0, + 0xFF, 0x00, 0xFF, 0x00, 0x9A, 0xE7, 0xDA, + 0xE7, 0x1A, 0xE7, 0xFF, 0x06, 0xE5, 0x1E, + 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, + 0xFD, 0xBF, 0x81, 0xBF, 0x81, 0xBD, 0x81, + 0xFD, 0x81, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xC1, 0x3E, 0xBD, 0x7E, 0xBD, 0x7E, + 0xBD, 0x7E, 0xBD, 0x7E, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xBB, 0xC7, + 0xFF, 0x83, 0xFF, 0x83, 0x7B, 0x03, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x7C, + 0xBB, 0x7C, 0xFB, 0x7C, 0xFB, 0x7C, 0x7B, + 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, + 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF9, 0x00, + 0xF7, 0x00, 0xEE, 0x00, 0xDD, 0x04, 0xDF, + 0x04, 0xBF, 0x08, 0xFF, 0x00, 0xFF, 0x01, + 0xFF, 0x07, 0xFF, 0x0F, 0xFF, 0x1F, 0xFB, + 0x3F, 0xFB, 0x3F, 0xF7, 0x7F, 0x80, 0x00, + 0x7F, 0x00, 0xFF, 0x00, 0x80, 0x00, 0x7F, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, + 0x08, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10, + 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, + 0x10, 0xF7, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, + 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, + 0x7F, 0xEF, 0x7F, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10, + 0xFF, 0x10, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, + 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, + 0xEF, 0x7F, 0xEF, 0x7F, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFE, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, + 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xC3, 0x7E, + 0xBD, 0xFF, 0x66, 0xFF, 0x7E, 0xFF, 0x7E, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC3, 0xFF, 0x81, 0xC3, 0x18, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x7E, 0xFF, 0x3C, 0xFF, + 0x00, 0x7E, 0x81, 0xBD, 0xC3, 0x42, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81, 0xFF, + 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x01, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x08, 0xFD, 0x12, 0xFD, 0x12, + 0xFD, 0x12, 0xFD, 0x12, 0xFB, 0x24, 0xFB, + 0x24, 0xFB, 0x24, 0xF7, 0xFE, 0xEF, 0xFC, + 0xEF, 0xFC, 0xEF, 0xFC, 0xEF, 0xFC, 0xDF, + 0xF8, 0xDF, 0xF8, 0xDF, 0xF8, 0xFF, 0x00, + 0xF0, 0x1E, 0xC0, 0x3F, 0x8D, 0x72, 0x0E, + 0xF3, 0x8F, 0xF0, 0x01, 0xFC, 0xA0, 0x1E, + 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xE0, 0xFF, + 0xC0, 0x7C, 0x9F, 0x7F, 0x9F, 0x7F, 0x87, + 0xFF, 0xC0, 0xFF, 0x00, 0xFC, 0x00, 0x78, + 0x87, 0x78, 0x87, 0xF0, 0x07, 0xF8, 0x07, + 0xE2, 0x0F, 0xE2, 0x1C, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFB, 0xFC, 0x7F, 0xFC, 0xFF, 0xF8, + 0xFF, 0xF2, 0xFD, 0xF3, 0xFF, 0xE6, 0xFF, + 0x00, 0x7C, 0x02, 0xFC, 0x01, 0x3C, 0xC3, + 0x3C, 0x83, 0x3E, 0xC1, 0x3C, 0xC3, 0x18, + 0xC7, 0xFF, 0xFF, 0xFD, 0xFE, 0xFF, 0x7C, + 0xBF, 0x7E, 0xFF, 0x3E, 0xFF, 0x7C, 0xFF, + 0x3C, 0xFB, 0x3C, 0xFF, 0x00, 0x1E, 0x01, + 0x1E, 0xE1, 0x1E, 0xE3, 0x1C, 0xF3, 0x0C, + 0xE7, 0x08, 0xF7, 0x19, 0x6F, 0xFF, 0xFF, + 0xFE, 0x3F, 0xFF, 0x3F, 0xFD, 0x1E, 0xEF, + 0x1E, 0xFB, 0x8C, 0xFF, 0xDD, 0xF6, 0x49, + 0xFF, 0x00, 0x18, 0x14, 0x08, 0xE3, 0x08, + 0xE3, 0x18, 0xF7, 0x1D, 0xE2, 0x18, 0xE7, + 0xB8, 0x47, 0xFF, 0xFF, 0xEB, 0x1C, 0xFF, + 0x0C, 0xFF, 0x18, 0xEF, 0x1C, 0xFF, 0x98, + 0xFF, 0x98, 0xFF, 0x18, 0xFF, 0x00, 0x06, + 0x05, 0x02, 0xF8, 0x02, 0xF9, 0xFE, 0x01, + 0xFF, 0x00, 0x06, 0xF9, 0x04, 0xF3, 0xFF, + 0xFF, 0xFA, 0x07, 0xFF, 0x02, 0xFF, 0x07, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x0E, 0xFD, + 0x06, 0xFF, 0x00, 0x03, 0x00, 0x01, 0xFF, + 0x30, 0xCF, 0x70, 0x8F, 0x30, 0xC6, 0x21, + 0xDC, 0x01, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, + 0xFE, 0x01, 0xFF, 0x01, 0xF7, 0x39, 0xFF, + 0x79, 0xFF, 0x01, 0xFF, 0x03, 0xFF, 0x00, + 0xF8, 0x01, 0xF0, 0x1F, 0xE1, 0x3E, 0x83, + 0x78, 0x87, 0x30, 0xCF, 0x30, 0x8F, 0x70, + 0xFF, 0xFF, 0xFF, 0xFD, 0xEF, 0xF8, 0xDF, + 0xE0, 0xBF, 0xC3, 0xFF, 0x87, 0xFF, 0x8F, + 0xFF, 0x9F, 0xFF, 0x00, 0x3C, 0xA0, 0x0C, + 0xE1, 0x07, 0xF0, 0x86, 0x38, 0xC7, 0x3C, + 0xC3, 0x18, 0xC7, 0x3C, 0xFF, 0xFF, 0xDF, + 0xBE, 0xFF, 0x0C, 0xFF, 0x06, 0xFF, 0x87, + 0xFB, 0xE7, 0xFF, 0xC7, 0xFB, 0xE7, 0xFF, + 0x00, 0x7C, 0x40, 0x78, 0x83, 0x39, 0xEE, + 0x19, 0xE7, 0x81, 0x7C, 0x03, 0x78, 0xC7, + 0x38, 0xFF, 0xFF, 0xBF, 0x7E, 0xFF, 0x38, + 0xD7, 0x38, 0xFE, 0x31, 0xFF, 0x13, 0xFF, + 0x83, 0xFF, 0x8F, 0xFF, 0x00, 0x3C, 0x43, + 0x7C, 0x83, 0xFC, 0x03, 0xFC, 0x03, 0xFC, + 0x03, 0xFD, 0x02, 0xFC, 0x03, 0xFF, 0xFF, + 0xBF, 0x7C, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, + 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0xFF, 0xFD, + 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x04, + 0xA3, 0xFD, 0xB6, 0x8C, 0x3A, 0x8B, 0x7C, + 0x99, 0x62, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0xF8, 0x4B, 0xFC, 0xF7, 0xCC, + 0xF3, 0xCD, 0xFF, 0xCB, 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x90, 0x3F, 0xD8, 0x46, + 0x09, 0xF6, 0x0D, 0xF1, 0x12, 0xF4, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xCF, 0x78, + 0xBF, 0xD9, 0x7F, 0x9F, 0xFE, 0x9B, 0xEF, + 0x9B, 0xFF, 0x00, 0x00, 0xFC, 0x02, 0xFC, + 0x46, 0x59, 0x13, 0xAC, 0x82, 0x68, 0x07, + 0xFC, 0x14, 0xE8, 0xFF, 0xFF, 0xFF, 0x03, + 0xFF, 0x02, 0xBF, 0x73, 0x5F, 0xB6, 0xFF, + 0x23, 0xFB, 0x07, 0xFF, 0x26, 0xFF, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x03, 0x9F, 0x21, + 0x55, 0x48, 0xB7, 0x8F, 0x60, 0x80, 0x6F, + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFC, + 0x27, 0xBA, 0xCD, 0x7F, 0x9D, 0xFF, 0x8F, + 0xF7, 0xD8, 0xFF, 0x00, 0x00, 0xFF, 0x0C, + 0xF3, 0x18, 0xF3, 0xD8, 0x2F, 0x90, 0x67, + 0xB0, 0x4F, 0x10, 0xEF, 0xFF, 0xFF, 0xFF, + 0x08, 0xFF, 0x18, 0xEF, 0xBC, 0xF7, 0xBC, + 0xFF, 0xD0, 0xFF, 0xD8, 0xFF, 0x30, 0xFF, + 0x00, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, + 0x80, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, + 0x7F, 0xFF, 0x7F, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x02, 0xFF, 0x04, 0xFF, 0x08, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, + 0xF7, 0x48, 0xF7, 0x48, 0xEF, 0x90, 0xEF, + 0x90, 0xDF, 0x20, 0xBF, 0x40, 0xBF, 0x40, + 0x7F, 0x80, 0xBF, 0xF0, 0xBF, 0xF0, 0x7F, + 0xE0, 0x7F, 0xE0, 0xFF, 0xC0, 0xFF, 0x80, + 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x10, 0xFF, + 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x10, + 0xFF, 0x10, 0xFF, 0x10, 0xBF, 0x48, 0xEF, + 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, + 0xEF, 0x7F, 0xEF, 0x7F, 0xEF, 0x7F, 0xF7, + 0x3F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFE, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F, + 0xF8, 0x07, 0x00, 0x57, 0x01, 0xFF, 0x05, + 0xF8, 0x87, 0x48, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xF8, 0xFF, 0xFC, 0xEF, 0x99, 0xFE, + 0x01, 0xFF, 0x83, 0xB7, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x3F, 0x80, 0x7F, 0x80, + 0x7F, 0x0F, 0x70, 0x8F, 0x70, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xE7, 0xBF, + 0xC0, 0xFF, 0xCF, 0xFF, 0x9F, 0xEF, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, + 0xE3, 0x18, 0xE3, 0x98, 0x77, 0x08, 0x67, + 0x1D, 0xE2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x3C, 0xFF, 0x18, 0xEF, 0x1C, + 0xFF, 0x0C, 0x7F, 0x88, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x01, 0x3E, 0xC2, 0xFF, + 0xC2, 0x3C, 0xE2, 0x18, 0xC6, 0x39, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x8B, + 0x3C, 0xC3, 0xFF, 0xC7, 0xFF, 0xC6, 0xFF, + 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x18, 0xEF, 0x10, 0xE6, 0x10, 0xC6, 0x30, + 0xEF, 0x30, 0xCF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xF7, 0x39, 0xFF, 0x39, 0xFF, + 0x10, 0xDF, 0x38, 0xFF, 0x38, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x09, 0xFC, + 0x01, 0x0C, 0x0B, 0x04, 0xFB, 0x06, 0xF1, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF7, + 0x0E, 0xFF, 0xFC, 0xF7, 0x0E, 0xFF, 0x0E, + 0xFF, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xCE, 0x70, 0xCF, 0x70, 0xFE, + 0x01, 0xFE, 0x0B, 0xF0, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x69, 0xB7, 0x79, + 0x8F, 0x79, 0xFF, 0x03, 0xFF, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x70, + 0x87, 0x78, 0x88, 0x72, 0x80, 0x7F, 0xC0, + 0x3F, 0xFC, 0x05, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x9F, 0xF7, 0x8F, 0xFD, 0xC7, 0xBF, + 0xC0, 0xDF, 0xF0, 0xFA, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE7, 0x18, 0x87, 0x38, 0x17, + 0xE8, 0x1F, 0xF0, 0x3F, 0xC0, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xFF, + 0x8F, 0xF7, 0x8F, 0xEF, 0x1F, 0xFF, 0x7F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, + 0x78, 0x8F, 0x70, 0xDF, 0x20, 0x8F, 0x70, + 0x8F, 0x70, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xF7, 0xCF, 0xFF, 0xCF, 0xFF, 0x8F, + 0xFF, 0x9F, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xFD, 0x03, + 0xFD, 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFC, + 0xFE, 0xFD, 0xFF, 0xFD, 0xFF, 0xFD, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x9F, 0x30, 0xA0, 0x9E, 0x00, 0x7F, 0x00, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xEF, 0xF9, 0x6F, 0xF8, 0xFF, + 0x00, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0xCF, 0xE1, + 0x1E, 0x40, 0xBF, 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7C, + 0xDB, 0xFF, 0xF3, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x3D, 0x82, 0x86, 0x79, 0x80, 0x7F, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0xA6, 0xBF, 0xEC, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x88, 0x27, + 0xD6, 0xA0, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFE, 0xDF, 0x7F, 0xCE, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x18, 0x47, 0x10, 0xC7, 0x00, + 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xFF, + 0x18, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, + 0x80, 0x7F, 0x80, 0x7F, 0x80, 0x7F, 0x80, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, + 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF, + 0x02, 0xFF, 0x0C, 0xFF, 0x10, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFD, 0xFF, 0xF3, 0xFF, 0xEF, 0xFF, + 0xFE, 0x11, 0xFD, 0x22, 0xFB, 0x44, 0xF7, + 0x88, 0xEF, 0x10, 0xDF, 0x20, 0xBF, 0x40, + 0x7F, 0x80, 0xEF, 0xFE, 0xDF, 0xFC, 0xBF, + 0xF8, 0x7F, 0xF0, 0xFF, 0xE0, 0xFF, 0xC0, + 0xFF, 0x80, 0xFF, 0x00, 0xBF, 0x48, 0xDF, + 0x24, 0xDF, 0x22, 0xEF, 0x11, 0xF7, 0x08, + 0xF9, 0x06, 0xFE, 0x01, 0xFF, 0x00, 0xF7, + 0x3F, 0xFB, 0x1F, 0xFD, 0x1F, 0xFE, 0x0F, + 0xFF, 0x07, 0xFF, 0x01, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x80, 0xFF, 0x7F, 0xFF, 0x00, 0x7F, + 0x80, 0x80, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7F, 0xFF, 0x80, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7F, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x03, 0xFF, 0xFC, 0xFF, 0x00, + 0xFC, 0x03, 0x03, 0xFC, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x03, 0xFF, 0x1C, 0xFF, 0xE0, + 0xFC, 0x03, 0xE3, 0x1C, 0x1F, 0xE0, 0xFF, + 0x00, 0xFF, 0xFF, 0xFC, 0xFF, 0xE3, 0xFF, + 0x1F, 0xFF, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, + 0x00, 0xFF, 0x00, 0xFC, 0xE3, 0xF3, 0x0C, + 0xEF, 0x10, 0x1F, 0xE0, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x1F, 0xFC, + 0xFF, 0xF0, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, +}; diff --git a/Core/memory.c b/Core/memory.c index 1720356..297656c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -206,7 +206,11 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address) } else if ((gb->accessed_oam_row & 0x18) == 0x00) { /* Everything in this specific case is *extremely* revision and instance specific. */ - if (gb->accessed_oam_row == 0x40) { + if (gb->model == GB_MODEL_MGB) { + /* TODO: This is rather simplified, research further */ + oam_bug_tertiary_read_corruption(gb, bitwise_glitch_tertiary_read_3); + } + else if (gb->accessed_oam_row == 0x40) { oam_bug_quaternary_read_corruption(gb, ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2)? bitwise_glitch_quaternary_read_sgb2: @@ -240,6 +244,9 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address) if (gb->accessed_oam_row == 0x80) { memcpy(gb->oam, gb->oam + gb->accessed_oam_row, 8); } + else if (gb->model == GB_MODEL_MGB && gb->accessed_oam_row == 0x40) { + memcpy(gb->oam, gb->oam + gb->accessed_oam_row, 8); + } } } } @@ -507,6 +514,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) 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: @@ -1015,6 +1023,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->extra_oam[addr - 0xfea0] = value; 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: diff --git a/Core/save_state.c b/Core/save_state.c index 6e7ca9d..cdbde2b 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -580,6 +580,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe switch (gb->model) { case GB_MODEL_DMG_B: bess_core.full_model = BE32('GDB '); break; + case GB_MODEL_MGB: bess_core.full_model = BE32('GM '); break; case GB_MODEL_SGB_NTSC: case GB_MODEL_SGB_NTSC_NO_SFC: diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index a918081..92adf46 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -207,7 +207,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); - if (/* gb->model != GB_MODEL_MGB && */ gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) { + if (gb->model != GB_MODEL_MGB && gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) { old_value &= ~2; } diff --git a/Makefile b/Makefile index 7bfe580..fadc025 100644 --- a/Makefile +++ b/Makefile @@ -199,9 +199,9 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders -bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin -tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/mgb_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/mgb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin +tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/mgb_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin all: cocoa sdl tester libretro # Get a list of our source files and their respective object file targets @@ -279,6 +279,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ Cocoa/Info.plist \ Misc/registers.sym \ $(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \ + $(BIN)/SameBoy.app/Contents/Resources/mgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/agb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/sgb_boot.bin \ @@ -417,6 +418,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(PB12_COMPRESS): BootROMs/pb12.c $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ +$(BIN)/BootROMs/mgb_boot.bin: BootROMs/mgb_boot.asm $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm diff --git a/SDL/gui.c b/SDL/gui.c index 6bccd87..d6afe60 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -388,7 +388,7 @@ static void cycle_model_backwards(unsigned index) const char *current_model_string(unsigned index) { - return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance", "Super Game Boy"} + return (const char *[]){"Game Boy", "Game Boy Color", "Game Boy Advance", "Super Game Boy", "Game Boy Pocket"} [configuration.model]; } diff --git a/SDL/gui.h b/SDL/gui.h index 1764c8b..1fe8a54 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -88,6 +88,7 @@ typedef struct { MODEL_CGB, MODEL_AGB, MODEL_SGB, + MODEL_MGB, MODEL_MAX, } model; diff --git a/SDL/main.c b/SDL/main.c index c0fd186..2e9911e 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -623,6 +623,7 @@ restart: [MODEL_DMG] = GB_MODEL_DMG_B, [MODEL_CGB] = GB_MODEL_CGB_E, [MODEL_AGB] = GB_MODEL_AGB, + [MODEL_MGB] = GB_MODEL_MGB, [MODEL_SGB] = (GB_model_t []) { [SGB_NTSC] = GB_MODEL_SGB_NTSC, From 5b9746084d8a060b1a7b7a0d1cca67d079774012 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Oct 2021 23:51:48 +0300 Subject: [PATCH 286/365] Writes to SVBK should work before the boot ROM is disabled --- Core/memory.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 297656c..43cc98f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1243,12 +1243,11 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[GB_IO_DMA] = value; return; case GB_IO_SVBK: - if (!gb->cgb_mode) { - return; - } - gb->cgb_ram_bank = value & 0x7; - if (!gb->cgb_ram_bank) { - gb->cgb_ram_bank++; + if (gb->cgb_mode || (GB_is_cgb(gb) && !gb->boot_rom_finished)) { + gb->cgb_ram_bank = value & 0x7; + if (!gb->cgb_ram_bank) { + gb->cgb_ram_bank++; + } } return; case GB_IO_VBK: From 2ec573c84a2e59c8c3c5bc0e6bb07f80a47d4656 Mon Sep 17 00:00:00 2001 From: Snowy Date: Sun, 24 Oct 2021 11:15:28 -0500 Subject: [PATCH 287/365] Change y to a signed value --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index ef1dde6..aa958de 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1526,7 +1526,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h uint8_t count = 0; *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; uint8_t oam_to_dest_index[40] = {0,}; - for (unsigned y = 0; y < LINES; y++) { + for (signed y = 0; y < LINES; y++) { GB_object_t *sprite = (GB_object_t *) &gb->oam; uint8_t sprites_in_line = 0; for (uint8_t i = 0; i < 40; i++, sprite++) { From e6c4ceaf5aa5885a62a65095b963f44fc95c79c4 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 27 Oct 2021 01:40:28 +0300 Subject: [PATCH 288/365] Add CGB-D support --- Cocoa/Preferences.xib | 2 +- Core/apu.c | 14 ++++++------ Core/display.c | 53 +++++++++++++++++++++++++++++++------------ Core/gb.c | 16 ++++++++++++- Core/gb.h | 2 +- Core/memory.c | 4 ---- Core/save_state.c | 1 + 7 files changed, 63 insertions(+), 29 deletions(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 6d72652..41c1cda 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -401,7 +401,7 @@ - + diff --git a/Core/apu.c b/Core/apu.c index c40ce61..e049c06 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -901,7 +901,6 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) effective_counter |= 0x20; } break; -#if 0 case GB_MODEL_CGB_D: if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8)? 0x40 : 0x80)) { // This is so weird effective_counter |= 0xFF; @@ -922,7 +921,6 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) effective_counter |= 0x10; } break; -#endif case GB_MODEL_CGB_E: if (effective_counter & ((gb->io_registers[GB_IO_NR43] & 8)? 0x40 : 0x80)) { // This is so weird effective_counter |= 0xFF; @@ -1067,7 +1065,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if ((value & 0x80) == 0 && 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->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)) { gb->apu.square_channels[index].current_sample_index--; gb->apu.square_channels[index].current_sample_index &= 7; @@ -1091,7 +1089,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } else { unsigned extra_delay = 0; - if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */) { + 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)) { gb->apu.square_channels[index].current_sample_index++; gb->apu.square_channels[index].current_sample_index &= 0x7; @@ -1153,10 +1151,12 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) else { gb->apu.sweep_length_addend = 0; } - gb->apu.channel_1_restart_hold = 2 - gb->apu.lf_div + GB_is_cgb(gb) * 2; - if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + gb->apu.channel_1_restart_hold = 2 - gb->apu.lf_div + (GB_is_cgb(gb) && gb->model != GB_MODEL_CGB_D) * 2; + /* + if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { + // TODO: This if makes channel_1_sweep_restart_2 fail on CGB-C mode gb->apu.channel_1_restart_hold += 2; - } + }*/ gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } } diff --git a/Core/display.c b/Core/display.c index ef1dde6..8a4c7f9 100644 --- a/Core/display.c +++ b/Core/display.c @@ -483,18 +483,29 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index) } } -static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use) +static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use, bool *cgb_d_glitch) { /* Based on Matt Currie's research here: https://github.com/mattcurrie/mealybug-tearoom-tests/blob/master/the-comprehensive-game-boy-ppu-documentation.md#tile_sel-bit-4 */ - *should_use = true; + *cgb_d_glitch = false; + if (gb->io_registers[GB_IO_LCDC] & 0x10) { - *should_use = !(gb->current_tile & 0x80); - /* if (gb->model != GB_MODEL_CGB_D) */ return gb->current_tile; - // TODO: CGB D behaves differently + if (gb->model != GB_MODEL_CGB_D) { + *should_use = !(gb->current_tile & 0x80); + return gb->current_tile; + } + *cgb_d_glitch = true; + *should_use = false; + gb->io_registers[GB_IO_LCDC] &= ~0x10; + if (gb->fetcher_state == 3) { + *should_use = false; + *cgb_d_glitch = true; + return 0; + } + return 0; } return gb->data_for_sel_glitch; } @@ -694,8 +705,9 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) case GB_FETCHER_GET_TILE_DATA_LOWER: { bool use_glitched = false; + bool cgb_d_glitch = false; if (gb->tile_sel_glitch) { - gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched); + gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch); } uint8_t y_flip = 0; uint16_t tile_address = 0; @@ -721,13 +733,19 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->current_tile_data[0] = 0xFF; } } - else { + 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; } } + 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->fetcher_state++; break; @@ -736,8 +754,9 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ bool use_glitched = false; + bool cgb_d_glitch = false; if (gb->tile_sel_glitch) { - gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched); + gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch); } uint16_t tile_address = 0; @@ -756,7 +775,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } - gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1; + 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]; @@ -764,12 +783,16 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->current_tile_data[1] = 0xFF; } } - else { - if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) { - gb->data_for_sel_glitch = gb->vram[gb->last_tile_data_address]; - if (gb->vram_ppu_blocked) { - gb->data_for_sel_glitch = 0xFF; - } + if ((gb->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; + } + } + 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; } } } diff --git a/Core/gb.c b/Core/gb.c index 6bd1f50..8d2545d 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1473,12 +1473,23 @@ static void reset_ram(GB_gameboy_t *gb) } } break; + case GB_MODEL_CGB_D: + for (unsigned i = 0; i < gb->ram_size; i++) { + gb->ram[i] = GB_random(); + if (i & 0x800) { + gb->ram[i] &= GB_random(); + } + else { + gb->ram[i] |= GB_random(); + } + } + break; } /* HRAM */ switch (gb->model) { case GB_MODEL_CGB_C: - // case GB_MODEL_CGB_D: + case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: case GB_MODEL_AGB: for (unsigned i = 0; i < sizeof(gb->hram); i++) { @@ -1508,6 +1519,7 @@ static void reset_ram(GB_gameboy_t *gb) /* OAM */ switch (gb->model) { 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*/ @@ -1538,6 +1550,7 @@ static void reset_ram(GB_gameboy_t *gb) /* Wave RAM */ switch (gb->model) { case GB_MODEL_CGB_C: + case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: case GB_MODEL_AGB: /* Initialized by CGB-A and newer, 0s in CGB-0*/ @@ -1610,6 +1623,7 @@ static void request_boot_rom(GB_gameboy_t *gb) type = GB_BOOT_ROM_SGB2; break; case GB_MODEL_CGB_C: + case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: type = GB_BOOT_ROM_CGB; break; diff --git a/Core/gb.h b/Core/gb.h index 0a1b358..bd42409 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -134,7 +134,7 @@ typedef enum { // GB_MODEL_CGB_A = 0x201, // GB_MODEL_CGB_B = 0x202, GB_MODEL_CGB_C = 0x203, - // GB_MODEL_CGB_D = 0x204, + GB_MODEL_CGB_D = 0x204, GB_MODEL_CGB_E = 0x205, GB_MODEL_AGB = 0x206, } GB_model_t; diff --git a/Core/memory.c b/Core/memory.c index 43cc98f..a4b1fca 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -496,13 +496,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) 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: /* @@ -1005,14 +1003,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->oam[addr & 0xFF] = value; } 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: diff --git a/Core/save_state.c b/Core/save_state.c index cdbde2b..e543702 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -596,6 +596,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 } From 739a9eb2bf9767562a19e9f993b9c4f02e939e8e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 27 Oct 2021 01:43:36 +0300 Subject: [PATCH 289/365] Use a monospaced font in the palette viewer --- Cocoa/GBColorCell.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cocoa/GBColorCell.m b/Cocoa/GBColorCell.m index 549c3dd..761ad43 100644 --- a/Cocoa/GBColorCell.m +++ b/Cocoa/GBColorCell.m @@ -13,13 +13,13 @@ static inline double scale_channel(uint8_t x) - (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] + NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor], + NSFontAttributeName: [NSFont userFixedPitchFontOfSize:12] }]; } @@ -36,6 +36,7 @@ static inline double scale_channel(uint8_t x) - (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]; } From 1f7b20251b677d0e34a79bec80243acdbeea6f46 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Oct 2021 16:03:13 +0300 Subject: [PATCH 290/365] Improved sanitation for save states for better security and stability --- Cocoa/GBView.m | 2 +- Core/gb.h | 3 ++- Core/joypad.c | 1 + Core/memory.c | 11 ++++---- Core/save_state.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--- Core/save_state.h | 2 +- Core/sm83_cpu.c | 1 + SDL/gui.c | 2 +- SDL/main.c | 2 +- 9 files changed, 73 insertions(+), 15 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 39a22e8..6c92c3f 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -658,7 +658,7 @@ static const uint8_t workboy_vk_to_key[] = { if ( [[pboard types] containsObject:NSURLPboardType] ) { NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; - if (GB_is_stave_state(fileURL.fileSystemRepresentation)) { + if (GB_is_save_state(fileURL.fileSystemRepresentation)) { return NSDragOperationGeneric; } } diff --git a/Core/gb.h b/Core/gb.h index bd42409..d00d438 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -24,6 +24,7 @@ #include "cheats.h" #include "rumble.h" #include "workboy.h" +#include "random.h" #define GB_STRUCT_VERSION 13 @@ -554,7 +555,7 @@ struct GB_gameboy_internal_s { /* Video Display */ GB_SECTION(video, uint32_t vram_size; // Different between CGB and DMG - uint8_t cgb_vram_bank; + bool cgb_vram_bank; uint8_t oam[0xA0]; uint8_t background_palettes_data[0x40]; uint8_t sprite_palettes_data[0x40]; diff --git a/Core/joypad.c b/Core/joypad.c index b30258c..c0655f0 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -52,6 +52,7 @@ void GB_update_joyp(GB_gameboy_t *gb) break; default: + __builtin_unreachable(); break; } diff --git a/Core/memory.c b/Core/memory.c index a4b1fca..22926be 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -298,7 +298,7 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) addr = gb->last_tile_data_address; } } - return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; + return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)]; } static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) @@ -471,7 +471,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) break; default: - break; + __builtin_unreachable(); } for (unsigned i = 0; i < 8; i++) { @@ -641,8 +641,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return 0xFF; } - /* Hardware registers */ - return 0; + __builtin_unreachable(); } if (addr == 0xFFFF) { @@ -821,7 +820,7 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) addr = gb->last_tile_data_address; } } - gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; + gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)] = value; } static bool huc3_write(GB_gameboy_t *gb, uint8_t value) @@ -1400,7 +1399,7 @@ static GB_write_function_t * const write_map[] = write_vram, write_vram, /* 8XXX, 9XXX */ write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */ write_ram, write_banked_ram, /* CXXX, DXXX */ - write_ram, write_high_memory, /* EXXX FXXX */ + write_ram, write_high_memory, /* EXXX FXXX */ }; void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) diff --git a/Core/save_state.c b/Core/save_state.c index e543702..6befa2d 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -307,6 +307,11 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t return false; } + if (GB_is_cgb(gb) != GB_is_cgb(save) || GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) { + GB_log(gb, "The save state is for a different Game Boy model. Try changing the emulated model.\n"); + return false; + } + if (gb->mbc_ram_size < save->mbc_ram_size) { GB_log(gb, "The save state has non-matching MBC RAM size.\n"); return false; @@ -333,7 +338,26 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t } } - return true; + switch (save->model) { + case GB_MODEL_DMG_B: return true; + case GB_MODEL_SGB_NTSC: return true; + case GB_MODEL_SGB_PAL: return true; + case GB_MODEL_SGB_NTSC_NO_SFC: return true; + case GB_MODEL_SGB_PAL_NO_SFC: return true; + case GB_MODEL_MGB: return true; + case GB_MODEL_SGB2: return true; + case GB_MODEL_SGB2_NO_SFC: return true; + 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; + } + if ((gb->model & GB_MODEL_FAMILY_MASK) == (save->model & GB_MODEL_FAMILY_MASK)) { + save->model = gb->model; + return true; + } + GB_log(gb, "This save state is for an unknown Game Boy model\n"); + return false; } static void sanitize_state(GB_gameboy_t *gb) @@ -347,6 +371,35 @@ static void sanitize_state(GB_gameboy_t *gb) gb->bg_fifo.write_end &= 0xF; gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; + gb->last_tile_index_address &= 0x1FFF; + gb->window_tile_x &= 0x1F; + + /* These are kind of DOS-ish if too large */ + if (abs(gb->display_cycles) > 0x8000) { + gb->display_cycles = 0; + } + + if (abs(gb->div_cycles) > 0x8000) { + gb->div_cycles = 0; + } + + if (!GB_is_cgb(gb)) { + gb->cgb_mode = false; + } + + if (gb->ram_size == 0x8000) { + gb->cgb_ram_bank &= 0x7; + } + else { + gb->cgb_ram_bank = 1; + } + if (gb->vram_size != 0x4000) { + gb->cgb_vram_bank = 0; + } + if (!GB_is_cgb(gb)) { + gb->current_tile_attributes = 0; + } + gb->object_low_line_address &= gb->vram_size & ~1; if (gb->lcd_x > gb->position_in_line) { gb->lcd_x = gb->position_in_line; @@ -920,7 +973,9 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo save.halted = core.execution_mode == 1; save.stopped = core.execution_mode == 2; - + + // Done early for compatibility with 0.14.x + GB_write_memory(&save, 0xFF00 + GB_IO_SVBK, core.io_registers[GB_IO_SVBK]); // CPU related // Determines DMG mode @@ -981,7 +1036,6 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo GB_write_memory(&save, 0xFF00 + GB_IO_BGPI, core.io_registers[GB_IO_BGPI]); GB_write_memory(&save, 0xFF00 + GB_IO_OBPI, core.io_registers[GB_IO_OBPI]); GB_write_memory(&save, 0xFF00 + GB_IO_OPRI, core.io_registers[GB_IO_OPRI]); - GB_write_memory(&save, 0xFF00 + GB_IO_SVBK, core.io_registers[GB_IO_SVBK]); // Interrupts GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]); @@ -1020,6 +1074,7 @@ 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; for (unsigned i = LE32(block.size); i > 0; i -= 3) { BESS_MBC_pair_t pair; file->read(file, &pair, sizeof(pair)); @@ -1167,6 +1222,7 @@ error: GB_log(gb, "Attempted to import a save state from a different emulator or incompatible version, but the save state is invalid.\n"); } GB_free(&save); + sanitize_state(gb); return errno; } @@ -1274,7 +1330,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le } -bool GB_is_stave_state(const char *path) +bool GB_is_save_state(const char *path) { bool ret = false; FILE *f = fopen(path, "rb"); diff --git a/Core/save_state.h b/Core/save_state.h index 4572a72..bf43a65 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -27,7 +27,7 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); -bool GB_is_stave_state(const char *path); +bool GB_is_save_state(const char *path); #ifdef GB_INTERNAL static inline uint32_t state_magic(void) { diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 92adf46..f2a22a1 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -656,6 +656,7 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) case 3: return (gb->af & GB_CARRY_FLAG); } + __builtin_unreachable(); return false; } diff --git a/SDL/gui.c b/SDL/gui.c index d6afe60..d5c899b 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1344,7 +1344,7 @@ void run_gui(bool is_running) break; } case SDL_DROPFILE: { - if (GB_is_stave_state(event.drop.file)) { + if (GB_is_save_state(event.drop.file)) { if (GB_is_inited(&gb)) { dropped_state_file = event.drop.file; pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; diff --git a/SDL/main.c b/SDL/main.c index 2e9911e..999f143 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -225,7 +225,7 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_DROPFILE: { - if (GB_is_stave_state(event.drop.file)) { + if (GB_is_save_state(event.drop.file)) { dropped_state_file = event.drop.file; pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; } From fd8c9bba5de78c1b774228bee3cae77d4a6b4c3a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Oct 2021 16:03:33 +0300 Subject: [PATCH 291/365] Detect missing ANSI support on Windows --- SDL/console.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/SDL/console.c b/SDL/console.c index bacdc3a..ad9c2b5 100644 --- a/SDL/console.c +++ b/SDL/console.c @@ -83,17 +83,41 @@ static listent_t *reverse_find(listent_t *entry, const char *string, bool exact) static bool is_term(void) { + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) return false; #ifdef _WIN32 if (AllocConsole()) { FreeConsole(); return false; } + + unsigned long input_mode, output_mode; + + GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode); + GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode); + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + CONSOLE_SCREEN_BUFFER_INFO before = {0,}; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &before); + + printf(SGR("0")); + + CONSOLE_SCREEN_BUFFER_INFO after = {0,}; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &after); + + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode); + + + if (before.dwCursorPosition.X != after.dwCursorPosition.X || + before.dwCursorPosition.Y != after.dwCursorPosition.Y) { + printf("\r \r"); + return false; + } + return true; +#else + return getenv("TERM"); #endif - return isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) -#ifndef _WIN32 - && getenv("TERM") -#endif - ; } static unsigned width, height; @@ -779,7 +803,6 @@ mainloop(char *(*completer)(const char *substring, uintptr_t *context)) complete_context = completion_length = 0; break; default: - printf("Unsupported extended key %x\n", c); printf("\a"); break; } @@ -809,7 +832,6 @@ mainloop(char *(*completer)(const char *substring, uintptr_t *context)) complete_context = completion_length = 0; } else { - printf("Unsupported key %x\n", c); printf("\a"); } break; From 0dcd233cbbcd86930962cd999cd2ca52f136c30d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Oct 2021 23:51:48 +0300 Subject: [PATCH 292/365] Writes to SVBK should work before the boot ROM is disabled --- Core/memory.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 1720356..2fdc72d 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1234,12 +1234,11 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[GB_IO_DMA] = value; return; case GB_IO_SVBK: - if (!gb->cgb_mode) { - return; - } - gb->cgb_ram_bank = value & 0x7; - if (!gb->cgb_ram_bank) { - gb->cgb_ram_bank++; + if (gb->cgb_mode || (GB_is_cgb(gb) && !gb->boot_rom_finished)) { + gb->cgb_ram_bank = value & 0x7; + if (!gb->cgb_ram_bank) { + gb->cgb_ram_bank++; + } } return; case GB_IO_VBK: From 0d7cc66ffd054683a3add04a750b3031cb1803e5 Mon Sep 17 00:00:00 2001 From: Snowy Date: Sun, 24 Oct 2021 11:15:28 -0500 Subject: [PATCH 293/365] Change y to a signed value --- Core/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index ef1dde6..aa958de 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1526,7 +1526,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h uint8_t count = 0; *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; uint8_t oam_to_dest_index[40] = {0,}; - for (unsigned y = 0; y < LINES; y++) { + for (signed y = 0; y < LINES; y++) { GB_object_t *sprite = (GB_object_t *) &gb->oam; uint8_t sprites_in_line = 0; for (uint8_t i = 0; i < 40; i++, sprite++) { From 8d319c65c29c4469f9671be607d242c1d17df505 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 27 Oct 2021 01:43:36 +0300 Subject: [PATCH 294/365] Use a monospaced font in the palette viewer --- Cocoa/GBColorCell.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cocoa/GBColorCell.m b/Cocoa/GBColorCell.m index 549c3dd..761ad43 100644 --- a/Cocoa/GBColorCell.m +++ b/Cocoa/GBColorCell.m @@ -13,13 +13,13 @@ static inline double scale_channel(uint8_t x) - (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] + NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor], + NSFontAttributeName: [NSFont userFixedPitchFontOfSize:12] }]; } @@ -36,6 +36,7 @@ static inline double scale_channel(uint8_t x) - (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]; } From 4498d16bed3f98fd0dc0654805d0fa6e50007e63 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Oct 2021 16:03:13 +0300 Subject: [PATCH 295/365] Improved sanitation for save states for better security and stability --- Cocoa/GBView.m | 2 +- Core/gb.h | 3 ++- Core/joypad.c | 1 + Core/memory.c | 11 ++++---- Core/save_state.c | 64 ++++++++++++++++++++++++++++++++++++++++++++--- Core/save_state.h | 2 +- Core/sm83_cpu.c | 1 + SDL/gui.c | 2 +- SDL/main.c | 2 +- 9 files changed, 73 insertions(+), 15 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 39a22e8..6c92c3f 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -658,7 +658,7 @@ static const uint8_t workboy_vk_to_key[] = { if ( [[pboard types] containsObject:NSURLPboardType] ) { NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; - if (GB_is_stave_state(fileURL.fileSystemRepresentation)) { + if (GB_is_save_state(fileURL.fileSystemRepresentation)) { return NSDragOperationGeneric; } } diff --git a/Core/gb.h b/Core/gb.h index ea4fe7d..655346b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -24,6 +24,7 @@ #include "cheats.h" #include "rumble.h" #include "workboy.h" +#include "random.h" #define GB_STRUCT_VERSION 13 @@ -554,7 +555,7 @@ struct GB_gameboy_internal_s { /* Video Display */ GB_SECTION(video, uint32_t vram_size; // Different between CGB and DMG - uint8_t cgb_vram_bank; + bool cgb_vram_bank; uint8_t oam[0xA0]; uint8_t background_palettes_data[0x40]; uint8_t sprite_palettes_data[0x40]; diff --git a/Core/joypad.c b/Core/joypad.c index b30258c..c0655f0 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -52,6 +52,7 @@ void GB_update_joyp(GB_gameboy_t *gb) break; default: + __builtin_unreachable(); break; } diff --git a/Core/memory.c b/Core/memory.c index 2fdc72d..426e5d6 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -291,7 +291,7 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) addr = gb->last_tile_data_address; } } - return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; + return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)]; } static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) @@ -464,7 +464,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) break; default: - break; + __builtin_unreachable(); } for (unsigned i = 0; i < 8; i++) { @@ -635,8 +635,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return 0xFF; } - /* Hardware registers */ - return 0; + __builtin_unreachable(); } if (addr == 0xFFFF) { @@ -815,7 +814,7 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) addr = gb->last_tile_data_address; } } - gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; + gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)] = value; } static bool huc3_write(GB_gameboy_t *gb, uint8_t value) @@ -1395,7 +1394,7 @@ static GB_write_function_t * const write_map[] = write_vram, write_vram, /* 8XXX, 9XXX */ write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */ write_ram, write_banked_ram, /* CXXX, DXXX */ - write_ram, write_high_memory, /* EXXX FXXX */ + write_ram, write_high_memory, /* EXXX FXXX */ }; void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) diff --git a/Core/save_state.c b/Core/save_state.c index 6e7ca9d..715baa9 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -307,6 +307,11 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t return false; } + if (GB_is_cgb(gb) != GB_is_cgb(save) || GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) { + GB_log(gb, "The save state is for a different Game Boy model. Try changing the emulated model.\n"); + return false; + } + if (gb->mbc_ram_size < save->mbc_ram_size) { GB_log(gb, "The save state has non-matching MBC RAM size.\n"); return false; @@ -333,7 +338,26 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t } } - return true; + switch (save->model) { + case GB_MODEL_DMG_B: return true; + case GB_MODEL_SGB_NTSC: return true; + case GB_MODEL_SGB_PAL: return true; + case GB_MODEL_SGB_NTSC_NO_SFC: return true; + case GB_MODEL_SGB_PAL_NO_SFC: return true; + case GB_MODEL_MGB: return true; + case GB_MODEL_SGB2: return true; + case GB_MODEL_SGB2_NO_SFC: return true; + 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; + } + if ((gb->model & GB_MODEL_FAMILY_MASK) == (save->model & GB_MODEL_FAMILY_MASK)) { + save->model = gb->model; + return true; + } + GB_log(gb, "This save state is for an unknown Game Boy model\n"); + return false; } static void sanitize_state(GB_gameboy_t *gb) @@ -347,6 +371,35 @@ static void sanitize_state(GB_gameboy_t *gb) gb->bg_fifo.write_end &= 0xF; gb->oam_fifo.read_end &= 0xF; gb->oam_fifo.write_end &= 0xF; + gb->last_tile_index_address &= 0x1FFF; + gb->window_tile_x &= 0x1F; + + /* These are kind of DOS-ish if too large */ + if (abs(gb->display_cycles) > 0x8000) { + gb->display_cycles = 0; + } + + if (abs(gb->div_cycles) > 0x8000) { + gb->div_cycles = 0; + } + + if (!GB_is_cgb(gb)) { + gb->cgb_mode = false; + } + + if (gb->ram_size == 0x8000) { + gb->cgb_ram_bank &= 0x7; + } + else { + gb->cgb_ram_bank = 1; + } + if (gb->vram_size != 0x4000) { + gb->cgb_vram_bank = 0; + } + if (!GB_is_cgb(gb)) { + gb->current_tile_attributes = 0; + } + gb->object_low_line_address &= gb->vram_size & ~1; if (gb->lcd_x > gb->position_in_line) { gb->lcd_x = gb->position_in_line; @@ -918,7 +971,9 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo save.halted = core.execution_mode == 1; save.stopped = core.execution_mode == 2; - + + // Done early for compatibility with 0.14.x + GB_write_memory(&save, 0xFF00 + GB_IO_SVBK, core.io_registers[GB_IO_SVBK]); // CPU related // Determines DMG mode @@ -979,7 +1034,6 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo GB_write_memory(&save, 0xFF00 + GB_IO_BGPI, core.io_registers[GB_IO_BGPI]); GB_write_memory(&save, 0xFF00 + GB_IO_OBPI, core.io_registers[GB_IO_OBPI]); GB_write_memory(&save, 0xFF00 + GB_IO_OPRI, core.io_registers[GB_IO_OPRI]); - GB_write_memory(&save, 0xFF00 + GB_IO_SVBK, core.io_registers[GB_IO_SVBK]); // Interrupts GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]); @@ -1018,6 +1072,7 @@ 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; for (unsigned i = LE32(block.size); i > 0; i -= 3) { BESS_MBC_pair_t pair; file->read(file, &pair, sizeof(pair)); @@ -1165,6 +1220,7 @@ error: GB_log(gb, "Attempted to import a save state from a different emulator or incompatible version, but the save state is invalid.\n"); } GB_free(&save); + sanitize_state(gb); return errno; } @@ -1272,7 +1328,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le } -bool GB_is_stave_state(const char *path) +bool GB_is_save_state(const char *path) { bool ret = false; FILE *f = fopen(path, "rb"); diff --git a/Core/save_state.h b/Core/save_state.h index 4572a72..bf43a65 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -27,7 +27,7 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); -bool GB_is_stave_state(const char *path); +bool GB_is_save_state(const char *path); #ifdef GB_INTERNAL static inline uint32_t state_magic(void) { diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index a918081..d4829d5 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -656,6 +656,7 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) case 3: return (gb->af & GB_CARRY_FLAG); } + __builtin_unreachable(); return false; } diff --git a/SDL/gui.c b/SDL/gui.c index 6bccd87..d9a58e5 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -1344,7 +1344,7 @@ void run_gui(bool is_running) break; } case SDL_DROPFILE: { - if (GB_is_stave_state(event.drop.file)) { + if (GB_is_save_state(event.drop.file)) { if (GB_is_inited(&gb)) { dropped_state_file = event.drop.file; pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; diff --git a/SDL/main.c b/SDL/main.c index c0fd186..ccfa906 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -225,7 +225,7 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_DROPFILE: { - if (GB_is_stave_state(event.drop.file)) { + if (GB_is_save_state(event.drop.file)) { dropped_state_file = event.drop.file; pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; } From deb037d87dac3ffc467100fbf21158256db12d90 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Oct 2021 16:03:33 +0300 Subject: [PATCH 296/365] Detect missing ANSI support on Windows --- SDL/console.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/SDL/console.c b/SDL/console.c index bacdc3a..ad9c2b5 100644 --- a/SDL/console.c +++ b/SDL/console.c @@ -83,17 +83,41 @@ static listent_t *reverse_find(listent_t *entry, const char *string, bool exact) static bool is_term(void) { + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) return false; #ifdef _WIN32 if (AllocConsole()) { FreeConsole(); return false; } + + unsigned long input_mode, output_mode; + + GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode); + GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode); + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + CONSOLE_SCREEN_BUFFER_INFO before = {0,}; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &before); + + printf(SGR("0")); + + CONSOLE_SCREEN_BUFFER_INFO after = {0,}; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &after); + + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode); + + + if (before.dwCursorPosition.X != after.dwCursorPosition.X || + before.dwCursorPosition.Y != after.dwCursorPosition.Y) { + printf("\r \r"); + return false; + } + return true; +#else + return getenv("TERM"); #endif - return isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) -#ifndef _WIN32 - && getenv("TERM") -#endif - ; } static unsigned width, height; @@ -779,7 +803,6 @@ mainloop(char *(*completer)(const char *substring, uintptr_t *context)) complete_context = completion_length = 0; break; default: - printf("Unsupported extended key %x\n", c); printf("\a"); break; } @@ -809,7 +832,6 @@ mainloop(char *(*completer)(const char *substring, uintptr_t *context)) complete_context = completion_length = 0; } else { - printf("Unsupported key %x\n", c); printf("\a"); } break; From 0f6a0186cd39ce89eb9e517ede8e90cad3084bd7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Oct 2021 16:09:13 +0300 Subject: [PATCH 297/365] Cherry picking conflicts --- Core/save_state.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/save_state.c b/Core/save_state.c index 715baa9..b4780e8 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -344,11 +344,9 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t case GB_MODEL_SGB_PAL: return true; case GB_MODEL_SGB_NTSC_NO_SFC: return true; case GB_MODEL_SGB_PAL_NO_SFC: return true; - case GB_MODEL_MGB: return true; case GB_MODEL_SGB2: return true; case GB_MODEL_SGB2_NO_SFC: return true; 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; } From 43831d0bc137d2f10d39939035dc10bfc4cc33e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Oct 2021 16:10:26 +0300 Subject: [PATCH 298/365] Update version to 0.14.7 --- version.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.mk b/version.mk index 6fab0f9..8964270 100644 --- a/version.mk +++ b/version.mk @@ -1 +1 @@ -VERSION := 0.14.6 \ No newline at end of file +VERSION := 0.14.7 \ No newline at end of file From 6cd13be6249b2285264af8c1c55ef662f9553153 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 30 Oct 2021 20:58:57 +0300 Subject: [PATCH 299/365] Add CGB-B support --- Cocoa/Preferences.xib | 4 ++-- Core/apu.c | 7 ++----- Core/gb.c | 11 ++++++++--- Core/gb.h | 2 +- Core/memory.c | 4 ++-- Core/save_state.c | 3 ++- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 41c1cda..5cb44ae 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -399,7 +399,7 @@ - + @@ -608,7 +608,7 @@ - + diff --git a/Core/apu.c b/Core/apu.c index e049c06..e0b75f9 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -834,7 +834,6 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) switch (gb->model) { /* Pre CGB revisions are assumed to be like CGB-C, A and 0 for the lack of a better guess. TODO: It could be verified with audio based test ROMs. */ -#if 0 case GB_MODEL_CGB_B: if (effective_counter & 8) { effective_counter |= 0xE; // Seems to me F under some circumstances? @@ -861,7 +860,6 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) effective_counter |= 0x20; } break; -#endif case GB_MODEL_DMG_B: case GB_MODEL_MGB: case GB_MODEL_SGB_NTSC: @@ -1056,7 +1054,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR14: case GB_IO_NR24: { - /* TODO: GB_MODEL_CGB_D fails channel_1_sweep_restart_2, don't forget when adding support for this revision! */ unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1; bool was_active = gb->apu.is_active[index]; /* TODO: When the sample length changes right before being updated, the countdown should change to the @@ -1162,7 +1159,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ - if ((value & 0x40) && + if (((value & 0x40) || (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_B)) && // Current value is irrelevant on CGB-B and older !gb->apu.square_channels[index].length_enabled && (gb->apu.div_divider & 1) && gb->apu.square_channels[index].pulse_length) { @@ -1262,7 +1259,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */ - if ((value & 0x40) && + if (((value & 0x40) || (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_B)) && // Current value is irrelevant on CGB-B and older !gb->apu.wave_channel.length_enabled && (gb->apu.div_divider & 1) && gb->apu.wave_channel.pulse_length) { diff --git a/Core/gb.c b/Core/gb.c index 8d2545d..86e012f 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1463,13 +1463,14 @@ static void reset_ram(GB_gameboy_t *gb) } break; + case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: for (unsigned i = 0; i < gb->ram_size; i++) { if ((i & 0x808) == 0x800 || (i & 0x808) == 0x008) { gb->ram[i] = 0; } else { - gb->ram[i] = GB_random() | GB_random() | GB_random() | GB_random(); + gb->ram[i] = GB_random() | GB_random() | GB_random() | GB_random() | GB_random(); } } break; @@ -1488,6 +1489,7 @@ static void reset_ram(GB_gameboy_t *gb) /* HRAM */ switch (gb->model) { + case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: @@ -1518,11 +1520,12 @@ static void reset_ram(GB_gameboy_t *gb) /* OAM */ switch (gb->model) { + case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: - case GB_MODEL_CGB_D: + case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: case GB_MODEL_AGB: - /* Zero'd out by boot ROM anyway*/ + /* Zero'd out by boot ROM anyway, extra OAM no accessible */ break; case GB_MODEL_DMG_B: @@ -1549,6 +1552,7 @@ static void reset_ram(GB_gameboy_t *gb) /* Wave RAM */ switch (gb->model) { + case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: @@ -1622,6 +1626,7 @@ static void request_boot_rom(GB_gameboy_t *gb) case GB_MODEL_SGB2_NO_SFC: type = GB_BOOT_ROM_SGB2; break; + case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: diff --git a/Core/gb.h b/Core/gb.h index d00d438..67ad95e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -133,7 +133,7 @@ typedef enum { GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, // GB_MODEL_CGB_0 = 0x200, // GB_MODEL_CGB_A = 0x201, - // GB_MODEL_CGB_B = 0x202, + GB_MODEL_CGB_B = 0x202, GB_MODEL_CGB_C = 0x203, GB_MODEL_CGB_D = 0x204, GB_MODEL_CGB_E = 0x205, diff --git a/Core/memory.c b/Core/memory.c index 22926be..a3647c2 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -503,8 +503,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->extra_oam[addr - 0xfea0]; case GB_MODEL_CGB_C: + case GB_MODEL_CGB_B: /* - case GB_MODEL_CGB_B: case GB_MODEL_CGB_A: case GB_MODEL_CGB_0: */ @@ -1009,8 +1009,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->extra_oam[addr - 0xfea0] = value; break; case GB_MODEL_CGB_C: + case GB_MODEL_CGB_B: /* - case GB_MODEL_CGB_B: case GB_MODEL_CGB_A: case GB_MODEL_CGB_0: */ diff --git a/Core/save_state.c b/Core/save_state.c index 6befa2d..e33dbf1 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -347,6 +347,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t case GB_MODEL_MGB: return true; case GB_MODEL_SGB2: return true; case GB_MODEL_SGB2_NO_SFC: return true; + case GB_MODEL_CGB_B: return true; case GB_MODEL_CGB_C: return true; case GB_MODEL_CGB_D: return true; case GB_MODEL_CGB_E: return true; @@ -647,7 +648,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe case GB_MODEL_SGB2: bess_core.full_model = BE32('S2 '); break; - + case GB_MODEL_CGB_B: bess_core.full_model = BE32('CCB '); break; 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; From f237b1e9b9ebec2089239c10b8efbb228f9ae26f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 4 Nov 2021 00:32:15 +0200 Subject: [PATCH 300/365] CGB-0 support --- BootROMs/cgb0_boot.asm | 2 ++ BootROMs/cgb_boot.asm | 2 ++ Cocoa/Document.m | 4 ++-- Cocoa/Preferences.xib | 2 +- Core/apu.c | 2 +- Core/display.c | 3 +++ Core/gb.c | 11 +++++++++-- Core/gb.h | 6 +++--- Core/memory.c | 10 +++------- Core/save_state.c | 2 ++ Makefile | 9 +++++---- SDL/main.c | 4 ++-- libretro/libretro.c | 12 ++++++------ 13 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 BootROMs/cgb0_boot.asm diff --git a/BootROMs/cgb0_boot.asm b/BootROMs/cgb0_boot.asm new file mode 100644 index 0000000..d49166d --- /dev/null +++ b/BootROMs/cgb0_boot.asm @@ -0,0 +1,2 @@ +CGB0 EQU 1 +include "cgb_boot.asm" \ No newline at end of file diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index 0bc2b17..ca3b57f 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -23,6 +23,7 @@ Start: dec c jr nz, .clearOAMLoop +IF !DEF(CGB0) ; Init waveform ld c, $10 ld hl, $FF30 @@ -31,6 +32,7 @@ Start: cpl dec c jr nz, .waveformLoop +ENDC ; Clear chosen input palette ldh [InputPalette], a diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 74fc98e..2a77259 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -576,12 +576,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void) loadBootROM: (GB_boot_rom_t)type { static NSString *const names[] = { - [GB_BOOT_ROM_DMG0] = @"dmg0_boot", + [GB_BOOT_ROM_DMG_0] = @"dmg0_boot", [GB_BOOT_ROM_DMG] = @"dmg_boot", [GB_BOOT_ROM_MGB] = @"mgb_boot", [GB_BOOT_ROM_SGB] = @"sgb_boot", [GB_BOOT_ROM_SGB2] = @"sgb2_boot", - [GB_BOOT_ROM_CGB0] = @"cgb0_boot", + [GB_BOOT_ROM_CGB_0] = @"cgb0_boot", [GB_BOOT_ROM_CGB] = @"cgb_boot", [GB_BOOT_ROM_AGB] = @"agb_boot", }; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 5cb44ae..67e57b1 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -397,7 +397,7 @@ - + diff --git a/Core/apu.c b/Core/apu.c index e0b75f9..aac87b5 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -868,7 +868,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) case GB_MODEL_SGB_PAL_NO_SFC: case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: - // case GB_MODEL_CGB_0: + case GB_MODEL_CGB_0: // case GB_MODEL_CGB_A: case GB_MODEL_CGB_C: if (effective_counter & 8) { diff --git a/Core/display.c b/Core/display.c index e8b0006..83a499f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -164,6 +164,9 @@ static void display_vblank(GB_gameboy_t *gb) 0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964, }; 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! + } gb->borrowed_border.palette[0] = LE16(colors[index]); gb->borrowed_border.palette[10] = LE16(colors[5 + index]); gb->borrowed_border.palette[14] = LE16(colors[10 + index]); diff --git a/Core/gb.c b/Core/gb.c index 86e012f..0c47ce9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1462,7 +1462,8 @@ static void reset_ram(GB_gameboy_t *gb) gb->ram[i] ^= GB_random() & GB_random() & GB_random(); } break; - + + case GB_MODEL_CGB_0: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: for (unsigned i = 0; i < gb->ram_size; i++) { @@ -1489,6 +1490,7 @@ static void reset_ram(GB_gameboy_t *gb) /* HRAM */ switch (gb->model) { + case GB_MODEL_CGB_0: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: @@ -1520,6 +1522,7 @@ static void reset_ram(GB_gameboy_t *gb) /* OAM */ switch (gb->model) { + case GB_MODEL_CGB_0: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: @@ -1552,12 +1555,13 @@ static void reset_ram(GB_gameboy_t *gb) /* Wave RAM */ switch (gb->model) { + case GB_MODEL_CGB_0: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: case GB_MODEL_CGB_E: case GB_MODEL_AGB: - /* Initialized by CGB-A and newer, 0s in CGB-0*/ + /* Initialized by CGB-A and newer, 0s in CGB-0 */ break; case GB_MODEL_MGB: { for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) { @@ -1626,6 +1630,9 @@ static void request_boot_rom(GB_gameboy_t *gb) case GB_MODEL_SGB2_NO_SFC: type = GB_BOOT_ROM_SGB2; break; + case GB_MODEL_CGB_0: + type = GB_BOOT_ROM_CGB_0; + break; case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: diff --git a/Core/gb.h b/Core/gb.h index 67ad95e..bd0fa83 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -131,7 +131,7 @@ typedef enum { GB_MODEL_MGB = 0x100, GB_MODEL_SGB2 = 0x101, GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, - // GB_MODEL_CGB_0 = 0x200, + GB_MODEL_CGB_0 = 0x200, // GB_MODEL_CGB_A = 0x201, GB_MODEL_CGB_B = 0x202, GB_MODEL_CGB_C = 0x203, @@ -277,12 +277,12 @@ typedef enum { } GB_log_attributes; typedef enum { - GB_BOOT_ROM_DMG0, + GB_BOOT_ROM_DMG_0, GB_BOOT_ROM_DMG, GB_BOOT_ROM_MGB, GB_BOOT_ROM_SGB, GB_BOOT_ROM_SGB2, - GB_BOOT_ROM_CGB0, + GB_BOOT_ROM_CGB_0, GB_BOOT_ROM_CGB, GB_BOOT_ROM_AGB, } GB_boot_rom_t; diff --git a/Core/memory.c b/Core/memory.c index a3647c2..c221352 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -504,10 +504,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_MODEL_CGB_C: case GB_MODEL_CGB_B: - /* - case GB_MODEL_CGB_A: + // case GB_MODEL_CGB_A: case GB_MODEL_CGB_0: - */ addr &= ~0x18; return gb->extra_oam[addr - 0xfea0]; @@ -1010,10 +1008,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_MODEL_CGB_C: case GB_MODEL_CGB_B: - /* - case GB_MODEL_CGB_A: - case GB_MODEL_CGB_0: - */ + // case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: addr &= ~0x18; gb->extra_oam[addr - 0xfea0] = value; break; diff --git a/Core/save_state.c b/Core/save_state.c index e33dbf1..e472995 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -347,6 +347,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t case GB_MODEL_MGB: return true; case GB_MODEL_SGB2: return true; case GB_MODEL_SGB2_NO_SFC: return true; + case GB_MODEL_CGB_0: return true; case GB_MODEL_CGB_B: return true; case GB_MODEL_CGB_C: return true; case GB_MODEL_CGB_D: return true; @@ -648,6 +649,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe case GB_MODEL_SGB2: bess_core.full_model = BE32('S2 '); break; + case GB_MODEL_CGB_0: bess_core.full_model = BE32('CC0 '); break; case GB_MODEL_CGB_B: bess_core.full_model = BE32('CCB '); break; case GB_MODEL_CGB_C: bess_core.full_model = BE32('CCC '); break; case GB_MODEL_CGB_D: bess_core.full_model = BE32('CCD '); break; diff --git a/Makefile b/Makefile index fadc025..7016ac3 100644 --- a/Makefile +++ b/Makefile @@ -199,9 +199,9 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/mgb_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders -bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/mgb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin -tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/mgb_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb0_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/cgb0_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin +tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin all: cocoa sdl tester libretro # Get a list of our source files and their respective object file targets @@ -280,6 +280,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ Misc/registers.sym \ $(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/mgb_boot.bin \ + $(BIN)/SameBoy.app/Contents/Resources/cgb0_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/agb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/sgb_boot.bin \ @@ -418,7 +419,7 @@ $(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRE $(PB12_COMPRESS): BootROMs/pb12.c $(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@ -$(BIN)/BootROMs/mgb_boot.bin: BootROMs/mgb_boot.asm +$(BIN)/BootROMs/cgb0_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm $(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm diff --git a/SDL/main.c b/SDL/main.c index 999f143..b6607f5 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -590,12 +590,12 @@ static bool handle_pending_command(void) static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) { static const char *const names[] = { - [GB_BOOT_ROM_DMG0] = "dmg0_boot.bin", + [GB_BOOT_ROM_DMG_0] = "dmg0_boot.bin", [GB_BOOT_ROM_DMG] = "dmg_boot.bin", [GB_BOOT_ROM_MGB] = "mgb_boot.bin", [GB_BOOT_ROM_SGB] = "sgb_boot.bin", [GB_BOOT_ROM_SGB2] = "sgb2_boot.bin", - [GB_BOOT_ROM_CGB0] = "cgb0_boot.bin", + [GB_BOOT_ROM_CGB_0] = "cgb0_boot.bin", [GB_BOOT_ROM_CGB] = "cgb_boot.bin", [GB_BOOT_ROM_AGB] = "agb_boot.bin", }; diff --git a/libretro/libretro.c b/libretro/libretro.c index 5a559c4..94b82a6 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -418,34 +418,34 @@ static void set_link_cable_state(bool state) static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type) { const char *model_name = (char *[]) { - [GB_BOOT_ROM_DMG0] = "dmg0", + [GB_BOOT_ROM_DMG_0] = "dmg0", [GB_BOOT_ROM_DMG] = "dmg", [GB_BOOT_ROM_MGB] = "mgb", [GB_BOOT_ROM_SGB] = "sgb", [GB_BOOT_ROM_SGB2] = "sgb2", - [GB_BOOT_ROM_CGB0] = "cgb0", + [GB_BOOT_ROM_CGB_0] = "cgb0", [GB_BOOT_ROM_CGB] = "cgb", [GB_BOOT_ROM_AGB] = "agb", }[type]; const uint8_t *boot_code = (const unsigned char *[]) { - [GB_BOOT_ROM_DMG0] = dmg_boot, // dmg0 not implemented yet + [GB_BOOT_ROM_DMG_0] = dmg_boot, // DMG_0 not implemented yet [GB_BOOT_ROM_DMG] = dmg_boot, [GB_BOOT_ROM_MGB] = dmg_boot, // mgb not implemented yet [GB_BOOT_ROM_SGB] = sgb_boot, [GB_BOOT_ROM_SGB2] = sgb2_boot, - [GB_BOOT_ROM_CGB0] = cgb_boot, // cgb0 not implemented yet + [GB_BOOT_ROM_CGB_0] = cgb_boot, // CGB_0 not implemented yet [GB_BOOT_ROM_CGB] = cgb_boot, [GB_BOOT_ROM_AGB] = agb_boot, }[type]; unsigned boot_length = (unsigned []) { - [GB_BOOT_ROM_DMG0] = dmg_boot_length, // dmg0 not implemented yet + [GB_BOOT_ROM_DMG_0] = dmg_boot_length, // DMG_0 not implemented yet [GB_BOOT_ROM_DMG] = dmg_boot_length, [GB_BOOT_ROM_MGB] = dmg_boot_length, // mgb not implemented yet [GB_BOOT_ROM_SGB] = sgb_boot_length, [GB_BOOT_ROM_SGB2] = sgb2_boot_length, - [GB_BOOT_ROM_CGB0] = cgb_boot_length, // cgb0 not implemented yet + [GB_BOOT_ROM_CGB_0] = cgb_boot_length, // CGB_0 not implemented yet [GB_BOOT_ROM_CGB] = cgb_boot_length, [GB_BOOT_ROM_AGB] = agb_boot_length, }[type]; From 178860e71580646530e95bbb90abbe3a32689664 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 5 Nov 2021 19:07:27 +0200 Subject: [PATCH 301/365] Custom palette and editor --- Cocoa/Document.m | 21 +- Cocoa/GBHueSliderCell.h | 9 + Cocoa/GBHueSliderCell.m | 113 +++++++++ Cocoa/GBPaletteEditorController.h | 18 ++ Cocoa/GBPaletteEditorController.m | 378 ++++++++++++++++++++++++++++++ Cocoa/GBPreferencesWindow.h | 3 + Cocoa/GBPreferencesWindow.m | 53 ++++- Cocoa/Preferences.xib | 266 ++++++++++++++++++++- Core/gb.h | 2 +- 9 files changed, 833 insertions(+), 30 deletions(-) create mode 100644 Cocoa/GBHueSliderCell.h create mode 100644 Cocoa/GBHueSliderCell.m create mode 100644 Cocoa/GBPaletteEditorController.h create mode 100644 Cocoa/GBPaletteEditorController.m diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 2a77259..c757ce1 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -10,6 +10,7 @@ #include "GBCheatWindowController.h" #include "GBTerminalTextFieldCell.h" #include "BigSurToolbar.h" +#import "GBPaletteEditorController.h" /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: Split into category files! This is so messy!!! */ @@ -256,23 +257,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) - (void) updatePalette { - switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]) { - case 1: - GB_set_palette(&gb, &GB_PALETTE_DMG); - break; - - case 2: - GB_set_palette(&gb, &GB_PALETTE_MGB); - break; - - case 3: - GB_set_palette(&gb, &GB_PALETTE_GBL); - break; - - default: - GB_set_palette(&gb, &GB_PALETTE_GREY); - break; - } + GB_set_palette(&gb, [GBPaletteEditorController userPalette]); } - (void) updateBorderMode @@ -1952,7 +1937,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) { bool shouldResume = running; [self stop]; - NSSavePanel * savePanel = [NSSavePanel savePanel]; + NSSavePanel *savePanel = [NSSavePanel savePanel]; [savePanel setAllowedFileTypes:@[@"png"]]; [savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result) { if (result == NSModalResponseOK) { diff --git a/Cocoa/GBHueSliderCell.h b/Cocoa/GBHueSliderCell.h new file mode 100644 index 0000000..f293124 --- /dev/null +++ b/Cocoa/GBHueSliderCell.h @@ -0,0 +1,9 @@ +#import + +@interface NSSlider (GBHueSlider) +-(NSColor *)colorValue; +@end + +@interface GBHueSliderCell : NSSliderCell +-(NSColor *)colorValue; +@end diff --git a/Cocoa/GBHueSliderCell.m b/Cocoa/GBHueSliderCell.m new file mode 100644 index 0000000..7f0bd82 --- /dev/null +++ b/Cocoa/GBHueSliderCell.m @@ -0,0 +1,113 @@ +#import "GBHueSliderCell.h" + +@interface NSSliderCell(privateAPI) +- (double)_normalizedDoubleValue; +@end + +@implementation GBHueSliderCell +{ + bool _drawingTrack; +} + +-(NSColor *)colorValue +{ + double hue = self.doubleValue / 360.0; + double r = 0, g = 0, b =0 ; + double t = fmod(hue * 6, 1); + switch ((int)(hue * 6) % 6) { + case 0: + r = 1; + g = t; + break; + case 1: + r = 1 - t; + g = 1; + b = 0; + case 2: + g = 1; + b = t; + break; + case 3: + g = 1 - t; + b = 1; + break; + case 4: + b = 1; + r = t; + break; + case 5: + b = 1 - t; + r = 1; + break; + } + return [NSColor colorWithRed:r green:g blue:b alpha:1.0]; +} + +-(void)drawKnob:(NSRect)knobRect +{ + [super drawKnob:knobRect]; + NSRect peekRect = knobRect; + peekRect.size.width /= 2; + peekRect.size.height /= 2; + peekRect.origin.x += peekRect.size.width / 2; + peekRect.origin.y += peekRect.size.height / 2; + NSColor *color = self.colorValue; + if (!self.enabled) { + color = [color colorWithAlphaComponent:0.5]; + } + [color setFill]; + NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:peekRect]; + [path fill]; + [[NSColor colorWithWhite:0 alpha:0.25] setStroke]; + [path setLineWidth:0.5]; + [path stroke]; +} + +-(double)_normalizedDoubleValue +{ + if (_drawingTrack) return 0; + return [super _normalizedDoubleValue]; +} + +-(void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped +{ + if (!self.enabled) { + [super drawBarInside:rect flipped:flipped]; + return; + } + + _drawingTrack = true; + [super drawBarInside:rect flipped:flipped]; + _drawingTrack = false; + + NSGradient *gradient = [[NSGradient alloc] initWithColors:@[ + [NSColor redColor], + [NSColor yellowColor], + [NSColor greenColor], + [NSColor cyanColor], + [NSColor blueColor], + [NSColor magentaColor], + [NSColor redColor], + ]]; + + rect.origin.y += rect.size.height / 2 - 0.5; + rect.size.height = 1; + rect.size.width -= 2; + rect.origin.x += 1; + [[NSColor redColor] set]; + NSRectFill(rect); + + rect.size.width -= self.knobThickness + 2; + rect.origin.x += self.knobThickness / 2 - 1; + + [gradient drawInRect:rect angle:0]; +} + +@end + +@implementation NSSlider (GBHueSlider) +- (NSColor *)colorValue +{ + return ((GBHueSliderCell *)self.cell).colorValue; +} +@end diff --git a/Cocoa/GBPaletteEditorController.h b/Cocoa/GBPaletteEditorController.h new file mode 100644 index 0000000..fd362ec --- /dev/null +++ b/Cocoa/GBPaletteEditorController.h @@ -0,0 +1,18 @@ +#import +#import + +@interface GBPaletteEditorController : NSObject +@property (weak) IBOutlet NSColorWell *colorWell0; +@property (weak) IBOutlet NSColorWell *colorWell1; +@property (weak) IBOutlet NSColorWell *colorWell2; +@property (weak) IBOutlet NSColorWell *colorWell3; +@property (weak) IBOutlet NSColorWell *colorWell4; +@property (weak) IBOutlet NSButton *disableLCDColorCheckbox; +@property (weak) IBOutlet NSButton *manualModeCheckbox; +@property (weak) IBOutlet NSSlider *brightnessSlider; +@property (weak) IBOutlet NSSlider *hueSlider; +@property (weak) IBOutlet NSSlider *hueStrengthSlider; +@property (weak) IBOutlet NSTableView *themesList; +@property (weak) IBOutlet NSMenu *menu; ++ (const GB_palette_t *)userPalette; +@end diff --git a/Cocoa/GBPaletteEditorController.m b/Cocoa/GBPaletteEditorController.m new file mode 100644 index 0000000..5df4aef --- /dev/null +++ b/Cocoa/GBPaletteEditorController.m @@ -0,0 +1,378 @@ +#import "GBPaletteEditorController.h" +#import "GBHueSliderCell.h" +#import + +#define MAGIC 'SBPL' + +typedef struct __attribute__ ((packed)) { + uint32_t magic; + bool manual:1; + bool disabled_lcd_color:1; + unsigned padding:6; + struct GB_color_s colors[5]; + int32_t brightness_bias; + uint32_t hue_bias; + uint32_t hue_bias_strength; +} theme_t; + +static double blend(double from, double to, double position) +{ + return from * (1 - position) + to * position; +} + +@implementation NSColor (GBColor) + +- (struct GB_color_s)gbColor +{ + NSColor *sRGB = [self colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + return (struct GB_color_s){round(sRGB.redComponent * 255), round(sRGB.greenComponent * 255), round(sRGB.blueComponent * 255)}; +} + +- (uint32_t)intValue +{ + struct GB_color_s color = self.gbColor; + return (color.r << 0) | (color.g << 8) | (color.b << 16) | 0xFF000000; +} + +@end + +@implementation GBPaletteEditorController + +- (NSArray *)colorWells +{ + return @[_colorWell0, _colorWell1, _colorWell2, _colorWell3, _colorWell4]; +} + +- (void)updateEnabledControls +{ + if (self.manualModeCheckbox.state) { + _brightnessSlider.enabled = false; + _hueSlider.enabled = false; + _hueStrengthSlider.enabled = false; + _colorWell1.enabled = true; + _colorWell2.enabled = true; + _colorWell3.enabled = true; + if (!(_colorWell4.enabled = self.disableLCDColorCheckbox.state)) { + _colorWell4.color = _colorWell3.color; + } + } + else { + _colorWell1.enabled = false; + _colorWell2.enabled = false; + _colorWell3.enabled = false; + _colorWell4.enabled = true; + _brightnessSlider.enabled = true; + _hueSlider.enabled = true; + _hueStrengthSlider.enabled = true; + [self updateAutoColors]; + } +} + +- (NSColor *)autoColorAtPositon:(double)position +{ + NSColor *first = [_colorWell0.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + NSColor *second = [_colorWell4.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + double brightness = 1 / pow(4, (_brightnessSlider.doubleValue - 128) / 128.0); + position = pow(position, brightness); + NSColor *hue = _hueSlider.colorValue; + double bias = _hueStrengthSlider.doubleValue / 256.0; + double red = 1 / pow(4, (hue.redComponent * 2 - 1) * bias); + double green = 1 / pow(4, (hue.greenComponent * 2 - 1) * bias); + double blue = 1 / pow(4, (hue.blueComponent * 2 - 1) * bias); + NSColor *ret = [NSColor colorWithRed:blend(first.redComponent, second.redComponent, pow(position, red)) + green:blend(first.greenComponent, second.greenComponent, pow(position, green)) + blue:blend(first.blueComponent, second.blueComponent, pow(position, blue)) + alpha:1.0]; + return ret; +} + +- (IBAction)updateAutoColors:(id)sender +{ + if (!self.manualModeCheckbox.state) { + [self updateAutoColors]; + } + else { + [self savePalette:sender]; + } +} + +- (void)updateAutoColors +{ + if (_disableLCDColorCheckbox.state) { + _colorWell1.color = [self autoColorAtPositon:8 / 25.0]; + _colorWell2.color = [self autoColorAtPositon:16 / 25.0]; + _colorWell3.color = [self autoColorAtPositon:24 / 25.0]; + } + else { + _colorWell1.color = [self autoColorAtPositon:1 / 3.0]; + _colorWell2.color = [self autoColorAtPositon:2 / 3.0]; + _colorWell3.color = _colorWell4.color; + } + [self savePalette:nil]; +} + +- (IBAction)disabledLCDColorCheckboxChanged:(id)sender +{ + [self updateEnabledControls]; +} + +- (IBAction)manualModeChanged:(id)sender +{ + [self updateEnabledControls]; +} + +- (IBAction)updateColor4:(id)sender +{ + if (!self.disableLCDColorCheckbox.state) { + self.colorWell4.color = self.colorWell3.color; + } + [self savePalette:self]; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"]; + if (themes.count == 0) { + [defaults setObject:@"Untitled Palette" forKey:@"GBCurrentTheme"]; + [self savePalette:nil]; + return 1; + } + return themes.count; +} + +-(void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + NSString *oldName = [self tableView:tableView objectValueForTableColumn:tableColumn row:row]; + if ([oldName isEqualToString:object]) { + return; + } + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @[] mutableCopy]; + NSString *newName = object; + unsigned i = 2; + if (!newName.length) { + newName = @"Untitled Palette"; + } + while (themes[newName]) { + newName = [NSString stringWithFormat:@"%@ %d", object, i]; + } + themes[newName] = themes[oldName]; + [themes removeObjectForKey:oldName]; + if ([oldName isEqualToString:[defaults stringForKey:@"GBCurrentTheme"]]) { + [defaults setObject:newName forKey:@"GBCurrentTheme"]; + } + [defaults setObject:themes forKey:@"GBThemes"]; + [tableView reloadData]; + [self awakeFromNib]; +} + +- (IBAction)deleteTheme:(id)sender +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSString *name = [defaults stringForKey:@"GBCurrentTheme"]; + NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @[] mutableCopy]; + [themes removeObjectForKey:name]; + [defaults setObject:themes forKey:@"GBThemes"]; + [_themesList reloadData]; + [self awakeFromNib]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + NSString *name = [self tableView:nil objectValueForTableColumn:nil row:_themesList.selectedRow]; + [[NSUserDefaults standardUserDefaults] setObject:name forKey:@"GBCurrentTheme"]; + [self loadPalette]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; +} + +- (void)tableViewSelectionIsChanging:(NSNotification *)notification +{ + [self tableViewSelectionDidChange:notification]; +} + +- (void)awakeFromNib +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"]; + NSString *theme = [defaults stringForKey:@"GBCurrentTheme"]; + if (theme && themes[theme]) { + unsigned index = [[themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] indexOfObject:theme]; + [_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:false]; + } + else { + [_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:false]; + } + [self tableViewSelectionDidChange:nil]; +} + +- (IBAction)addTheme:(id)sender +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"]; + NSString *newName = @"Untitled Palette"; + unsigned i = 2; + while (themes[newName]) { + newName = [NSString stringWithFormat:@"Untitled Palette %d", i++]; + } + [defaults setObject:newName forKey:@"GBCurrentTheme"]; + [self savePalette:sender]; + [_themesList reloadData]; + [self awakeFromNib]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"]; + return [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)][row]; +} + +- (void)loadPalette +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *theme = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]]; + NSArray *colors = theme[@"Colors"]; + if (colors.count == 5) { + unsigned i = 0; + for (NSNumber *color in colors) { + uint32_t c = [color unsignedIntValue]; + self.colorWells[i++].color = [NSColor colorWithRed:(c & 0xFF) / 255.0 + green:((c >> 8) & 0xFF) / 255.0 + blue:((c >> 16) & 0xFF) / 255.0 + alpha:1.0]; + } + } + _disableLCDColorCheckbox.state = [theme[@"DisabledLCDColor"] boolValue]; + _manualModeCheckbox.state = [theme[@"Manual"] boolValue]; + _brightnessSlider.doubleValue = [theme[@"BrightnessBias"] doubleValue] * 128 + 128; + _hueSlider.doubleValue = [theme[@"HueBias"] doubleValue] * 360; + _hueStrengthSlider.doubleValue = [theme[@"HueBiasStrength"] doubleValue] * 256; + [self updateEnabledControls]; +} + +- (IBAction)savePalette:(id)sender +{ + NSDictionary *theme = @{ + @"Colors": + @[@(_colorWell0.color.intValue), + @(_colorWell1.color.intValue), + @(_colorWell2.color.intValue), + @(_colorWell3.color.intValue), + @(_colorWell4.color.intValue)], + @"DisabledLCDColor": _disableLCDColorCheckbox.state? @YES : @NO, + @"Manual": _manualModeCheckbox.state? @YES : @NO, + @"BrightnessBias": @((_brightnessSlider.doubleValue - 128) / 128.0), + @"HueBias": @(_hueSlider.doubleValue / 360.0), + @"HueBiasStrength": @(_hueStrengthSlider.doubleValue / 256.0) + }; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @[] mutableCopy]; + themes[[defaults stringForKey:@"GBCurrentTheme"]] = theme; + [defaults setObject:themes forKey:@"GBThemes"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; +} + ++ (const GB_palette_t *)userPalette +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + switch ([defaults integerForKey:@"GBColorPalette"]) { + case 1: return &GB_PALETTE_DMG; + case 2: return &GB_PALETTE_MGB; + case 3: return &GB_PALETTE_GBL; + default: return &GB_PALETTE_GREY; + case -1: { + static GB_palette_t customPalette; + NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]][@"Colors"]; + if (colors.count == 5) { + unsigned i = 0; + for (NSNumber *color in colors) { + uint32_t c = [color unsignedIntValue]; + customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16}; + } + } + return &customPalette; + } + } +} + +- (IBAction)export:(id)sender +{ + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setAllowedFileTypes:@[@"sbp"]]; + savePanel.nameFieldStringValue = [NSString stringWithFormat:@"%@.sbp", [[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"]]; + if ([savePanel runModal] == NSModalResponseOK) { + theme_t theme = {0,}; + theme.magic = MAGIC; + theme.manual = _manualModeCheckbox.state; + theme.disabled_lcd_color = _disableLCDColorCheckbox.state; + unsigned i = 0; + for (NSColorWell *well in self.colorWells) { + theme.colors[i++] = well.color.gbColor; + } + theme.brightness_bias = (_brightnessSlider.doubleValue - 128) * (0x40000000 / 128); + theme.hue_bias = round(_hueSlider.doubleValue * (0x80000000 / 360.0)); + theme.hue_bias_strength = (_hueStrengthSlider.doubleValue) * (0x80000000 / 256); + size_t size = sizeof(theme); + if (theme.manual) { + size = theme.disabled_lcd_color? 5 + 5 * sizeof(theme.colors[0]) : 5 + 4 * sizeof(theme.colors[0]); + } + [[NSData dataWithBytes:&theme length:size] writeToURL:savePanel.URL atomically:false]; + } +} + +- (IBAction)import:(id)sender +{ + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + [openPanel setAllowedFileTypes:@[@"sbp"]]; + if ([openPanel runModal] == NSModalResponseOK) { + NSData *data = [NSData dataWithContentsOfURL:openPanel.URL]; + theme_t theme = {0,}; + memcpy(&theme, data.bytes, MIN(sizeof(theme), data.length)); + if (theme.magic != MAGIC) { + NSBeep(); + return; + } + _manualModeCheckbox.state = theme.manual; + _disableLCDColorCheckbox.state = theme.disabled_lcd_color; + unsigned i = 0; + for (NSColorWell *well in self.colorWells) { + well.color = [NSColor colorWithRed:theme.colors[i].r / 255.0 + green:theme.colors[i].g / 255.0 + blue:theme.colors[i].b / 255.0 + alpha:1.0]; + i++; + } + if (!theme.disabled_lcd_color) { + _colorWell4.color = _colorWell3.color; + } + _brightnessSlider.doubleValue = theme.brightness_bias / (0x40000000 / 128.0) + 128; + _hueSlider.doubleValue = theme.hue_bias / (0x80000000 / 360.0); + _hueStrengthSlider.doubleValue = theme.hue_bias_strength / (0x80000000 / 256.0); + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"]; + NSString *baseName = openPanel.URL.lastPathComponent.stringByDeletingPathExtension; + NSString *newName = baseName; + i = 2; + while (themes[newName]) { + newName = [NSString stringWithFormat:@"%@ %d", baseName, i++]; + } + [defaults setObject:newName forKey:@"GBCurrentTheme"]; + [self savePalette:sender]; + [self awakeFromNib]; + } +} + +- (IBAction)done:(NSButton *)sender +{ + [sender.window.sheetParent endSheet:sender.window]; +} + +- (instancetype)init +{ + static id singleton = nil; + if (singleton) return singleton; + return (singleton = [super init]); +} +@end diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index e11c5d3..355dc6e 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -1,5 +1,6 @@ #import #import +#import "GBPaletteEditorController.h" @interface GBPreferencesWindow : NSWindow @property (nonatomic, strong) IBOutlet NSTableView *controlsTableView; @@ -29,4 +30,6 @@ @property (weak) IBOutlet NSSlider *volumeSlider; @property (weak) IBOutlet NSButton *OSDCheckbox; @property (weak) IBOutlet NSButton *screenshotFilterCheckbox; +@property (weak) IBOutlet GBPaletteEditorController *paletteEditorController; +@property (strong) IBOutlet NSWindow *paletteEditor; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 60df2dd..e1f9fc0 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -153,8 +153,14 @@ - (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton { _colorPalettePopupButton = colorPalettePopupButton; + [self updatePalettesMenu]; NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]; - [_colorPalettePopupButton selectItemAtIndex:mode]; + if (mode >= 0) { + [_colorPalettePopupButton selectItemWithTag:mode]; + } + else { + [_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""]; + } } - (NSPopUpButton *)colorPalettePopupButton @@ -366,10 +372,51 @@ } +- (void)updatePalettesMenu +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"]; + NSMenu *menu = _colorPalettePopupButton.menu; + while (menu.itemArray.count != 4) { + [menu removeItemAtIndex:4]; + } + [menu addItem:[NSMenuItem separatorItem]]; + for (NSString *name in [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]) { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:name action:nil keyEquivalent:@""]; + item.tag = -2; + [menu addItem:item]; + } + if (themes) { + [menu addItem:[NSMenuItem separatorItem]]; + } + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Custom…" action:nil keyEquivalent:@""]; + item.tag = -1; + [menu addItem:item]; +} + - (IBAction)colorPaletteChanged:(id)sender { - [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) - forKey:@"GBColorPalette"]; + signed tag = [sender selectedItem].tag; + if (tag == -2) { + [[NSUserDefaults standardUserDefaults] setObject:@(-1) + forKey:@"GBColorPalette"]; + [[NSUserDefaults standardUserDefaults] setObject:[sender selectedItem].title + forKey:@"GBCurrentTheme"]; + + } + else if (tag == -1) { + [[NSUserDefaults standardUserDefaults] setObject:@(-1) + forKey:@"GBColorPalette"]; + [_paletteEditorController awakeFromNib]; + [self beginSheet:_paletteEditor completionHandler:^(NSModalResponse returnCode) { + [self updatePalettesMenu]; + [_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""]; + }]; + } + else { + [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) + forKey:@"GBColorPalette"]; + } [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 67e57b1..c9fb8ce 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -85,6 +85,8 @@ + + @@ -257,9 +259,9 @@ - - - + + + @@ -319,7 +321,7 @@
    - + @@ -579,7 +581,7 @@
    - + @@ -608,7 +610,7 @@ - + @@ -770,7 +772,7 @@ - + @@ -803,14 +805,262 @@
    - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/gb.h b/Core/gb.h index bd0fa83..636d98e 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -81,7 +81,7 @@ #endif typedef struct { - struct { + struct GB_color_s { uint8_t r, g, b; } colors[5]; } GB_palette_t; From 4a7afb246dd98e40d9e792f80fa57b972e77f324 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 5 Nov 2021 21:45:54 +0200 Subject: [PATCH 302/365] Fix some oopsies --- Cocoa/GBHueSliderCell.m | 2 +- Cocoa/GBPaletteEditorController.m | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cocoa/GBHueSliderCell.m b/Cocoa/GBHueSliderCell.m index 7f0bd82..11fdc09 100644 --- a/Cocoa/GBHueSliderCell.m +++ b/Cocoa/GBHueSliderCell.m @@ -48,7 +48,7 @@ [super drawKnob:knobRect]; NSRect peekRect = knobRect; peekRect.size.width /= 2; - peekRect.size.height /= 2; + peekRect.size.height = peekRect.size.width; peekRect.origin.x += peekRect.size.width / 2; peekRect.origin.y += peekRect.size.height / 2; NSColor *color = self.colorValue; diff --git a/Cocoa/GBPaletteEditorController.m b/Cocoa/GBPaletteEditorController.m index 5df4aef..0a613fb 100644 --- a/Cocoa/GBPaletteEditorController.m +++ b/Cocoa/GBPaletteEditorController.m @@ -148,7 +148,7 @@ static double blend(double from, double to, double position) return; } NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @[] mutableCopy]; + NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy]; NSString *newName = object; unsigned i = 2; if (!newName.length) { @@ -171,7 +171,7 @@ static double blend(double from, double to, double position) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *name = [defaults stringForKey:@"GBCurrentTheme"]; - NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @[] mutableCopy]; + NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy]; [themes removeObjectForKey:name]; [defaults setObject:themes forKey:@"GBThemes"]; [_themesList reloadData]; @@ -267,7 +267,7 @@ static double blend(double from, double to, double position) @"HueBiasStrength": @(_hueStrengthSlider.doubleValue / 256.0) }; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @[] mutableCopy]; + NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy]; themes[[defaults stringForKey:@"GBCurrentTheme"]] = theme; [defaults setObject:themes forKey:@"GBThemes"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; From 5565c2540b329f95f4fd389954256d7bc97056b0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 6 Nov 2021 13:34:34 +0200 Subject: [PATCH 303/365] Register name and info update --- Core/gb.h | 16 ++++++---------- Core/memory.c | 16 ++++++++-------- Misc/registers.sym | 11 +++++------ 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 636d98e..a23922a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -224,10 +224,7 @@ enum { 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) - // Has some undocumented compatibility flags written at boot. - // Unfortunately it is not readable or writable after boot has finished, so research of this - // register is quite limited. The value written to this register, however, can be controlled - // in some cases. + // Controls DMG mode and PGB mode GB_IO_KEY0 = 0x4c, /* General CGB features */ @@ -260,13 +257,12 @@ enum { /* Missing */ GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank - GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write) - GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write) - GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only + 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 GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write) - GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes - GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes - GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only + GB_IO_PCM12 = 0x76, // Channels 1 and 2 amplitudes + GB_IO_PCM34 = 0x77, // Channels 3 and 4 amplitudes }; typedef enum { diff --git a/Core/memory.c b/Core/memory.c index c221352..bcfff64 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -539,11 +539,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return gb->io_registers[GB_IO_OPRI] | 0xFE; - case GB_IO_PCM_12: + case GB_IO_PCM12: if (!GB_is_cgb(gb)) return 0xFF; return ((gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) | (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[0] : 0xFF); - case GB_IO_PCM_34: + case GB_IO_PCM34: if (!GB_is_cgb(gb)) return 0xFF; return ((gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[1] : 0xFF); @@ -626,10 +626,10 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return ret; } - case GB_IO_UNKNOWN2: - case GB_IO_UNKNOWN3: + case GB_IO_PSWX: + case GB_IO_PSWY: return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] : 0xFF; - case GB_IO_UNKNOWN4: + case GB_IO_PSW: return gb->cgb_mode? gb->io_registers[addr & 0xFF] : 0xFF; case GB_IO_UNKNOWN5: return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] | 0x8F : 0xFF; @@ -1077,9 +1077,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_OBP0: case GB_IO_OBP1: case GB_IO_SB: - case GB_IO_UNKNOWN2: - case GB_IO_UNKNOWN3: - case GB_IO_UNKNOWN4: + case GB_IO_PSWX: + case GB_IO_PSWY: + case GB_IO_PSW: case GB_IO_UNKNOWN5: gb->io_registers[addr & 0xFF] = value; return; diff --git a/Misc/registers.sym b/Misc/registers.sym index 3b31b74..affe663 100644 --- a/Misc/registers.sym +++ b/Misc/registers.sym @@ -57,11 +57,10 @@ 00:FF6B IO_OBPD 00:FF6C IO_OPRI 00:FF70 IO_SVBK -00:FF72 IO_UNKNOWN2 -00:FF73 IO_UNKNOWN3 -00:FF74 IO_UNKNOWN4 +00:FF72 IO_PSWX +00:FF73 IO_PSWY +00:FF74 IO_PSW 00:FF75 IO_UNKNOWN5 -00:FF76 IO_PCM_12 -00:FF77 IO_PCM_34 -00:FF7F IO_UNKNOWN8 +00:FF76 IO_PCM12 +00:FF77 IO_PCM34 00:FFFF IO_IE From fbf1bb7f981978e58afc2ac94a81c9a76d4a8cba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 7 Nov 2021 01:10:58 +0200 Subject: [PATCH 304/365] Save state compatibility breaking cleanup --- Cocoa/Document.m | 2 + Core/apu.c | 104 ++++++++++++++-------------- Core/apu.h | 43 +++++------- Core/debugger.c | 10 +-- Core/display.c | 14 ++-- Core/display.h | 1 - Core/gb.c | 145 +++++++++++++++++++-------------------- Core/gb.h | 35 +++++----- Core/mbc.c | 8 +-- Core/memory.c | 109 ++++++++++++++--------------- Core/save_state.c | 117 +++++-------------------------- Core/sgb.h | 10 +-- Core/sm83_cpu.c | 16 ++--- Core/sm83_disassembler.c | 4 +- Core/symbol_hash.c | 6 +- Core/timing.c | 41 ++++------- Core/timing.h | 1 + 17 files changed, 280 insertions(+), 386 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index c757ce1..39f0852 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -12,6 +12,8 @@ #include "BigSurToolbar.h" #import "GBPaletteEditorController.h" +#define GB_MODEL_PAL_BIT_OLD 0x1000 + /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: Split into category files! This is so messy!!! */ diff --git a/Core/apu.c b/Core/apu.c index aac87b5..8386b91 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -280,7 +280,7 @@ static void render(GB_gameboy_t *gb) static void update_square_sample(GB_gameboy_t *gb, unsigned index) { - if (gb->apu.square_channels[index].current_sample_index & 0x80) return; + if (gb->apu.square_channels[index].sample_surpressed) return; uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6; update_sample(gb, index, @@ -377,7 +377,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) { uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; - if (gb->apu.square_envelope_clock[index].locked) return; + if (gb->apu.square_channels[index].envelope_clock.locked) return; if (!(nrx2 & 7)) return; if (gb->cgb_double_speed) { if (index == GB_SQUARE_1) { @@ -393,7 +393,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) gb->apu.square_channels[index].current_volume++; } else { - gb->apu.square_envelope_clock[index].locked = true; + gb->apu.square_channels[index].envelope_clock.locked = true; } } else { @@ -401,7 +401,7 @@ static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index) gb->apu.square_channels[index].current_volume--; } else { - gb->apu.square_envelope_clock[index].locked = true; + gb->apu.square_channels[index].envelope_clock.locked = true; } } @@ -414,7 +414,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb) { uint8_t nr42 = gb->io_registers[GB_IO_NR42]; - if (gb->apu.noise_envelope_clock.locked) return; + if (gb->apu.noise_channel.envelope_clock.locked) return; if (!(nr42 & 7)) return; if (gb->cgb_double_speed) { @@ -426,7 +426,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb) gb->apu.noise_channel.current_volume++; } else { - gb->apu.noise_envelope_clock.locked = true; + gb->apu.noise_channel.envelope_clock.locked = true; } } else { @@ -434,7 +434,7 @@ static void tick_noise_envelope(GB_gameboy_t *gb) gb->apu.noise_channel.current_volume--; } else { - gb->apu.noise_envelope_clock.locked = true; + gb->apu.noise_channel.envelope_clock.locked = true; } } @@ -486,27 +486,27 @@ void GB_apu_div_event(GB_gameboy_t *gb) if ((gb->apu.div_divider & 7) == 7) { unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { - if (!gb->apu.square_envelope_clock[i].clock) { + if (!gb->apu.square_channels[i].envelope_clock.clock) { gb->apu.square_channels[i].volume_countdown--; gb->apu.square_channels[i].volume_countdown &= 7; } } - if (!gb->apu.noise_envelope_clock.clock) { + if (!gb->apu.noise_channel.envelope_clock.clock) { gb->apu.noise_channel.volume_countdown--; gb->apu.noise_channel.volume_countdown &= 7; } } unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { - if (gb->apu.square_envelope_clock[i].clock) { + if (gb->apu.square_channels[i].envelope_clock.clock) { tick_square_envelope(gb, i); - gb->apu.square_envelope_clock[i].clock = false; + gb->apu.square_channels[i].envelope_clock.clock = false; } } - if (gb->apu.noise_envelope_clock.clock) { + if (gb->apu.noise_channel.envelope_clock.clock) { tick_noise_envelope(gb); - gb->apu.noise_envelope_clock.clock = false; + gb->apu.noise_channel.envelope_clock.clock = false; } if ((gb->apu.div_divider & 1) == 1) { @@ -562,12 +562,12 @@ void GB_apu_div_secondary_event(GB_gameboy_t *gb) unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) { - gb->apu.square_envelope_clock[i].clock = (gb->apu.square_channels[i].volume_countdown = nrx2 & 7); + gb->apu.square_channels[i].envelope_clock.clock = (gb->apu.square_channels[i].volume_countdown = nrx2 & 7); } } if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0) { - gb->apu.noise_envelope_clock.clock = (gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7); + gb->apu.noise_channel.envelope_clock.clock = (gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7); } } @@ -585,10 +585,10 @@ static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset) gb->apu.noise_channel.lfsr &= ~high_bit_mask; } - gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; + gb->apu.noise_channel.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1; if (gb->apu.is_active[GB_NOISE]) { update_sample(gb, GB_NOISE, - gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, cycles_offset); } @@ -601,26 +601,26 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.apu_cycles = 0; if (!cycles) return; - if (unlikely(gb->apu.channel_3_delayed_bugged_read)) { - gb->apu.channel_3_delayed_bugged_read = false; + if (unlikely(gb->apu.wave_channel.delayed_bugged_read)) { + gb->apu.wave_channel.delayed_bugged_read = false; gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)]; } bool start_ch4 = false; if (likely(!gb->stopped || GB_is_cgb(gb))) { - if (gb->apu.channel_4_dmg_delayed_start) { - if (gb->apu.channel_4_dmg_delayed_start == cycles) { - gb->apu.channel_4_dmg_delayed_start = 0; + if (gb->apu.noise_channel.dmg_delayed_start) { + if (gb->apu.noise_channel.dmg_delayed_start == cycles) { + gb->apu.noise_channel.dmg_delayed_start = 0; start_ch4 = true; } - else if (gb->apu.channel_4_dmg_delayed_start > cycles) { - gb->apu.channel_4_dmg_delayed_start -= cycles; + else if (gb->apu.noise_channel.dmg_delayed_start > cycles) { + gb->apu.noise_channel.dmg_delayed_start -= cycles; } else { /* Split it into two */ - cycles -= gb->apu.channel_4_dmg_delayed_start; - gb->apu.apu_cycles = gb->apu.channel_4_dmg_delayed_start * 4; + cycles -= gb->apu.noise_channel.dmg_delayed_start; + gb->apu.apu_cycles = gb->apu.noise_channel.dmg_delayed_start * 4; GB_apu_run(gb); } } @@ -669,6 +669,7 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1; gb->apu.square_channels[i].current_sample_index++; gb->apu.square_channels[i].current_sample_index &= 0x7; + gb->apu.square_channels[i].sample_surpressed = false; if (cycles_left == 0 && gb->apu.samples[i] == 0) { gb->apu.pcm_mask[0] &= i == GB_SQUARE_1? 0xF0 : 0x0F; } @@ -699,7 +700,7 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.wave_channel.wave_form_just_read = false; } } - else if (gb->apu.wave_channel.enable && gb->apu.channel_3_pulsed && gb->model < GB_MODEL_AGB) { + else if (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed && gb->model < GB_MODEL_AGB) { uint8_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { cycles_left -= gb->apu.wave_channel.sample_countdown + 1; @@ -709,7 +710,7 @@ void GB_apu_run(GB_gameboy_t *gb) gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)]; } else { - gb->apu.channel_3_delayed_bugged_read = true; + gb->apu.wave_channel.delayed_bugged_read = true; } } if (cycles_left) { @@ -727,8 +728,8 @@ void GB_apu_run(GB_gameboy_t *gb) } while (unlikely(cycles_left >= gb->apu.noise_channel.counter_countdown)) { cycles_left -= gb->apu.noise_channel.counter_countdown; - gb->apu.noise_channel.counter_countdown = divisor + gb->apu.channel_4_delta; - gb->apu.channel_4_delta = 0; + gb->apu.noise_channel.counter_countdown = divisor + gb->apu.noise_channel.delta; + gb->apu.noise_channel.delta = 0; bool old_bit = (gb->apu.noise_channel.counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; gb->apu.noise_channel.counter++; gb->apu.noise_channel.counter &= 0x3FFF; @@ -744,10 +745,10 @@ void GB_apu_run(GB_gameboy_t *gb) } if (cycles_left) { gb->apu.noise_channel.counter_countdown -= cycles_left; - gb->apu.channel_4_countdown_reloaded = false; + gb->apu.noise_channel.countdown_reloaded = false; } else { - gb->apu.channel_4_countdown_reloaded = true; + gb->apu.noise_channel.countdown_reloaded = true; } } } @@ -1037,7 +1038,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) else if (gb->apu.is_active[index]) { nrx2_glitch(gb, &gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg], &gb->apu.square_channels[index].volume_countdown, - &gb->apu.square_envelope_clock[index]); + &gb->apu.square_channels[index].envelope_clock); update_square_sample(gb, index); } @@ -1066,6 +1067,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (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; } } } @@ -1076,8 +1078,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (value & 0x80) { /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by turning the APU off. */ - gb->apu.square_envelope_clock[index].locked = false; - gb->apu.square_envelope_clock[index].clock = false; + gb->apu.square_channels[index].envelope_clock.locked = false; + gb->apu.square_channels[index].envelope_clock.clock = 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_C && gb->apu.lf_div) { @@ -1090,11 +1092,12 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1) / 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; } /* Todo: verify with the schematics what's going on in here */ else if (gb->apu.square_channels[index].sample_length == 0x7FF && old_sample_length != 0x7FF && - (gb->apu.square_channels[index].current_sample_index & 0x80)) { + (gb->apu.square_channels[index].sample_surpressed)) { extra_delay += 2; } } @@ -1117,8 +1120,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); - /* We use the highest bit in current_sample_index to mark this sample is not actually playing yet, */ - gb->apu.square_channels[index].current_sample_index |= 0x80; + gb->apu.square_channels[index].sample_surpressed = true; } if (gb->apu.square_channels[index].pulse_length == 0) { gb->apu.square_channels[index].pulse_length = 0x40; @@ -1182,7 +1184,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR30: gb->apu.wave_channel.enable = value & 0x80; if (!gb->apu.wave_channel.enable) { - gb->apu.channel_3_pulsed = false; + 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) { @@ -1213,7 +1215,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.wave_channel.sample_length &= 0xFF; gb->apu.wave_channel.sample_length |= (value & 7) << 8; if (value & 0x80) { - gb->apu.channel_3_pulsed = true; + gb->apu.wave_channel.pulsed = true; /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU reads from it. */ if (!GB_is_cgb(gb) && @@ -1295,9 +1297,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) else if (gb->apu.is_active[GB_NOISE]) { nrx2_glitch(gb, &gb->apu.noise_channel.current_volume, value, gb->io_registers[reg], &gb->apu.noise_channel.volume_countdown, - &gb->apu.noise_envelope_clock); + &gb->apu.noise_channel.envelope_clock); update_sample(gb, GB_NOISE, - gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, 0); } @@ -1310,7 +1312,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) bool old_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; gb->io_registers[GB_IO_NR43] = value; bool new_bit = (effective_counter >> (gb->io_registers[GB_IO_NR43] >> 4)) & 1; - if (gb->apu.channel_4_countdown_reloaded) { + if (gb->apu.noise_channel.countdown_reloaded) { unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; if (!divisor) divisor = 2; if (gb->model > GB_MODEL_CGB_C) { @@ -1321,7 +1323,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.noise_channel.counter_countdown = divisor + (divisor == 2? 0 : (uint8_t[]){2, 1, 4, 3}[(gb->apu.noise_channel.alignment) & 3]); } - gb->apu.channel_4_delta = 0; + gb->apu.noise_channel.delta = 0; } /* Step LFSR */ if (new_bit && (!old_bit || gb->model <= GB_MODEL_CGB_C)) { @@ -1340,15 +1342,15 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) case GB_IO_NR44: { if (value & 0x80) { - gb->apu.noise_envelope_clock.locked = false; - gb->apu.noise_envelope_clock.clock = false; + gb->apu.noise_channel.envelope_clock.locked = false; + gb->apu.noise_channel.envelope_clock.clock = false; if (!GB_is_cgb(gb) && (gb->apu.noise_channel.alignment & 3) != 0) { - gb->apu.channel_4_dmg_delayed_start = 6; + gb->apu.noise_channel.dmg_delayed_start = 6; } else { unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; if (!divisor) divisor = 2; - gb->apu.channel_4_delta = 0; + gb->apu.noise_channel.delta = 0; gb->apu.noise_channel.counter_countdown = divisor + 4; if (divisor == 2) { if (gb->model <= GB_MODEL_CGB_C) { @@ -1371,7 +1373,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) if (((gb->apu.noise_channel.alignment + 1) & 3) < 2) { if ((gb->io_registers[GB_IO_NR43] & 0x07) == 1) { gb->apu.noise_channel.counter_countdown -= 2; - gb->apu.channel_4_delta = 2; + gb->apu.noise_channel.delta = 2; } else { gb->apu.noise_channel.counter_countdown -= 4; @@ -1401,12 +1403,12 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) cases. */ if (gb->apu.is_active[GB_NOISE]) { update_sample(gb, GB_NOISE, - gb->apu.current_lfsr_sample ? + gb->apu.noise_channel.current_lfsr_sample ? gb->apu.noise_channel.current_volume : 0, 0); } gb->apu.noise_channel.lfsr = 0; - gb->apu.current_lfsr_sample = false; + gb->apu.noise_channel.current_lfsr_sample = false; gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7; if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) { diff --git a/Core/apu.h b/Core/apu.h index ead4088..ad1e197 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -75,19 +75,20 @@ typedef struct uint16_t shadow_sweep_sample_length; bool unshifted_sweep; bool enable_zombie_calculate_stepping; - + + uint8_t channel_1_restart_hold; + uint16_t channel1_completed_addend; struct { uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks uint8_t current_volume; // Reloaded from NRX2 uint8_t volume_countdown; // Reloaded from NRX2 - uint8_t current_sample_index; /* For save state compatibility, - highest bit is reused (See NR14/NR24's - write code)*/ + uint8_t current_sample_index; + bool sample_surpressed; uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint16_t sample_length; // From NRX3, NRX4, in APU ticks bool length_enabled; // NRX4 - + GB_envelope_clock_t envelope_clock; } square_channels[2]; struct { @@ -100,9 +101,9 @@ typedef struct uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint8_t current_sample_index; uint8_t current_sample_byte; // Current sample byte. - - GB_PADDING(int8_t, wave_form)[32]; bool wave_form_just_read; + bool pulsed; + bool delayed_bugged_read; } wave_channel; struct { @@ -113,32 +114,24 @@ typedef struct bool narrow; uint8_t counter_countdown; // Counts from 0-7 to 0 to tick counter (Scaled from 512KHz to 2MHz) - uint8_t __padding; uint16_t counter; // A bit from this 14-bit register ticks LFSR bool length_enabled; // NR44 uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of // 1MHz. This variable keeps track of the alignment. - + bool current_lfsr_sample; + int8_t delta; + bool countdown_reloaded; + uint8_t dmg_delayed_start; + GB_envelope_clock_t envelope_clock; } noise_channel; - /* Todo: merge these into their structs when breaking save state compatibility */ -#define GB_SKIP_DIV_EVENT_INACTIVE 0 -#define GB_SKIP_DIV_EVENT_SKIPPED 1 -#define GB_SKIP_DIV_EVENT_SKIP 2 - uint8_t skip_div_event; - bool current_lfsr_sample; + enum { + GB_SKIP_DIV_EVENT_INACTIVE, + GB_SKIP_DIV_EVENT_SKIPPED, + GB_SKIP_DIV_EVENT_SKIP, + } skip_div_event:8; uint8_t pcm_mask[2]; // For CGB-0 to CGB-C PCM read glitch - uint8_t channel_1_restart_hold; - int8_t channel_4_delta; - bool channel_4_countdown_reloaded; - uint8_t channel_4_dmg_delayed_start; - uint16_t channel1_completed_addend; - - GB_envelope_clock_t square_envelope_clock[2]; - GB_envelope_clock_t noise_envelope_clock; - bool channel_3_pulsed; - bool channel_3_delayed_bugged_read; } GB_apu_t; typedef enum { diff --git a/Core/debugger.c b/Core/debugger.c index db4b02f..80f4d46 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1771,8 +1771,8 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n", duty > 3? "" : (const char *[]){"12.5", " 25", " 50", " 75"}[duty], duty > 3? "" : (const char *[]){"_______-", "-______-", "-____---", "_------_"}[duty], - gb->apu.square_channels[channel].current_sample_index & 0x7f, - gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : ""); + gb->apu.square_channels[channel].current_sample_index, + gb->apu.square_channels[channel].sample_surpressed ? " (suppressed)" : ""); if (channel == GB_SQUARE_1) { GB_log(gb, " Frequency sweep %s and %s\n", @@ -2541,7 +2541,7 @@ static bool is_in_trivial_memory(uint16_t addr) return false; } -typedef uint16_t GB_opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode); +typedef uint16_t opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode); uint16_t trivial_1(GB_gameboy_t *gb, uint8_t opcode) { @@ -2631,7 +2631,7 @@ static uint16_t jp_hl(GB_gameboy_t *gb, uint8_t opcode) return gb->hl; } -static GB_opcode_address_getter_t *opcodes[256] = { +static opcode_address_getter_t *opcodes[256] = { /* X0 X1 X2 X3 X4 X5 X6 X7 */ /* X8 X9 Xa Xb Xc Xd Xe Xf */ trivial_1, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 0X */ @@ -2709,7 +2709,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add return JUMP_TO_NONE; } - GB_opcode_address_getter_t *getter = opcodes[opcode]; + opcode_address_getter_t *getter = opcodes[opcode]; if (!getter) { gb->n_watchpoints = n_watchpoints; return JUMP_TO_NONE; diff --git a/Core/display.c b/Core/display.c index 83a499f..46fd672 100644 --- a/Core/display.c +++ b/Core/display.c @@ -107,7 +107,7 @@ typedef struct __attribute__((packed)) { uint8_t x; uint8_t tile; uint8_t flags; -} GB_object_t; +} object_t; static void display_vblank(GB_gameboy_t *gb) { @@ -470,7 +470,7 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index) } /* This reverse sorts the visible objects by location and priority */ - GB_object_t *objects = (GB_object_t *) &gb->oam; + object_t *objects = (object_t *) &gb->oam; bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; signed y = objects[index].y - 16; if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) { @@ -825,11 +825,11 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) } } -static uint16_t get_object_line_address(GB_gameboy_t *gb, const GB_object_t *object) +static uint16_t get_object_line_address(GB_gameboy_t *gb, const object_t *object) { /* TODO: what does the PPU read if DMA is active? */ if (gb->oam_ppu_blocked) { - static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF}; + static const object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF}; object = &blocked; } @@ -864,7 +864,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } return; } - GB_object_t *objects = (GB_object_t *) &gb->oam; + object_t *objects = (object_t *) &gb->oam; GB_STATE_MACHINE(gb, display, cycles, 2) { GB_STATE(gb, display, 1); @@ -1208,7 +1208,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line++; GB_SLEEP(gb, display, 40, 1); - const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; + const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; uint16_t line_address = get_object_line_address(gb, object); @@ -1553,7 +1553,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; uint8_t oam_to_dest_index[40] = {0,}; for (signed y = 0; y < LINES; y++) { - GB_object_t *sprite = (GB_object_t *) &gb->oam; + object_t *sprite = (object_t *) &gb->oam; uint8_t sprites_in_line = 0; for (uint8_t i = 0; i < 40; i++, sprite++) { signed sprite_y = sprite->y - 16; diff --git a/Core/display.h b/Core/display.h index c9411dc..085985d 100644 --- a/Core/display.h +++ b/Core/display.h @@ -12,7 +12,6 @@ void GB_STAT_update(GB_gameboy_t *gb); void GB_lcd_off(GB_gameboy_t *gb); enum { - GB_OBJECT_PRIORITY_UNDEFINED, // For save state compatibility GB_OBJECT_PRIORITY_X, GB_OBJECT_PRIORITY_INDEX, }; diff --git a/Core/gb.c b/Core/gb.c index 0c47ce9..9662663 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -753,7 +753,7 @@ typedef struct { uint8_t padding4[3]; uint8_t high; uint8_t padding5[3]; -} GB_vba_rtc_time_t; +} vba_rtc_time_t; typedef struct __attribute__((packed)) { uint32_t magic; @@ -762,7 +762,7 @@ typedef struct __attribute__((packed)) { uint8_t reserved; uint64_t last_rtc_second; uint8_t rtc_data[4]; -} GB_tpp1_rtc_save_t; +} tpp1_rtc_save_t; typedef union { struct __attribute__((packed)) { @@ -771,17 +771,17 @@ typedef union { } sameboy_legacy; struct { /* Used by VBA versions with 32-bit timestamp*/ - GB_vba_rtc_time_t rtc_real, rtc_latched; + vba_rtc_time_t rtc_real, rtc_latched; uint32_t last_rtc_second; /* Always little endian */ } vba32; struct { /* Used by BGB and VBA versions with 64-bit timestamp*/ - GB_vba_rtc_time_t rtc_real, rtc_latched; + vba_rtc_time_t rtc_real, rtc_latched; uint64_t last_rtc_second; /* Always little endian */ } vba64; -} GB_rtc_save_t; +} rtc_save_t; -static void GB_fill_tpp1_save_data(GB_gameboy_t *gb, GB_tpp1_rtc_save_t *data) +static void fill_tpp1_save_data(GB_gameboy_t *gb, tpp1_rtc_save_t *data) { data->magic = BE32('TPP1'); data->version = BE16(0x100); @@ -805,10 +805,10 @@ int GB_save_battery_size(GB_gameboy_t *gb) } if (gb->cartridge_type->mbc_type == GB_TPP1) { - return gb->mbc_ram_size + sizeof(GB_tpp1_rtc_save_t); + return gb->mbc_ram_size + sizeof(tpp1_rtc_save_t); } - GB_rtc_save_t rtc_save_size; + rtc_save_t rtc_save_size; return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); } @@ -824,8 +824,8 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) if (gb->cartridge_type->mbc_type == GB_TPP1) { buffer += gb->mbc_ram_size; - GB_tpp1_rtc_save_t rtc_save; - GB_fill_tpp1_save_data(gb, &rtc_save); + tpp1_rtc_save_t rtc_save; + fill_tpp1_save_data(gb, &rtc_save); memcpy(buffer, &rtc_save, sizeof(rtc_save)); } else if (gb->cartridge_type->mbc_type == GB_HUC3) { @@ -834,26 +834,26 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) #ifdef GB_BIG_ENDIAN GB_huc3_rtc_time_t rtc_save = { __builtin_bswap64(gb->last_rtc_second), - __builtin_bswap16(gb->huc3_minutes), - __builtin_bswap16(gb->huc3_days), - __builtin_bswap16(gb->huc3_alarm_minutes), - __builtin_bswap16(gb->huc3_alarm_days), - gb->huc3_alarm_enabled, + __builtin_bswap16(gb->huc3.minutes), + __builtin_bswap16(gb->huc3.days), + __builtin_bswap16(gb->huc3.alarm_minutes), + __builtin_bswap16(gb->huc3.alarm_days), + gb->huc3.alarm_enabled, }; #else GB_huc3_rtc_time_t rtc_save = { gb->last_rtc_second, - gb->huc3_minutes, - gb->huc3_days, - gb->huc3_alarm_minutes, - gb->huc3_alarm_days, - gb->huc3_alarm_enabled, + gb->huc3.minutes, + gb->huc3.days, + gb->huc3.alarm_minutes, + gb->huc3.alarm_days, + gb->huc3.alarm_enabled, }; #endif memcpy(buffer, &rtc_save, sizeof(rtc_save)); } else if (gb->cartridge_type->has_rtc) { - GB_rtc_save_t rtc_save = {{{{0,}},},}; + rtc_save_t rtc_save = {{{{0,}},},}; rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours; @@ -892,8 +892,8 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return EIO; } if (gb->cartridge_type->mbc_type == GB_TPP1) { - GB_tpp1_rtc_save_t rtc_save; - GB_fill_tpp1_save_data(gb, &rtc_save); + tpp1_rtc_save_t rtc_save; + fill_tpp1_save_data(gb, &rtc_save); if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { fclose(f); @@ -904,20 +904,20 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) #ifdef GB_BIG_ENDIAN GB_huc3_rtc_time_t rtc_save = { __builtin_bswap64(gb->last_rtc_second), - __builtin_bswap16(gb->huc3_minutes), - __builtin_bswap16(gb->huc3_days), - __builtin_bswap16(gb->huc3_alarm_minutes), - __builtin_bswap16(gb->huc3_alarm_days), - gb->huc3_alarm_enabled, + __builtin_bswap16(gb->huc3.minutes), + __builtin_bswap16(gb->huc3.days), + __builtin_bswap16(gb->huc3.alarm_minutes), + __builtin_bswap16(gb->huc3.alarm_days), + gb->huc3.alarm_enabled, }; #else GB_huc3_rtc_time_t rtc_save = { gb->last_rtc_second, - gb->huc3_minutes, - gb->huc3_days, - gb->huc3_alarm_minutes, - gb->huc3_alarm_days, - gb->huc3_alarm_enabled, + gb->huc3.minutes, + gb->huc3.days, + gb->huc3.alarm_minutes, + gb->huc3.alarm_days, + gb->huc3.alarm_enabled, }; #endif @@ -927,7 +927,7 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) } } else if (gb->cartridge_type->has_rtc) { - GB_rtc_save_t rtc_save = {{{{0,}},},}; + rtc_save_t rtc_save = {{{{0,}},},}; rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours; @@ -955,7 +955,7 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return errno; } -static void GB_load_tpp1_save_data(GB_gameboy_t *gb, const GB_tpp1_rtc_save_t *data) +static void load_tpp1_save_data(GB_gameboy_t *gb, const tpp1_rtc_save_t *data) { gb->last_rtc_second = LE64(data->last_rtc_second); unrolled for (unsigned i = 4; i--;) { @@ -971,13 +971,13 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t } if (gb->cartridge_type->mbc_type == GB_TPP1) { - GB_tpp1_rtc_save_t rtc_save; + tpp1_rtc_save_t rtc_save; if (size - gb->mbc_ram_size < sizeof(rtc_save)) { goto reset_rtc; } memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); - GB_load_tpp1_save_data(gb, &rtc_save); + load_tpp1_save_data(gb, &rtc_save); if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -994,18 +994,18 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); #ifdef GB_BIG_ENDIAN gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); - gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); - gb->huc3_days = __builtin_bswap16(rtc_save.days); - gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); - gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); - gb->huc3_alarm_enabled = rtc_save.alarm_enabled; + gb->huc3.minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3.days = __builtin_bswap16(rtc_save.days); + gb->huc3.alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3.alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3.alarm_enabled = rtc_save.alarm_enabled; #else gb->last_rtc_second = rtc_save.last_rtc_second; - gb->huc3_minutes = rtc_save.minutes; - gb->huc3_days = rtc_save.days; - gb->huc3_alarm_minutes = rtc_save.alarm_minutes; - gb->huc3_alarm_days = rtc_save.alarm_days; - gb->huc3_alarm_enabled = rtc_save.alarm_enabled; + gb->huc3.minutes = rtc_save.minutes; + gb->huc3.days = rtc_save.days; + gb->huc3.alarm_minutes = rtc_save.alarm_minutes; + gb->huc3.alarm_days = rtc_save.alarm_days; + gb->huc3.alarm_enabled = rtc_save.alarm_enabled; #endif if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -1014,7 +1014,7 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t return; } - GB_rtc_save_t rtc_save; + rtc_save_t rtc_save; memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size)); switch (size - gb->mbc_ram_size) { case sizeof(rtc_save.sameboy_legacy): @@ -1076,9 +1076,9 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ - gb->huc3_days = 0xFFFF; - gb->huc3_minutes = 0xFFF; - gb->huc3_alarm_enabled = false; + gb->huc3.days = 0xFFFF; + gb->huc3.minutes = 0xFFF; + gb->huc3.alarm_enabled = false; exit: return; } @@ -1096,12 +1096,12 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) } if (gb->cartridge_type->mbc_type == GB_TPP1) { - GB_tpp1_rtc_save_t rtc_save; + tpp1_rtc_save_t rtc_save; if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { goto reset_rtc; } - GB_load_tpp1_save_data(gb, &rtc_save); + load_tpp1_save_data(gb, &rtc_save); if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -1117,18 +1117,18 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) } #ifdef GB_BIG_ENDIAN gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); - gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); - gb->huc3_days = __builtin_bswap16(rtc_save.days); - gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); - gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days); - gb->huc3_alarm_enabled = rtc_save.alarm_enabled; + gb->huc3.minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3.days = __builtin_bswap16(rtc_save.days); + gb->huc3.alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); + gb->huc3.alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->huc3.alarm_enabled = rtc_save.alarm_enabled; #else gb->last_rtc_second = rtc_save.last_rtc_second; - gb->huc3_minutes = rtc_save.minutes; - gb->huc3_days = rtc_save.days; - gb->huc3_alarm_minutes = rtc_save.alarm_minutes; - gb->huc3_alarm_days = rtc_save.alarm_days; - gb->huc3_alarm_enabled = rtc_save.alarm_enabled; + gb->huc3.minutes = rtc_save.minutes; + gb->huc3.days = rtc_save.days; + gb->huc3.alarm_minutes = rtc_save.alarm_minutes; + gb->huc3.alarm_days = rtc_save.alarm_days; + gb->huc3.alarm_enabled = rtc_save.alarm_enabled; #endif if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ @@ -1137,7 +1137,7 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) return; } - GB_rtc_save_t rtc_save; + rtc_save_t rtc_save; switch (fread(&rtc_save, 1, sizeof(rtc_save), f)) { case sizeof(rtc_save.sameboy_legacy): memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); @@ -1198,9 +1198,9 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ - gb->huc3_days = 0xFFFF; - gb->huc3_minutes = 0xFFF; - gb->huc3_alarm_enabled = false; + gb->huc3.days = 0xFFFF; + gb->huc3.minutes = 0xFFF; + gb->huc3.alarm_enabled = false; exit: fclose(f); return; @@ -1711,8 +1711,7 @@ void GB_reset(GB_gameboy_t *gb) } } - /* Todo: Ugly, fixme, see comment in the timer state machine */ - gb->div_state = 3; + GB_set_internal_div_counter(gb, 8); GB_apu_update_cycles_per_sample(gb); @@ -1910,10 +1909,10 @@ void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t unsigned GB_time_to_alarm(GB_gameboy_t *gb) { if (gb->cartridge_type->mbc_type != GB_HUC3) return 0; - if (!gb->huc3_alarm_enabled) return 0; - if (!(gb->huc3_alarm_days & 0x2000)) return 0; - unsigned current_time = (gb->huc3_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_minutes * 60 + (time(NULL) % 60); - unsigned alarm_time = (gb->huc3_alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_alarm_minutes * 60; + if (!gb->huc3.alarm_enabled) return 0; + if (!(gb->huc3.alarm_days & 0x2000)) return 0; + unsigned current_time = (gb->huc3.days & 0x1FFF) * 24 * 60 * 60 + gb->huc3.minutes * 60 + (time(NULL) % 60); + unsigned alarm_time = (gb->huc3.alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3.alarm_minutes * 60; if (current_time > alarm_time) return 0; return alarm_time - current_time; } diff --git a/Core/gb.h b/Core/gb.h index a23922a..3dbd7b3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -26,7 +26,7 @@ #include "workboy.h" #include "random.h" -#define GB_STRUCT_VERSION 13 +#define GB_STRUCT_VERSION 14 #define GB_MODEL_FAMILY_MASK 0xF00 #define GB_MODEL_DMG_FAMILY 0x000 @@ -35,9 +35,6 @@ #define GB_MODEL_PAL_BIT 0x40 #define GB_MODEL_NO_SFC_BIT 0x80 -#define GB_MODEL_PAL_BIT_OLD 0x1000 -#define GB_MODEL_NO_SFC_BIT_OLD 0x2000 - #ifdef GB_INTERNAL #if __clang__ #define unrolled _Pragma("unroll") @@ -471,6 +468,7 @@ struct GB_gameboy_internal_s { struct { uint8_t rom_bank:8; uint8_t ram_bank:3; + bool rtc_mapped:1; } mbc3; struct { @@ -490,26 +488,27 @@ struct GB_gameboy_internal_s { uint8_t rom_bank:7; uint8_t padding:1; uint8_t ram_bank:4; + uint8_t mode; + uint8_t access_index; + uint16_t minutes, days; + uint16_t alarm_minutes, alarm_days; + bool alarm_enabled; + uint8_t read; + uint8_t access_flags; } huc3; + + struct { + uint16_t rom_bank; + uint8_t ram_bank; + 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; - - // TODO: move to huc3/mbc3/tpp1 struct when breaking save compat - uint8_t huc3_mode; - uint8_t huc3_access_index; - uint16_t huc3_minutes, huc3_days; - uint16_t huc3_alarm_minutes, huc3_alarm_days; - bool huc3_alarm_enabled; - uint8_t huc3_read; - uint8_t huc3_access_flags; - bool mbc3_rtc_mapped; - uint16_t tpp1_rom_bank; - uint8_t tpp1_ram_bank; - uint8_t tpp1_mode; + ); @@ -543,7 +542,6 @@ struct GB_gameboy_internal_s { GB_SECTION(rtc, GB_rtc_time_t rtc_real, rtc_latched; uint64_t last_rtc_second; - GB_PADDING(bool, rtc_latch); uint32_t rtc_cycles; uint8_t tpp1_mr4; ); @@ -579,7 +577,6 @@ struct GB_gameboy_internal_s { uint8_t current_line; uint16_t ly_for_comparison; GB_fifo_t bg_fifo, oam_fifo; - GB_PADDING(uint8_t, fetcher_x); uint8_t fetcher_y; uint16_t cycles_for_line; uint8_t current_tile; diff --git a/Core/mbc.c b/Core/mbc.c index a9e758e..b76778c 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -112,9 +112,9 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_ram_bank = gb->huc3.ram_bank; break; case GB_TPP1: - gb->mbc_rom_bank = gb->tpp1_rom_bank; - gb->mbc_ram_bank = gb->tpp1_ram_bank; - gb->mbc_ram_enable = (gb->tpp1_mode == 2) || (gb->tpp1_mode == 3); + gb->mbc_rom_bank = gb->tpp1.rom_bank; + gb->mbc_ram_bank = gb->tpp1.ram_bank; + gb->mbc_ram_enable = (gb->tpp1.mode == 2) || (gb->tpp1.mode == 3); break; } } @@ -127,7 +127,7 @@ void GB_configure_cart(GB_gameboy_t *gb) gb->rom[0x14a] == 0x65) { static const GB_cartridge_t tpp1 = {GB_TPP1, GB_STANDARD_MBC, true, true, true, true}; gb->cartridge_type = &tpp1; - gb->tpp1_rom_bank = 1; + gb->tpp1.rom_bank = 1; } if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { diff --git a/Core/memory.c b/Core/memory.c index bcfff64..0174eb1 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -2,17 +2,17 @@ #include #include "gb.h" -typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); -typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +typedef uint8_t read_function_t(GB_gameboy_t *gb, uint16_t addr); +typedef void write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); typedef enum { GB_BUS_MAIN, /* In DMG: Cart and RAM. In CGB: Cart only */ GB_BUS_RAM, /* In CGB only. */ GB_BUS_VRAM, GB_BUS_INTERNAL, /* Anything in highram. Might not be the most correct name. */ -} GB_bus_t; +} bus_t; -static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) +static bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0x8000) { return GB_BUS_MAIN; @@ -304,18 +304,18 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { if (gb->cartridge_type->mbc_type == GB_HUC3) { - switch (gb->huc3_mode) { + switch (gb->huc3.mode) { case 0xC: // RTC read - if (gb->huc3_access_flags == 0x2) { + if (gb->huc3.access_flags == 0x2) { return 1; } - return gb->huc3_read; + return gb->huc3.read; case 0xD: // RTC status return 1; case 0xE: // IR mode return gb->effective_ir_input; // TODO: What are the other bits? default: - GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr); + GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3.mode, addr); return 1; // TODO: What happens in this case? case 0: // TODO: R/O RAM? (or is it disabled?) case 0xA: // RAM @@ -324,12 +324,12 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } if (gb->cartridge_type->mbc_type == GB_TPP1) { - switch (gb->tpp1_mode) { + switch (gb->tpp1.mode) { case 0: switch (addr & 3) { - case 0: return gb->tpp1_rom_bank; - case 1: return gb->tpp1_rom_bank >> 8; - case 2: return gb->tpp1_ram_bank; + case 0: return gb->tpp1.rom_bank; + case 1: return gb->tpp1.rom_bank >> 8; + case 2: return gb->tpp1.ram_bank; case 3: return gb->rumble_strength | gb->tpp1_mr4; } case 2: @@ -353,7 +353,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 && - gb->mbc3_rtc_mapped) { + gb->mbc3.rtc_mapped) { /* RTC read */ if (gb->mbc_ram_bank <= 4) { gb->rtc_latched.seconds &= 0x3F; @@ -651,7 +651,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->hram[addr - 0xFF80]; } -static GB_read_function_t * const read_map[] = +static read_function_t *const read_map[] = { read_rom, read_rom, read_rom, read_rom, /* 0XXX, 1XXX, 2XXX, 3XXX */ read_mbc_rom, read_mbc_rom, read_mbc_rom, read_mbc_rom, /* 4XXX, 5XXX, 6XXX, 7XXX */ @@ -706,7 +706,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; - gb->mbc3_rtc_mapped = value & 8; + gb->mbc3.rtc_mapped = value & 8; break; case 0x6000: case 0x7000: memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); @@ -741,8 +741,8 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_HUC3: switch (addr & 0xF000) { case 0x0000: case 0x1000: - gb->huc3_mode = value & 0xF; - gb->mbc_ram_enable = gb->huc3_mode == 0xA; + gb->huc3.mode = value & 0xF; + gb->mbc_ram_enable = gb->huc3.mode == 0xA; break; case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break; case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; @@ -751,15 +751,15 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_TPP1: switch (addr & 3) { case 0: - gb->tpp1_rom_bank &= 0xFF00; - gb->tpp1_rom_bank |= value; + gb->tpp1.rom_bank &= 0xFF00; + gb->tpp1.rom_bank |= value; break; case 1: - gb->tpp1_rom_bank &= 0xFF; - gb->tpp1_rom_bank |= value << 8; + gb->tpp1.rom_bank &= 0xFF; + gb->tpp1.rom_bank |= value << 8; break; case 2: - gb->tpp1_ram_bank = value; + gb->tpp1.ram_bank = value; break; case 3: switch (value) { @@ -767,7 +767,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 2: case 3: case 5: - gb->tpp1_mode = value; + gb->tpp1.mode = value; break; case 0x10: memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); @@ -823,59 +823,59 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static bool huc3_write(GB_gameboy_t *gb, uint8_t value) { - switch (gb->huc3_mode) { + switch (gb->huc3.mode) { case 0xB: // RTC Write switch (value >> 4) { case 1: - if (gb->huc3_access_index < 3) { - gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; + if (gb->huc3.access_index < 3) { + gb->huc3.read = (gb->huc3.minutes >> (gb->huc3.access_index * 4)) & 0xF; } - else if (gb->huc3_access_index < 7) { - gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; + else if (gb->huc3.access_index < 7) { + gb->huc3.read = (gb->huc3.days >> ((gb->huc3.access_index - 3) * 4)) & 0xF; } else { - // GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); + // GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3.access_index); } - gb->huc3_access_index++; + gb->huc3.access_index++; break; case 2: case 3: - if (gb->huc3_access_index < 3) { - gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); - gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); + if (gb->huc3.access_index < 3) { + gb->huc3.minutes &= ~(0xF << (gb->huc3.access_index * 4)); + gb->huc3.minutes |= ((value & 0xF) << (gb->huc3.access_index * 4)); } - else if (gb->huc3_access_index < 7) { - gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); - gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); + else if (gb->huc3.access_index < 7) { + gb->huc3.days &= ~(0xF << ((gb->huc3.access_index - 3) * 4)); + gb->huc3.days |= ((value & 0xF) << ((gb->huc3.access_index - 3) * 4)); } - else if (gb->huc3_access_index >= 0x58 && gb->huc3_access_index <= 0x5a) { - gb->huc3_alarm_minutes &= ~(0xF << ((gb->huc3_access_index - 0x58) * 4)); - gb->huc3_alarm_minutes |= ((value & 0xF) << ((gb->huc3_access_index - 0x58) * 4)); + else if (gb->huc3.access_index >= 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) { - gb->huc3_alarm_enabled = value & 1; + else if (gb->huc3.access_index == 0x5f) { + gb->huc3.alarm_enabled = value & 1; } else { - // GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3_access_index); + // GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3.access_index); } if ((value >> 4) == 3) { - gb->huc3_access_index++; + gb->huc3.access_index++; } break; case 4: - gb->huc3_access_index &= 0xF0; - gb->huc3_access_index |= value & 0xF; + gb->huc3.access_index &= 0xF0; + gb->huc3.access_index |= value & 0xF; break; case 5: - gb->huc3_access_index &= 0x0F; - gb->huc3_access_index |= (value & 0xF) << 4; + gb->huc3.access_index &= 0x0F; + gb->huc3.access_index |= (value & 0xF) << 4; break; case 6: - gb->huc3_access_flags = (value & 0xF); + gb->huc3.access_flags = (value & 0xF); break; default: @@ -917,7 +917,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (gb->cartridge_type->mbc_type == GB_TPP1) { - switch (gb->tpp1_mode) { + switch (gb->tpp1.mode) { case 3: break; case 5: @@ -941,7 +941,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } - if (gb->cartridge_type->has_rtc && gb->mbc3_rtc_mapped) { + if (gb->cartridge_type->has_rtc && gb->mbc3.rtc_mapped) { if (gb->mbc_ram_bank <= 4) { if (gb->mbc_ram_bank == 0) { gb->rtc_cycles = 0; @@ -1191,6 +1191,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: + GB_set_internal_div_counter(gb, 0); /* Reset the div state machine */ gb->div_state = 0; gb->div_cycles = 0; @@ -1388,7 +1389,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) -static GB_write_function_t * const write_map[] = +static write_function_t *const write_map[] = { write_mbc, write_mbc, write_mbc, write_mbc, /* 0XXX, 1XXX, 2XXX, 3XXX */ write_mbc, write_mbc, write_mbc, write_mbc, /* 4XXX, 5XXX, 6XXX, 7XXX */ diff --git a/Core/save_state.c b/Core/save_state.c index e472995..7e1767c 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -276,30 +276,6 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save, bool *attempt_bess) { *attempt_bess = false; - if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) { - /* This is a save state with a bad printer struct from a 32-bit OS */ - memmove(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam); - } - if (save->ram_size == 0) { - /* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially - incorrect RAM amount if it's a CGB instance */ - if (GB_is_cgb(save)) { - save->ram_size = 0x2000 * 8; // Incorrect RAM size - } - else { - save->ram_size = gb->ram_size; - } - } - - if (save->model & GB_MODEL_PAL_BIT_OLD) { - save->model &= ~GB_MODEL_PAL_BIT_OLD; - save->model |= GB_MODEL_PAL_BIT; - } - - if (save->model & GB_MODEL_NO_SFC_BIT_OLD) { - save->model &= ~GB_MODEL_NO_SFC_BIT_OLD; - save->model |= GB_MODEL_NO_SFC_BIT; - } if (gb->version != save->version) { GB_log(gb, "The save state is for a different version of SameBoy.\n"); @@ -328,14 +304,8 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t } if (gb->ram_size != save->ram_size) { - if (gb->ram_size == 0x1000 * 8 && save->ram_size == 0x2000 * 8) { - /* A bug in versions prior to 0.12 made CGB instances allocate twice the ammount of RAM. - Ignore this issue to retain compatibility with older, 0.11, save states. */ - } - else { - GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); - return false; - } + GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); + return false; } switch (save->model) { @@ -407,62 +377,12 @@ static void sanitize_state(GB_gameboy_t *gb) gb->lcd_x = gb->position_in_line; } - if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) { - gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X; - } if (gb->sgb) { if (gb->sgb->player_count != 1 && gb->sgb->player_count != 2 && gb->sgb->player_count != 4) { gb->sgb->player_count = 1; } gb->sgb->current_player &= gb->sgb->player_count - 1; } - if (gb->sgb && !gb->sgb->v14_3) { -#ifdef GB_BIG_ENDIAN - for (unsigned i = 0; i < sizeof(gb->sgb->border.raw_data) / 2; i++) { - gb->sgb->border.raw_data[i] = LE16(gb->sgb->border.raw_data[i]); - } - - for (unsigned i = 0; i < sizeof(gb->sgb->pending_border.raw_data) / 2; i++) { - gb->sgb->pending_border.raw_data[i] = LE16(gb->sgb->pending_border.raw_data[i]); - } - - for (unsigned i = 0; i < sizeof(gb->sgb->effective_palettes) / 2; i++) { - gb->sgb->effective_palettes[i] = LE16(gb->sgb->effective_palettes[i]); - } - - for (unsigned i = 0; i < sizeof(gb->sgb->ram_palettes) / 2; i++) { - gb->sgb->ram_palettes[i] = LE16(gb->sgb->ram_palettes[i]); - } -#endif - uint8_t converted_tiles[sizeof(gb->sgb->border.tiles)] = {0,}; - for (unsigned tile = 0; tile < sizeof(gb->sgb->border.tiles_legacy) / 64; tile++) { - for (unsigned y = 0; y < 8; y++) { - unsigned base = tile * 32 + y * 2; - for (unsigned x = 0; x < 8; x++) { - uint8_t pixel = gb->sgb->border.tiles_legacy[tile * 8 * 8 + y * 8 + x]; - if (pixel & 1) converted_tiles[base] |= (1 << (7 ^ x)); - if (pixel & 2) converted_tiles[base + 1] |= (1 << (7 ^ x)); - if (pixel & 4) converted_tiles[base + 16] |= (1 << (7 ^ x)); - if (pixel & 8) converted_tiles[base + 17] |= (1 << (7 ^ x)); - } - } - } - memcpy(gb->sgb->border.tiles, converted_tiles, sizeof(converted_tiles)); - memset(converted_tiles, 0, sizeof(converted_tiles)); - for (unsigned tile = 0; tile < sizeof(gb->sgb->pending_border.tiles_legacy) / 64; tile++) { - for (unsigned y = 0; y < 8; y++) { - unsigned base = tile * 32 + y * 2; - for (unsigned x = 0; x < 8; x++) { - uint8_t pixel = gb->sgb->pending_border.tiles_legacy[tile * 8 * 8 + y * 8 + x]; - if (pixel & 1) converted_tiles[base] |= (1 << (7 ^ x)); - if (pixel & 2) converted_tiles[base + 1] |= (1 << (7 ^ x)); - if (pixel & 4) converted_tiles[base + 16] |= (1 << (7 ^ x)); - if (pixel & 8) converted_tiles[base + 17] |= (1 << (7 ^ x)); - } - } - } - memcpy(gb->sgb->pending_border.tiles, converted_tiles, sizeof(converted_tiles)); - } } static bool dump_section(virtual_file_t *file, const void *src, uint32_t size) @@ -503,7 +423,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) case GB_MBC3: pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0}; pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc3.rom_bank}; - pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc3.ram_bank | (gb->mbc3_rtc_mapped? 8 : 0)}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc3.ram_bank | (gb->mbc3.rtc_mapped? 8 : 0)}; mbc_block.size = 3 * sizeof(pairs[0]); break; case GB_MBC5: @@ -521,17 +441,17 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) mbc_block.size = 4 * sizeof(pairs[0]); case GB_HUC3: - pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc3_mode}; + 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}; - pairs[2] = (BESS_MBC_pair_t){LE16(0x0002), gb->tpp1_rom_bank}; - pairs[3] = (BESS_MBC_pair_t){LE16(0x0003), gb->tpp1_mode}; + 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}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x0002), gb->tpp1.rom_bank}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x0003), gb->tpp1.mode}; mbc_block.size = 4 * sizeof(pairs[0]); break; } @@ -566,7 +486,6 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe uint32_t sgb_offset = 0; if (GB_is_hle_sgb(gb)) { - gb->sgb->v14_3 = true; sgb_offset = file->tell(file) + 4; if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; } @@ -746,11 +665,11 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe bess_huc3.data = (GB_huc3_rtc_time_t) { LE64(gb->last_rtc_second), - LE16(gb->huc3_minutes), - LE16(gb->huc3_days), - LE16(gb->huc3_alarm_minutes), - LE16(gb->huc3_alarm_days), - gb->huc3_alarm_enabled, + LE16(gb->huc3.minutes), + LE16(gb->huc3.days), + LE16(gb->huc3.alarm_minutes), + LE16(gb->huc3.alarm_days), + gb->huc3.alarm_enabled, }; if (file->write(file, &bess_huc3, sizeof(bess_huc3)) != sizeof(bess_huc3)) { goto error; @@ -1116,11 +1035,11 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) { save.last_rtc_second = MIN(LE64(bess_huc3.data.last_rtc_second), time(NULL)); } - save.huc3_minutes = LE16(bess_huc3.data.minutes); - save.huc3_days = LE16(bess_huc3.data.days); - save.huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes); - save.huc3_alarm_days = LE16(bess_huc3.data.alarm_days); - save.huc3_alarm_enabled = bess_huc3.data.alarm_enabled; + save.huc3.minutes = LE16(bess_huc3.data.minutes); + save.huc3.days = LE16(bess_huc3.data.days); + save.huc3.alarm_minutes = LE16(bess_huc3.data.alarm_minutes); + save.huc3.alarm_days = LE16(bess_huc3.data.alarm_days); + save.huc3.alarm_enabled = bess_huc3.data.alarm_enabled; break; case BE32('TPP1'): if (!found_core) goto parse_error; diff --git a/Core/sgb.h b/Core/sgb.h index 1e1a5c2..f4d60fe 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -6,10 +6,7 @@ typedef struct GB_sgb_s GB_sgb_t; typedef struct { - union { - uint8_t tiles[0x100 * 8 * 4]; - uint8_t tiles_legacy[0x100 * 8 * 8]; /* High nibble not used; TODO: Remove when breaking save-state compatibility! */ - }; + uint8_t tiles[0x100 * 8 * 4]; union { struct { uint16_t map[32 * 32]; @@ -59,11 +56,6 @@ struct GB_sgb_s { /* GB Header */ uint8_t received_header[0x54]; - - /* Multiplayer (cont) */ - GB_PADDING(bool, mlt_lock); - - bool v14_3; // True on save states created on 0.14.3 or newer; Remove when breaking save state compatibility! }; void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index f2a22a1..6920c79 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -4,7 +4,7 @@ #include "gb.h" -typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); +typedef void opcode_t(GB_gameboy_t *gb, uint8_t opcode); typedef enum { /* Default behavior. If the CPU writes while another component reads, it reads the old value */ @@ -23,10 +23,10 @@ typedef enum { GB_CONFLICT_WX, GB_CONFLICT_CGB_LCDC, GB_CONFLICT_NR10, -} GB_conflict_t; +} conflict_t; /* Todo: How does double speed mode affect these? */ -static const GB_conflict_t cgb_conflict_map[0x80] = { +static const conflict_t cgb_conflict_map[0x80] = { [GB_IO_LCDC] = GB_CONFLICT_CGB_LCDC, [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, @@ -41,7 +41,7 @@ static const GB_conflict_t cgb_conflict_map[0x80] = { }; /* Todo: verify on an MGB */ -static const GB_conflict_t dmg_conflict_map[0x80] = { +static const conflict_t dmg_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, [GB_IO_LCDC] = GB_CONFLICT_DMG_LCDC, @@ -60,7 +60,7 @@ static const GB_conflict_t dmg_conflict_map[0x80] = { }; /* Todo: Verify on an SGB1 */ -static const GB_conflict_t sgb_conflict_map[0x80] = { +static const conflict_t sgb_conflict_map[0x80] = { [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, [GB_IO_LCDC] = GB_CONFLICT_SGB_LCDC, @@ -109,9 +109,9 @@ static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value) static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { assert(gb->pending_cycles); - GB_conflict_t conflict = GB_CONFLICT_READ_OLD; + conflict_t conflict = GB_CONFLICT_READ_OLD; if ((addr & 0xFF80) == 0xFF00) { - const GB_conflict_t *map = NULL; + const conflict_t *map = NULL; if (GB_is_cgb(gb)) { map = cgb_conflict_map; } @@ -1530,7 +1530,7 @@ static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) } } -static GB_opcode_t *opcodes[256] = { +static opcode_t *opcodes[256] = { /* X0 X1 X2 X3 X4 X5 X6 X7 */ /* X8 X9 Xa Xb Xc Xd Xe Xf */ nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ diff --git a/Core/sm83_disassembler.c b/Core/sm83_disassembler.c index 7dacd9e..894c766 100644 --- a/Core/sm83_disassembler.c +++ b/Core/sm83_disassembler.c @@ -3,7 +3,7 @@ #include "gb.h" -typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc); +typedef void opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc); static void ill(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) { @@ -716,7 +716,7 @@ static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc) } } -static GB_opcode_t *opcodes[256] = { +static opcode_t *opcodes[256] = { /* X0 X1 X2 X3 X4 X5 X6 X7 */ /* X8 X9 Xa Xb Xc Xd Xe Xf */ nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ diff --git a/Core/symbol_hash.c b/Core/symbol_hash.c index a3718b8..66894f1 100644 --- a/Core/symbol_hash.c +++ b/Core/symbol_hash.c @@ -4,7 +4,7 @@ #include #include -static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) +static size_t map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) { if (!map->symbols) { return 0; @@ -26,7 +26,7 @@ static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr) GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name) { - size_t index = GB_map_find_symbol_index(map, addr); + size_t index = map_find_symbol_index(map, addr); map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0])); memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0])); @@ -39,7 +39,7 @@ GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const c const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr) { if (!map) return NULL; - size_t index = GB_map_find_symbol_index(map, addr); + size_t index = map_find_symbol_index(map, addr); if (index >= map->n_symbols || map->symbols[index].addr != addr) { index--; } diff --git a/Core/timing.c b/Core/timing.c index 786dbc9..67c206e 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -8,7 +8,7 @@ #include #endif -static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; +static const unsigned TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; #ifndef GB_DISABLE_TIMEKEEPING static int64_t get_nanoseconds(void) @@ -99,7 +99,7 @@ void GB_timing_sync(GB_gameboy_t *gb) #define IR_THRESHOLD 19900 #define IR_MAX IR_THRESHOLD * 2 + IR_DECAY -static void GB_ir_run(GB_gameboy_t *gb, uint32_t cycles) +static void ir_run(GB_gameboy_t *gb, uint32_t cycles) { if (gb->model == GB_MODEL_AGB) return; if (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1)) { @@ -142,11 +142,11 @@ static void increase_tima(GB_gameboy_t *gb) } } -static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) { /* TIMA increases when a specific high-bit becomes a low-bit. */ uint16_t triggers = gb->div_counter & ~value; - if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { + if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); } @@ -166,7 +166,7 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) gb->div_counter = value; } -static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) +static void timers_run(GB_gameboy_t *gb, uint8_t cycles) { if (gb->stopped) { if (GB_is_cgb(gb)) { @@ -178,11 +178,8 @@ static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE_MACHINE(gb, div, cycles, 1) { GB_STATE(gb, div, 1); GB_STATE(gb, div, 2); - GB_STATE(gb, div, 3); } - GB_set_internal_div_counter(gb, 0); -main: GB_SLEEP(gb, div, 1, 3); while (true) { advance_tima_state_machine(gb); @@ -190,14 +187,6 @@ main: gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; GB_SLEEP(gb, div, 2, 4); } - - /* Todo: This is ugly to allow compatibility with 0.11 save states. Fix me when breaking save compatibility */ - { - div3: - /* Compensate for lack of prefetch emulation, as well as DIV's internal initial value */ - GB_set_internal_div_counter(gb, 8); - goto main; - } } static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) @@ -247,7 +236,7 @@ static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) } -static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) +static void rtc_run(GB_gameboy_t *gb, uint8_t cycles) { if (gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc) return; gb->rtc_cycles += cycles; @@ -274,10 +263,10 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) if (gb->cartridge_type->mbc_type == GB_HUC3) { while (gb->last_rtc_second / 60 < current_time / 60) { gb->last_rtc_second += 60; - gb->huc3_minutes++; - if (gb->huc3_minutes == 60 * 24) { - gb->huc3_days++; - gb->huc3_minutes = 0; + gb->huc3.minutes++; + if (gb->huc3.minutes == 60 * 24) { + gb->huc3.days++; + gb->huc3.minutes = 0; } } return; @@ -366,7 +355,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // Affected by speed boost gb->dma_cycles += cycles; - GB_timers_run(gb, cycles); + timers_run(gb, cycles); if (!gb->stopped) { advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode } @@ -414,8 +403,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) } GB_apu_run(gb); GB_display_run(gb, cycles); - GB_ir_run(gb, cycles); - GB_rtc_run(gb, cycles); + ir_run(gb, cycles); + rtc_run(gb, cycles); } /* @@ -428,8 +417,8 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) /* Glitch only happens when old_tac is enabled. */ if (!(old_tac & 4)) return; - unsigned old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3]; - unsigned new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3]; + unsigned old_clocks = TAC_TRIGGER_BITS[old_tac & 3]; + unsigned new_clocks = TAC_TRIGGER_BITS[new_tac & 3]; /* The bit used for overflow testing must have been 1 */ if (gb->div_counter & old_clocks) { diff --git a/Core/timing.h b/Core/timing.h index 07e0473..b53eeec 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -7,6 +7,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ void GB_timing_sync(GB_gameboy_t *gb); +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value); enum { GB_TIMA_RUNNING = 0, From 18e7a3f4fa69e5256148329aeed7207ba3f1ece8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 7 Nov 2021 13:39:18 +0200 Subject: [PATCH 305/365] Cleanup, better symbol handling, improves LTO --- Core/apu.c | 3 --- Core/apu.h | 19 +++++++++---------- Core/camera.h | 2 +- Core/cheats.h | 4 ++-- Core/debugger.h | 18 +++++++++--------- Core/defs.h | 44 ++++++++++++++++++++++++++++++++++++++++++++ Core/display.h | 8 ++++---- Core/gb.h | 37 ++----------------------------------- Core/gb_struct_def.h | 5 ----- Core/joypad.h | 4 ++-- Core/mbc.h | 6 +++--- Core/memory.h | 8 ++++---- Core/printer.h | 2 +- Core/rewind.h | 6 +++--- Core/rumble.h | 4 ++-- Core/save_state.h | 4 ++-- Core/sgb.h | 8 ++++---- Core/sm83_cpu.h | 4 ++-- Core/symbol_hash.h | 12 ++++++------ Core/timing.h | 12 ++++++------ Core/workboy.h | 2 +- Makefile | 2 +- 22 files changed, 108 insertions(+), 106 deletions(-) create mode 100644 Core/defs.h delete mode 100644 Core/gb_struct_def.h diff --git a/Core/apu.c b/Core/apu.c index 8386b91..9a5f852 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -4,9 +4,6 @@ #include #include "gb.h" -#define likely(x) __builtin_expect((x), 1) -#define unlikely(x) __builtin_expect((x), 0) - static const uint8_t duties[] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, diff --git a/Core/apu.h b/Core/apu.h index ad1e197..7fa91da 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -3,8 +3,7 @@ #include #include #include -#include "gb_struct_def.h" - +#include "defs.h" #ifdef GB_INTERNAL /* Speed = 1 / Length (in seconds) */ @@ -172,14 +171,14 @@ void GB_set_interference_volume(GB_gameboy_t *gb, double volume); void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); #ifdef GB_INTERNAL -bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); -void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); -uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); -void GB_apu_div_event(GB_gameboy_t *gb); -void GB_apu_div_secondary_event(GB_gameboy_t *gb); -void GB_apu_init(GB_gameboy_t *gb); -void GB_apu_run(GB_gameboy_t *gb); -void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); +bool internal GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); +void internal GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); +uint8_t internal GB_apu_read(GB_gameboy_t *gb, uint8_t reg); +void internal GB_apu_div_event(GB_gameboy_t *gb); +void internal GB_apu_div_secondary_event(GB_gameboy_t *gb); +void internal GB_apu_init(GB_gameboy_t *gb); +void internal GB_apu_run(GB_gameboy_t *gb); +void internal GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); #endif #endif /* apu_h */ diff --git a/Core/camera.h b/Core/camera.h index 21c69b6..1461f3a 100644 --- a/Core/camera.h +++ b/Core/camera.h @@ -1,7 +1,7 @@ #ifndef camera_h #define camera_h #include -#include "gb_struct_def.h" +#include "defs.h" typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y); typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb); diff --git a/Core/cheats.h b/Core/cheats.h index cf8aa20..68bbe1b 100644 --- a/Core/cheats.h +++ b/Core/cheats.h @@ -1,6 +1,6 @@ #ifndef cheats_h #define cheats_h -#include "gb_struct_def.h" +#include "defs.h" #define GB_CHEAT_ANY_BANK 0xFFFF @@ -20,7 +20,7 @@ int GB_save_cheats(GB_gameboy_t *gb, const char *path); #ifdef GB_DISABLE_CHEATS #define GB_apply_cheat(...) #else -void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); +void internal GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); #endif #endif diff --git a/Core/debugger.h b/Core/debugger.h index 0678b30..e2e49f2 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -2,7 +2,7 @@ #define debugger_h #include #include -#include "gb_struct_def.h" +#include "defs.h" #include "symbol_hash.h" @@ -17,14 +17,14 @@ #define GB_debugger_add_symbol(gb, bank, address, symbol) ((void)bank, (void)address, (void)symbol) #else -void GB_debugger_run(GB_gameboy_t *gb); -void GB_debugger_handle_async_commands(GB_gameboy_t *gb); -void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr); -void GB_debugger_ret_hook(GB_gameboy_t *gb); -void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); -void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); -const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); -void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol); +void internal GB_debugger_run(GB_gameboy_t *gb); +void internal GB_debugger_handle_async_commands(GB_gameboy_t *gb); +void internal GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr); +void internal GB_debugger_ret_hook(GB_gameboy_t *gb); +void internal GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +void internal GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); +const GB_bank_symbol_t *internal GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); +void internal GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol); #endif /* GB_DISABLE_DEBUGGER */ #endif diff --git a/Core/defs.h b/Core/defs.h new file mode 100644 index 0000000..629cc2b --- /dev/null +++ b/Core/defs.h @@ -0,0 +1,44 @@ +#ifndef defs_h +#define defs_h + +#ifdef GB_INTERNAL + +// "Keyword" definitions +#define likely(x) __builtin_expect((x), 1) +#define unlikely(x) __builtin_expect((x), 0) + +#define internal __attribute__((visibility("hidden"))) + +#if __clang__ +#define unrolled _Pragma("unroll") +#elif __GNUC__ >= 8 +#define unrolled _Pragma("GCC unroll 8") +#else +#define unrolled +#endif + +/* Todo: similar macros are everywhere, clean this up and remove direct calls to bswap */ +#ifdef GB_BIG_ENDIAN +#define LE16(x) __builtin_bswap16(x) +#define LE32(x) __builtin_bswap32(x) +#define LE64(x) __builtin_bswap64(x) +#define BE16(x) (x) +#define BE32(x) (x) +#define BE64(x) (x) +#else +#define LE16(x) (x) +#define LE32(x) (x) +#define LE64(x) (x) +#define BE16(x) __builtin_bswap16(x) +#define BE32(x) __builtin_bswap32(x) +#define BE64(x) __builtin_bswap64(x) +#endif +#endif + +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) +#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; }) +#endif + +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; +#endif diff --git a/Core/display.h b/Core/display.h index 085985d..cd6064a 100644 --- a/Core/display.h +++ b/Core/display.h @@ -6,10 +6,10 @@ #include #ifdef GB_INTERNAL -void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); -void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); -void GB_STAT_update(GB_gameboy_t *gb); -void GB_lcd_off(GB_gameboy_t *gb); +void internal GB_display_run(GB_gameboy_t *gb, uint8_t cycles); +void internal GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); +void internal GB_STAT_update(GB_gameboy_t *gb); +void internal GB_lcd_off(GB_gameboy_t *gb); enum { GB_OBJECT_PRIORITY_X, diff --git a/Core/gb.h b/Core/gb.h index 3dbd7b3..145eac5 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -5,7 +5,7 @@ #include #include -#include "gb_struct_def.h" +#include "defs.h" #include "save_state.h" #include "apu.h" @@ -35,17 +35,6 @@ #define GB_MODEL_PAL_BIT 0x40 #define GB_MODEL_NO_SFC_BIT 0x80 -#ifdef GB_INTERNAL -#if __clang__ -#define unrolled _Pragma("unroll") -#elif __GNUC__ >= 8 -#define unrolled _Pragma("GCC unroll 8") -#else -#define unrolled -#endif - -#endif - #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define GB_BIG_ENDIAN #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ @@ -54,28 +43,6 @@ #error Unable to detect endianess #endif -#ifdef GB_INTERNAL -/* Todo: similar macros are everywhere, clean this up and remove direct calls to bswap */ -#ifdef GB_BIG_ENDIAN -#define LE16(x) __builtin_bswap16(x) -#define LE32(x) __builtin_bswap32(x) -#define LE64(x) __builtin_bswap64(x) -#define BE16(x) (x) -#define BE32(x) (x) -#define BE64(x) (x) -#else -#define LE16(x) (x) -#define LE32(x) (x) -#define LE64(x) (x) -#define BE16(x) __builtin_bswap16(x) -#define BE32(x) __builtin_bswap32(x) -#define BE64(x) __builtin_bswap64(x) -#endif -#endif - -#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) -#define __builtin_bswap16(x) ({ typeof(x) _x = (x); _x >> 8 | _x << 8; }) -#endif typedef struct { struct GB_color_s { @@ -889,7 +856,7 @@ void GB_get_rom_title(GB_gameboy_t *gb, char *title); uint32_t GB_get_rom_crc32(GB_gameboy_t *gb); #ifdef GB_INTERNAL -void GB_borrow_sgb_border(GB_gameboy_t *gb); +void internal GB_borrow_sgb_border(GB_gameboy_t *gb); #endif #endif /* GB_h */ diff --git a/Core/gb_struct_def.h b/Core/gb_struct_def.h deleted file mode 100644 index 0e0ebd1..0000000 --- a/Core/gb_struct_def.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef gb_struct_def_h -#define gb_struct_def_h -struct GB_gameboy_s; -typedef struct GB_gameboy_s GB_gameboy_t; -#endif diff --git a/Core/joypad.h b/Core/joypad.h index 21fad53..645021d 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -1,6 +1,6 @@ #ifndef joypad_h #define joypad_h -#include "gb_struct_def.h" +#include "defs.h" #include typedef enum { @@ -20,6 +20,6 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); #ifdef GB_INTERNAL -void GB_update_joyp(GB_gameboy_t *gb); +void internal GB_update_joyp(GB_gameboy_t *gb); #endif #endif /* joypad_h */ diff --git a/Core/mbc.h b/Core/mbc.h index 3bbe782..3253ec5 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -1,6 +1,6 @@ #ifndef MBC_h #define MBC_h -#include "gb_struct_def.h" +#include "defs.h" #include typedef struct { @@ -26,8 +26,8 @@ typedef struct { #ifdef GB_INTERNAL extern const GB_cartridge_t GB_cart_defs[256]; -void GB_update_mbc_mappings(GB_gameboy_t *gb); -void GB_configure_cart(GB_gameboy_t *gb); +void internal GB_update_mbc_mappings(GB_gameboy_t *gb); +void internal GB_configure_cart(GB_gameboy_t *gb); #endif #endif /* MBC_h */ diff --git a/Core/memory.h b/Core/memory.h index 80020f1..a53ab7c 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -1,6 +1,6 @@ #ifndef memory_h #define memory_h -#include "gb_struct_def.h" +#include "defs.h" #include typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data); @@ -9,9 +9,9 @@ void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t cal uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); #ifdef GB_INTERNAL -void GB_dma_run(GB_gameboy_t *gb); -void GB_hdma_run(GB_gameboy_t *gb); -void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); +void internal GB_dma_run(GB_gameboy_t *gb); +void internal GB_hdma_run(GB_gameboy_t *gb); +void internal GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); #endif #endif /* memory_h */ diff --git a/Core/printer.h b/Core/printer.h index f5c9b27..f4ccfe4 100644 --- a/Core/printer.h +++ b/Core/printer.h @@ -2,7 +2,7 @@ #define printer_h #include #include -#include "gb_struct_def.h" +#include "defs.h" #define GB_PRINTER_MAX_COMMAND_LENGTH 0x280 #define GB_PRINTER_DATA_SIZE 0x280 diff --git a/Core/rewind.h b/Core/rewind.h index ad54841..a3e2edf 100644 --- a/Core/rewind.h +++ b/Core/rewind.h @@ -2,11 +2,11 @@ #define rewind_h #include -#include "gb_struct_def.h" +#include "defs.h" #ifdef GB_INTERNAL -void GB_rewind_push(GB_gameboy_t *gb); -void GB_rewind_free(GB_gameboy_t *gb); +void internal GB_rewind_push(GB_gameboy_t *gb); +void internal GB_rewind_free(GB_gameboy_t *gb); #endif bool GB_rewind_pop(GB_gameboy_t *gb); void GB_set_rewind_length(GB_gameboy_t *gb, double seconds); diff --git a/Core/rumble.h b/Core/rumble.h index eae9f37..196c191 100644 --- a/Core/rumble.h +++ b/Core/rumble.h @@ -1,7 +1,7 @@ #ifndef rumble_h #define rumble_h -#include "gb_struct_def.h" +#include "defs.h" typedef enum { GB_RUMBLE_DISABLED, @@ -10,7 +10,7 @@ typedef enum { } GB_rumble_mode_t; #ifdef GB_INTERNAL -void GB_handle_rumble(GB_gameboy_t *gb); +void internal GB_handle_rumble(GB_gameboy_t *gb); #endif void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode); diff --git a/Core/save_state.h b/Core/save_state.h index bf43a65..b57130f 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -36,8 +36,8 @@ static inline uint32_t state_magic(void) } /* For internal in-memory save states (rewind, debugger) that do not need BESS */ -size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb); -void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); +size_t internal GB_get_save_state_size_no_bess(GB_gameboy_t *gb); +void internal GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); #endif #endif /* save_state_h */ diff --git a/Core/sgb.h b/Core/sgb.h index f4d60fe..7d961a1 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -1,6 +1,6 @@ #ifndef sgb_h #define sgb_h -#include "gb_struct_def.h" +#include "defs.h" #include #include @@ -58,9 +58,9 @@ struct GB_sgb_s { uint8_t received_header[0x54]; }; -void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); -void GB_sgb_render(GB_gameboy_t *gb); -void GB_sgb_load_default_data(GB_gameboy_t *gb); +void internal GB_sgb_write(GB_gameboy_t *gb, uint8_t value); +void internal GB_sgb_render(GB_gameboy_t *gb); +void internal GB_sgb_load_default_data(GB_gameboy_t *gb); #endif diff --git a/Core/sm83_cpu.h b/Core/sm83_cpu.h index 49fa80b..95e6462 100644 --- a/Core/sm83_cpu.h +++ b/Core/sm83_cpu.h @@ -1,11 +1,11 @@ #ifndef sm83_cpu_h #define sm83_cpu_h -#include "gb_struct_def.h" +#include "defs.h" #include void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); #ifdef GB_INTERNAL -void GB_cpu_run(GB_gameboy_t *gb); +void internal GB_cpu_run(GB_gameboy_t *gb); #endif #endif /* sm83_cpu_h */ diff --git a/Core/symbol_hash.h b/Core/symbol_hash.h index 2a03c96..7efc475 100644 --- a/Core/symbol_hash.h +++ b/Core/symbol_hash.h @@ -27,12 +27,12 @@ typedef struct { } GB_reversed_symbol_map_t; #ifdef GB_INTERNAL -void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol); -const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name); -GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); -const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr); -GB_symbol_map_t *GB_map_alloc(void); -void GB_map_free(GB_symbol_map_t *map); +void internal GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol); +const GB_symbol_t *internal GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name); +GB_bank_symbol_t *internal GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); +const GB_bank_symbol_t *internal GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr); +GB_symbol_map_t *internal GB_map_alloc(void); +void internal GB_map_free(GB_symbol_map_t *map); #endif #endif /* symbol_hash_h */ diff --git a/Core/timing.h b/Core/timing.h index b53eeec..5e29e80 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -1,13 +1,13 @@ #ifndef timing_h #define timing_h -#include "gb_struct_def.h" +#include "defs.h" #ifdef GB_INTERNAL -void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); -bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ -void GB_timing_sync(GB_gameboy_t *gb); -void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value); +void internal GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); +void internal GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); +bool internal GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ +void internal GB_timing_sync(GB_gameboy_t *gb); +void internal GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value); enum { GB_TIMA_RUNNING = 0, diff --git a/Core/workboy.h b/Core/workboy.h index d21f273..c99c272 100644 --- a/Core/workboy.h +++ b/Core/workboy.h @@ -3,7 +3,7 @@ #include #include #include -#include "gb_struct_def.h" +#include "defs.h" typedef struct { diff --git a/Makefile b/Makefile index 7016ac3..842883f 100644 --- a/Makefile +++ b/Makefile @@ -175,7 +175,7 @@ CFLAGS += -O3 -DNDEBUG STRIP := strip ifeq ($(PLATFORM),Darwin) LDFLAGS += -Wl,-exported_symbols_list,$(NULL) -STRIP := -@true +STRIP := strip -x endif ifneq ($(PLATFORM),windows32) LDFLAGS += -flto From 1650820edbc8b1cd053e8bbe736df1d090f21220 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 7 Nov 2021 13:57:43 +0200 Subject: [PATCH 306/365] Clean up endian-related code --- Core/cheats.c | 4 +- Core/defs.h | 1 - Core/gb.c | 156 ++++++++++++-------------------------------------- Core/sgb.c | 8 +-- 4 files changed, 41 insertions(+), 128 deletions(-) diff --git a/Core/cheats.c b/Core/cheats.c index c7c43fe..1263183 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -250,7 +250,7 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path) uint32_t struct_size = 0; fread(&magic, sizeof(magic), 1, f); fread(&struct_size, sizeof(struct_size), 1, f); - if (magic != CHEAT_MAGIC && magic != __builtin_bswap32(CHEAT_MAGIC)) { + if (magic != LE32(CHEAT_MAGIC) && magic != BE32(CHEAT_MAGIC)) { GB_log(gb, "The file is not a SameBoy cheat database"); return; } @@ -267,7 +267,7 @@ void GB_load_cheats(GB_gameboy_t *gb, const char *path) GB_cheat_t cheat; while (fread(&cheat, sizeof(cheat), 1, f)) { - if (magic == __builtin_bswap32(CHEAT_MAGIC)) { + if (magic != CHEAT_MAGIC) { cheat.address = __builtin_bswap16(cheat.address); cheat.bank = __builtin_bswap16(cheat.bank); } diff --git a/Core/defs.h b/Core/defs.h index 629cc2b..63deb40 100644 --- a/Core/defs.h +++ b/Core/defs.h @@ -17,7 +17,6 @@ #define unrolled #endif -/* Todo: similar macros are everywhere, clean this up and remove direct calls to bswap */ #ifdef GB_BIG_ENDIAN #define LE16(x) __builtin_bswap16(x) #define LE32(x) __builtin_bswap32(x) diff --git a/Core/gb.c b/Core/gb.c index 9662663..7142d90 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -496,11 +496,8 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) #define READ(x) if (fread(&x, sizeof(x), 1, f) != 1) goto error fread(magic, 1, sizeof(magic), f); -#ifdef GB_BIG_ENDIAN - bool extended = *(uint32_t *)&magic == 'ISX '; -#else - bool extended = *(uint32_t *)&magic == __builtin_bswap32('ISX '); -#endif + + bool extended = *(uint32_t *)&magic == BE32('ISX '); fseek(f, extended? 0x20 : 0, SEEK_SET); @@ -527,15 +524,11 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) } READ(address); -#ifdef GB_BIG_ENDIAN - address = __builtin_bswap16(address); -#endif + address = LE16(address); address &= 0x3FFF; READ(length); -#ifdef GB_BIG_ENDIAN - length = __builtin_bswap16(length); -#endif + length = LE16(length); size_t needed_size = bank * 0x4000 + address + length; if (needed_size > 1024 * 1024 * 32) goto error; @@ -556,14 +549,10 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) uint32_t length; READ(address); -#ifdef GB_BIG_ENDIAN - address = __builtin_bswap32(address); -#endif + address = LE32(address); READ(length); -#ifdef GB_BIG_ENDIAN - length = __builtin_bswap32(length); -#endif + length = LE32(length); size_t needed_size = address + length; if (needed_size > 1024 * 1024 * 32) goto error; @@ -587,9 +576,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) uint16_t address; uint8_t byte; READ(count); -#ifdef GB_BIG_ENDIAN - count = __builtin_bswap16(count); -#endif + count = LE16(count); while (count--) { READ(length); if (fread(name, length, 1, f) != 1) goto error; @@ -604,9 +591,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) } READ(address); -#ifdef GB_BIG_ENDIAN - address = __builtin_bswap16(address); -#endif + address = LE16(address); GB_debugger_add_symbol(gb, bank, address, name); } break; @@ -619,9 +604,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) uint8_t flag; uint32_t address; READ(count); -#ifdef GB_BIG_ENDIAN - count = __builtin_bswap16(count); -#endif + count = LE16(count); while (count--) { READ(length); if (fread(name, length + 1, 1, f) != 1) goto error; @@ -629,9 +612,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path) READ(flag); // unused READ(address); -#ifdef GB_BIG_ENDIAN - address = __builtin_bswap32(address); -#endif + address = LE32(address); // TODO: How to convert 32-bit addresses to Bank:Address? Needs to tell RAM and ROM apart } break; @@ -831,25 +812,14 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) else if (gb->cartridge_type->mbc_type == GB_HUC3) { buffer += gb->mbc_ram_size; -#ifdef GB_BIG_ENDIAN GB_huc3_rtc_time_t rtc_save = { - __builtin_bswap64(gb->last_rtc_second), - __builtin_bswap16(gb->huc3.minutes), - __builtin_bswap16(gb->huc3.days), - __builtin_bswap16(gb->huc3.alarm_minutes), - __builtin_bswap16(gb->huc3.alarm_days), + LE64(gb->last_rtc_second), + LE16(gb->huc3.minutes), + LE16(gb->huc3.days), + LE16(gb->huc3.alarm_minutes), + LE16(gb->huc3.alarm_days), gb->huc3.alarm_enabled, }; -#else - GB_huc3_rtc_time_t rtc_save = { - gb->last_rtc_second, - gb->huc3.minutes, - gb->huc3.days, - gb->huc3.alarm_minutes, - gb->huc3.alarm_days, - gb->huc3.alarm_enabled, - }; -#endif memcpy(buffer, &rtc_save, sizeof(rtc_save)); } else if (gb->cartridge_type->has_rtc) { @@ -864,11 +834,7 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours; rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; -#ifdef GB_BIG_ENDIAN - rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL)); -#else - rtc_save.vba64.last_rtc_second = time(NULL); -#endif + rtc_save.vba64.last_rtc_second = LE64(time(NULL)); memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64)); } @@ -901,25 +867,14 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) } } else if (gb->cartridge_type->mbc_type == GB_HUC3) { -#ifdef GB_BIG_ENDIAN GB_huc3_rtc_time_t rtc_save = { - __builtin_bswap64(gb->last_rtc_second), - __builtin_bswap16(gb->huc3.minutes), - __builtin_bswap16(gb->huc3.days), - __builtin_bswap16(gb->huc3.alarm_minutes), - __builtin_bswap16(gb->huc3.alarm_days), + LE64(gb->last_rtc_second), + LE16(gb->huc3.minutes), + LE16(gb->huc3.days), + LE16(gb->huc3.alarm_minutes), + LE16(gb->huc3.alarm_days), gb->huc3.alarm_enabled, }; -#else - GB_huc3_rtc_time_t rtc_save = { - gb->last_rtc_second, - gb->huc3.minutes, - gb->huc3.days, - gb->huc3.alarm_minutes, - gb->huc3.alarm_days, - gb->huc3.alarm_enabled, - }; -#endif if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { fclose(f); @@ -938,11 +893,7 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours; rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; -#ifdef GB_BIG_ENDIAN - rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL)); -#else - rtc_save.vba64.last_rtc_second = time(NULL); -#endif + rtc_save.vba64.last_rtc_second = LE64(time(NULL)); if (fwrite(&rtc_save.vba64, 1, sizeof(rtc_save.vba64), f) != sizeof(rtc_save.vba64)) { fclose(f); return EIO; @@ -992,21 +943,12 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t goto reset_rtc; } memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); -#ifdef GB_BIG_ENDIAN - gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); - gb->huc3.minutes = __builtin_bswap16(rtc_save.minutes); - gb->huc3.days = __builtin_bswap16(rtc_save.days); - gb->huc3.alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); - gb->huc3.alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->last_rtc_second = LE64(rtc_save.last_rtc_second); + gb->huc3.minutes = LE16(rtc_save.minutes); + gb->huc3.days = LE16(rtc_save.days); + gb->huc3.alarm_minutes = LE16(rtc_save.alarm_minutes); + gb->huc3.alarm_days = LE16(rtc_save.alarm_days); gb->huc3.alarm_enabled = rtc_save.alarm_enabled; -#else - gb->last_rtc_second = rtc_save.last_rtc_second; - gb->huc3.minutes = rtc_save.minutes; - gb->huc3.days = rtc_save.days; - gb->huc3.alarm_minutes = rtc_save.alarm_minutes; - gb->huc3.alarm_days = rtc_save.alarm_days; - gb->huc3.alarm_enabled = rtc_save.alarm_enabled; -#endif if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ goto reset_rtc; @@ -1034,11 +976,7 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours; gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days; gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high; -#ifdef GB_BIG_ENDIAN - gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second); -#else - gb->last_rtc_second = rtc_save.vba32.last_rtc_second; -#endif + gb->last_rtc_second = LE32(rtc_save.vba32.last_rtc_second); break; case sizeof(rtc_save.vba64): @@ -1052,11 +990,7 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours; gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days; gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high; -#ifdef GB_BIG_ENDIAN - gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second); -#else - gb->last_rtc_second = rtc_save.vba64.last_rtc_second; -#endif + gb->last_rtc_second = LE64(rtc_save.vba64.last_rtc_second); break; default: @@ -1115,21 +1049,13 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { goto reset_rtc; } -#ifdef GB_BIG_ENDIAN - gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); - gb->huc3.minutes = __builtin_bswap16(rtc_save.minutes); - gb->huc3.days = __builtin_bswap16(rtc_save.days); - gb->huc3.alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes); - gb->huc3.alarm_days = __builtin_bswap16(rtc_save.alarm_days); + gb->last_rtc_second = LE64(rtc_save.last_rtc_second); + gb->huc3.minutes = LE16(rtc_save.minutes); + gb->huc3.days = LE16(rtc_save.days); + gb->huc3.alarm_minutes = LE16(rtc_save.alarm_minutes); + gb->huc3.alarm_days = LE16(rtc_save.alarm_days); gb->huc3.alarm_enabled = rtc_save.alarm_enabled; -#else - gb->last_rtc_second = rtc_save.last_rtc_second; - gb->huc3.minutes = rtc_save.minutes; - gb->huc3.days = rtc_save.days; - gb->huc3.alarm_minutes = rtc_save.alarm_minutes; - gb->huc3.alarm_days = rtc_save.alarm_days; - gb->huc3.alarm_enabled = rtc_save.alarm_enabled; -#endif + if (gb->last_rtc_second > time(NULL)) { /* We must reset RTC here, or it will not advance. */ goto reset_rtc; @@ -1156,11 +1082,7 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours; gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days; gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high; -#ifdef GB_BIG_ENDIAN - gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second); -#else - gb->last_rtc_second = rtc_save.vba32.last_rtc_second; -#endif + gb->last_rtc_second = LE32(rtc_save.vba32.last_rtc_second); break; case sizeof(rtc_save.vba64): @@ -1174,11 +1096,7 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours; gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days; gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high; -#ifdef GB_BIG_ENDIAN - gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second); -#else - gb->last_rtc_second = rtc_save.vba64.last_rtc_second; -#endif + gb->last_rtc_second = LE64(rtc_save.vba64.last_rtc_second); break; default: diff --git a/Core/sgb.c b/Core/sgb.c index 0e53266..7cdd77f 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -262,9 +262,7 @@ static void command_ready(GB_gameboy_t *gb) } *command = (void *)(gb->sgb->command + 1); uint16_t count = command->length; -#ifdef GB_BIG_ENDIAN - count = __builtin_bswap16(count); -#endif + count = LE16(count); uint8_t x = command->x; uint8_t y = command->y; if (x >= 20 || y >= 18) { @@ -641,9 +639,7 @@ void GB_sgb_render(GB_gameboy_t *gb) for (unsigned x = 0; x < 8; x++) { *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x; } -#ifdef GB_BIG_ENDIAN - *data = __builtin_bswap16(*data); -#endif + *data = LE16(*data); data++; } } From 02f55d12d305d0a778617c6b7ed225fc09d0c09d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 7 Nov 2021 14:13:52 +0200 Subject: [PATCH 307/365] Maybe one day GCC will stop being shit at handling __attribute__s --- Core/apu.h | 16 ++++++++-------- Core/cheats.h | 2 +- Core/debugger.h | 16 ++++++++-------- Core/defs.h | 2 +- Core/display.h | 8 ++++---- Core/gb.h | 2 +- Core/joypad.h | 2 +- Core/mbc.h | 4 ++-- Core/memory.h | 6 +++--- Core/rewind.h | 4 ++-- Core/rumble.h | 2 +- Core/save_state.h | 4 ++-- Core/sgb.h | 6 +++--- Core/sm83_cpu.h | 2 +- Core/symbol_hash.h | 12 ++++++------ Core/timing.h | 10 +++++----- 16 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Core/apu.h b/Core/apu.h index 7fa91da..f7b125c 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -171,14 +171,14 @@ void GB_set_interference_volume(GB_gameboy_t *gb, double volume); void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); #ifdef GB_INTERNAL -bool internal GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index); -void internal GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); -uint8_t internal GB_apu_read(GB_gameboy_t *gb, uint8_t reg); -void internal GB_apu_div_event(GB_gameboy_t *gb); -void internal GB_apu_div_secondary_event(GB_gameboy_t *gb); -void internal GB_apu_init(GB_gameboy_t *gb); -void internal GB_apu_run(GB_gameboy_t *gb); -void internal GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); +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); +internal uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); +internal void GB_apu_div_event(GB_gameboy_t *gb); +internal void GB_apu_div_secondary_event(GB_gameboy_t *gb); +internal void GB_apu_init(GB_gameboy_t *gb); +internal void GB_apu_run(GB_gameboy_t *gb); +internal void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); #endif #endif /* apu_h */ diff --git a/Core/cheats.h b/Core/cheats.h index 68bbe1b..f9c076c 100644 --- a/Core/cheats.h +++ b/Core/cheats.h @@ -20,7 +20,7 @@ int GB_save_cheats(GB_gameboy_t *gb, const char *path); #ifdef GB_DISABLE_CHEATS #define GB_apply_cheat(...) #else -void internal GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); +internal void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value); #endif #endif diff --git a/Core/debugger.h b/Core/debugger.h index e2e49f2..3d77b7a 100644 --- a/Core/debugger.h +++ b/Core/debugger.h @@ -17,14 +17,14 @@ #define GB_debugger_add_symbol(gb, bank, address, symbol) ((void)bank, (void)address, (void)symbol) #else -void internal GB_debugger_run(GB_gameboy_t *gb); -void internal GB_debugger_handle_async_commands(GB_gameboy_t *gb); -void internal GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr); -void internal GB_debugger_ret_hook(GB_gameboy_t *gb); -void internal GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); -void internal GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); -const GB_bank_symbol_t *internal GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); -void internal GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol); +internal void GB_debugger_run(GB_gameboy_t *gb); +internal void GB_debugger_handle_async_commands(GB_gameboy_t *gb); +internal void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr); +internal void GB_debugger_ret_hook(GB_gameboy_t *gb); +internal void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +internal void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr); +internal const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr); +internal void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol); #endif /* GB_DISABLE_DEBUGGER */ #endif diff --git a/Core/defs.h b/Core/defs.h index 63deb40..b4f13f5 100644 --- a/Core/defs.h +++ b/Core/defs.h @@ -7,7 +7,7 @@ #define likely(x) __builtin_expect((x), 1) #define unlikely(x) __builtin_expect((x), 0) -#define internal __attribute__((visibility("hidden"))) +#define internal __attribute__((visibility("internal"))) #if __clang__ #define unrolled _Pragma("unroll") diff --git a/Core/display.h b/Core/display.h index cd6064a..1cac6ae 100644 --- a/Core/display.h +++ b/Core/display.h @@ -6,10 +6,10 @@ #include #ifdef GB_INTERNAL -void internal GB_display_run(GB_gameboy_t *gb, uint8_t cycles); -void internal GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); -void internal GB_STAT_update(GB_gameboy_t *gb); -void internal GB_lcd_off(GB_gameboy_t *gb); +internal void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); +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); enum { GB_OBJECT_PRIORITY_X, diff --git a/Core/gb.h b/Core/gb.h index 145eac5..1cec2ac 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -856,7 +856,7 @@ void GB_get_rom_title(GB_gameboy_t *gb, char *title); uint32_t GB_get_rom_crc32(GB_gameboy_t *gb); #ifdef GB_INTERNAL -void internal GB_borrow_sgb_border(GB_gameboy_t *gb); +internal void GB_borrow_sgb_border(GB_gameboy_t *gb); #endif #endif /* GB_h */ diff --git a/Core/joypad.h b/Core/joypad.h index 645021d..615e34a 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -20,6 +20,6 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); #ifdef GB_INTERNAL -void internal GB_update_joyp(GB_gameboy_t *gb); +internal void GB_update_joyp(GB_gameboy_t *gb); #endif #endif /* joypad_h */ diff --git a/Core/mbc.h b/Core/mbc.h index 3253ec5..709bd1f 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -26,8 +26,8 @@ typedef struct { #ifdef GB_INTERNAL extern const GB_cartridge_t GB_cart_defs[256]; -void internal GB_update_mbc_mappings(GB_gameboy_t *gb); -void internal GB_configure_cart(GB_gameboy_t *gb); +internal void GB_update_mbc_mappings(GB_gameboy_t *gb); +internal void GB_configure_cart(GB_gameboy_t *gb); #endif #endif /* MBC_h */ diff --git a/Core/memory.h b/Core/memory.h index a53ab7c..0b7a43c 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -9,9 +9,9 @@ void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t cal uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); #ifdef GB_INTERNAL -void internal GB_dma_run(GB_gameboy_t *gb); -void internal GB_hdma_run(GB_gameboy_t *gb); -void internal GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address); +internal void GB_dma_run(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); #endif #endif /* memory_h */ diff --git a/Core/rewind.h b/Core/rewind.h index a3e2edf..3cc23ed 100644 --- a/Core/rewind.h +++ b/Core/rewind.h @@ -5,8 +5,8 @@ #include "defs.h" #ifdef GB_INTERNAL -void internal GB_rewind_push(GB_gameboy_t *gb); -void internal GB_rewind_free(GB_gameboy_t *gb); +internal void GB_rewind_push(GB_gameboy_t *gb); +internal void GB_rewind_free(GB_gameboy_t *gb); #endif bool GB_rewind_pop(GB_gameboy_t *gb); void GB_set_rewind_length(GB_gameboy_t *gb, double seconds); diff --git a/Core/rumble.h b/Core/rumble.h index 196c191..ca34737 100644 --- a/Core/rumble.h +++ b/Core/rumble.h @@ -10,7 +10,7 @@ typedef enum { } GB_rumble_mode_t; #ifdef GB_INTERNAL -void internal GB_handle_rumble(GB_gameboy_t *gb); +internal void GB_handle_rumble(GB_gameboy_t *gb); #endif void GB_set_rumble_mode(GB_gameboy_t *gb, GB_rumble_mode_t mode); diff --git a/Core/save_state.h b/Core/save_state.h index b57130f..2352cac 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -36,8 +36,8 @@ static inline uint32_t state_magic(void) } /* For internal in-memory save states (rewind, debugger) that do not need BESS */ -size_t internal GB_get_save_state_size_no_bess(GB_gameboy_t *gb); -void internal GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); +internal size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb); +internal void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); #endif #endif /* save_state_h */ diff --git a/Core/sgb.h b/Core/sgb.h index 7d961a1..4ed2bea 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -58,9 +58,9 @@ struct GB_sgb_s { uint8_t received_header[0x54]; }; -void internal GB_sgb_write(GB_gameboy_t *gb, uint8_t value); -void internal GB_sgb_render(GB_gameboy_t *gb); -void internal GB_sgb_load_default_data(GB_gameboy_t *gb); +internal void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); +internal void GB_sgb_render(GB_gameboy_t *gb); +internal void GB_sgb_load_default_data(GB_gameboy_t *gb); #endif diff --git a/Core/sm83_cpu.h b/Core/sm83_cpu.h index 95e6462..1221fd7 100644 --- a/Core/sm83_cpu.h +++ b/Core/sm83_cpu.h @@ -5,7 +5,7 @@ void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); #ifdef GB_INTERNAL -void internal GB_cpu_run(GB_gameboy_t *gb); +internal void GB_cpu_run(GB_gameboy_t *gb); #endif #endif /* sm83_cpu_h */ diff --git a/Core/symbol_hash.h b/Core/symbol_hash.h index 7efc475..d063312 100644 --- a/Core/symbol_hash.h +++ b/Core/symbol_hash.h @@ -27,12 +27,12 @@ typedef struct { } GB_reversed_symbol_map_t; #ifdef GB_INTERNAL -void internal GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol); -const GB_symbol_t *internal GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name); -GB_bank_symbol_t *internal GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); -const GB_bank_symbol_t *internal GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr); -GB_symbol_map_t *internal GB_map_alloc(void); -void internal GB_map_free(GB_symbol_map_t *map); +internal void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol); +internal const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name); +internal GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name); +internal const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr); +internal GB_symbol_map_t *GB_map_alloc(void); +internal void GB_map_free(GB_symbol_map_t *map); #endif #endif /* symbol_hash_h */ diff --git a/Core/timing.h b/Core/timing.h index 5e29e80..d5d7356 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -3,11 +3,11 @@ #include "defs.h" #ifdef GB_INTERNAL -void internal GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -void internal GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); -bool internal GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ -void internal GB_timing_sync(GB_gameboy_t *gb); -void internal GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value); +internal void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); +internal void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); +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); enum { GB_TIMA_RUNNING = 0, From c6f39bc60bc0a07cb3c0b46d9b8e9f1e45c0f467 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Nov 2021 17:44:51 +0200 Subject: [PATCH 308/365] Initial MBC7 support --- Core/debugger.c | 1 + Core/gb.c | 27 +++++++-- Core/gb.h | 21 +++++++ Core/mbc.c | 16 +++++ Core/mbc.h | 1 + Core/memory.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 212 insertions(+), 6 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 80f4d46..5547721 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1541,6 +1541,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg [GB_MBC2] = "MBC2", [GB_MBC3] = "MBC3", [GB_MBC5] = "MBC5", + [GB_MBC7] = "MBC7", [GB_HUC1] = "HUC-1", [GB_HUC3] = "HUC-3", }; diff --git a/Core/gb.c b/Core/gb.c index 7142d90..24d7329 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1010,9 +1010,11 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ - gb->huc3.days = 0xFFFF; - gb->huc3.minutes = 0xFFF; - gb->huc3.alarm_enabled = false; + if (gb->cartridge_type->mbc_type == GB_HUC3) { + gb->huc3.days = 0xFFFF; + gb->huc3.minutes = 0xFFF; + gb->huc3.alarm_enabled = false; + } exit: return; } @@ -1116,9 +1118,11 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ - gb->huc3.days = 0xFFFF; - gb->huc3.minutes = 0xFFF; - gb->huc3.alarm_enabled = false; + if (gb->cartridge_type->mbc_type == GB_HUC3) { + gb->huc3.days = 0xFFFF; + gb->huc3.minutes = 0xFFF; + gb->huc3.alarm_enabled = false; + } exit: fclose(f); return; @@ -1844,6 +1848,17 @@ void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode) } } +bool GB_has_accelerometer(GB_gameboy_t *gb) +{ + return gb->cartridge_type->mbc_type == GB_MBC7; +} + +void GB_set_accelerometer_values(GB_gameboy_t *gb, double x, double y) +{ + gb->accelerometer_x = x; + gb->accelerometer_y = y; +} + void GB_get_rom_title(GB_gameboy_t *gb, char *title) { memset(title, 0, 17); diff --git a/Core/gb.h b/Core/gb.h index 1cec2ac..448c3e5 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -443,6 +443,22 @@ struct GB_gameboy_internal_s { uint8_t rom_bank_high:1; uint8_t ram_bank:4; } mbc5; + + struct { + uint8_t rom_bank; + uint16_t x_latch; + uint16_t y_latch; + bool latch_ready:1; + bool eeprom_do:1; + bool eeprom_di:1; + bool eeprom_clk:1; + bool eeprom_cs:1; + uint16_t eeprom_command:11; + uint16_t read_bits; + uint8_t bits_countdown:5; + bool secondary_ram_enable:1; + bool eeprom_write_enabled:1; + } mbc7; struct { uint8_t bank_low:6; @@ -609,6 +625,7 @@ struct GB_gameboy_internal_s { GB_color_correction_mode_t color_correction_mode; double light_temperature; bool keys[4][GB_KEY_MAX]; + double accelerometer_x, accelerometer_y; GB_border_mode_t border_mode; GB_sgb_border_t borrowed_border; bool tried_loading_sgb_border; @@ -835,6 +852,10 @@ unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm /* RTC emulation mode */ void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode); +/* For cartridges motion controls */ +bool GB_has_accelerometer(GB_gameboy_t *gb); +void GB_set_accelerometer_values(GB_gameboy_t *gb, double x, double y); + /* For integration with SFC/SNES emulators */ void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback); void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback); diff --git a/Core/mbc.c b/Core/mbc.c index b76778c..82771eb 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -34,6 +34,8 @@ const GB_cartridge_t GB_cart_defs[256] = { { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0x22] = + { GB_MBC7 , GB_STANDARD_MBC, true, true, false, false}, // 22h MBC7+ACCEL+EEPROM [0xFC] = { 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) @@ -97,6 +99,9 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); gb->mbc_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); @@ -148,6 +153,9 @@ void GB_configure_cart(GB_gameboy_t *gb) if (gb->cartridge_type->mbc_type == GB_MBC2) { gb->mbc_ram_size = 0x200; } + else if (gb->cartridge_type->mbc_type == GB_MBC7) { + gb->mbc_ram_size = 0x100; + } else if (gb->cartridge_type->mbc_type == GB_TPP1) { if (gb->rom[0x152] >= 1 && gb->rom[0x152] <= 9) { gb->mbc_ram_size = 0x2000 << (gb->rom[0x152] - 1); @@ -187,4 +195,12 @@ void GB_configure_cart(GB_gameboy_t *gb) if (gb->cartridge_type->mbc_type == GB_MBC5) { gb->mbc5.rom_bank_low = 1; } + + /* Initial MBC7 state */ + if (gb->cartridge_type->mbc_type == GB_MBC7) { + gb->mbc7.x_latch = gb->mbc7.y_latch = 0x8000; + gb->mbc7.latch_ready = true; + gb->mbc7.read_bits = -1; + gb->mbc7.eeprom_do = true; + } } diff --git a/Core/mbc.h b/Core/mbc.h index 709bd1f..7e6cc82 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -10,6 +10,7 @@ typedef struct { GB_MBC2, GB_MBC3, GB_MBC5, + GB_MBC7, GB_HUC1, GB_HUC3, GB_TPP1, diff --git a/Core/memory.c b/Core/memory.c index 0174eb1..4e6665e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -301,8 +301,28 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)]; } +static uint8_t read_mbc7_ram(GB_gameboy_t *gb, uint16_t addr) +{ + if (!gb->mbc_ram_enable || !gb->mbc7.secondary_ram_enable) return 0xFF; + if (addr >= 0xB000) return 0xFF; + switch ((addr >> 4) & 0xF) { + case 2: return gb->mbc7.x_latch; + case 3: return gb->mbc7.x_latch >> 8; + case 4: return gb->mbc7.y_latch; + case 5: return gb->mbc7.y_latch >> 8; + case 6: return 0; + case 8: return gb->mbc7.eeprom_do | (gb->mbc7.eeprom_di << 1) | + (gb->mbc7.eeprom_clk << 6) | (gb->mbc7.eeprom_cs << 7); + } + return 0xFF; +} + static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { + if (gb->cartridge_type->mbc_type == GB_MBC7) { + return read_mbc7_ram(gb, addr); + } + if (gb->cartridge_type->mbc_type == GB_HUC3) { switch (gb->huc3.mode) { case 0xC: // RTC read @@ -730,6 +750,13 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; } break; + case GB_MBC7: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = value == 0x0A; break; + case 0x2000: case 0x3000: gb->mbc7.rom_bank = value; break; + case 0x4000: case 0x5000: gb->mbc7.secondary_ram_enable = value == 0x40; break; + } + break; case GB_HUC1: switch (addr & 0xF000) { case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break; @@ -905,8 +932,133 @@ static bool huc3_write(GB_gameboy_t *gb, uint8_t value) } } +static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (!gb->mbc_ram_enable || !gb->mbc7.secondary_ram_enable) return; + if (addr >= 0xB000) return; + switch ((addr >> 4) & 0xF) { + case 0: { + if (value == 0x55) { + gb->mbc7.latch_ready = true; + gb->mbc7.x_latch = gb->mbc7.y_latch = 0x8000; + } + } + case 1: { + if (value == 0xAA) { + gb->mbc7.latch_ready = false; + gb->mbc7.x_latch = 0x81D0 + 0x70 * gb->accelerometer_x; + gb->mbc7.y_latch = 0x81D0 + 0x70 * gb->accelerometer_y; + } + } + case 8: { + gb->mbc7.eeprom_cs = value & 0x80; + gb->mbc7.eeprom_di = value & 2; + if (gb->mbc7.eeprom_cs) { + if (!gb->mbc7.eeprom_clk && (value & 0x40)) { // Clocked + 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) { + /* Not transferring extra bits for a command*/ + gb->mbc7.eeprom_command <<= 1; + gb->mbc7.eeprom_command |= gb->mbc7.eeprom_di; + if (gb->mbc7.eeprom_command & 0x400) { + // Got full command + switch ((gb->mbc7.eeprom_command >> 6) & 0xF) { + case 0x8: + case 0x9: + case 0xA: + case 0xB: + // READ + gb->mbc7.read_bits = LE16(((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F]); + gb->mbc7.eeprom_command = 0; + break; + case 0x3: // EWEN (Eeprom Write ENable) + gb->mbc7.eeprom_write_enabled = true; + gb->mbc7.eeprom_command = 0; + break; + case 0x0: // EWDS (Eeprom Write DiSable) + gb->mbc7.eeprom_write_enabled = false; + gb->mbc7.eeprom_command = 0; + break; + case 0x4: + case 0x5: + case 0x6: + case 0x7: + // WRITE + if (gb->mbc7.eeprom_write_enabled) { + ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0; + } + gb->mbc7.bits_countdown = 16; + // We still need to process this command, don't erase eeprom_command + break; + case 0xC: + case 0xD: + case 0xE: + case 0xF: + // ERASE + if (gb->mbc7.eeprom_write_enabled) { + ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0xFFFF; + gb->mbc7.read_bits = 0x3FFF; // Emulate some time to settle + } + gb->mbc7.eeprom_command = 0; + break; + case 0x2: + // ERAL (ERase ALl) + if (gb->mbc7.eeprom_write_enabled) { + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); + ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0xFFFF; + gb->mbc7.read_bits = 0xFF; // Emulate some time to settle + } + gb->mbc7.eeprom_command = 0; + break; + case 0x1: + // WRAL (WRite ALl) + if (gb->mbc7.eeprom_write_enabled) { + memset(gb->mbc_ram, 0, gb->mbc_ram_size); + } + gb->mbc7.bits_countdown = 16; + // We still need to process this command, don't erase eeprom_command + break; + } + } + } + else { + // We're shifting in extra bits for a WRITE/WRAL command + gb->mbc7.bits_countdown--; + gb->mbc7.eeprom_do = true; + if (gb->mbc7.eeprom_di) { + uint16_t bit = LE16(1 << gb->mbc7.bits_countdown); + if (gb->mbc7.eeprom_command & 0x100) { + // WRITE + ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit; + } + else { + // WRAL + for (unsigned i = 0; i < 0x7F; i++) { + ((uint16_t *)gb->mbc_ram)[i] |= bit; + } + } + } + if (gb->mbc7.bits_countdown == 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 + } + } + } + } + gb->mbc7.eeprom_clk = value & 0x40; + } + } +} + static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { + if (gb->cartridge_type->mbc_type == GB_MBC7) { + write_mbc7_ram(gb, addr, value); + return; + } + if (gb->cartridge_type->mbc_type == GB_HUC3) { if (huc3_write(gb, value)) return; } From 06ce30d3a89eee22c7add8a8d76d9a007718ed74 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Nov 2021 18:10:03 +0200 Subject: [PATCH 309/365] Map joysticks to motion controls --- Cocoa/GBView.m | 14 ++++++++++++++ JoyKit/JOYButton.h | 10 ++++++++-- JoyKit/JOYButton.m | 5 +++++ JoyKit/JOYController.m | 20 ++++++++++---------- JoyKit/JOYEmulatedButton.h | 2 +- JoyKit/JOYEmulatedButton.m | 9 ++++++++- 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 6c92c3f..5ff3f3c 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -459,6 +459,12 @@ static const uint8_t workboy_vk_to_key[] = { [lastController setRumbleAmplitude:amp]; } +- (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller +{ + if (!GB_has_accelerometer(_gb)) return false; + return true; +} + - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis { if (![self.window isMainWindow]) return; @@ -481,9 +487,17 @@ static const uint8_t workboy_vk_to_key[] = { } } +- (void)controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes +{ + if ([self shouldControllerUseJoystickForMotion:controller]) { + GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y); + } +} + - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button { if (![self.window isMainWindow]) return; + if (button.type == JOYButtonTypeAxes2DEmulated && [self shouldControllerUseJoystickForMotion:controller]) return; unsigned player_count = GB_get_player_count(_gb); if (self.document.partner) { diff --git a/JoyKit/JOYButton.h b/JoyKit/JOYButton.h index 6a67c6c..08c3ace 100644 --- a/JoyKit/JOYButton.h +++ b/JoyKit/JOYButton.h @@ -1,7 +1,5 @@ #import - - typedef enum { JOYButtonUsageNone, JOYButtonUsageA, @@ -41,12 +39,20 @@ typedef enum { JOYButtonUsageGeneric0 = 0x10000, } JOYButtonUsage; +typedef enum { + JOYButtonTypeNormal, + JOYButtonTypeAxisEmulated, + JOYButtonTypeAxes2DEmulated, + JOYButtonTypeHatEmulated, +} JOYButtonType; + @interface JOYButton : NSObject - (NSString *)usageString; + (NSString *)usageToString: (JOYButtonUsage) usage; - (uint64_t)uniqueID; - (bool) isPressed; @property JOYButtonUsage usage; +@property (readonly) JOYButtonType type; @end diff --git a/JoyKit/JOYButton.m b/JoyKit/JOYButton.m index 18970cd..568d383 100644 --- a/JoyKit/JOYButton.m +++ b/JoyKit/JOYButton.m @@ -105,4 +105,9 @@ } return false; } + +- (JOYButtonType)type +{ + return JOYButtonTypeNormal; +} @end diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 1097ef6..c9a49ac 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -3,7 +3,7 @@ #import "JOYElement.h" #import "JOYSubElement.h" #import "JOYFullReportElement.h" - +#import "JOYButton.h" #import "JOYEmulatedButton.h" #include @@ -307,10 +307,10 @@ typedef union { if (axes2DEmulateButtons) { _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x400000000L], ]; } @@ -346,7 +346,7 @@ typedef union { if ([_hacks[JOYEmulateAxisButtons] boolValue]) { _axisEmulatedButtons[@(axis.uniqueID)] = - [[JOYEmulatedButton alloc] initWithUsage:axis.equivalentButtonUsage uniqueID:axis.uniqueID]; + [[JOYEmulatedButton alloc] initWithUsage:axis.equivalentButtonUsage type:JOYButtonTypeAxisEmulated uniqueID:axis.uniqueID]; } @@ -366,10 +366,10 @@ typedef union { [_hats setObject:hat forKey:element]; if (hatsEmulateButtons) { _hatEmulatedButtons[@(hat.uniqueID)] = @[ - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x400000000L], ]; } break; diff --git a/JoyKit/JOYEmulatedButton.h b/JoyKit/JOYEmulatedButton.h index 491e0c7..05ccde8 100644 --- a/JoyKit/JOYEmulatedButton.h +++ b/JoyKit/JOYEmulatedButton.h @@ -4,7 +4,7 @@ #import "JOYHat.h" @interface JOYEmulatedButton : JOYButton -- (instancetype)initWithUsage:(JOYButtonUsage)usage uniqueID:(uint64_t)uniqueID; +- (instancetype)initWithUsage:(JOYButtonUsage)usage type:(JOYButtonType)type uniqueID:(uint64_t)uniqueID; - (bool)updateStateFromAxis:(JOYAxis *)axis; - (bool)updateStateFromAxes2D:(JOYAxes2D *)axes; - (bool)updateStateFromHat:(JOYHat *)hat; diff --git a/JoyKit/JOYEmulatedButton.m b/JoyKit/JOYEmulatedButton.m index b62670a..5e6d1b3 100644 --- a/JoyKit/JOYEmulatedButton.m +++ b/JoyKit/JOYEmulatedButton.m @@ -10,13 +10,15 @@ @implementation JOYEmulatedButton { uint64_t _uniqueID; + JOYButtonType _type; } -- (instancetype)initWithUsage:(JOYButtonUsage)usage uniqueID:(uint64_t)uniqueID; +- (instancetype)initWithUsage:(JOYButtonUsage)usage type:(JOYButtonType)type uniqueID:(uint64_t)uniqueID; { self = [super init]; self.usage = usage; _uniqueID = uniqueID; + _type = type; return self; } @@ -89,4 +91,9 @@ return _state != old; } +- (JOYButtonType)type +{ + return _type; +} + @end From 7a78649e210fd6b6d26ed8c64dffcd60441e98ff Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 13 Nov 2021 19:23:45 +0200 Subject: [PATCH 310/365] Implement motion controls in JoyKit, implement accel/gyro in DualSense and DualShock 4, implement motion controls in Cocoa --- Cocoa/GBView.m | 23 ++++++ JoyKit/ControllerConfiguration.inc | 16 ++++ JoyKit/JOYAxes3D.h | 27 +++++++ JoyKit/JOYAxes3D.m | 108 +++++++++++++++++++++++++++ JoyKit/JOYController.h | 3 + JoyKit/JOYController.m | 114 ++++++++++++++++++++++++----- JoyKit/JOYFullReportElement.m | 5 +- JoyKit/JOYSubElement.m | 6 ++ 8 files changed, 281 insertions(+), 21 deletions(-) create mode 100644 JoyKit/JOYAxes3D.h create mode 100644 JoyKit/JOYAxes3D.m diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 5ff3f3c..b374b37 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -462,6 +462,11 @@ static const uint8_t workboy_vk_to_key[] = { - (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller { if (!GB_has_accelerometer(_gb)) return false; + for (JOYAxes3D *axes in controller.axes3D) { + if (axes.usage == JOYAxes3DUsageOrientation || axes.usage == JOYAxes3DUsageAcceleration) { + return false; + } + } return true; } @@ -494,6 +499,24 @@ static const uint8_t workboy_vk_to_key[] = { } } +- (void)controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes +{ + if (axes.usage == JOYAxes3DUsageOrientation) { + for (JOYAxes3D *axes in controller.axes3D) { + // Only use orientation if there's no acceleration axes + if (axes.usage == JOYAxes3DUsageAcceleration) { + return; + } + } + JOYPoint3D point = axes.normalizedValue; + GB_set_accelerometer_values(_gb, point.x, point.z); + } + else if (axes.usage == JOYAxes3DUsageAcceleration) { + JOYPoint3D point = axes.gUnitsValue; + GB_set_accelerometer_values(_gb, point.x, point.z); + } +} + - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button { if (![self.window isMainWindow]) return; diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index fb7df63..269ad6a 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -110,6 +110,22 @@ hacksByManufacturer = @{ @{@"reportID": @(0x31), @"size":@1, @"offset":@0x50, @"usagePage":@(kHIDPage_Button), @"usage":@13}, @{@"reportID": @(0x31), @"size":@1, @"offset":@0x51, @"usagePage":@(kHIDPage_Button), @"usage":@14}, @{@"reportID": @(0x31), @"size":@1, @"offset":@0x52, @"usagePage":@(kHIDPage_Button), @"usage":@15}, + + @{@"reportID": @(0x31), @"size":@16, @"offset":@0x80, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis)}, + @{@"reportID": @(0x31), @"size":@16, @"offset":@0x90, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis)}, + @{@"reportID": @(0x31), @"size":@16, @"offset":@0xA0, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis)}, + @{@"reportID": @(0x31), @"size":@16, @"offset":@0xB0, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisX)}, + @{@"reportID": @(0x31), @"size":@16, @"offset":@0xC0, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisY)}, + @{@"reportID": @(0x31), @"size":@16, @"offset":@0xD0, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ)}, + ], + + @(1): @[ + @{@"reportID": @(1), @"size":@16, @"offset":@0x78, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis)}, + @{@"reportID": @(1), @"size":@16, @"offset":@0x88, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis)}, + @{@"reportID": @(1), @"size":@16, @"offset":@0x98, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis)}, + @{@"reportID": @(1), @"size":@16, @"offset":@0xA8, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisX)}, + @{@"reportID": @(1), @"size":@16, @"offset":@0xB8, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisY)}, + @{@"reportID": @(1), @"size":@16, @"offset":@0xC8, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ)}, ], }, diff --git a/JoyKit/JOYAxes3D.h b/JoyKit/JOYAxes3D.h new file mode 100644 index 0000000..5c83807 --- /dev/null +++ b/JoyKit/JOYAxes3D.h @@ -0,0 +1,27 @@ +#import + +typedef enum { + JOYAxes3DUsageNone, + JOYAxes3DUsageAcceleration, + JOYAxes3DUsageOrientation, + JOYAxes3DUsageGyroscope, + JOYAxes3DUsageNonGenericMax, + + JOYAxes3DUsageGeneric0 = 0x10000, +} JOYAxes3DUsage; + +typedef struct { + double x, y, z; +} JOYPoint3D; + +@interface JOYAxes3D : NSObject +- (NSString *)usageString; ++ (NSString *)usageToString: (JOYAxes3DUsage) usage; +- (uint64_t)uniqueID; +- (JOYPoint3D)rawValue; +- (JOYPoint3D)normalizedValue; // For orientation +- (JOYPoint3D)gUnitsValue; // For acceleration +@property JOYAxes3DUsage usage; +@end + + diff --git a/JoyKit/JOYAxes3D.m b/JoyKit/JOYAxes3D.m new file mode 100644 index 0000000..6ec146a --- /dev/null +++ b/JoyKit/JOYAxes3D.m @@ -0,0 +1,108 @@ +#import "JOYAxes3D.h" +#import "JOYElement.h" + +@implementation JOYAxes3D +{ + JOYElement *_element1, *_element2, *_element3; + double _state1, _state2, _state3; + int32_t _minX, _minY, _minZ; + int32_t _maxX, _maxY, _maxZ; + double _gApproximation; +} + ++ (NSString *)usageToString: (JOYAxes3DUsage) usage +{ + if (usage < JOYAxes3DUsageNonGenericMax) { + return (NSString *[]) { + @"None", + @"Acceleretion", + @"Orientation", + @"Gyroscope", + }[usage]; + } + if (usage >= JOYAxes3DUsageGeneric0) { + return [NSString stringWithFormat:@"Generic 3D Analog Control %d", usage - JOYAxes3DUsageGeneric0]; + } + + return [NSString stringWithFormat:@"Unknown Usage 3D Axes %d", usage]; +} + +- (NSString *)usageString +{ + return [self.class usageToString:_usage]; +} + +- (uint64_t)uniqueID +{ + return _element1.uniqueID; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: (%.2f, %.2f, %.2f)>", self.className, self, self.usageString, self.uniqueID, _state1, _state2, _state3]; +} + +- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 thirdElement:(JOYElement *)element3 +{ + self = [super init]; + if (!self) return self; + + _element1 = element1; + _element2 = element2; + _element3 = element3; + + _maxX = element1? element1.max : 1; + _maxY = element2? element2.max : 1; + _maxZ = element3? element3.max : 1; + _minX = element1? element1.min : -1; + _minY = element2? element2.min : -1; + _minZ = element3? element3.min : -1; + + return self; +} + +- (JOYPoint3D)rawValue +{ + return (JOYPoint3D){_state1, _state2, _state3}; +} + +- (JOYPoint3D)normalizedValue +{ + double distance = sqrt(_state1 * _state1 + _state2 * _state2 + _state3 * _state3); + if (distance == 0) { + distance = 1; + } + return (JOYPoint3D){_state1 / distance, _state2 / distance, _state3 / distance}; +} + +- (JOYPoint3D)gUnitsValue +{ + double distance = _gApproximation ?: 1; + return (JOYPoint3D){_state1 / distance, _state2 / distance, _state3 / distance}; +} + +- (bool)updateState +{ + int32_t x = [_element1 value]; + int32_t y = [_element2 value]; + int32_t z = [_element3 value]; + + if (x == 0 && y == 0 && z == 0) return false; + + double old1 = _state1, old2 = _state2, old3 = _state3; + _state1 = (x - _minX) / (double)(_maxX - _minX) * 2 - 1; + _state2 = (y - _minY) / (double)(_maxY - _minY) * 2 - 1; + _state3 = (z - _minZ) / (double)(_maxZ - _minZ) * 2 - 1; + + double distance = sqrt(_state1 * _state1 + _state2 * _state2 + _state3 * _state3); + if (_gApproximation == 0) { + _gApproximation = distance; + } + else { + _gApproximation = _gApproximation * 0.9999 + distance * 0.0001; + } + + return old1 != _state1 || old2 != _state2 || old3 != _state3; +} + +@end diff --git a/JoyKit/JOYController.h b/JoyKit/JOYController.h index 8f5f6f4..a21175c 100644 --- a/JoyKit/JOYController.h +++ b/JoyKit/JOYController.h @@ -2,6 +2,7 @@ #import "JOYButton.h" #import "JOYAxis.h" #import "JOYAxes2D.h" +#import "JOYAxes3D.h" #import "JOYHat.h" static NSString const *JOYAxes2DEmulateButtonsKey = @"JOYAxes2DEmulateButtons"; @@ -17,6 +18,7 @@ static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons"; -(void) controller:(JOYController *)controller buttonChangedState:(JOYButton *)button; -(void) controller:(JOYController *)controller movedAxis:(JOYAxis *)axis; -(void) controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes; +-(void) controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes; -(void) controller:(JOYController *)controller movedHat:(JOYHat *)hat; @end @@ -31,6 +33,7 @@ static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons"; - (NSArray *) buttons; - (NSArray *) axes; - (NSArray *) axes2D; +- (NSArray *) axes3D; - (NSArray *) hats; - (void)setRumbleAmplitude:(double)amp; - (void)setPlayerLEDs:(uint8_t)mask; diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index c9a49ac..a89c227 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -71,6 +71,14 @@ static bool hatsEmulateButtons = false; - (bool)updateState; @end +@interface JOYAxes3D () +{ + @public JOYElement *_element1, *_element2, *_element3; +} +- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 thirdElement:(JOYElement *)element2; +- (bool)updateState; +@end + static NSDictionary *CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage) { return @{ @@ -168,10 +176,12 @@ typedef union { NSMutableDictionary *_buttons; NSMutableDictionary *_axes; NSMutableDictionary *_axes2D; + NSMutableDictionary *_axes3D; NSMutableDictionary *_hats; NSMutableDictionary *_fullReportElements; NSMutableDictionary *> *_multiElements; - + JOYAxes3D *_lastAxes3D; + // Button emulation NSMutableDictionary *_axisEmulatedButtons; NSMutableDictionary *> *_axes2DEmulatedButtons; @@ -246,6 +256,69 @@ typedef union { @(kHIDUsage_GD_Rz): @(1), }; + if (element.usagePage == kHIDPage_Sensor) { + JOYAxes3DUsage usage; + JOYElement *element1 = nil, *element2 = nil, *element3 = nil; + + switch (element.usage) { + case kHIDUsage_Snsr_Data_Motion_AccelerationAxisX: + case kHIDUsage_Snsr_Data_Motion_AccelerationAxisY: + case kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ: + usage = JOYAxes3DUsageAcceleration; + break; + case kHIDUsage_Snsr_Data_Motion_AngularPositionXAxis: + case kHIDUsage_Snsr_Data_Motion_AngularPositionYAxis: + case kHIDUsage_Snsr_Data_Motion_AngularPositionZAxis: + usage = JOYAxes3DUsageOrientation; + break; + case kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis: + case kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis: + case kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis: + usage = JOYAxes3DUsageGyroscope; + break; + default: + return; + } + + switch (element.usage) { + case kHIDUsage_Snsr_Data_Motion_AccelerationAxisX: + case kHIDUsage_Snsr_Data_Motion_AngularPositionXAxis: + case kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis: + element1 = element; + if (_lastAxes3D && !_lastAxes3D->_element1 && _lastAxes3D.usage == usage) { + element2 = _lastAxes3D->_element2; + element3 = _lastAxes3D->_element3; + } + break; + case kHIDUsage_Snsr_Data_Motion_AccelerationAxisY: + case kHIDUsage_Snsr_Data_Motion_AngularPositionYAxis: + case kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis: + element2 = element; + if (_lastAxes3D && !_lastAxes3D->_element2 && _lastAxes3D.usage == usage) { + element1 = _lastAxes3D->_element1; + element3 = _lastAxes3D->_element3; + } + break; + case kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ: + case kHIDUsage_Snsr_Data_Motion_AngularPositionZAxis: + case kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis: + element3 = element; + if (_lastAxes3D && !_lastAxes3D->_element3 && _lastAxes3D.usage == usage) { + element1 = _lastAxes3D->_element1; + element2 = _lastAxes3D->_element2; + } + break; + } + + _lastAxes3D = [[JOYAxes3D alloc] initWithFirstElement:element1 secondElement:element2 thirdElement:element3]; + _lastAxes3D.usage = usage; + if (element1) _axes3D[element1] = _lastAxes3D; + if (element2) _axes3D[element2] = _lastAxes3D; + if (element3) _axes3D[element3] = _lastAxes3D; + + return; + } + axisGroups = _hacks[JOYAxisGroups] ?: axisGroups; if (element.usagePage == kHIDPage_Button || @@ -313,23 +386,6 @@ typedef union { [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x400000000L], ]; } - - /* - for (NSArray *group in axes2d) { - break; - IOHIDElementRef first = (__bridge IOHIDElementRef)group[0]; - IOHIDElementRef second = (__bridge IOHIDElementRef)group[1]; - if (IOHIDElementGetUsage(first) > element.usage) continue; - if (IOHIDElementGetUsage(second) > element.usage) continue; - if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue; - if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue; - if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue; - - [axes2d removeObject:group]; - [axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]]; - found = true; - break; - }*/ break; } case kHIDUsage_GD_Slider: @@ -396,6 +452,7 @@ typedef union { _buttons = [NSMutableDictionary dictionary]; _axes = [NSMutableDictionary dictionary]; _axes2D = [NSMutableDictionary dictionary]; + _axes3D = [NSMutableDictionary dictionary]; _hats = [NSMutableDictionary dictionary]; _axisEmulatedButtons = [NSMutableDictionary dictionary]; _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; @@ -619,6 +676,11 @@ typedef union { return [[NSSet setWithArray:[_axes2D allValues]] allObjects]; } +- (NSArray *)axes3D +{ + return [[NSSet setWithArray:[_axes3D allValues]] allObjects]; +} + - (NSArray *)hats { return [_hats allValues]; @@ -736,6 +798,20 @@ typedef union { } } + { + JOYAxes3D *axes = _axes3D[element]; + if (axes) { + if ([axes updateState]) { + for (id listener in listeners) { + if ([listener respondsToSelector:@selector(controller:movedAxes3D:)]) { + [listener controller:self movedAxes3D:axes]; + } + } + } + return; + } + } + { JOYHat *hat = _hats[element]; if (hat) { @@ -865,7 +941,7 @@ typedef union { if (_isDualShock3) { return 2 << player; } - if (_isUSBDualSense) { + if (_isDualSense) { switch (player) { case 0: return 0x04; case 1: return 0x0A; diff --git a/JoyKit/JOYFullReportElement.m b/JoyKit/JOYFullReportElement.m index c8efb27..a19a530 100644 --- a/JoyKit/JOYFullReportElement.m +++ b/JoyKit/JOYFullReportElement.m @@ -61,9 +61,10 @@ return self.uniqueID; } -- (BOOL)isEqual:(id)object +- (BOOL)isEqual:(JOYFullReportElement *)object { - return self.uniqueID == self.uniqueID; + if ([object isKindOfClass:self.class]) return false; + return self.uniqueID == object.uniqueID; } - (id)copyWithZone:(nullable NSZone *)zone; diff --git a/JoyKit/JOYSubElement.m b/JoyKit/JOYSubElement.m index c94badc..186caf9 100644 --- a/JoyKit/JOYSubElement.m +++ b/JoyKit/JOYSubElement.m @@ -57,6 +57,12 @@ memcpy(temp, bytes + _offset / 8, (_offset + _size - 1) / 8 - _offset / 8 + 1); uint32_t ret = (*(uint32_t *)temp) >> (_offset % 8); ret &= (1 << _size) - 1; + // + if (_min < 0 || _max < 0) { // Uses unsigned values + if (ret & (1 << (_size - 1)) ) { // Is negative + ret |= ~((1 << _size) - 1); // Fill with 1s + } + } if (_max < _min) { return _max + _min - ret; From ae930472f003e970e97750c8b5996697e8ab4b95 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 14 Nov 2021 13:18:58 +0200 Subject: [PATCH 311/365] Units info --- Core/gb.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/gb.h b/Core/gb.h index 448c3e5..0d3846a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -854,6 +854,8 @@ void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode); /* For cartridges motion controls */ bool GB_has_accelerometer(GB_gameboy_t *gb); +// In units of g (gravity's acceleration). +// Values within ±4 recommended void GB_set_accelerometer_values(GB_gameboy_t *gb, double x, double y); /* For integration with SFC/SNES emulators */ From d15eaf4134d5e674caefba3da231464d1944552d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 14 Nov 2021 21:27:12 +0200 Subject: [PATCH 312/365] Mouse controls for MBC7 --- Cocoa/AppDelegate.m | 3 ++ Cocoa/GBPreferencesWindow.h | 2 + Cocoa/GBPreferencesWindow.m | 37 +++++++++++++++++ Cocoa/GBView.m | 67 +++++++++++++++++++++++++++++-- Cocoa/Preferences.xib | 79 ++++++++++++++++++++++++++----------- 5 files changed, 162 insertions(+), 26 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 48514a0..eccd137 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -68,6 +68,9 @@ static uint32_t color_to_int(NSColor *color) @"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY), @"GBVolume": @(1.0), + + @"GBMBC7JoystickOverride": @NO, + @"GBMBC7AllowMouse": @YES, }]; [JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{ diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 355dc6e..30b045c 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -32,4 +32,6 @@ @property (weak) IBOutlet NSButton *screenshotFilterCheckbox; @property (weak) IBOutlet GBPaletteEditorController *paletteEditorController; @property (strong) IBOutlet NSWindow *paletteEditor; +@property (weak) IBOutlet NSButton *joystickMBC7Checkbox; +@property (weak) IBOutlet NSButton *mouseMBC7Checkbox; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index e1f9fc0..c77a5b7 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -34,6 +34,8 @@ NSButton *_autoUpdatesCheckbox; NSButton *_OSDCheckbox; NSButton *_screenshotFilterCheckbox; + NSButton *_joystickMBC7Checkbox; + NSButton *_mouseMBC7Checkbox; } + (NSArray *)filterList @@ -324,6 +326,19 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil]; } + +- (IBAction)changeMBC7JoystickOverride:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBMBC7JoystickOverride"]; +} + +- (IBAction)changeMBC7AllowMouse:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState + forKey:@"GBMBC7AllowMouse"]; +} + - (IBAction)changeAnalogControls:(id)sender { [[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState @@ -572,6 +587,28 @@ [self advanceConfigurationStateMachine]; } +- (NSButton *)joystickMBC7Checkbox +{ + return _joystickMBC7Checkbox; +} + +- (void)setJoystickMBC7Checkbox:(NSButton *)joystickMBC7Checkbox +{ + _joystickMBC7Checkbox = joystickMBC7Checkbox; + [_joystickMBC7Checkbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]]; +} + +- (NSButton *)mouseMBC7Checkbox +{ + return _mouseMBC7Checkbox; +} + +- (void)setMouseMBC7Checkbox:(NSButton *)mouseMBC7Checkbox +{ + _mouseMBC7Checkbox = mouseMBC7Checkbox; + [_mouseMBC7Checkbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7AllowMouse"]]; +} + - (NSButton *)analogControlsCheckbox { return _analogControlsCheckbox; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index b374b37..5ae9c79 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -118,6 +118,7 @@ static const uint8_t workboy_vk_to_key[] = { JOYController *lastController; GB_frame_blending_mode_t _frameBlendingMode; bool _turbo; + bool _mouseControlEnabled; } + (instancetype)alloc @@ -147,7 +148,7 @@ static const uint8_t workboy_vk_to_key[] = { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} - options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect + options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseMoved owner:self userInfo:nil]; [self addTrackingArea:tracking_area]; @@ -156,6 +157,7 @@ static const uint8_t workboy_vk_to_key[] = { [self addSubview:self.internalView]; self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; [JOYController registerListener:self]; + _mouseControlEnabled = true; } - (void)screenSizeChanged @@ -461,7 +463,9 @@ static const uint8_t workboy_vk_to_key[] = { - (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller { + if (!_gb) return false; if (!GB_has_accelerometer(_gb)) return false; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return true; for (JOYAxes3D *axes in controller.axes3D) { if (axes.usage == JOYAxes3DUsageOrientation || axes.usage == JOYAxes3DUsageAcceleration) { return false; @@ -472,6 +476,7 @@ static const uint8_t workboy_vk_to_key[] = { - (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis { + if (!_gb) return; if (![self.window isMainWindow]) return; NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID]; @@ -494,13 +499,20 @@ static const uint8_t workboy_vk_to_key[] = { - (void)controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes { + if (!_gb) return; if ([self shouldControllerUseJoystickForMotion:controller]) { - GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y); + if (!self.mouseControlsActive) { + GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y); + } } } - (void)controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes { + if (!_gb) return; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return; + if (self.mouseControlsActive) return; + if (axes.usage == JOYAxes3DUsageOrientation) { for (JOYAxes3D *axes in controller.axes3D) { // Only use orientation if there's no acceleration axes @@ -519,7 +531,9 @@ static const uint8_t workboy_vk_to_key[] = { - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button { + if (!_gb) return; if (![self.window isMainWindow]) return; + _mouseControlEnabled = false; if (button.type == JOYButtonTypeAxes2DEmulated && [self shouldControllerUseJoystickForMotion:controller]) return; unsigned player_count = GB_get_player_count(_gb); @@ -625,11 +639,18 @@ static const uint8_t workboy_vk_to_key[] = { return true; } +- (bool)mouseControlsActive +{ + return _gb && GB_is_inited(_gb) && GB_has_accelerometer(_gb) && + _mouseControlEnabled && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7AllowMouse"]; +} + - (void)mouseEntered:(NSEvent *)theEvent { if (!mouse_hidden) { mouse_hidden = true; - if (_mouseHidingEnabled) { + if (_mouseHidingEnabled && + !self.mouseControlsActive) { [NSCursor hide]; } } @@ -647,6 +668,46 @@ static const uint8_t workboy_vk_to_key[] = { [super mouseExited:theEvent]; } +- (void)mouseDown:(NSEvent *)event +{ + _mouseControlEnabled = true; + if (self.mouseControlsActive) { + if (event.type == NSEventTypeLeftMouseDown) { + GB_set_key_state(_gb, GB_KEY_A, true); + } + } +} + +- (void)mouseUp:(NSEvent *)event +{ + if (self.mouseControlsActive) { + if (event.type == NSEventTypeLeftMouseUp) { + GB_set_key_state(_gb, GB_KEY_A, false); + } + } +} + +- (void)mouseMoved:(NSEvent *)event +{ + if (self.mouseControlsActive) { + NSPoint point = [self convertPoint:[event locationInWindow] toView:nil]; + + point.x /= self.frame.size.width; + point.x *= 2; + point.x -= 1; + + point.y /= self.frame.size.height; + point.y *= 2; + point.y -= 1; + + if (GB_get_screen_width(_gb) != 160) { // has border + point.x *= 256 / 160.0; + point.y *= 224 / 114.0; + } + GB_set_accelerometer_values(_gb, -point.x, point.y); + } +} + - (void)setMouseHidingEnabled:(bool)mouseHidingEnabled { if (mouseHidingEnabled == _mouseHidingEnabled) return; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index c9fb8ce..699f264 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -85,6 +85,8 @@ + + @@ -584,11 +586,11 @@ - + - + @@ -596,17 +598,17 @@ - - + + - + - + @@ -664,7 +666,7 @@ - + @@ -673,7 +675,7 @@ - + @@ -689,11 +691,11 @@ - + - + @@ -702,7 +704,7 @@ - + @@ -720,6 +722,48 @@ + + + + + + + + + + + + @@ -738,17 +782,6 @@ - - + From d94c8b912569532f004f10c158bd60ef88801229 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 22 Nov 2021 23:29:10 +0200 Subject: [PATCH 313/365] Switch Pro Controller motion controls --- JoyKit/ControllerConfiguration.inc | 9 +++++++-- JoyKit/JOYController.m | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index 269ad6a..86988c7 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -422,8 +422,6 @@ hacksByName = @{ JOYCustomReports: @{ @(0x30): @[ - // For USB mode, which uses the wrong report descriptor - @{@"reportID": @(1), @"size":@1, @"offset":@16, @"usagePage":@(kHIDPage_Button), @"usage":@3}, @{@"reportID": @(1), @"size":@1, @"offset":@17, @"usagePage":@(kHIDPage_Button), @"usage":@4}, @{@"reportID": @(1), @"size":@1, @"offset":@18, @"usagePage":@(kHIDPage_Button), @"usage":@1}, @@ -456,6 +454,13 @@ hacksByName = @{ @{@"reportID": @(1), @"size":@12, @"offset":@64, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @0xFFF}, @{@"reportID": @(1), @"size":@12, @"offset":@76, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @0xFFF, @"max": @0}, + + @{@"reportID": @(1), @"size":@16, @"offset":@96, @"usagePage":@(kHIDPage_Sensor), @"min": @0x7FFF, @"max": @-0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ)}, + @{@"reportID": @(1), @"size":@16, @"offset":@112, @"usagePage":@(kHIDPage_Sensor), @"min": @0x7FFF, @"max": @-0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisX)}, + @{@"reportID": @(1), @"size":@16, @"offset":@128, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisY)}, + @{@"reportID": @(1), @"size":@16, @"offset":@144, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis)}, + @{@"reportID": @(1), @"size":@16, @"offset":@160, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis)}, + @{@"reportID": @(1), @"size":@16, @"offset":@176, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis)}, ], }, diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index a89c227..caae2cc 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -460,8 +460,6 @@ typedef union { _iokitToJOY = [NSMutableDictionary dictionary]; - //NSMutableArray *axes3d = [NSMutableArray array]; - _hacks = hacks; _isSwitch = [_hacks[JOYIsSwitch] boolValue]; _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue]; @@ -579,6 +577,19 @@ typedef union { if (_isSwitch) { [self sendReport:[NSData dataWithBytes:(uint8_t[]){0x80, 0x04} length:2]]; [self sendReport:[NSData dataWithBytes:(uint8_t[]){0x80, 0x02} length:2]]; + + _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 3; // Set input report mode + _lastVendorSpecificOutput.switchPacket.commandData[0] = 0x30; // Standard full mode + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0x40; // Enable/disableIMU + _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // Enabled + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; } if (_isDualShock3) { From f08f16346e807da0b4987c705e0aaecd2bd8d521 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 24 Nov 2021 23:13:52 +0200 Subject: [PATCH 314/365] Fix #293 --- Core/display.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 46fd672..e81c87f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1072,8 +1072,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->lcd_x = 0; - gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); - + gb->extra_penalty_for_sprite_at_0 = MIN((gb->io_registers[GB_IO_SCX] & 7), 5); /* The actual rendering cycle */ gb->fetcher_state = 0; From d0d39015eefb4aec0fe28c58276a046623501639 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Nov 2021 21:17:49 +0200 Subject: [PATCH 315/365] Let update_input_hint_callback get called during turbo --- Core/timing.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/timing.c b/Core/timing.c index 67c206e..5c5eccf 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -54,13 +54,17 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb) void GB_timing_sync(GB_gameboy_t *gb) { - if (gb->turbo) { - gb->cycles_since_last_sync = 0; - return; - } /* Prevent syncing if not enough time has passed.*/ if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; + if (gb->turbo) { + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } + return; + } + uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ int64_t nanoseconds = get_nanoseconds(); int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds; From f1e5e041983f1433eebdb3c8a55c328d56467ca5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 25 Nov 2021 21:46:51 +0200 Subject: [PATCH 316/365] ...even when timekeeping is disabled --- Core/timing.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Core/timing.c b/Core/timing.c index 5c5eccf..e3023b4 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -95,6 +95,16 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb) void GB_timing_sync(GB_gameboy_t *gb) { + if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; + gb->cycles_since_last_sync = 0; + + if (gb->turbo) { + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } + return; + } } #endif From d0a9d2f72ad8e80631eea958d6c1d957cb67ff51 Mon Sep 17 00:00:00 2001 From: Snowy Date: Thu, 25 Nov 2021 17:16:11 -0600 Subject: [PATCH 317/365] Add GB_is_cgb_in_cgb_mode --- Core/gb.c | 5 +++++ Core/gb.h | 1 + 2 files changed, 6 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index 24d7329..0f027ef 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1319,6 +1319,11 @@ bool GB_is_cgb(GB_gameboy_t *gb) return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; } +bool GB_is_cgb_in_cgb_mode(GB_gameboy_t *gb) +{ + return gb->cgb_mode; +} + bool GB_is_sgb(GB_gameboy_t *gb) { return (gb->model & ~GB_MODEL_PAL_BIT & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB || (gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2; diff --git a/Core/gb.h b/Core/gb.h index 0d3846a..e929900 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -763,6 +763,7 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) void GB_init(GB_gameboy_t *gb, GB_model_t model); bool GB_is_inited(GB_gameboy_t *gb); bool GB_is_cgb(GB_gameboy_t *gb); +bool GB_is_cgb_in_cgb_mode(GB_gameboy_t *gb); bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd GB_model_t GB_get_model(GB_gameboy_t *gb); From 33090a5cc0f34bcc1f50973777a90edc8965e33c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Nov 2021 13:38:52 +0200 Subject: [PATCH 318/365] Fix an oops from the last commit --- Core/timing.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Core/timing.c b/Core/timing.c index e3023b4..001869c 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -98,13 +98,11 @@ void GB_timing_sync(GB_gameboy_t *gb) if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; gb->cycles_since_last_sync = 0; - if (gb->turbo) { - gb->cycles_since_last_sync = 0; - if (gb->update_input_hint_callback) { - gb->update_input_hint_callback(gb); - } - return; + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); } + return; } #endif From bdbe02b043d624dc3e0de9f8d0bb79b9b26a1c17 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Nov 2021 13:54:28 +0200 Subject: [PATCH 319/365] Add a safe memory read API --- Cocoa/Document.m | 2 +- Core/debugger.c | 4 ++-- Core/gb.h | 1 + Core/memory.c | 14 +++++++++++++- Core/memory.h | 1 + Core/sm83_disassembler.c | 1 + 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 39f0852..fafdefc 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1335,7 +1335,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (uint8_t) readMemory:(uint16_t)addr { while (!GB_is_inited(&gb)); - return GB_read_memory(&gb, addr); + return GB_safe_read_memory(&gb, addr); } - (void) writeMemory:(uint16_t)addr value:(uint8_t)value diff --git a/Core/debugger.c b/Core/debugger.c index 5547721..b8d88d7 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1451,7 +1451,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de while (count) { GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); for (unsigned i = 0; i < 16 && count; i++) { - GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + GB_log(gb, "%02x ", GB_safe_read_memory(gb, addr.value + i)); count--; } addr.value += 16; @@ -1464,7 +1464,7 @@ static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const de while (count) { GB_log(gb, "%04x: ", addr.value); for (unsigned i = 0; i < 16 && count; i++) { - GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + GB_log(gb, "%02x ", GB_safe_read_memory(gb, addr.value + i)); count--; } addr.value += 16; diff --git a/Core/gb.h b/Core/gb.h index 0d3846a..d7d988d 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -742,6 +742,7 @@ struct GB_gameboy_internal_s { /* Temporary state */ bool wx_just_changed; bool tile_sel_glitch; + bool disable_oam_corruption; // For safe memory reads GB_gbs_header_t gbs_header; ); diff --git a/Core/memory.c b/Core/memory.c index 4e6665e..f428f05 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -444,7 +444,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (gb->oam_read_blocked) { - if (!GB_is_cgb(gb)) { + if (!GB_is_cgb(gb) && !gb->disable_oam_corruption) { if (addr < 0xFEA0) { uint16_t *oam = (uint16_t *)gb->oam; if (gb->accessed_oam_row == 0) { @@ -702,6 +702,18 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) return data; } +uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr) +{ + gb->disable_oam_corruption = true; + uint8_t data = read_map[addr >> 12](gb, addr); + gb->disable_oam_corruption = false; + GB_apply_cheat(gb, addr, &data); + if (gb->read_memory_callback) { + data = gb->read_memory_callback(gb, addr, data); + } + return data; +} + static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { switch (gb->cartridge_type->mbc_type) { diff --git a/Core/memory.h b/Core/memory.h index 0b7a43c..f351339 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -7,6 +7,7 @@ typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, ui void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback); uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); +uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr); // Without side effects 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); diff --git a/Core/sm83_disassembler.c b/Core/sm83_disassembler.c index 894c766..f85bfc2 100644 --- a/Core/sm83_disassembler.c +++ b/Core/sm83_disassembler.c @@ -2,6 +2,7 @@ #include #include "gb.h" +#define GB_read_memory GB_safe_read_memory typedef void opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc); From 06b744259be749cb7a5d6611d76b04581541ea99 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 26 Nov 2021 14:09:41 +0200 Subject: [PATCH 320/365] Add memory write callback, optimize memory access with likely/unlikely --- Core/cheats.c | 7 ++++--- Core/defs.h | 4 ++-- Core/gb.h | 1 + Core/memory.c | 22 ++++++++++++++++------ Core/memory.h | 2 ++ 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Core/cheats.c b/Core/cheats.c index 1263183..8b5a7a0 100644 --- a/Core/cheats.c +++ b/Core/cheats.c @@ -32,10 +32,11 @@ static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value) { - if (!gb->cheat_enabled) return; - if (!gb->boot_rom_finished) return; + if (likely(!gb->cheat_enabled)) return; + if (likely(gb->cheat_count == 0)) return; // Optimization + if (unlikely(!gb->boot_rom_finished)) return; const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)]; - if (hash) { + if (unlikely(hash)) { for (unsigned i = 0; i < hash->size; i++) { GB_cheat_t *cheat = hash->cheats[i]; if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) { diff --git a/Core/defs.h b/Core/defs.h index b4f13f5..0517b22 100644 --- a/Core/defs.h +++ b/Core/defs.h @@ -4,8 +4,8 @@ #ifdef GB_INTERNAL // "Keyword" definitions -#define likely(x) __builtin_expect((x), 1) -#define unlikely(x) __builtin_expect((x), 0) +#define likely(x) __builtin_expect((bool)(x), 1) +#define unlikely(x) __builtin_expect((bool)(x), 0) #define internal __attribute__((visibility("internal"))) diff --git a/Core/gb.h b/Core/gb.h index d7d988d..cc235e8 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -658,6 +658,7 @@ struct GB_gameboy_internal_s { GB_icd_vreset_callback_t icd_hreset_callback; GB_icd_vreset_callback_t icd_vreset_callback; GB_read_memory_callback_t read_memory_callback; + GB_write_memory_callback_t write_memory_callback; GB_boot_rom_load_callback_t boot_rom_load_callback; GB_print_image_callback_t printer_callback; GB_workboy_set_time_callback workboy_set_time_callback; diff --git a/Core/memory.c b/Core/memory.c index f428f05..bedf339 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -688,15 +688,15 @@ void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t cal uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) { - if (gb->n_watchpoints) { + if (unlikely(gb->n_watchpoints)) { GB_debugger_test_read_watchpoint(gb, addr); } - if (is_addr_in_dma_use(gb, addr)) { + if (unlikely(is_addr_in_dma_use(gb, addr))) { addr = gb->dma_current_src; } uint8_t data = read_map[addr >> 12](gb, addr); GB_apply_cheat(gb, addr, &data); - if (gb->read_memory_callback) { + if (unlikely(gb->read_memory_callback)) { data = gb->read_memory_callback(gb, addr, data); } return data; @@ -708,7 +708,7 @@ uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr) uint8_t data = read_map[addr >> 12](gb, addr); gb->disable_oam_corruption = false; GB_apply_cheat(gb, addr, &data); - if (gb->read_memory_callback) { + if (unlikely(gb->read_memory_callback)) { data = gb->read_memory_callback(gb, addr, data); } return data; @@ -1563,12 +1563,22 @@ static write_function_t *const write_map[] = write_ram, write_high_memory, /* EXXX FXXX */ }; +void GB_set_write_memory_callback(GB_gameboy_t *gb, GB_write_memory_callback_t callback) +{ + gb->write_memory_callback = callback; +} + void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (gb->n_watchpoints) { + if (unlikely(gb->n_watchpoints)) { GB_debugger_test_write_watchpoint(gb, addr, value); } - if (is_addr_in_dma_use(gb, addr)) { + + if (unlikely(gb->write_memory_callback)) { + if (!gb->write_memory_callback(gb, addr, value)) return; + } + + if (unlikely(is_addr_in_dma_use(gb, addr))) { /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */ return; } diff --git a/Core/memory.h b/Core/memory.h index f351339..adfdcaa 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -4,7 +4,9 @@ #include typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data); +typedef bool (*GB_write_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data); // Return false to prevent the write void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback); +void GB_set_write_memory_callback(GB_gameboy_t *gb, GB_write_memory_callback_t callback); uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr); // Without side effects From b770bbea2eb3358a0dfcb5432653c7280aab1acd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 2 Dec 2021 11:21:12 +0200 Subject: [PATCH 321/365] Fix save state issue that caused vblank callbacks timings to differ --- Core/save_state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/save_state.c b/Core/save_state.c index 7e1767c..eefdd08 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -347,7 +347,7 @@ static void sanitize_state(GB_gameboy_t *gb) gb->window_tile_x &= 0x1F; /* These are kind of DOS-ish if too large */ - if (abs(gb->display_cycles) > 0x8000) { + if (abs(gb->display_cycles) > 0x80000) { gb->display_cycles = 0; } From 8660e20eeb9111f5819eaa051965d188ac028132 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 2 Dec 2021 11:23:44 +0200 Subject: [PATCH 322/365] New inputs API --- Core/joypad.c | 28 ++++++++++++++++++++++++++++ Core/joypad.h | 16 ++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/Core/joypad.c b/Core/joypad.c index c0655f0..b916447 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -91,3 +91,31 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play gb->keys[player][index] = pressed; GB_update_joyp(gb); } + +void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask) +{ + memset(gb->keys, 0, sizeof(gb->keys)); + bool *key = &gb->keys[0][0]; + while (mask) { + if (mask & 1) { + *key = true; + } + mask >>= 1; + } + + GB_update_joyp(gb); +} + +void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned player) +{ + memset(gb->keys[player], 0, sizeof(gb->keys[player])); + bool *key = gb->keys[player]; + while (mask) { + if (mask & 1) { + *key = true; + } + mask >>= 1; + } + + GB_update_joyp(gb); +} diff --git a/Core/joypad.h b/Core/joypad.h index 615e34a..beb532e 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -15,8 +15,24 @@ typedef enum { GB_KEY_MAX } GB_key_t; +typedef enum { + GB_KEY_RIGHT_MASK = 1 << GB_KEY_RIGHT, + GB_KEY_LEFT_MASK = 1 << GB_KEY_LEFT, + GB_KEY_UP_MASK = 1 << GB_KEY_UP, + GB_KEY_DOWN_MASK = 1 << GB_KEY_DOWN, + GB_KEY_A_MASK = 1 << GB_KEY_A, + GB_KEY_B_MASK = 1 << GB_KEY_B, + GB_KEY_SELECT_MASK = 1 << GB_KEY_SELECT, + GB_KEY_START_MASK = 1 << GB_KEY_START, +} GB_key_mask_t; + +// For example, for player 2's (0-based; logical player 3) A button, use GB_MASK_FOR_PLAYER(GB_KEY_A_MASK, 2) +#define GB_MASK_FOR_PLAYER(mask, player) ((x) << (player * 8)) + void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed); void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed); +void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask); +void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned player); void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); #ifdef GB_INTERNAL From 4b3c77bfa5cf7777e86e5943ac326494406d95bb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 2 Dec 2021 11:54:26 +0200 Subject: [PATCH 323/365] oops --- Core/joypad.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/joypad.c b/Core/joypad.c index b916447..06fb53f 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -101,6 +101,7 @@ void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask) *key = true; } mask >>= 1; + key++; } GB_update_joyp(gb); @@ -115,6 +116,7 @@ void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned p *key = true; } mask >>= 1; + key++; } GB_update_joyp(gb); From 25e3414974cbc463ac2ab261764deb91a65b56fc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 4 Dec 2021 15:04:46 +0200 Subject: [PATCH 324/365] Redesigned vblank callback scheduling scheme, should be more regular and less prune to various sorts of frontend DOS --- Core/display.c | 28 ++++++++++++++++------------ Core/display.h | 1 + Core/gb.h | 4 +++- Core/memory.c | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Core/display.c b/Core/display.c index e81c87f..caf517c 100644 --- a/Core/display.c +++ b/Core/display.c @@ -109,9 +109,11 @@ typedef struct __attribute__((packed)) { uint8_t flags; } object_t; -static void display_vblank(GB_gameboy_t *gb) +void GB_display_vblank(GB_gameboy_t *gb) { gb->vblank_just_occured = true; + gb->cycles_since_vblank_callback = 0; + gb->lcd_disabled_outside_of_vblank = false; /* TODO: Slow in turbo mode! */ if (GB_is_hle_sgb(gb)) { @@ -126,7 +128,7 @@ static void display_vblank(GB_gameboy_t *gb) bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80; - if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame)) { + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ if (!GB_is_sgb(gb)) { uint32_t color = 0; @@ -855,12 +857,12 @@ static uint16_t get_object_line_address(GB_gameboy_t *gb, const object_t *object */ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) { + 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)) { - gb->cycles_in_stop_mode += cycles; - if (gb->cycles_in_stop_mode >= LCDC_PERIOD) { - gb->cycles_in_stop_mode -= LCDC_PERIOD; - display_vblank(gb); + if (gb->cycles_since_vblank_callback >= LCDC_PERIOD) { + GB_display_vblank(gb); } return; } @@ -912,8 +914,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { while (true) { - GB_SLEEP(gb, display, 1, LCDC_PERIOD); - display_vblank(gb); + 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->cgb_repeated_a_frame = true; } return; @@ -1327,7 +1331,7 @@ abort_fetching_object: // Todo: unverified timing gb->current_lcd_line++; if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) { - display_vblank(gb); + GB_display_vblank(gb); } if (gb->icd_hreset_callback) { @@ -1361,13 +1365,13 @@ abort_fetching_object: if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { if (GB_is_cgb(gb)) { - GB_timing_sync(gb); + GB_display_vblank(gb); 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; - display_vblank(gb); + GB_display_vblank(gb); } gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; } @@ -1375,7 +1379,7 @@ abort_fetching_object: else { if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { gb->is_odd_frame ^= true; - display_vblank(gb); + GB_display_vblank(gb); } if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) { gb->cgb_repeated_a_frame = true; diff --git a/Core/display.h b/Core/display.h index 1cac6ae..1d35d96 100644 --- a/Core/display.h +++ b/Core/display.h @@ -10,6 +10,7 @@ internal void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); 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); enum { GB_OBJECT_PRIORITY_X, diff --git a/Core/gb.h b/Core/gb.h index d7eda27..cbe4601 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -514,6 +514,9 @@ struct GB_gameboy_internal_s { int32_t speed_switch_halt_countdown; uint8_t speed_switch_countdown; // To compensate for the lack of pipeline emulation uint8_t speed_switch_freeze; // Solely for realigning the PPU, should be removed when the odd modes are implemented + /* For timing of the vblank callback */ + uint32_t cycles_since_vblank_callback; + bool lcd_disabled_outside_of_vblank; ); /* APU */ @@ -579,7 +582,6 @@ struct GB_gameboy_internal_s { bool lyc_interrupt_line; bool cgb_palettes_blocked; uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. - uint32_t cycles_in_stop_mode; uint8_t object_priority; bool oam_ppu_blocked; bool vram_ppu_blocked; diff --git a/Core/memory.c b/Core/memory.c index bedf339..de2124c 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1311,6 +1311,21 @@ 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; + } + } gb->display_cycles = 0; gb->display_state = 0; gb->double_speed_alignment = 0; From 9e57201b0820ece8b1087579422347f2f776e3a6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 5 Dec 2021 16:18:54 +0200 Subject: [PATCH 325/365] Accurate IF clear timing --- Core/sm83_cpu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 6920c79..4980871 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1639,6 +1639,10 @@ void GB_cpu_run(GB_gameboy_t *gb) interrupt_queue >>= 1; interrupt_bit++; } + assert(gb->pending_cycles > 2); + gb->pending_cycles -= 2; + flush_pending_cycles(gb); + gb->pending_cycles = 2; gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit); gb->pc = interrupt_bit * 8 + 0x40; } From e087bd5218a2f294aa33a2bb6f71c99244340bdc Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Dec 2021 02:06:12 +0200 Subject: [PATCH 326/365] The GBS visualizer should use custom color palettes --- Cocoa/GBVisualizerView.m | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/Cocoa/GBVisualizerView.m b/Cocoa/GBVisualizerView.m index 61688e6..08f6024 100644 --- a/Cocoa/GBVisualizerView.m +++ b/Cocoa/GBVisualizerView.m @@ -1,4 +1,5 @@ #import "GBVisualizerView.h" +#import "GBPaletteEditorController.h" #include #define SAMPLE_COUNT 1024 @@ -28,24 +29,7 @@ static NSColor *color_to_effect_color(typeof(GB_PALETTE_DMG.colors[0]) color) - (void)drawRect:(NSRect)dirtyRect { - const GB_palette_t *palette; - switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"]) { - case 1: - palette = &GB_PALETTE_DMG; - break; - - case 2: - palette = &GB_PALETTE_MGB; - break; - - case 3: - palette = &GB_PALETTE_GBL; - break; - - default: - palette = &GB_PALETTE_GREY; - break; - } + const GB_palette_t *palette = [GBPaletteEditorController userPalette]; NSSize size = self.bounds.size; [color_to_effect_color(palette->colors[0]) setFill]; From 7508ddb0cfe11fa17d3652784cdf2e723a81e839 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Dec 2021 19:42:47 +0200 Subject: [PATCH 327/365] Execute callback (for #422) --- Core/gb.c | 5 +++++ Core/gb.h | 5 +++++ Core/sm83_cpu.c | 5 ++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index 0f027ef..d177822 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1206,6 +1206,11 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) #endif } +void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback) +{ + gb->execution_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}}}; diff --git a/Core/gb.h b/Core/gb.h index cbe4601..872ab19 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -283,6 +283,8 @@ typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb); typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb); typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type); +typedef void (*GB_execution_callback_t)(GB_gameboy_t *gb, uint16_t address, uint8_t opcode); + struct GB_breakpoint_s; struct GB_watchpoint_s; @@ -665,6 +667,7 @@ struct GB_gameboy_internal_s { GB_print_image_callback_t printer_callback; GB_workboy_set_time_callback workboy_set_time_callback; GB_workboy_get_time_callback workboy_get_time_callback; + GB_execution_callback_t execution_callback; /*** Debugger ***/ volatile bool debug_stopped, debug_disable; @@ -839,6 +842,8 @@ void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_ca /* Called when a new boot ROM is needed. The callback should call GB_load_boot_rom or GB_load_boot_rom_from_buffer */ void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback); +void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback); + void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); /* These APIs are used when using internal clock */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 4980871..85db7ae 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -1655,7 +1655,10 @@ void GB_cpu_run(GB_gameboy_t *gb) /* Run mode */ else if (!gb->halted) { gb->last_opcode_read = cycle_read(gb, gb->pc++); - if (gb->halt_bug) { + if (unlikely(gb->execution_callback)) { + gb->execution_callback(gb, gb->pc - 1, gb->last_opcode_read); + } + if (unlikely(gb->halt_bug)) { gb->pc--; gb->halt_bug = false; } From a30247cf16445fc99dafaca6328ee65e5502a806 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 10 Dec 2021 19:49:52 +0200 Subject: [PATCH 328/365] LCD line callback (for #422) --- Core/display.c | 10 ++++++++++ Core/gb.c | 5 +++++ Core/gb.h | 5 ++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index caf517c..f1f474e 100644 --- a/Core/display.c +++ b/Core/display.c @@ -456,6 +456,10 @@ void GB_lcd_off(GB_gameboy_t *gb) gb->accessed_oam_row = -1; gb->wy_triggered = false; + + if (unlikely(gb->lcd_line_callback)) { + gb->lcd_line_callback(gb, 0); + } } static void add_object_from_index(GB_gameboy_t *gb, unsigned index) @@ -983,6 +987,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* Lines 0 - 143 */ gb->window_y = -1; for (; gb->current_line < LINES; gb->current_line++) { + if (unlikely(gb->lcd_line_callback)) { + gb->lcd_line_callback(gb, gb->current_line); + } gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed; gb->accessed_oam_row = 0; @@ -1343,6 +1350,9 @@ abort_fetching_object: for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { gb->io_registers[GB_IO_LY] = gb->current_line; gb->ly_for_comparison = -1; + if (unlikely(gb->lcd_line_callback)) { + gb->lcd_line_callback(gb, gb->current_line); + } GB_SLEEP(gb, display, 26, 2); if (gb->current_line == LINES && !gb->stat_interrupt_line && (gb->io_registers[GB_IO_STAT] & 0x20)) { gb->io_registers[GB_IO_IF] |= 2; diff --git a/Core/gb.c b/Core/gb.c index d177822..cc85c92 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1211,6 +1211,11 @@ void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callbac gb->execution_callback = callback; } +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}}}; diff --git a/Core/gb.h b/Core/gb.h index 872ab19..d5a6471 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -284,6 +284,7 @@ typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb); typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type); typedef void (*GB_execution_callback_t)(GB_gameboy_t *gb, uint16_t address, uint8_t opcode); +typedef void (*GB_lcd_line_callback_t)(GB_gameboy_t *gb, uint8_t line); struct GB_breakpoint_s; struct GB_watchpoint_s; @@ -668,6 +669,7 @@ struct GB_gameboy_internal_s { GB_workboy_set_time_callback workboy_set_time_callback; GB_workboy_get_time_callback workboy_get_time_callback; GB_execution_callback_t execution_callback; + GB_lcd_line_callback_t lcd_line_callback; /*** Debugger ***/ volatile bool debug_stopped, debug_disable; @@ -843,7 +845,8 @@ void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_ca void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback); void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback); - +void GB_set_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback); + void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); /* These APIs are used when using internal clock */ From 7e5e67298823e2ff6fee98f1db52c07decfda38f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 11 Dec 2021 02:51:21 +0200 Subject: [PATCH 329/365] RTC speed multiplier, for TAS syncing (#422) --- Core/gb.c | 9 --------- Core/gb.h | 11 ++--------- Core/timing.c | 25 +++++++++++++++++++++++-- Core/timing.h | 12 +++++++++++- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index cc85c92..1021dd9 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1854,15 +1854,6 @@ unsigned GB_time_to_alarm(GB_gameboy_t *gb) return alarm_time - current_time; } -void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode) -{ - if (gb->rtc_mode != mode) { - gb->rtc_mode = mode; - gb->rtc_cycles = 0; - gb->last_rtc_second = time(NULL); - } -} - bool GB_has_accelerometer(GB_gameboy_t *gb) { return gb->cartridge_type->mbc_type == GB_MBC7; diff --git a/Core/gb.h b/Core/gb.h index d5a6471..3c77aed 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -247,11 +247,6 @@ typedef enum { GB_BOOT_ROM_AGB, } GB_boot_rom_t; -typedef enum { - GB_RTC_MODE_SYNC_TO_HOST, - GB_RTC_MODE_ACCURATE, -} GB_rtc_mode_t; - #ifdef GB_INTERNAL #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 @@ -640,6 +635,7 @@ struct GB_gameboy_internal_s { uint64_t last_sync; uint64_t cycles_since_last_sync; // In 8MHz units GB_rtc_mode_t rtc_mode; + uint32_t rtc_second_length; /* Audio */ GB_apu_output_t apu_output; @@ -861,10 +857,7 @@ void GB_disconnect_serial(GB_gameboy_t *gb); /* For cartridges with an alarm clock */ unsigned GB_time_to_alarm(GB_gameboy_t *gb); // 0 if no alarm - -/* RTC emulation mode */ -void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode); - + /* For cartridges motion controls */ bool GB_has_accelerometer(GB_gameboy_t *gb); // In units of g (gravity's acceleration). diff --git a/Core/timing.c b/Core/timing.c index 001869c..f8ffd80 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -248,11 +248,32 @@ static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) } +void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode) +{ + if (gb->rtc_mode != mode) { + gb->rtc_mode = mode; + gb->rtc_cycles = 0; + gb->last_rtc_second = time(NULL); + } +} + + +void GB_set_rtc_multiplier(GB_gameboy_t *gb, double multiplier) +{ + if (multiplier == 1) { + gb->rtc_second_length = 0; + return; + } + + gb->rtc_second_length = GB_get_unmultiplied_clock_rate(gb) * 2 * multiplier; +} + static void rtc_run(GB_gameboy_t *gb, uint8_t cycles) { if (gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc) return; gb->rtc_cycles += cycles; time_t current_time = 0; + uint32_t rtc_second_length = unlikely(gb->rtc_second_length)? gb->rtc_second_length : GB_get_unmultiplied_clock_rate(gb) * 2; switch (gb->rtc_mode) { case GB_RTC_MODE_SYNC_TO_HOST: @@ -266,8 +287,8 @@ static void rtc_run(GB_gameboy_t *gb, uint8_t cycles) gb->rtc_cycles -= cycles; return; } - if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) * 2) return; - gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) * 2; + if (gb->rtc_cycles < rtc_second_length) return; + gb->rtc_cycles -= rtc_second_length; current_time = gb->last_rtc_second + 1; break; } diff --git a/Core/timing.h b/Core/timing.h index d5d7356..96b2082 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -2,13 +2,23 @@ #define timing_h #include "defs.h" +typedef enum { + GB_RTC_MODE_SYNC_TO_HOST, + GB_RTC_MODE_ACCURATE, +} GB_rtc_mode_t; + +/* RTC emulation mode */ +void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode); + +/* Speed multiplier for the RTC, mostly for TAS syncing */ +void GB_set_rtc_multiplier(GB_gameboy_t *gb, double multiplier); + #ifdef GB_INTERNAL internal void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); internal void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); 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); - enum { GB_TIMA_RUNNING = 0, GB_TIMA_RELOADING = 1, From f78fac12c2456e67363efa78fad1ee87998f9f3a Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 14 Dec 2021 20:27:38 +0200 Subject: [PATCH 330/365] Fixed several issues involving LY change timing, as well as an LYC issue in models prior to CGB-D --- Core/display.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Core/display.c b/Core/display.c index f1f474e..1d4ee52 100644 --- a/Core/display.c +++ b/Core/display.c @@ -890,7 +890,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STATE(gb, display, 15); GB_STATE(gb, display, 16); GB_STATE(gb, display, 17); - // GB_STATE(gb, display, 19); + GB_STATE(gb, display, 19); GB_STATE(gb, display, 20); GB_STATE(gb, display, 21); GB_STATE(gb, display, 22); @@ -1348,12 +1348,13 @@ abort_fetching_object: gb->wx166_glitch = false; /* Lines 144 - 152 */ for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) { - gb->io_registers[GB_IO_LY] = gb->current_line; gb->ly_for_comparison = -1; if (unlikely(gb->lcd_line_callback)) { gb->lcd_line_callback(gb, gb->current_line); } + GB_STAT_update(gb); GB_SLEEP(gb, display, 26, 2); + gb->io_registers[GB_IO_LY] = gb->current_line; if (gb->current_line == LINES && !gb->stat_interrupt_line && (gb->io_registers[GB_IO_STAT] & 0x20)) { gb->io_registers[GB_IO_IF] |= 2; } @@ -1406,12 +1407,13 @@ abort_fetching_object: /* TODO: Verified on SGB2 and CGB-E. Actual interrupt timings not tested. */ /* Lines 153 */ - gb->io_registers[GB_IO_LY] = 153; gb->ly_for_comparison = -1; GB_STAT_update(gb); - GB_SLEEP(gb, display, 14, (gb->model > GB_MODEL_CGB_C)? 4: 6); + GB_SLEEP(gb, display, 19, 2); + gb->io_registers[GB_IO_LY] = 153; + GB_SLEEP(gb, display, 14, (gb->model > GB_MODEL_CGB_C)? 2: 4); - if (!GB_is_cgb(gb)) { + if (gb->model <= GB_MODEL_CGB_C && !gb->cgb_double_speed) { gb->io_registers[GB_IO_LY] = 0; } gb->ly_for_comparison = 153; From c1ae129ed4c6a337e9ca038844a1926270270f25 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 Dec 2021 21:12:26 +0200 Subject: [PATCH 331/365] Allow hiding background/object "layers" (#422) --- Cocoa/Document.m | 17 +++++++++++++++++ Cocoa/MainMenu.xib | 13 +++++++++++++ Core/display.c | 33 ++++++++++++++++++++++++++++++--- Core/display.h | 7 +++++++ Core/gb.h | 2 ++ 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index fafdefc..bb1fc6f 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1043,6 +1043,13 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) else if ([anItem action] == @selector(toggleCheats:)) { [(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)]; } + else if ([anItem action] == @selector(toggleDisplayBackground:)) { + [(NSMenuItem*)anItem setState:!GB_is_background_rendering_disabled(&gb)]; + } + else if ([anItem action] == @selector(toggleDisplayObjects:)) { + [(NSMenuItem*)anItem setState:!GB_is_object_rendering_disabled(&gb)]; + } + return [super validateUserInterfaceItem:anItem]; } @@ -2307,4 +2314,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self.osdView displayText:@"Screenshot copied"]; } +- (IBAction)toggleDisplayBackground:(id)sender +{ + GB_set_background_rendering_disabled(&gb, !GB_is_background_rendering_disabled(&gb)); +} + +- (IBAction)toggleDisplayObjects:(id)sender +{ + GB_set_object_rendering_disabled(&gb, !GB_is_object_rendering_disabled(&gb)); +} + @end diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index ca15de4..aafa8aa 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -456,6 +456,19 @@ + + + + + + + + + + + + + diff --git a/Core/display.c b/Core/display.c index 1d4ee52..6d54ec6 100644 --- a/Core/display.c +++ b/Core/display.c @@ -522,8 +522,8 @@ static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use, bool static void render_pixel_if_possible(GB_gameboy_t *gb) { - GB_fifo_item_t *fifo_item = NULL; - GB_fifo_item_t *oam_fifo_item = NULL; + const GB_fifo_item_t *fifo_item = NULL; + const GB_fifo_item_t *oam_fifo_item = NULL; bool draw_oam = false; bool bg_enabled = true, bg_priority = false; @@ -533,7 +533,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) 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)) { + 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; } @@ -559,6 +559,12 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) bg_enabled = false; } } + + if (unlikely(gb->background_disabled)) { + bg_enabled = false; + static const GB_fifo_item_t empty_item = {0,}; + fifo_item = &empty_item; + } uint8_t icd_pixel = 0; uint32_t *dest = NULL; @@ -1624,3 +1630,24 @@ bool GB_is_odd_frame(GB_gameboy_t *gb) { return gb->is_odd_frame; } + +void GB_set_object_rendering_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->objects_disabled = disabled; +} + +void GB_set_background_rendering_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->background_disabled = disabled; +} + +bool GB_is_object_rendering_disabled(GB_gameboy_t *gb) +{ + return gb->objects_disabled; +} + +bool GB_is_background_rendering_disabled(GB_gameboy_t *gb) +{ + return gb->background_disabled; +} + diff --git a/Core/display.h b/Core/display.h index 1d35d96..fdc855f 100644 --- a/Core/display.h +++ b/Core/display.h @@ -61,4 +61,11 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); void GB_set_light_temperature(GB_gameboy_t *gb, double temperature); bool GB_is_odd_frame(GB_gameboy_t *gb); + +void GB_set_object_rendering_disabled(GB_gameboy_t *gb, bool disabled); +void GB_set_background_rendering_disabled(GB_gameboy_t *gb, bool disabled); +bool GB_is_object_rendering_disabled(GB_gameboy_t *gb); +bool GB_is_background_rendering_disabled(GB_gameboy_t *gb); + + #endif /* display_h */ diff --git a/Core/gb.h b/Core/gb.h index 3c77aed..e6b5f41 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -630,6 +630,8 @@ struct GB_gameboy_internal_s { GB_sgb_border_t borrowed_border; bool tried_loading_sgb_border; bool has_sgb_border; + bool objects_disabled; + bool background_disabled; /* Timing */ uint64_t last_sync; From c3d9141b7c2b850591a71ed958d3adde779d0a62 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 17 Dec 2021 21:16:23 +0200 Subject: [PATCH 332/365] Replace the term sprite with object for consistency --- BESS.md | 2 +- Cocoa/Document.h | 2 +- Cocoa/Document.m | 10 ++++----- Cocoa/Document.xib | 6 ++--- Core/debugger.c | 6 ++--- Core/display.c | 56 +++++++++++++++++++++++----------------------- Core/display.h | 2 +- Core/gb.c | 14 ++++++------ Core/gb.h | 14 ++++++------ Core/memory.c | 4 ++-- Core/save_state.c | 8 +++---- Core/sm83_cpu.c | 2 +- 12 files changed, 63 insertions(+), 63 deletions(-) diff --git a/BESS.md b/BESS.md index e040d90..7c9296e 100644 --- a/BESS.md +++ b/BESS.md @@ -89,7 +89,7 @@ The values of memory-mapped registers should be written 'as-is' to memory as if * Unused register bits have Don't-Care values which should be ignored * If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode * Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode. -* Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect +* Object priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect * If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. * BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. * Implementations should not start a serial transfer when writing the value of SB diff --git a/Cocoa/Document.h b/Cocoa/Document.h index d6f89de..2cfaa87 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -30,7 +30,7 @@ @property (nonatomic, strong) IBOutlet NSPanel *vramWindow; @property (nonatomic, strong) IBOutlet NSTextField *vramStatusLabel; @property (nonatomic, strong) IBOutlet NSTableView *paletteTableView; -@property (nonatomic, strong) IBOutlet NSTableView *spritesTableView; +@property (nonatomic, strong) IBOutlet NSTableView *objectsTableView; @property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow; @property (nonatomic, strong) IBOutlet NSImageView *feedImageView; @property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index bb1fc6f..d539d7d 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1466,7 +1466,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) dispatch_async(dispatch_get_main_queue(), ^{ if (!oamUpdating) { oamUpdating = true; - [self.spritesTableView reloadData]; + [self.objectsTableView reloadData]; oamUpdating = false; } }); @@ -1829,7 +1829,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) if (tableView == self.paletteTableView) { return 16; /* 8 BG palettes, 8 OBJ palettes*/ } - else if (tableView == self.spritesTableView) { + else if (tableView == self.objectsTableView) { return oamCount; } return 0; @@ -1848,7 +1848,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) uint16_t index = columnIndex - 1 + (row & 7) * 4; return @((palette_data[(index << 1) + 1] << 8) | palette_data[(index << 1)]); } - else if (tableView == self.spritesTableView) { + else if (tableView == self.objectsTableView) { switch (columnIndex) { case 0: return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image @@ -1882,7 +1882,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) oamInfo[row].flags & 0x20? 'X' : '-', oamInfo[row].flags & 0x10? 1 : 0]; case 7: - return oamInfo[row].obscured_by_line_limit? @"Dropped: Too many sprites in line": @""; + return oamInfo[row].obscured_by_line_limit? @"Dropped: Too many objects in line": @""; } } @@ -1891,7 +1891,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row { - return tableView == self.spritesTableView; + return tableView == self.objectsTableView; } - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row diff --git a/Cocoa/Document.xib b/Cocoa/Document.xib index c76c148..4c98007 100644 --- a/Cocoa/Document.xib +++ b/Cocoa/Document.xib @@ -25,10 +25,10 @@ + - @@ -502,7 +502,7 @@ - + @@ -770,7 +770,7 @@ - + diff --git a/Core/debugger.c b/Core/debugger.c index b8d88d7..2088ebd 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1639,9 +1639,9 @@ static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const d } } - GB_log(gb, "Sprites palettes: \n"); + GB_log(gb, "Object palettes: \n"); for (unsigned i = 0; i < 32; i++) { - GB_log(gb, "%04x ", ((uint16_t *)&gb->sprite_palettes_data)[i]); + GB_log(gb, "%04x ", ((uint16_t *)&gb->object_palettes_data)[i]); if (i % 4 == 3) { GB_log(gb, "\n"); } @@ -1659,7 +1659,7 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg } GB_log(gb, "LCDC:\n"); GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled"); - GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Sprite priority flags" : "Background and Window"), + GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Object priority flags" : "Background and Window"), (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled"); GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled"); GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8"); diff --git a/Core/display.c b/Core/display.c index 6d54ec6..c859c33 100644 --- a/Core/display.c +++ b/Core/display.c @@ -85,7 +85,7 @@ static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t uppe /* - Each line is 456 cycles. Without scrolling, sprites or a window: + Each line is 456 cycles. Without scrolling, objects or a window: Mode 2 - 80 cycles / OAM Transfer Mode 3 - 172 cycles / Rendering Mode 0 - 204 cycles / HBlank @@ -354,10 +354,10 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border) void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index) { if (!gb->rgb_encode_callback || !GB_is_cgb(gb)) return; - uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data; + uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->object_palettes_data; uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); - (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color, false); + (background_palette? gb->background_palettes_rgb : gb->object_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color, false); } void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode) @@ -623,7 +623,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) *dest = gb->rgb_encode_callback(gb, 0, 0, 0); } else { - *dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; + *dest = gb->object_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; } } @@ -1089,7 +1089,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->lcd_x = 0; - gb->extra_penalty_for_sprite_at_0 = MIN((gb->io_registers[GB_IO_SCX] & 7), 5); + gb->extra_penalty_for_object_at_0 = MIN((gb->io_registers[GB_IO_SCX] & 7), 5); /* The actual rendering cycle */ gb->fetcher_state = 0; @@ -1160,7 +1160,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } /* Handle objects */ - /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB. + /* 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 && @@ -1184,11 +1184,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } /* Todo: Measure if penalty occurs before or after waiting for the fetcher. */ - if (gb->extra_penalty_for_sprite_at_0 != 0) { + 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_sprite_at_0; - GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0); - gb->extra_penalty_for_sprite_at_0 = 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; } @@ -1466,7 +1466,7 @@ void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); break; case GB_PALETTE_OAM: - palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7)); + palette = gb->object_palettes_rgb + (4 * (palette_index & 7)); break; } @@ -1516,7 +1516,7 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); break; case GB_PALETTE_OAM: - palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7)); + palette = gb->object_palettes_rgb + (4 * (palette_index & 7)); break; case GB_PALETTE_AUTO: break; @@ -1568,31 +1568,31 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette } } -uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height) +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *object_height) { uint8_t count = 0; - *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; + *object_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; uint8_t oam_to_dest_index[40] = {0,}; for (signed y = 0; y < LINES; y++) { - object_t *sprite = (object_t *) &gb->oam; - uint8_t sprites_in_line = 0; - for (uint8_t i = 0; i < 40; i++, sprite++) { - signed sprite_y = sprite->y - 16; + object_t *object = (object_t *) &gb->oam; + uint8_t objects_in_line = 0; + for (uint8_t i = 0; i < 40; i++, object++) { + signed object_y = object->y - 16; bool obscured = false; - // Is sprite not in this line? - if (sprite_y > y || sprite_y + *sprite_height <= y) continue; - if (++sprites_in_line == 11) obscured = true; + // Is object not in this line? + if (object_y > y || object_y + *object_height <= y) continue; + if (++objects_in_line == 11) obscured = true; GB_oam_info_t *info = NULL; if (!oam_to_dest_index[i]) { info = dest + count; oam_to_dest_index[i] = ++count; - info->x = sprite->x; - info->y = sprite->y; - info->tile = *sprite_height == 16? sprite->tile & 0xFE : sprite->tile; - info->flags = sprite->flags; + info->x = object->x; + info->y = object->y; + info->tile = *object_height == 16? object->tile & 0xFE : object->tile; + info->flags = object->flags; info->obscured_by_line_limit = false; - info->oam_addr = 0xFE00 + i * sizeof(*sprite); + info->oam_addr = 0xFE00 + i * sizeof(*object); } else { info = dest + oam_to_dest_index[i] - 1; @@ -1609,7 +1609,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h vram_address += 0x2000; } - for (unsigned y = 0; y < *sprite_height; y++) { + for (unsigned y = 0; y < *object_height; y++) { unrolled for (unsigned x = 0; x < 8; x++) { uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); @@ -1617,7 +1617,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h if (!gb->cgb_mode) { color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3; } - dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?*sprite_height - 1 -y:y) * 8] = gb->sprite_palettes_rgb[palette * 4 + color]; + dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?*object_height - 1 -y:y) * 8] = gb->object_palettes_rgb[palette * 4 + color]; } vram_address += 2; } diff --git a/Core/display.h b/Core/display.h index fdc855f..04b85b3 100644 --- a/Core/display.h +++ b/Core/display.h @@ -56,7 +56,7 @@ typedef enum { void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); -uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *object_height); uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); void GB_set_light_temperature(GB_gameboy_t *gb, double temperature); diff --git a/Core/gb.c b/Core/gb.c index 1021dd9..ab35b78 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1225,13 +1225,13 @@ static void update_dmg_palette(GB_gameboy_t *gb) { const GB_palette_t *palette = gb->dmg_palette ?: &GB_PALETTE_GREY; if (gb->rgb_encode_callback && !GB_is_cgb(gb)) { - gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = + gb->object_palettes_rgb[4] = gb->object_palettes_rgb[0] = gb->background_palettes_rgb[0] = gb->rgb_encode_callback(gb, palette->colors[3].r, palette->colors[3].g, palette->colors[3].b); - gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = + gb->object_palettes_rgb[5] = gb->object_palettes_rgb[1] = gb->background_palettes_rgb[1] = gb->rgb_encode_callback(gb, palette->colors[2].r, palette->colors[2].g, palette->colors[2].b); - gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = + gb->object_palettes_rgb[6] = gb->object_palettes_rgb[2] = gb->background_palettes_rgb[2] = gb->rgb_encode_callback(gb, palette->colors[1].r, palette->colors[1].g, palette->colors[1].b); - gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = + gb->object_palettes_rgb[7] = gb->object_palettes_rgb[3] = gb->background_palettes_rgb[3] = gb->rgb_encode_callback(gb, palette->colors[0].r, palette->colors[0].g, palette->colors[0].b); // LCD off color @@ -1537,7 +1537,7 @@ static void reset_ram(GB_gameboy_t *gb) if (GB_is_cgb(gb)) { for (unsigned i = 0; i < 64; i++) { gb->background_palettes_data[i] = GB_random(); /* Doesn't really matter as the boot ROM overrides it anyway*/ - gb->sprite_palettes_data[i] = GB_random(); + gb->object_palettes_data[i] = GB_random(); } for (unsigned i = 0; i < 32; i++) { GB_palette_changed(gb, true, i * 2); @@ -1733,9 +1733,9 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * *bank = 0; return &gb->background_palettes_data; case GB_DIRECT_ACCESS_OBP: - *size = sizeof(gb->sprite_palettes_data); + *size = sizeof(gb->object_palettes_data); *bank = 0; - return &gb->sprite_palettes_data; + return &gb->object_palettes_data; case GB_DIRECT_ACCESS_IE: *size = sizeof(gb->interrupt_enable); *bank = 0; diff --git a/Core/gb.h b/Core/gb.h index e6b5f41..e5bb128 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -214,8 +214,8 @@ enum { /* CGB Paletts */ 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 - Sprite Palette Index - GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite 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) /* Missing */ @@ -287,8 +287,8 @@ struct GB_watchpoint_s; typedef struct { uint8_t pixel; // Color, 0-3 uint8_t palette; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) - uint8_t priority; // Sprite priority – 0 in DMG, OAM index in CGB - bool bg_priority; // For sprite FIFO – the BG priority bit. For the BG FIFO – the CGB attributes priority bit + uint8_t priority; // Object priority – 0 in DMG, OAM index in CGB + 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 @@ -536,7 +536,7 @@ struct GB_gameboy_internal_s { bool cgb_vram_bank; uint8_t oam[0xA0]; uint8_t background_palettes_data[0x40]; - uint8_t sprite_palettes_data[0x40]; + uint8_t object_palettes_data[0x40]; uint8_t position_in_line; bool stat_interrupt_line; uint8_t effective_scx; @@ -575,7 +575,7 @@ struct GB_gameboy_internal_s { uint8_t n_visible_objs; uint8_t oam_search_index; uint8_t accessed_oam_row; - uint8_t extra_penalty_for_sprite_at_0; + uint8_t extra_penalty_for_object_at_0; uint8_t mode_for_interrupt; bool lyc_interrupt_line; bool cgb_palettes_blocked; @@ -620,7 +620,7 @@ struct GB_gameboy_internal_s { /* I/O */ uint32_t *screen; uint32_t background_palettes_rgb[0x20]; - uint32_t sprite_palettes_rgb[0x20]; + uint32_t object_palettes_rgb[0x20]; const GB_palette_t *dmg_palette; GB_color_correction_mode_t color_correction_mode; double light_temperature; diff --git a/Core/memory.c b/Core/memory.c index de2124c..7ca770a 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -625,7 +625,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) uint8_t index_reg = (addr & 0xFF) - 1; return ((addr & 0xFF) == GB_IO_BGPD? gb->background_palettes_data : - gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F]; + gb->object_palettes_data)[gb->io_registers[index_reg] & 0x3F]; } case GB_IO_KEY1: @@ -1453,7 +1453,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } ((addr & 0xFF) == GB_IO_BGPD? gb->background_palettes_data : - gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F] = value; + gb->object_palettes_data)[gb->io_registers[index_reg] & 0x3F] = value; GB_palette_changed(gb, (addr & 0xFF) == GB_IO_BGPD, gb->io_registers[index_reg] & 0x3F); if (gb->io_registers[index_reg] & 0x80) { gb->io_registers[index_reg]++; diff --git a/Core/save_state.c b/Core/save_state.c index eefdd08..4dc304e 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -67,7 +67,7 @@ typedef struct __attribute__((packed)) { BESS_buffer_t oam; BESS_buffer_t hram; BESS_buffer_t background_palettes; - BESS_buffer_t sprite_palettes; + BESS_buffer_t object_palettes; } BESS_CORE_t; typedef struct __attribute__((packed)) { @@ -604,8 +604,8 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe if (GB_is_cgb(gb)) { bess_core.background_palettes.size = LE32(sizeof(gb->background_palettes_data)); bess_core.background_palettes.offset = LE32(video_offset + offsetof(GB_gameboy_t, background_palettes_data) - GB_SECTION_OFFSET(video)); - bess_core.sprite_palettes.size = LE32(sizeof(gb->sprite_palettes_data)); - bess_core.sprite_palettes.offset = LE32(video_offset + offsetof(GB_gameboy_t, sprite_palettes_data) - GB_SECTION_OFFSET(video)); + bess_core.object_palettes.size = LE32(sizeof(gb->object_palettes_data)); + bess_core.object_palettes.offset = LE32(video_offset + offsetof(GB_gameboy_t, object_palettes_data) - GB_SECTION_OFFSET(video)); } if (file->write(file, &bess_core, sizeof(bess_core)) != sizeof(bess_core)) { @@ -1088,7 +1088,7 @@ done: read_bess_buffer(&core.oam, file, gb->oam, sizeof(gb->oam)); read_bess_buffer(&core.hram, file, gb->hram, sizeof(gb->hram)); read_bess_buffer(&core.background_palettes, file, gb->background_palettes_data, sizeof(gb->background_palettes_data)); - read_bess_buffer(&core.sprite_palettes, file, gb->sprite_palettes_data, sizeof(gb->sprite_palettes_data)); + read_bess_buffer(&core.object_palettes, file, gb->object_palettes_data, sizeof(gb->object_palettes_data)); if (gb->sgb) { memset(gb->sgb, 0, sizeof(*gb->sgb)); GB_sgb_load_default_data(gb); diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 85db7ae..035779b 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -198,7 +198,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Similar to the palette registers, these interact directly with the LCD, so they appear to be affected by it. Both my DMG (B, blob) and Game Boy Light behave this way though. Additionally, LCDC.1 is very nasty because on the it is read both by the FIFO when popping pixels, - and the sprite-fetching state machine, and both behave differently when it comes to access conflicts. + and the object-fetching state machine, and both behave differently when it comes to access conflicts. Hacks ahead. */ From c63ddbe771d28462af1c96c4e75015ed4b54a45d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Dec 2021 01:25:06 +0200 Subject: [PATCH 333/365] Lag frame detection API (#422) --- Core/gb.c | 3 +++ Core/gb.h | 3 ++- Core/joypad.c | 10 ++++++++++ Core/joypad.h | 3 +++ Core/memory.c | 1 + Core/sm83_cpu.c | 6 ++++++ 6 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index ab35b78..2afeb81 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1151,6 +1151,9 @@ uint8_t GB_run(GB_gameboy_t *gb) GB_debugger_handle_async_commands(gb); GB_rewind_push(gb); } + if (!(gb->io_registers[GB_IO_IF] & 0x10) && (gb->io_registers[GB_IO_JOYP] & 0x30) != 0x30) { + gb->joyp_accessed = true; + } return gb->cycles_since_run; } diff --git a/Core/gb.h b/Core/gb.h index e5bb128..0a3b5da 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -632,7 +632,8 @@ struct GB_gameboy_internal_s { bool has_sgb_border; bool objects_disabled; bool background_disabled; - + bool joyp_accessed; + /* Timing */ uint64_t last_sync; uint64_t cycles_since_last_sync; // In 8MHz units diff --git a/Core/joypad.c b/Core/joypad.c index 06fb53f..f16aa9e 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -121,3 +121,13 @@ void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned p GB_update_joyp(gb); } + +bool GB_get_joyp_accessed(GB_gameboy_t *gb) +{ + return gb->joyp_accessed; +} + +void GB_clear_joyp_accessed(GB_gameboy_t *gb) +{ + gb->joyp_accessed = false; +} diff --git a/Core/joypad.h b/Core/joypad.h index beb532e..a360af1 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -34,6 +34,9 @@ void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned play void GB_set_key_mask(GB_gameboy_t *gb, GB_key_mask_t mask); void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned player); void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); +bool GB_get_joyp_accessed(GB_gameboy_t *gb); +void GB_clear_joyp_accessed(GB_gameboy_t *gb); + #ifdef GB_INTERNAL internal void GB_update_joyp(GB_gameboy_t *gb); diff --git a/Core/memory.c b/Core/memory.c index 7ca770a..43a9dde 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -568,6 +568,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return ((gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[1] : 0xFF); case GB_IO_JOYP: + gb->joyp_accessed = true; GB_timing_sync(gb); case GB_IO_TMA: case GB_IO_LCDC: diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 035779b..130bfb3 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -363,6 +363,9 @@ static void leave_stop_mode(GB_gameboy_t *gb) static void stop(GB_gameboy_t *gb, uint8_t opcode) { flush_pending_cycles(gb); + if ((gb->io_registers[GB_IO_JOYP] & 0x30) != 0x30) { + gb->joyp_accessed = true; + } bool exit_by_joyp = ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF); bool speed_switch = (gb->io_registers[GB_IO_KEY1] & 0x1) && !exit_by_joyp; bool immediate_exit = speed_switch || exit_by_joyp; @@ -1575,6 +1578,9 @@ void GB_cpu_run(GB_gameboy_t *gb) if (gb->stopped) { GB_timing_sync(gb); GB_advance_cycles(gb, 4); + if ((gb->io_registers[GB_IO_JOYP] & 0x30) != 0x30) { + gb->joyp_accessed = true; + } if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { leave_stop_mode(gb); GB_advance_cycles(gb, 8); From 5127cb00221e41c8f1abda77f8ecc040856a4b91 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Dec 2021 14:51:14 +0200 Subject: [PATCH 334/365] Direct access to registers (#422) --- Core/gb.c | 5 +++++ Core/gb.h | 67 ++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index 2afeb81..b2d271a 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1750,6 +1750,11 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * } } +GB_registers_t *GB_get_registers(GB_gameboy_t *gb) +{ + return (GB_registers_t *)&gb->registers; +} + void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) { gb->clock_multiplier = multiplier; diff --git a/Core/gb.h b/Core/gb.h index 0a3b5da..bcd98c4 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -321,6 +321,31 @@ typedef struct { char copyright[33]; } GB_gbs_info_t; +/* Duplicated so it can remain anonymous in GB_gameboy_t */ +typedef union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp; + }; + struct { +#ifdef GB_BIG_ENDIAN + uint8_t a, f, + b, c, + d, e, + h, l; +#else + uint8_t f, a, + c, b, + e, d, + l, h; +#endif + }; +} GB_registers_t; + /* When state saving, each section is dumped independently of other sections. This allows adding data to the end of the section without worrying about future compatibility. Some other changes might be "safe" as well. @@ -345,30 +370,29 @@ struct GB_gameboy_internal_s { GB_SECTION(core_state, /* Registers */ uint16_t pc; - union { - uint16_t registers[GB_REGISTERS_16_BIT]; - struct { - uint16_t af, - bc, - de, - hl, - sp; - }; - struct { + union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp; + }; + struct { #ifdef GB_BIG_ENDIAN - uint8_t a, f, - b, c, - d, e, - h, l; + uint8_t a, f, + b, c, + d, e, + h, l; #else - uint8_t f, a, - c, b, - e, d, - l, h; + uint8_t f, a, + c, b, + e, d, + l, h; #endif - }; - - }; + }; + }; uint8_t ime; uint8_t interrupt_enable; uint8_t cgb_ram_bank; @@ -801,6 +825,7 @@ typedef enum { /* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank is returned at *bank, even if only a portion of the memory is banked. */ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank); +GB_registers_t *GB_get_registers(GB_gameboy_t *gb); void *GB_get_user_data(GB_gameboy_t *gb); void GB_set_user_data(GB_gameboy_t *gb, void *data); From eaccd792ed75738603c97fc6367e9bf8bfb41573 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 18 Dec 2021 14:56:33 +0200 Subject: [PATCH 335/365] Fixes to safe reads, closes #422 --- Core/memory.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/memory.c b/Core/memory.c index 43a9dde..ea9152f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -434,7 +434,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (addr < 0xFF00) { if (gb->oam_write_blocked && !GB_is_cgb(gb)) { - GB_trigger_oam_bug_read(gb, addr); + if (!gb->disable_oam_corruption) { + GB_trigger_oam_bug_read(gb, addr); + } return 0xff; } @@ -705,6 +707,9 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr) { + if (unlikely(addr == 0xFF00 + GB_IO_JOYP)) { + return gb->io_registers[GB_IO_JOYP]; + } gb->disable_oam_corruption = true; uint8_t data = read_map[addr >> 12](gb, addr); gb->disable_oam_corruption = false; From cdc3321c3613911d3e91a8f7c5713461466bb47e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 19 Dec 2021 00:28:24 +0200 Subject: [PATCH 336/365] Add an API to allow illegal inputs --- Core/gb.h | 1 + Core/joypad.c | 17 ++++++++++++----- Core/joypad.h | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index bcd98c4..9af5a53 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -657,6 +657,7 @@ struct GB_gameboy_internal_s { bool objects_disabled; bool background_disabled; bool joyp_accessed; + bool illegal_inputs_allowed; /* Timing */ uint64_t last_sync; diff --git a/Core/joypad.c b/Core/joypad.c index f16aa9e..3527742 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -30,11 +30,13 @@ void GB_update_joyp(GB_gameboy_t *gb) gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i; } /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ - if (!(gb->io_registers[GB_IO_JOYP] & 1)) { - gb->io_registers[GB_IO_JOYP] |= 2; - } - if (!(gb->io_registers[GB_IO_JOYP] & 4)) { - gb->io_registers[GB_IO_JOYP] |= 8; + if (likely(!gb->illegal_inputs_allowed)) { + if (!(gb->io_registers[GB_IO_JOYP] & 1)) { + gb->io_registers[GB_IO_JOYP] |= 2; + } + if (!(gb->io_registers[GB_IO_JOYP] & 4)) { + gb->io_registers[GB_IO_JOYP] |= 8; + } } break; @@ -131,3 +133,8 @@ void GB_clear_joyp_accessed(GB_gameboy_t *gb) { gb->joyp_accessed = false; } + +void GB_set_allow_illegal_inputs(GB_gameboy_t *gb, bool allow) +{ + gb->illegal_inputs_allowed = allow; +} diff --git a/Core/joypad.h b/Core/joypad.h index a360af1..21574e0 100644 --- a/Core/joypad.h +++ b/Core/joypad.h @@ -36,6 +36,7 @@ void GB_set_key_mask_for_player(GB_gameboy_t *gb, GB_key_mask_t mask, unsigned p void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value); bool GB_get_joyp_accessed(GB_gameboy_t *gb); void GB_clear_joyp_accessed(GB_gameboy_t *gb); +void GB_set_allow_illegal_inputs(GB_gameboy_t *gb, bool allow); #ifdef GB_INTERNAL From e9629407a5d64a9fa3b65edec8f0f05727248d6f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 19 Dec 2021 00:54:29 +0200 Subject: [PATCH 337/365] Fix potential alignment issues --- Core/gb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/gb.h b/Core/gb.h index 9af5a53..574e0b9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -782,7 +782,7 @@ struct GB_gameboy_internal_s { #ifndef GB_INTERNAL struct GB_gameboy_s { - char __internal[sizeof(struct GB_gameboy_internal_s)]; + _Alignas(struct GB_gameboy_internal_s) uint8_t __internal[sizeof(struct GB_gameboy_internal_s)]; }; #endif From f866441481dbec7e32fdd9dc3a3785a2f9027cba Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 19 Dec 2021 19:27:01 +0200 Subject: [PATCH 338/365] Improved emulation of channel 3 wave RAM read glitch --- Core/apu.c | 22 +++++++++++++++++----- Core/apu.h | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 9a5f852..be444d3 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -598,10 +598,19 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.apu_cycles = 0; if (!cycles) return; - if (unlikely(gb->apu.wave_channel.delayed_bugged_read)) { - gb->apu.wave_channel.delayed_bugged_read = false; - gb->apu.wave_channel.current_sample_byte = - gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)]; + if (unlikely(gb->apu.wave_channel.bugged_read_countdown)) { + uint8_t cycles_left = cycles; + while (cycles_left) { + cycles_left--; + if (--gb->apu.wave_channel.bugged_read_countdown == 0) { + gb->apu.wave_channel.current_sample_byte = + gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)]; + if (gb->apu.is_active[GB_WAVE]) { + update_wave_sample(gb, 0); + } + break; + } + } } bool start_ch4 = false; @@ -707,12 +716,15 @@ void GB_apu_run(GB_gameboy_t *gb) gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)]; } else { - gb->apu.wave_channel.delayed_bugged_read = true; + gb->apu.wave_channel.bugged_read_countdown = 1; } } if (cycles_left) { gb->apu.wave_channel.sample_countdown -= cycles_left; } + if (gb->apu.wave_channel.sample_countdown == 0) { + gb->apu.wave_channel.bugged_read_countdown = 2; + } } // The noise channel can step even if inactive on the DMG diff --git a/Core/apu.h b/Core/apu.h index f7b125c..6242860 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -102,7 +102,7 @@ typedef struct uint8_t current_sample_byte; // Current sample byte. bool wave_form_just_read; bool pulsed; - bool delayed_bugged_read; + uint8_t bugged_read_countdown; } wave_channel; struct { From e9906e44cdd50231b67a31f582bee6f96a3b9683 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 19 Dec 2021 21:46:22 +0200 Subject: [PATCH 339/365] Sure, why not --- Core/gb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/gb.c b/Core/gb.c index b2d271a..2b95d55 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1329,7 +1329,7 @@ bool GB_is_inited(GB_gameboy_t *gb) bool GB_is_cgb(GB_gameboy_t *gb) { - return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; + return gb->model >= GB_MODEL_CGB_0; } bool GB_is_cgb_in_cgb_mode(GB_gameboy_t *gb) From f3277ab8d32e966198923be6cdbcac7301b4115c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 20 Dec 2021 18:59:51 +0200 Subject: [PATCH 340/365] Sorry C++ users --- Core/gb.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/gb.h b/Core/gb.h index 574e0b9..4592ec9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -3,6 +3,7 @@ #define typeof __typeof__ #include #include +#include #include #include "defs.h" @@ -782,7 +783,7 @@ struct GB_gameboy_internal_s { #ifndef GB_INTERNAL struct GB_gameboy_s { - _Alignas(struct GB_gameboy_internal_s) uint8_t __internal[sizeof(struct GB_gameboy_internal_s)]; + alignas(struct GB_gameboy_internal_s) uint8_t __internal[sizeof(struct GB_gameboy_internal_s)]; }; #endif From 69de3f0fae260338caafb2130b8f7896cdcf6d25 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 01:47:59 +0200 Subject: [PATCH 341/365] Implement a PPU fast path, up to 34% performance boost --- Cocoa/Document.m | 5 +- Core/debugger.c | 2 + Core/display.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++- Core/display.h | 3 +- Core/gb.c | 4 +- Core/gb.h | 4 +- Core/memory.c | 40 +++++ Core/timing.c | 2 +- Core/timing.h | 16 +- 9 files changed, 448 insertions(+), 14 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index d539d7d..e6f2bd9 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1413,6 +1413,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (IBAction) reloadVRAMData: (id) sender { if (self.vramWindow.isVisible) { + uint8_t *io_regs = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IO, NULL, NULL); switch ([self.vramTabView.tabViewItems indexOfObject:self.vramTabView.selectedTabViewItem]) { case 0: /* Tileset */ @@ -1451,8 +1452,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem, (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem); - self.tilemapImageView.scrollRect = NSMakeRect(GB_read_memory(&gb, 0xFF00 | GB_IO_SCX), - GB_read_memory(&gb, 0xFF00 | GB_IO_SCY), + self.tilemapImageView.scrollRect = NSMakeRect(io_regs[GB_IO_SCX], + io_regs[GB_IO_SCY], 160, 144); self.tilemapImageView.image = [Document imageFromData:data width:256 height:256 scale:1.0]; self.tilemapImageView.layer.magnificationFilter = kCAFilterNearest; diff --git a/Core/debugger.c b/Core/debugger.c index 2088ebd..0abba51 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -2196,6 +2196,8 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) if (!input[0]) { return true; } + + GB_display_sync(gb); char *command_string = input; char *arguments = strchr(input, ' '); diff --git a/Core/display.c b/Core/display.c index c859c33..e25a6c2 100644 --- a/Core/display.c +++ b/Core/display.c @@ -861,11 +861,366 @@ static uint16_t get_object_line_address(GB_gameboy_t *gb, const object_t *object return line_address; } +static inline uint8_t flip(uint8_t x) +{ + x = (x & 0xF0) >> 4 | (x & 0x0F) << 4; + x = (x & 0xCC) >> 2 | (x & 0x33) << 2; + x = (x & 0xAA) >> 1 | (x & 0x55) << 1; + return x; +} + +static inline void get_tile_data(const GB_gameboy_t *gb, uint8_t tile_x, uint8_t y, uint16_t map, uint8_t *attributes, uint8_t *data0, uint8_t *data1) +{ + uint8_t current_tile = gb->vram[map + (tile_x & 0x1F) + y / 8 * 32]; + *attributes = GB_is_cgb(gb)? gb->vram[0x2000 + map + (tile_x & 0x1F) + y / 8 * 32] : 0; + + uint16_t tile_address = 0; + + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = current_tile * 0x10; + } + else { + tile_address = (int8_t)current_tile * 0x10 + 0x1000; + } + if (*attributes & 8) { + tile_address += 0x2000; + } + uint8_t y_flip = 0; + if (*attributes & 0x40) { + y_flip = 0x7; + } + + *data0 = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; + *data1 = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; + + if (*attributes & 0x20) { + *data0 = flip(*data0); + *data1 = flip(*data1); + } + +} + +static void render_line(GB_gameboy_t *gb) +{ + if (gb->disable_rendering) return; + if (!gb->screen) return; + if (gb->current_line > 144) return; // Corrupt save state + + struct { + unsigned pixel:2; // Color, 0-3 + unsigned priority:6; // Object priority – 0 in DMG, OAM index in CGB + unsigned palette:3; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) + bool bg_priority:1; // BG priority bit + } object_buffer[160 + 16]; // allocate extra to avoid per pixel checks + memset(object_buffer, 0, sizeof(object_buffer)); + + if (gb->n_visible_objs && !gb->objects_disabled && (gb->io_registers[GB_IO_LCDC] & 2)) { + object_t *objects = (object_t *) &gb->oam; + + while (gb->n_visible_objs) { + unsigned object_index = gb->visible_objs[gb->n_visible_objs - 1]; + unsigned priority = gb->object_priority == GB_OBJECT_PRIORITY_X? 0 : object_index; + const object_t *object = &objects[object_index]; + gb->n_visible_objs--; + + uint16_t line_address = get_object_line_address(gb, object); + uint8_t data0 = gb->vram[line_address]; + uint8_t data1 = gb->vram[line_address + 1]; + if (object->flags & 0x20) { + data0 = flip(data0); + data1 = flip(data1); + } + + typeof(object_buffer[0]) *p = object_buffer + object->x; + if (object->x >= 168) { + continue; + } + unrolled for (unsigned x = 0; x < 8; x++) { + unsigned pixel = (data0 >> 7) | ((data1 >> 7) << 1); + data0 <<= 1; + data1 <<= 1; + if (pixel && (!p->pixel || priority < p->priority)) { + p->pixel = pixel; + p->priority = priority; + + if (gb->cgb_mode) { + p->palette = object->flags & 0x7; + } + else { + p->palette = (object->flags & 0x10) >> 4; + } + p->bg_priority = object->flags & 0x80; + } + p++; + } + } + } + + + uint32_t *restrict p = gb->screen; + typeof(object_buffer[0]) *object_buffer_pointer = object_buffer + 8; + if (gb->border_mode == GB_BORDER_ALWAYS) { + p += (BORDERED_WIDTH - (WIDTH)) / 2 + BORDERED_WIDTH * (BORDERED_HEIGHT - LINES) / 2; + p += BORDERED_WIDTH * gb->current_line; + } + else { + p += WIDTH * gb->current_line; + } + + if (unlikely(gb->background_disabled) || (!gb->cgb_mode && !(gb->io_registers[GB_IO_LCDC] & 1))) { + uint32_t bg = gb->background_palettes_rgb[gb->cgb_mode? 0 : (gb->io_registers[GB_IO_BGP] & 3)]; + for (unsigned i = 160; i--;) { + if (unlikely(object_buffer_pointer->pixel)) { + uint8_t pixel = object_buffer_pointer->pixel; + if (!gb->cgb_mode) { + pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3); + } + *(p++) = gb->object_palettes_rgb[pixel + (object_buffer_pointer->palette & 7) * 4]; + } + else { + *(p++) = bg; + } + object_buffer_pointer++; + } + return; + } + + unsigned pixels = 0; + uint8_t tile_x = gb->io_registers[GB_IO_SCX] / 8; + unsigned fractional_scroll = gb->io_registers[GB_IO_SCX] & 7; + uint16_t map = 0x1800; + if (gb->io_registers[GB_IO_LCDC] & 0x08) { + map = 0x1C00; + } + uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY]; + uint8_t attributes; + uint8_t data0, data1; + get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1); + +#define DO_PIXEL() \ +uint8_t pixel = (data0 >> 7) | ((data1 >> 7) << 1);\ +data0 <<= 1;\ +data1 <<= 1;\ +\ +if (unlikely(object_buffer_pointer->pixel) && (pixel == 0 || !(object_buffer_pointer->bg_priority || (attributes & 0x80)) || !(gb->io_registers[GB_IO_LCDC] & 1))) {\ + pixel = object_buffer_pointer->pixel;\ + if (!gb->cgb_mode) {\ + pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3);\ + }\ + *(p++) = gb->object_palettes_rgb[pixel + (object_buffer_pointer->palette & 7) * 4];\ +}\ +else {\ + if (!gb->cgb_mode) {\ + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);\ + }\ + *(p++) = gb->background_palettes_rgb[pixel + (attributes & 7) * 4];\ +}\ +pixels++;\ +object_buffer_pointer++\ + + // First 1-8 pixels + data0 <<= fractional_scroll; + data1 <<= fractional_scroll; + bool check_window = gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20); + for (unsigned i = fractional_scroll; i < 8; i++) { + if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) { +activate_window: + check_window = false; + map = gb->io_registers[GB_IO_LCDC] & 0x40? 0x1C00 : 0x1800; + tile_x = -1; + y = ++gb->window_y; + break; + } + DO_PIXEL(); + } + tile_x++; + + while (pixels < 160 - 8) { + get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1); + for (unsigned i = 0; i < 8; i++) { + if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) { + goto activate_window; + } + DO_PIXEL(); + } + tile_x++; + } + + gb->fetcher_state = (160 - pixels) & 7; + get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1); + while (pixels < 160) { + if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) { + goto activate_window; + } + DO_PIXEL(); + } + tile_x++; + + get_tile_data(gb, tile_x, y, map, &attributes, gb->current_tile_data, gb->current_tile_data + 1); +#undef DO_PIXEL +} + +static void render_line_sgb(GB_gameboy_t *gb) +{ + if (gb->current_line > 144) return; // Corrupt save state + + struct { + unsigned pixel:2; // Color, 0-3 + unsigned palette:1; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) + bool bg_priority:1; // BG priority bit + } object_buffer[160 + 16]; // allocate extra to avoid per pixel checks + memset(object_buffer, 0, sizeof(object_buffer)); + + if (gb->n_visible_objs && !gb->objects_disabled && (gb->io_registers[GB_IO_LCDC] & 2)) { + object_t *objects = (object_t *) &gb->oam; + + while (gb->n_visible_objs) { + 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); + uint8_t data0 = gb->vram[line_address]; + uint8_t data1 = gb->vram[line_address + 1]; + if (object->flags & 0x20) { + data0 = flip(data0); + data1 = flip(data1); + } + + typeof(object_buffer[0]) *p = object_buffer + object->x; + if (object->x >= 168) { + continue; + } + unrolled for (unsigned x = 0; x < 8; x++) { + unsigned pixel = (data0 >> 7) | ((data1 >> 7) << 1); + data0 <<= 1; + data1 <<= 1; + if (!p->pixel) { + p->pixel = pixel; + p->palette = (object->flags & 0x10) >> 4; + p->bg_priority = object->flags & 0x80; + } + p++; + } + } + } + + + uint8_t *restrict p = gb->sgb->screen_buffer; + typeof(object_buffer[0]) *object_buffer_pointer = object_buffer + 8; + p += WIDTH * gb->current_line; + + if (unlikely(gb->background_disabled) || (!gb->cgb_mode && !(gb->io_registers[GB_IO_LCDC] & 1))) { + for (unsigned i = 160; i--;) { + if (unlikely(object_buffer_pointer->pixel)) { + uint8_t pixel = object_buffer_pointer->pixel; + pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3); + *(p++) = pixel; + } + else { + *(p++) = gb->io_registers[GB_IO_BGP] & 3; + } + object_buffer_pointer++; + } + return; + } + + unsigned pixels = 0; + uint8_t tile_x = gb->io_registers[GB_IO_SCX] / 8; + unsigned fractional_scroll = gb->io_registers[GB_IO_SCX] & 7; + uint16_t map = 0x1800; + if (gb->io_registers[GB_IO_LCDC] & 0x08) { + map = 0x1C00; + } + uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY]; + uint8_t attributes; + uint8_t data0, data1; + get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1); + +#define DO_PIXEL() \ +uint8_t pixel = (data0 >> 7) | ((data1 >> 7) << 1);\ +data0 <<= 1;\ +data1 <<= 1;\ +\ +if (unlikely(object_buffer_pointer->pixel) && (pixel == 0 || !object_buffer_pointer->bg_priority || !(gb->io_registers[GB_IO_LCDC] & 1))) {\ + pixel = object_buffer_pointer->pixel;\ + pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3);\ + *(p++) = pixel;\ +}\ +else {\ + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);\ + *(p++) = pixel;\ +}\ +pixels++;\ +object_buffer_pointer++\ + + // First 1-8 pixels + data0 <<= fractional_scroll; + data1 <<= fractional_scroll; + bool check_window = gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20); + for (unsigned i = fractional_scroll; i < 8; i++) { + if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) { + activate_window: + check_window = false; + map = gb->io_registers[GB_IO_LCDC] & 0x40? 0x1C00 : 0x1800; + tile_x = -1; + y = ++gb->window_y; + break; + } + DO_PIXEL(); + } + tile_x++; + + while (pixels < 160 - 8) { + get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1); + for (unsigned i = 0; i < 8; i++) { + if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) { + goto activate_window; + } + DO_PIXEL(); + } + tile_x++; + } + + get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1); + while (pixels < 160) { + if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) { + goto activate_window; + } + DO_PIXEL(); + } +} + +static inline uint16_t mode3_batching_length(GB_gameboy_t *gb) +{ + 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->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; + } + + // No objects or window, timing is trivial + if (gb->n_visible_objs == 0 && !(gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20))) return 167 + (gb->io_registers[GB_IO_SCX] & 7); + + if (gb->hdma_on_hblank) return 0; + + // 300 is a bit more than the maximum Mode 3 length + + // No HBlank interrupt + if (!(gb->io_registers[GB_IO_STAT] & 0x8)) return 300; + // No STAT interrupt requested + if (!(gb->interrupt_enable & 2)) return 300; + + + return 0; +} + /* 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, uint8_t cycles) +void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) { gb->cycles_since_vblank_callback += cycles / 2; @@ -878,12 +1233,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) } object_t *objects = (object_t *) &gb->oam; - GB_STATE_MACHINE(gb, display, cycles, 2) { + GB_BATCHABLE_STATE_MACHINE(gb, display, cycles, 2, !force) { GB_STATE(gb, display, 1); GB_STATE(gb, display, 2); - // GB_STATE(gb, display, 3); - // GB_STATE(gb, display, 4); - // GB_STATE(gb, display, 5); + GB_STATE(gb, display, 3); + GB_STATE(gb, display, 4); + GB_STATE(gb, display, 5); GB_STATE(gb, display, 6); GB_STATE(gb, display, 7); GB_STATE(gb, display, 8); @@ -1031,6 +1386,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); gb->n_visible_objs = 0; + if (!gb->dma_steps_left && !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++) { if (GB_is_cgb(gb)) { add_object_from_index(gb, gb->oam_search_index); @@ -1046,7 +1404,6 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->vram_write_blocked = false; gb->cgb_palettes_blocked = false; gb->oam_write_blocked = GB_is_cgb(gb); - GB_STAT_update(gb); } } gb->cycles_for_line = MODE2_LENGTH + 4; @@ -1093,6 +1450,22 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) /* The actual rendering cycle */ gb->fetcher_state = 0; + if ((gb->mode3_batching_length = mode3_batching_length(gb))) { + GB_BATCHPOINT(gb, display, 3, gb->mode3_batching_length); + if (GB_BATCHED_CYCLES(gb, display) >= gb->mode3_batching_length) { + // Successfully batched! + gb->lcd_x = gb->position_in_line = 160; + gb->cycles_for_line += gb->mode3_batching_length; + if (gb->sgb) { + render_line_sgb(gb); + } + else { + render_line(gb); + } + GB_SLEEP(gb, display, 4, gb->mode3_batching_length); + goto skip_slow_mode_3; + } + } while (true) { /* Handle window */ /* TODO: It appears that WX checks if the window begins *next* pixel, not *this* pixel. For this reason, @@ -1255,6 +1628,7 @@ abort_fetching_object: gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); } +skip_slow_mode_3: /* TODO: Verify */ if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { diff --git a/Core/display.h b/Core/display.h index 04b85b3..d50dc18 100644 --- a/Core/display.h +++ b/Core/display.h @@ -6,11 +6,12 @@ #include #ifdef GB_INTERNAL -internal void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); +internal void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force); 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); +#define GB_display_sync(gb) GB_display_run(gb, 0, true) enum { GB_OBJECT_PRIORITY_X, diff --git a/Core/gb.c b/Core/gb.c index 2b95d55..0279eef 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1139,7 +1139,7 @@ uint8_t GB_run(GB_gameboy_t *gb) we just halt the CPU (with hacky code) until the correct time. This ensures the Nintendo logo doesn't flash on screen, and the game does "run in background" while the animation is playing. */ - GB_display_run(gb, 228); + GB_display_run(gb, 228, true); gb->cycles_since_last_sync += 228; return 228; } @@ -1327,7 +1327,7 @@ bool GB_is_inited(GB_gameboy_t *gb) return gb->magic == state_magic(); } -bool GB_is_cgb(GB_gameboy_t *gb) +bool GB_is_cgb(const GB_gameboy_t *gb) { return gb->model >= GB_MODEL_CGB_0; } diff --git a/Core/gb.h b/Core/gb.h index 4592ec9..54fa0f3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -540,6 +540,8 @@ struct GB_gameboy_internal_s { /* For timing of the vblank callback */ uint32_t cycles_since_vblank_callback; bool lcd_disabled_outside_of_vblank; + int32_t allowed_pending_cycles; + uint16_t mode3_batching_length; ); /* APU */ @@ -796,7 +798,7 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) void GB_init(GB_gameboy_t *gb, GB_model_t model); bool GB_is_inited(GB_gameboy_t *gb); -bool GB_is_cgb(GB_gameboy_t *gb); +bool GB_is_cgb(const GB_gameboy_t *gb); bool GB_is_cgb_in_cgb_mode(GB_gameboy_t *gb); bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd diff --git a/Core/memory.c b/Core/memory.c index ea9152f..cd76d57 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -98,6 +98,7 @@ void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address) if (GB_is_cgb(gb)) return; if (address >= 0xFE00 && address < 0xFF00) { + GB_display_sync(gb); 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], @@ -283,6 +284,7 @@ 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 (gb->vram_read_blocked) { return 0xFF; } @@ -421,6 +423,37 @@ static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr) return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000]; } +static inline void sync_ppu_if_needed(GB_gameboy_t *gb, uint8_t register_accessed) +{ + switch (register_accessed) { + case GB_IO_IF: + case GB_IO_LCDC: + case GB_IO_STAT: + case GB_IO_SCY: + case GB_IO_SCX: + case GB_IO_LY: + case GB_IO_LYC: + case GB_IO_DMA: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_WY: + case GB_IO_WX: + case GB_IO_HDMA1: + case GB_IO_HDMA2: + case GB_IO_HDMA3: + case GB_IO_HDMA4: + case GB_IO_HDMA5: + case GB_IO_BGPI: + case GB_IO_BGPD: + case GB_IO_OBPI: + case GB_IO_OBPD: + case GB_IO_OPRI: + GB_display_sync(gb); + break; + } +} + static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) { @@ -433,6 +466,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF00) { + GB_display_sync(gb); if (gb->oam_write_blocked && !GB_is_cgb(gb)) { if (!gb->disable_oam_corruption) { GB_trigger_oam_bug_read(gb, addr); @@ -548,6 +582,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF80) { + sync_ppu_if_needed(gb, addr); switch (addr & 0xFF) { case GB_IO_IF: return gb->io_registers[GB_IO_IF] | 0xE0; @@ -846,6 +881,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { + GB_display_sync(gb); if (gb->vram_write_blocked) { //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); return; @@ -1155,6 +1191,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (addr < 0xFF00) { + GB_display_sync(gb); if (gb->oam_write_blocked) { GB_trigger_oam_bug(gb, addr); return; @@ -1233,6 +1270,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Todo: Clean this code up: use a function table and move relevant code to display.c and timing.c (APU read and writes are already at apu.c) */ if (addr < 0xFF80) { + sync_ppu_if_needed(gb, addr); + /* Hardware registers */ switch (addr & 0xFF) { case GB_IO_WY: @@ -1563,6 +1602,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (addr == 0xFFFF) { + GB_display_sync(gb); /* Interrupt mask */ gb->interrupt_enable = value; return; diff --git a/Core/timing.c b/Core/timing.c index f8ffd80..3cf62bc 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -435,7 +435,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) GB_hdma_run(gb); } GB_apu_run(gb); - GB_display_run(gb, cycles); + GB_display_run(gb, cycles, false); ir_run(gb, cycles); rtc_run(gb, cycles); } diff --git a/Core/timing.h b/Core/timing.h index 96b2082..ee817d1 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -28,13 +28,23 @@ enum { #define GB_SLEEP(gb, unit, state, cycles) do {\ (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \ - if ((gb)->unit##_cycles <= 0) {\ + if (unlikely((gb)->unit##_cycles <= 0)) {\ (gb)->unit##_state = state;\ return;\ unit##state:; \ }\ } while (0) +#define GB_BATCHPOINT(gb, unit, state, cycles) do {\ +unit##state:; \ +if (likely(__state_machine_allow_batching && (gb)->unit##_cycles < (cycles * 2))) {\ + (gb)->unit##_state = state;\ + return;\ +}\ +} while (0) + +#define GB_BATCHED_CYCLES(gb, unit) ((gb)->unit##_cycles / __state_machine_divisor) + #define GB_STATE_MACHINE(gb, unit, cycles, divisor) \ static const int __state_machine_divisor = divisor;\ (gb)->unit##_cycles += cycles; \ @@ -44,6 +54,10 @@ if ((gb)->unit##_cycles <= 0) {\ switch ((gb)->unit##_state) #endif +#define GB_BATCHABLE_STATE_MACHINE(gb, unit, cycles, divisor, allow_batching) \ +const bool __state_machine_allow_batching = (allow_batching); \ +GB_STATE_MACHINE(gb, unit, cycles, divisor) + #define GB_STATE(gb, unit, state) case state: goto unit##state #define GB_UNIT(unit) int32_t unit##_cycles, unit##_state From c5f6be1e644edbf11eebfda08b04c783c4419977 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 02:38:54 +0200 Subject: [PATCH 342/365] Several likely/unlikely optimization, saving on a memset --- Core/display.c | 28 ++++++++++++++++++++-------- Core/memory.c | 4 ++-- Core/timing.c | 20 ++++++++++---------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Core/display.c b/Core/display.c index e25a6c2..38dca24 100644 --- a/Core/display.c +++ b/Core/display.c @@ -912,12 +912,15 @@ static void render_line(GB_gameboy_t *gb) unsigned priority:6; // Object priority – 0 in DMG, OAM index in CGB unsigned palette:3; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) bool bg_priority:1; // BG priority bit - } object_buffer[160 + 16]; // allocate extra to avoid per pixel checks - memset(object_buffer, 0, sizeof(object_buffer)); - + } _object_buffer[160 + 16]; // allocate extra to avoid per pixel checks + static const uint8_t empty_object_buffer[sizeof(_object_buffer)]; + const typeof(_object_buffer[0]) *object_buffer; + if (gb->n_visible_objs && !gb->objects_disabled && (gb->io_registers[GB_IO_LCDC] & 2)) { + object_buffer = &_object_buffer[0]; object_t *objects = (object_t *) &gb->oam; - + memset(_object_buffer, 0, sizeof(_object_buffer)); + while (gb->n_visible_objs) { unsigned object_index = gb->visible_objs[gb->n_visible_objs - 1]; unsigned priority = gb->object_priority == GB_OBJECT_PRIORITY_X? 0 : object_index; @@ -932,7 +935,7 @@ static void render_line(GB_gameboy_t *gb) data1 = flip(data1); } - typeof(object_buffer[0]) *p = object_buffer + object->x; + typeof(_object_buffer[0]) *p = _object_buffer + object->x; if (object->x >= 168) { continue; } @@ -956,6 +959,9 @@ static void render_line(GB_gameboy_t *gb) } } } + else { + object_buffer = (const void *)empty_object_buffer; + } uint32_t *restrict p = gb->screen; @@ -1069,11 +1075,14 @@ static void render_line_sgb(GB_gameboy_t *gb) unsigned pixel:2; // Color, 0-3 unsigned palette:1; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG) bool bg_priority:1; // BG priority bit - } object_buffer[160 + 16]; // allocate extra to avoid per pixel checks - memset(object_buffer, 0, sizeof(object_buffer)); + } _object_buffer[160 + 16]; // allocate extra to avoid per pixel checks + static const uint8_t empty_object_buffer[sizeof(_object_buffer)]; + const typeof(_object_buffer[0]) *object_buffer; if (gb->n_visible_objs && !gb->objects_disabled && (gb->io_registers[GB_IO_LCDC] & 2)) { + object_buffer = &_object_buffer[0]; object_t *objects = (object_t *) &gb->oam; + memset(_object_buffer, 0, sizeof(_object_buffer)); while (gb->n_visible_objs) { const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; @@ -1087,7 +1096,7 @@ static void render_line_sgb(GB_gameboy_t *gb) data1 = flip(data1); } - typeof(object_buffer[0]) *p = object_buffer + object->x; + typeof(_object_buffer[0]) *p = _object_buffer + object->x; if (object->x >= 168) { continue; } @@ -1104,6 +1113,9 @@ static void render_line_sgb(GB_gameboy_t *gb) } } } + else { + object_buffer = (const void *)empty_object_buffer; + } uint8_t *restrict p = gb->sgb->screen_buffer; diff --git a/Core/memory.c b/Core/memory.c index cd76d57..2493238 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1648,7 +1648,7 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) void GB_dma_run(GB_gameboy_t *gb) { - while (gb->dma_cycles >= 4 && gb->dma_steps_left) { + while (unlikely(gb->dma_cycles >= 4 && gb->dma_steps_left)) { /* Todo: measure this value */ gb->dma_cycles -= 4; gb->dma_steps_left--; @@ -1671,7 +1671,7 @@ void GB_dma_run(GB_gameboy_t *gb) void GB_hdma_run(GB_gameboy_t *gb) { - if (!gb->hdma_on) return; + if (likely(!gb->hdma_on)) return; while (gb->hdma_cycles >= 0x4) { gb->hdma_cycles -= 0x4; diff --git a/Core/timing.c b/Core/timing.c index 3cf62bc..d405918 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -113,7 +113,7 @@ void GB_timing_sync(GB_gameboy_t *gb) static void ir_run(GB_gameboy_t *gb, uint32_t cycles) { - if (gb->model == GB_MODEL_AGB) return; + 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)) { gb->ir_sensor += cycles; if (gb->ir_sensor > IR_MAX) { @@ -203,10 +203,10 @@ static void timers_run(GB_gameboy_t *gb, uint8_t cycles) static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) { - if (gb->printer.command_state || gb->printer.bits_received) { + if (unlikely(gb->printer_callback && (gb->printer.command_state || gb->printer.bits_received))) { gb->printer.idle_time += cycles; } - if (gb->serial_length == 0) { + if (likely(gb->serial_length == 0)) { gb->serial_cycles += cycles; return; } @@ -270,7 +270,7 @@ void GB_set_rtc_multiplier(GB_gameboy_t *gb, double multiplier) static void rtc_run(GB_gameboy_t *gb, uint8_t cycles) { - if (gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc) return; + if (likely(gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc)) return; gb->rtc_cycles += cycles; time_t current_time = 0; uint32_t rtc_second_length = unlikely(gb->rtc_second_length)? gb->rtc_second_length : GB_get_unmultiplied_clock_rate(gb) * 2; @@ -368,7 +368,7 @@ static void rtc_run(GB_gameboy_t *gb, uint8_t cycles) void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { - if (gb->speed_switch_countdown) { + if (unlikely(gb->speed_switch_countdown)) { if (gb->speed_switch_countdown == cycles) { gb->cgb_double_speed ^= true; gb->speed_switch_countdown = 0; @@ -389,11 +389,11 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->dma_cycles += cycles; timers_run(gb, cycles); - if (!gb->stopped) { + if (unlikely(!gb->stopped)) { advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode } - if (gb->speed_switch_halt_countdown) { + if (unlikely(gb->speed_switch_halt_countdown)) { gb->speed_switch_halt_countdown -= cycles; if (gb->speed_switch_halt_countdown <= 0) { gb->speed_switch_halt_countdown = 0; @@ -412,14 +412,14 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->speed_switch_freeze = 0; } - if (!gb->cgb_double_speed) { + if (unlikely(!gb->cgb_double_speed)) { cycles <<= 1; } gb->absolute_debugger_ticks += cycles; // Not affected by speed boost - if (gb->io_registers[GB_IO_LCDC] & 0x80) { + if (likely(gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->double_speed_alignment += cycles; } gb->hdma_cycles += cycles; @@ -430,7 +430,7 @@ 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 (!gb->stopped) { // TODO: Verify what happens in STOP mode + if (unlikely(!gb->stopped)) { // TODO: Verify what happens in STOP mode GB_dma_run(gb); GB_hdma_run(gb); } From c53d99dbc4de00a683a63d529c480172606d9488 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 15:20:46 +0200 Subject: [PATCH 343/365] Abolished slow double use --- Core/apu.c | 17 +++-------------- Core/apu.h | 5 +---- Core/gb.c | 3 --- Core/timing.c | 2 +- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index be444d3..248f250 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -765,8 +765,9 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - if (gb->apu_output.sample_cycles >= gb->apu_output.cycles_per_sample) { - gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample; + uint32_t clock_rate = GB_get_clock_rate(gb) * 2; + if (gb->apu_output.sample_cycles >= clock_rate) { + gb->apu_output.sample_cycles -= clock_rate; render(gb); } } @@ -1462,8 +1463,6 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate) if (sample_rate) { gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); } - gb->apu_output.rate_set_in_clocks = false; - GB_apu_update_cycles_per_sample(gb); } void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample) @@ -1473,10 +1472,8 @@ void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample) GB_set_sample_rate(gb, 0); return; } - gb->apu_output.cycles_per_sample = cycles_per_sample; gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2; gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample); - gb->apu_output.rate_set_in_clocks = true; } void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) @@ -1489,14 +1486,6 @@ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode) gb->apu_output.highpass_mode = mode; } -void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb) -{ - if (gb->apu_output.rate_set_in_clocks) return; - if (gb->apu_output.sample_rate) { - gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */ - } -} - void GB_set_interference_volume(GB_gameboy_t *gb, double volume) { gb->apu_output.interference_volume = volume; diff --git a/Core/apu.h b/Core/apu.h index 6242860..8893f1b 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -143,8 +143,7 @@ typedef enum { typedef struct { unsigned sample_rate; - double sample_cycles; // In 8 MHz units - double cycles_per_sample; + unsigned sample_cycles; // Counts by sample_rate until it reaches the clock frequency // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage! unsigned cycles_since_render; @@ -159,7 +158,6 @@ typedef struct { GB_sample_callback_t sample_callback; - bool rate_set_in_clocks; double interference_volume; double interference_highpass; } GB_apu_output_t; @@ -178,7 +176,6 @@ internal void GB_apu_div_event(GB_gameboy_t *gb); internal void GB_apu_div_secondary_event(GB_gameboy_t *gb); internal void GB_apu_init(GB_gameboy_t *gb); internal void GB_apu_run(GB_gameboy_t *gb); -internal void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); #endif #endif /* apu_h */ diff --git a/Core/gb.c b/Core/gb.c index 0279eef..a98b08f 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1653,8 +1653,6 @@ void GB_reset(GB_gameboy_t *gb) GB_set_internal_div_counter(gb, 8); - GB_apu_update_cycles_per_sample(gb); - if (gb->nontrivial_jump_state) { free(gb->nontrivial_jump_state); gb->nontrivial_jump_state = NULL; @@ -1758,7 +1756,6 @@ GB_registers_t *GB_get_registers(GB_gameboy_t *gb) void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) { gb->clock_multiplier = multiplier; - GB_apu_update_cycles_per_sample(gb); } uint32_t GB_get_clock_rate(GB_gameboy_t *gb) diff --git a/Core/timing.c b/Core/timing.c index d405918..9b2f736 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -423,7 +423,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->double_speed_alignment += cycles; } gb->hdma_cycles += cycles; - gb->apu_output.sample_cycles += cycles; + gb->apu_output.sample_cycles += cycles * gb->apu_output.sample_rate; gb->cycles_since_last_sync += cycles; gb->cycles_since_run += cycles; From 66f7babe86983abbf6006f1ba5175956413f9123 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 15:50:24 +0200 Subject: [PATCH 344/365] Cache the clock rate --- Core/gb.c | 29 ++++++++++++++++++++--------- Core/gb.h | 3 +++ Core/save_state.c | 1 + 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Core/gb.c b/Core/gb.c index a98b08f..bb08cf5 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1591,6 +1591,7 @@ void GB_reset(GB_gameboy_t *gb) { uint32_t mbc_ram_size = gb->mbc_ram_size; GB_model_t model = gb->model; + GB_update_clock_rate(gb); uint8_t rtc_section[GB_SECTION_SIZE(rtc)]; memcpy(rtc_section, GB_GET_SECTION(gb, rtc), sizeof(rtc_section)); memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved)); @@ -1756,24 +1757,34 @@ GB_registers_t *GB_get_registers(GB_gameboy_t *gb) void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) { gb->clock_multiplier = multiplier; + GB_update_clock_rate(gb); } uint32_t GB_get_clock_rate(GB_gameboy_t *gb) { - return GB_get_unmultiplied_clock_rate(gb) * gb->clock_multiplier; + return gb->clock_rate; } - uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb) { - if (gb->model & GB_MODEL_PAL_BIT) { - return SGB_PAL_FREQUENCY; - } - if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { - return SGB_NTSC_FREQUENCY; - } - return CPU_FREQUENCY; + return gb->unmultiplied_clock_rate; } + +void GB_update_clock_rate(GB_gameboy_t *gb) +{ + if (gb->model & GB_MODEL_PAL_BIT) { + gb->unmultiplied_clock_rate = SGB_PAL_FREQUENCY; + } + else if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) { + gb->unmultiplied_clock_rate = SGB_NTSC_FREQUENCY; + } + else { + gb->unmultiplied_clock_rate = CPU_FREQUENCY; + } + + gb->clock_rate = gb->unmultiplied_clock_rate * gb->clock_multiplier; +} + void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode) { if (gb->border_mode > GB_BORDER_ALWAYS) return; diff --git a/Core/gb.h b/Core/gb.h index 54fa0f3..de1b4aa 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -667,6 +667,8 @@ struct GB_gameboy_internal_s { uint64_t cycles_since_last_sync; // In 8MHz units GB_rtc_mode_t rtc_mode; uint32_t rtc_second_length; + uint32_t clock_rate; + uint32_t unmultiplied_clock_rate; /* Audio */ GB_apu_output_t apu_output; @@ -918,6 +920,7 @@ uint32_t GB_get_rom_crc32(GB_gameboy_t *gb); #ifdef GB_INTERNAL internal void GB_borrow_sgb_border(GB_gameboy_t *gb); +internal void GB_update_clock_rate(GB_gameboy_t *gb); #endif #endif /* GB_h */ diff --git a/Core/save_state.c b/Core/save_state.c index 4dc304e..d5297b5 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -383,6 +383,7 @@ static void sanitize_state(GB_gameboy_t *gb) } gb->sgb->current_player &= gb->sgb->player_count - 1; } + GB_update_clock_rate(gb); } static bool dump_section(virtual_file_t *file, const void *src, uint32_t size) From 6e7ba7589c2be5ce0411185bb7935422ca93945e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 18:38:08 +0200 Subject: [PATCH 345/365] Fixed blurred unfiltered screenshots --- Cocoa/Document.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index e6f2bd9..90ce260 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -2210,6 +2210,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) NSImage *ret = nil; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]) { ret = [_view renderToImage]; + [ret lockFocus]; + NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, + ret.size.width, ret.size.height)]; + [ret unlockFocus]; + ret = [[NSImage alloc] initWithSize:ret.size]; + [ret addRepresentation:bitmapRep]; } if (!ret) { ret = [Document imageFromData:[NSData dataWithBytesNoCopy:_view.currentBuffer @@ -2219,12 +2225,6 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) height:GB_get_screen_height(&gb) scale:1.0]; } - [ret lockFocus]; - NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, - ret.size.width, ret.size.height)]; - [ret unlockFocus]; - ret = [[NSImage alloc] initWithSize:ret.size]; - [ret addRepresentation:bitmapRep]; return ret; } From d178ece9090d6aea39f759bb3a96f1c8866b039d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 19:57:18 +0200 Subject: [PATCH 346/365] Disabled an incorrectly emulated portion of the TILE_SET glitch --- Core/display.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/display.c b/Core/display.c index 38dca24..ff3d39f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1642,14 +1642,15 @@ abort_fetching_object: } skip_slow_mode_3: - /* TODO: Verify */ + /* TODO: This seems incorrect (glitches Tesserae), verify further */ + /* if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { gb->data_for_sel_glitch = gb->current_tile_data[0]; } else { gb->data_for_sel_glitch = gb->current_tile_data[1]; } - + */ while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) { /* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */ uint32_t *dest = NULL; From 6ffe924637a978001ee66c65aa43d45a3f1ec38c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 21:43:54 +0200 Subject: [PATCH 347/365] This is probably better but still wrong --- Core/display.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/display.c b/Core/display.c index ff3d39f..e2385f7 100644 --- a/Core/display.c +++ b/Core/display.c @@ -1643,6 +1643,7 @@ abort_fetching_object: skip_slow_mode_3: /* TODO: This seems incorrect (glitches Tesserae), verify further */ + gb->data_for_sel_glitch = 0; /* if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { gb->data_for_sel_glitch = gb->current_tile_data[0]; From 59dfb1a85a6ed4b3dd44f94de7b8b7f2d2cea4d1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 21:57:40 +0200 Subject: [PATCH 348/365] It's not verified because it's wrong --- Core/memory.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 2493238..88682f7 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -285,10 +285,10 @@ 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 (gb->vram_read_blocked) { + if (unlikely(gb->vram_read_blocked)) { return 0xFF; } - if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + if (unlikely(gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed)) { if (addr & 0x1000) { addr = gb->last_tile_index_address; } @@ -882,23 +882,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { GB_display_sync(gb); - if (gb->vram_write_blocked) { + if (unlikely(gb->vram_write_blocked)) { //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); return; } - /* TODO: not verified */ - if (gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed) { - if (addr & 0x1000) { - addr = gb->last_tile_index_address; - } - else if (gb->last_tile_data_address & 0x1000) { - /* TODO: This is case is more complicated then the rest and differ between revisions - It's probably affected by how VRAM is layed out, might be easier after a decap is done */ - } - else { - addr = gb->last_tile_data_address; - } - } gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)] = value; } From db50462710d59ce215b4dbe152221a275f296d21 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 26 Dec 2021 23:24:08 +0200 Subject: [PATCH 349/365] More accurate fix --- Core/display.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index e2385f7..2e03039 100644 --- a/Core/display.c +++ b/Core/display.c @@ -930,6 +930,9 @@ static void render_line(GB_gameboy_t *gb) uint16_t line_address = get_object_line_address(gb, object); uint8_t data0 = gb->vram[line_address]; uint8_t data1 = gb->vram[line_address + 1]; + if (gb->n_visible_objs == 0) { + gb->data_for_sel_glitch = data1; + } if (object->flags & 0x20) { data0 = flip(data0); data1 = flip(data1); @@ -1643,7 +1646,6 @@ abort_fetching_object: skip_slow_mode_3: /* TODO: This seems incorrect (glitches Tesserae), verify further */ - gb->data_for_sel_glitch = 0; /* if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { gb->data_for_sel_glitch = gb->current_tile_data[0]; From 769aac93c04d4bc8a395170c64d2c69e2998f6c8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Dec 2021 00:43:10 +0200 Subject: [PATCH 350/365] Lazy APU, extra ~17% speed up --- Core/apu.c | 36 ++++++++++++++++++++++++++---------- Core/apu.h | 4 ++-- Core/debugger.c | 1 + Core/memory.c | 2 ++ Core/timing.c | 4 +--- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 248f250..c4da0f5 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -469,6 +469,7 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb) void GB_apu_div_event(GB_gameboy_t *gb) { + GB_apu_run(gb, true); if (!gb->apu.global_enable) return; if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIP) { gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIPPED; @@ -556,6 +557,8 @@ void GB_apu_div_event(GB_gameboy_t *gb) void GB_apu_div_secondary_event(GB_gameboy_t *gb) { + GB_apu_run(gb, true); + if (!gb->apu.global_enable) return; unrolled for (unsigned i = GB_SQUARE_2 + 1; i--;) { uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22]; if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0) { @@ -591,15 +594,26 @@ static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset) } } -void GB_apu_run(GB_gameboy_t *gb) +void GB_apu_run(GB_gameboy_t *gb, bool force) { + uint32_t clock_rate = GB_get_clock_rate(gb) * 2; + if (!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)))) { + force = true; + } + if (!force) { + return; + } /* Convert 4MHZ to 2MHz. apu_cycles is always divisable by 4. */ - uint8_t cycles = gb->apu.apu_cycles >> 2; + uint16_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; if (unlikely(gb->apu.wave_channel.bugged_read_countdown)) { - uint8_t cycles_left = cycles; + uint16_t cycles_left = cycles; while (cycles_left) { cycles_left--; if (--gb->apu.wave_channel.bugged_read_countdown == 0) { @@ -627,7 +641,7 @@ void GB_apu_run(GB_gameboy_t *gb) /* Split it into two */ cycles -= gb->apu.noise_channel.dmg_delayed_start; gb->apu.apu_cycles = gb->apu.noise_channel.dmg_delayed_start * 4; - GB_apu_run(gb); + GB_apu_run(gb, true); } } /* To align the square signal to 1MHz */ @@ -669,7 +683,7 @@ void GB_apu_run(GB_gameboy_t *gb) unrolled for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) { if (gb->apu.is_active[i]) { - uint8_t cycles_left = cycles; + uint16_t cycles_left = cycles; 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; @@ -690,7 +704,7 @@ void GB_apu_run(GB_gameboy_t *gb) gb->apu.wave_channel.wave_form_just_read = false; if (gb->apu.is_active[GB_WAVE]) { - uint8_t cycles_left = cycles; + uint16_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { cycles_left -= gb->apu.wave_channel.sample_countdown + 1; gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; @@ -707,7 +721,7 @@ void GB_apu_run(GB_gameboy_t *gb) } } else if (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed && gb->model < GB_MODEL_AGB) { - uint8_t cycles_left = cycles; + uint16_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { cycles_left -= gb->apu.wave_channel.sample_countdown + 1; gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; @@ -729,13 +743,14 @@ void GB_apu_run(GB_gameboy_t *gb) // The noise channel can step even if inactive on the DMG if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) { - uint8_t cycles_left = cycles; + uint16_t cycles_left = cycles; unsigned divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 2; if (!divisor) divisor = 2; if (gb->apu.noise_channel.counter_countdown == 0) { gb->apu.noise_channel.counter_countdown = divisor; } - while (unlikely(cycles_left >= gb->apu.noise_channel.counter_countdown)) { + // This while doesn't get an unlikely because the noise channel steps frequently enough + while (cycles_left >= gb->apu.noise_channel.counter_countdown) { cycles_left -= gb->apu.noise_channel.counter_countdown; gb->apu.noise_channel.counter_countdown = divisor + gb->apu.noise_channel.delta; gb->apu.noise_channel.delta = 0; @@ -765,7 +780,6 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - uint32_t clock_rate = GB_get_clock_rate(gb) * 2; if (gb->apu_output.sample_cycles >= clock_rate) { gb->apu_output.sample_cycles -= clock_rate; render(gb); @@ -791,6 +805,7 @@ void GB_apu_init(GB_gameboy_t *gb) uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) { + GB_apu_run(gb, true); if (reg == GB_IO_NR52) { uint8_t value = 0; for (unsigned i = 0; i < GB_N_CHANNELS; i++) { @@ -953,6 +968,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { + GB_apu_run(gb, true); if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) || ( reg != GB_IO_NR11 && diff --git a/Core/apu.h b/Core/apu.h index 8893f1b..729ccce 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -57,7 +57,7 @@ typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample); typedef struct { bool global_enable; - uint8_t apu_cycles; + uint16_t apu_cycles; uint8_t samples[GB_N_CHANNELS]; bool is_active[GB_N_CHANNELS]; @@ -175,7 +175,7 @@ internal uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); internal void GB_apu_div_event(GB_gameboy_t *gb); internal void GB_apu_div_secondary_event(GB_gameboy_t *gb); internal void GB_apu_init(GB_gameboy_t *gb); -internal void GB_apu_run(GB_gameboy_t *gb); +internal void GB_apu_run(GB_gameboy_t *gb, bool force); #endif #endif /* apu_h */ diff --git a/Core/debugger.c b/Core/debugger.c index 0abba51..becc475 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -2198,6 +2198,7 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) } GB_display_sync(gb); + GB_apu_run(gb, true); char *command_string = input; char *arguments = strchr(input, ' '); diff --git a/Core/memory.c b/Core/memory.c index 88682f7..6917d81 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -598,10 +598,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_PCM12: if (!GB_is_cgb(gb)) return 0xFF; + GB_apu_run(gb, true); return ((gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) | (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[0] : 0xFF); case GB_IO_PCM34: if (!GB_is_cgb(gb)) return 0xFF; + GB_apu_run(gb, true); return ((gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) | (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0)) & (gb->model <= GB_MODEL_CGB_C? gb->apu.pcm_mask[1] : 0xFF); case GB_IO_JOYP: diff --git a/Core/timing.c b/Core/timing.c index 9b2f736..c5d328f 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -165,13 +165,11 @@ void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) /* TODO: Can switching to double speed mode trigger an event? */ uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000; if (triggers & apu_bit) { - GB_apu_run(gb); GB_apu_div_event(gb); } else { uint16_t secondary_triggers = ~gb->div_counter & value; if (secondary_triggers & apu_bit) { - GB_apu_run(gb); GB_apu_div_secondary_event(gb); } } @@ -434,7 +432,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) GB_dma_run(gb); GB_hdma_run(gb); } - GB_apu_run(gb); + GB_apu_run(gb, false); GB_display_run(gb, cycles, false); ir_run(gb, cycles); rtc_run(gb, cycles); From b72c2ea2255d989338fa8c6f17cfa954c5d5bec8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Dec 2021 13:08:46 +0200 Subject: [PATCH 351/365] DMG palette getter --- Core/gb.c | 5 +++++ Core/gb.h | 1 + 2 files changed, 6 insertions(+) diff --git a/Core/gb.c b/Core/gb.c index bb08cf5..34d9136 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1249,6 +1249,11 @@ void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette) update_dmg_palette(gb); } +const GB_palette_t *GB_get_palette(GB_gameboy_t *gb) +{ + return gb->dmg_palette; +} + void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) { diff --git a/Core/gb.h b/Core/gb.h index de1b4aa..11b4d9c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -878,6 +878,7 @@ void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callbac void GB_set_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback); void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); +const GB_palette_t *GB_get_palette(GB_gameboy_t *gb); /* These APIs are used when using internal clock */ void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback); From 59c315a5ddfb26d39a70ac0c37d4a53050d63e68 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Dec 2021 16:33:04 +0200 Subject: [PATCH 352/365] Minor free optimization --- Core/apu.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index c4da0f5..b3a8e8e 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -64,6 +64,13 @@ 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) { /* 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 @@ -74,11 +81,6 @@ 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); From 97652b7460d413ed97a48eb6e106937a3286e070 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Dec 2021 16:53:28 +0200 Subject: [PATCH 353/365] Cocoa audio bugfix --- Cocoa/Document.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 90ce260..e6b6599 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -399,7 +399,13 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) return; } - if (audioBufferPosition >= nFrames && audioBufferPosition < nFrames + 4800) { + if (audioBufferPosition < nFrames) { + // Not enough audio + memset(buffer, 0, (nFrames - audioBufferPosition) * sizeof(*buffer)); + memcpy(buffer, audioBuffer, audioBufferPosition * sizeof(*buffer)); + audioBufferPosition = 0; + } + else if (audioBufferPosition < nFrames + 4800) { memcpy(buffer, audioBuffer, nFrames * sizeof(*buffer)); memmove(audioBuffer, audioBuffer + nFrames, (audioBufferPosition - nFrames) * sizeof(*buffer)); audioBufferPosition = audioBufferPosition - nFrames; From 38eafeb0cfb12fcd0a89c47927fa6924452a641d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Dec 2021 17:03:44 +0200 Subject: [PATCH 354/365] Never deadlock ever again thanks --- Cocoa/Document.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index e6b6599..ab7ad67 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -390,7 +390,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) if (audioBufferPosition < nFrames) { audioBufferNeeded = nFrames; - [audioLock wait]; + [audioLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.125]]; } if (stopping || GB_debugger_is_stopped(&gb)) { From 52c56105284e519112be3b7590d01747d75792a6 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 29 Dec 2021 17:21:06 +0200 Subject: [PATCH 355/365] Cocoa audio driver goes brrrr --- Cocoa/Document.m | 45 +++++++++++++++++++++++-------------- Cocoa/GBPreferencesWindow.m | 1 + 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index ab7ad67..92b348d 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -69,6 +69,7 @@ enum model { size_t audioBufferSize; size_t audioBufferPosition; size_t audioBufferNeeded; + double _volume; bool borderModeChanged; @@ -212,6 +213,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) debugger_input_queue = [[NSMutableArray alloc] init]; console_output_lock = [[NSRecursiveLock alloc] init]; audioLock = [[NSCondition alloc] init]; + _volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; } return self; } @@ -346,7 +348,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) [_gbsVisualizer addSample:sample]; } [audioLock lock]; - if (self.audioClient.isPlaying) { + if (_audioClient.isPlaying) { if (audioBufferPosition == audioBufferSize) { if (audioBufferSize >= 0x4000) { audioBufferPosition = 0; @@ -362,10 +364,9 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) } audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize); } - double volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; - if (volume != 1) { - sample->left *= volume; - sample->right *= volume; + if (_volume != 1) { + sample->left *= _volume; + sample->right *= _volume; } audioBuffer[audioBufferPosition++] = *sample; } @@ -385,7 +386,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) { GB_set_pixels_output(&gb, self.view.pixels); GB_set_sample_rate(&gb, 96000); - self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { + _audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) { [audioLock lock]; if (audioBufferPosition < nFrames) { @@ -417,7 +418,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) [audioLock unlock]; } andSampleRate:96000]; if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) { - [self.audioClient start]; + [_audioClient start]; } hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:true]; [[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode]; @@ -497,8 +498,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) audioBufferPosition = audioBufferNeeded; [audioLock signal]; [audioLock unlock]; - [self.audioClient stop]; - self.audioClient = nil; + [_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]); @@ -771,6 +772,11 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) name:@"GBCGBModelChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateVolume) + name:@"GBVolumeChanged" + object:nil]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { current_model = MODEL_DMG; } @@ -929,8 +935,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self.gbsNextPrevButton imageForSegment:i].template = true; } - if (!self.audioClient.isPlaying) { - [self.audioClient start]; + if (!_audioClient.isPlaying) { + [_audioClient start]; } if (@available(macOS 10.10, *)) { @@ -1001,22 +1007,22 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (IBAction)mute:(id)sender { - if (self.audioClient.isPlaying) { - [self.audioClient stop]; + if (_audioClient.isPlaying) { + [_audioClient stop]; } else { - [self.audioClient start]; - if ([[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] == 0) { + [_audioClient start]; + if (_volume == 0) { [GBWarningPopover popoverWithContents:@"Warning: Volume is set to to zero in the preferences panel" onWindow:self.mainWindow]; } } - [[NSUserDefaults standardUserDefaults] setBool:!self.audioClient.isPlaying forKey:@"Mute"]; + [[NSUserDefaults standardUserDefaults] setBool:!_audioClient.isPlaying forKey:@"Mute"]; } - (BOOL)validateUserInterfaceItem:(id)anItem { if ([anItem action] == @selector(mute:)) { - [(NSMenuItem *)anItem setState:!self.audioClient.isPlaying]; + [(NSMenuItem *)anItem setState:!_audioClient.isPlaying]; } else if ([anItem action] == @selector(togglePause:)) { if (master) { @@ -2000,6 +2006,11 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) }]; } +- (void) updateVolume +{ + _volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; +} + - (void) updateHighpassFilter { if (GB_is_inited(&gb)) { diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index c77a5b7..cd4dea9 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -377,6 +377,7 @@ { [[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0) forKey:@"GBVolume"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBVolumeChanged" object:nil]; } - (IBAction)franeBlendingModeChanged:(id)sender From bc073e3d09df019017dc613c36e463097cfa8c71 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 30 Dec 2021 23:53:24 +0200 Subject: [PATCH 356/365] Expose PC --- Core/gb.h | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 11b4d9c..e779759 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -111,6 +111,7 @@ enum { GB_REGISTER_DE, GB_REGISTER_HL, GB_REGISTER_SP, + GB_REGISTER_PC, GB_REGISTERS_16_BIT /* Count */ }; @@ -327,22 +328,23 @@ typedef union { uint16_t registers[GB_REGISTERS_16_BIT]; struct { uint16_t af, - bc, - de, - hl, - sp; + bc, + de, + hl, + sp, + pc; }; struct { #ifdef GB_BIG_ENDIAN uint8_t a, f, - b, c, - d, e, - h, l; + b, c, + d, e, + h, l; #else uint8_t f, a, - c, b, - e, d, - l, h; + c, b, + e, d, + l, h; #endif }; } GB_registers_t; @@ -370,7 +372,6 @@ struct GB_gameboy_internal_s { GB_SECTION(core_state, /* Registers */ - uint16_t pc; union { uint16_t registers[GB_REGISTERS_16_BIT]; struct { @@ -378,7 +379,8 @@ struct GB_gameboy_internal_s { bc, de, hl, - sp; + sp, + pc; }; struct { #ifdef GB_BIG_ENDIAN From 8e675295a82c1861cc89aca62c2ba7380a33634b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 31 Dec 2021 00:40:34 +0200 Subject: [PATCH 357/365] Enable LTO in Windows builds, fix missing MGB boot in SDL --- Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 842883f..73aea5f 100644 --- a/Makefile +++ b/Makefile @@ -177,11 +177,12 @@ ifeq ($(PLATFORM),Darwin) LDFLAGS += -Wl,-exported_symbols_list,$(NULL) STRIP := strip -x endif -ifneq ($(PLATFORM),windows32) +ifeq ($(PLATFORM),windows32) +LDFLAGS += -fuse-ld=lld +endif LDFLAGS += -flto CFLAGS += -flto LDFLAGS += -Wno-lto-type-mismatch # For GCC's LTO -endif else $(error Invalid value for CONF: $(CONF). Use "debug", "release" or "native_release") @@ -199,7 +200,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb0_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/mgb_boot.bin $(BIN)/SDL/cgb0_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/cgb0_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin all: cocoa sdl tester libretro From c7298ae5a6bec7d6d46a2e8ac26a2f309373396d Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Jan 2022 16:51:05 +0200 Subject: [PATCH 358/365] Fix a silly underflow --- Core/memory.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 6917d81..d4f83a3 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1194,6 +1194,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (GB_is_cgb(gb)) { if (addr < 0xFEA0) { gb->oam[addr & 0xFF] = value; + return; } switch (gb->model) { case GB_MODEL_CGB_D: @@ -1209,6 +1210,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) 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: @@ -1217,9 +1221,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_MODEL_SGB_PAL_NO_SFC: case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: - case GB_MODEL_CGB_E: - case GB_MODEL_AGB: - break; + __builtin_unreachable(); } return; } From 81e45b00b939a54af3655c970bc8667544140738 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Jan 2022 16:51:24 +0200 Subject: [PATCH 359/365] Minor Cocoa bug fixes --- Cocoa/AppDelegate.m | 1 + Cocoa/GBHueSliderCell.m | 2 +- Cocoa/GBPreferencesWindow.m | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index eccd137..fbdbc79 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -327,6 +327,7 @@ static uint32_t color_to_int(NSColor *color) self.updateProgressButton.enabled = true; [self.updateProgressSpinner stopAnimation:nil]; }); + return; } unzipTask = [[NSTask alloc] init]; diff --git a/Cocoa/GBHueSliderCell.m b/Cocoa/GBHueSliderCell.m index 11fdc09..9b397cb 100644 --- a/Cocoa/GBHueSliderCell.m +++ b/Cocoa/GBHueSliderCell.m @@ -22,7 +22,7 @@ case 1: r = 1 - t; g = 1; - b = 0; + break; case 2: g = 1; b = t; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index cd4dea9..02d428a 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -561,7 +561,6 @@ double max = 0; for (JOYAxis *axis in controller.axes) { if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) { - max = axis.value; mapping[@"AnalogUnderclock"] = @(axis.uniqueID); break; } From cd16431699efd159a36557322d550b42e2786ad9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Jan 2022 16:51:45 +0200 Subject: [PATCH 360/365] OMA DMA improvements (WIP) --- Core/memory.c | 62 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index d4f83a3..dd24f84 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -9,7 +9,6 @@ typedef enum { GB_BUS_MAIN, /* In DMG: Cart and RAM. In CGB: Cart only */ GB_BUS_RAM, /* In CGB only. */ GB_BUS_VRAM, - GB_BUS_INTERNAL, /* Anything in highram. Might not be the most correct name. */ } bus_t; static bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) @@ -23,10 +22,7 @@ static bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) if (addr < 0xC000) { return GB_BUS_MAIN; } - if (addr < 0xFE00) { - return GB_is_cgb(gb)? GB_BUS_RAM : GB_BUS_MAIN; - } - return GB_BUS_INTERNAL; + return GB_is_cgb(gb)? GB_BUS_RAM : GB_BUS_MAIN; } static uint16_t bitwise_glitch(uint16_t a, uint16_t b, uint16_t c) @@ -255,7 +251,18 @@ 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 (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xfe00) return false; + 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_is_cgb(gb)) { + if (addr >= 0xe000) { + return bus_for_addr(gb, gb->dma_current_src) != GB_BUS_VRAM; + } + if (gb->dma_current_src >= 0xe000) { + return bus_for_addr(gb, addr) != GB_BUS_VRAM; + } + } return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); } @@ -732,7 +739,22 @@ 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))) { - addr = gb->dma_current_src; + 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) { + // TODO: this should probably affect the DMA dest as well + addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 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; + } + else { + addr = gb->dma_current_src; + } } uint8_t data = read_map[addr >> 12](gb, addr); GB_apply_cheat(gb, addr, &data); @@ -1631,8 +1653,22 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (unlikely(is_addr_in_dma_use(gb, addr))) { - /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */ - return; + 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) { + // TODO: this should probably affect the DMA dest as well + addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 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; + } + else { + addr = gb->dma_current_src; + } } write_map[addr >> 12](gb, addr, value); } @@ -1648,8 +1684,12 @@ void GB_dma_run(GB_gameboy_t *gb) gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); } else { - /* Todo: Not correct on the CGB */ - gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000); + if (GB_is_cgb(gb)) { + gb->oam[gb->dma_current_dest++] = 0xFF; + } + else { + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000); + } } /* dma_current_src must be the correct value during GB_read_memory */ From 5c17c0ec3bc5dc1456628158f55d1d38284d56f1 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Jan 2022 17:17:35 +0200 Subject: [PATCH 361/365] unreachable fun --- Core/apu.c | 7 ++++++- Core/defs.h | 3 +++ Core/display.c | 2 ++ Core/joypad.c | 4 +--- Core/mbc.c | 2 ++ Core/memory.c | 9 +++++---- Core/sm83_cpu.c | 2 +- 7 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index b3a8e8e..5919427 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -40,6 +40,8 @@ bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index) case GB_NOISE: return gb->io_registers[GB_IO_NR42] & 0xF8; + + nodefault; } return false; @@ -58,6 +60,8 @@ static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index) return 0; case GB_NOISE: return gb->apu.noise_channel.current_volume; + + nodefault; } return 0; } @@ -257,7 +261,8 @@ static void render(GB_gameboy_t *gb) {left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate, right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate}; - case GB_HIGHPASS_MAX:; + case GB_HIGHPASS_MAX: + nodefault; } } diff --git a/Core/defs.h b/Core/defs.h index 0517b22..b512986 100644 --- a/Core/defs.h +++ b/Core/defs.h @@ -17,6 +17,9 @@ #define unrolled #endif +#define unreachable() __builtin_unreachable(); +#define nodefault default: unreachable() + #ifdef GB_BIG_ENDIAN #define LE16(x) __builtin_bswap16(x) #define LE32(x) __builtin_bswap32(x) diff --git a/Core/display.c b/Core/display.c index 2e03039..1a36916 100644 --- a/Core/display.c +++ b/Core/display.c @@ -834,6 +834,8 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) gb->fetcher_state++; } break; + + nodefault; } } diff --git a/Core/joypad.c b/Core/joypad.c index 3527742..db50f66 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -53,9 +53,7 @@ void GB_update_joyp(GB_gameboy_t *gb) } break; - default: - __builtin_unreachable(); - break; + nodefault; } /* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */ diff --git a/Core/mbc.c b/Core/mbc.c index 82771eb..5ade9aa 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -77,6 +77,7 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_rom_bank++; } break; + nodefault; } break; case GB_MBC2: @@ -121,6 +122,7 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_ram_bank = gb->tpp1.ram_bank; gb->mbc_ram_enable = (gb->tpp1.mode == 2) || (gb->tpp1.mode == 3); break; + nodefault; } } diff --git a/Core/memory.c b/Core/memory.c index dd24f84..29d8534 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -360,6 +360,7 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) case 1: return gb->tpp1.rom_bank >> 8; case 2: return gb->tpp1.ram_bank; case 3: return gb->rumble_strength | gb->tpp1_mr4; + nodefault; } case 2: case 3: @@ -533,8 +534,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) oam[target >> 1] = bitwise_glitch_read(a, b, c); break; - default: - __builtin_unreachable(); + nodefault; } for (unsigned i = 0; i < 8; i++) { @@ -706,7 +706,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return 0xFF; } - __builtin_unreachable(); + unreachable(); } if (addr == 0xFFFF) { @@ -899,6 +899,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } } break; + nodefault; } GB_update_mbc_mappings(gb); } @@ -1243,7 +1244,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_MODEL_SGB_PAL_NO_SFC: case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: - __builtin_unreachable(); + unreachable(); } return; } diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 130bfb3..fdaf85d 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -658,8 +658,8 @@ static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) return !(gb->af & GB_CARRY_FLAG); case 3: return (gb->af & GB_CARRY_FLAG); + nodefault; } - __builtin_unreachable(); return false; } From 79ec22b28e075e1fe8386907b47432c39ae39453 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 3 Jan 2022 17:55:00 +0200 Subject: [PATCH 362/365] Clang hates this --- Core/apu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/apu.c b/Core/apu.c index 5919427..76171ff 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -261,8 +261,7 @@ static void render(GB_gameboy_t *gb) {left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate, right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate}; - case GB_HIGHPASS_MAX: - nodefault; + case GB_HIGHPASS_MAX:; } } From 5e7bb0f89100db18acea1fa192cccdf6f83b5e47 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 4 Jan 2022 19:59:46 +0200 Subject: [PATCH 363/365] DMA write conflict revision differences --- Core/gb.h | 1 + Core/memory.c | 11 +++++++++-- Core/save_state.c | 5 +++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index e779759..8a0dbd0 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -436,6 +436,7 @@ struct GB_gameboy_internal_s { uint16_t dma_current_src; int16_t dma_cycles; bool is_dma_restarting; + bool dma_write_zero; uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */ bool hdma_starting; ); diff --git a/Core/memory.c b/Core/memory.c index 29d8534..8f78de9 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1670,6 +1670,10 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) else { addr = gb->dma_current_src; } + if (GB_is_cgb(gb)) { + gb->dma_write_zero = true; + if (gb->model < GB_MODEL_CGB_E) return; + } } write_map[addr >> 12](gb, addr, value); } @@ -1680,8 +1684,11 @@ void GB_dma_run(GB_gameboy_t *gb) /* Todo: measure this value */ gb->dma_cycles -= 4; gb->dma_steps_left--; - - if (gb->dma_current_src < 0xe000) { + if (unlikely(gb->dma_write_zero)) { + gb->oam[gb->dma_current_dest++] = 0; + gb->dma_write_zero = false; + } + else if (gb->dma_current_src < 0xe000) { gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); } else { diff --git a/Core/save_state.c b/Core/save_state.c index d5297b5..e9470bf 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -371,6 +371,11 @@ 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) { From ab1d4cd26e7949acfd761a529a92ceaf1a0d6c44 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 5 Jan 2022 21:40:10 +0200 Subject: [PATCH 364/365] More DMA write conflicts --- Core/gb.h | 3 ++- Core/memory.c | 28 +++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 8a0dbd0..c0a16bd 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -436,7 +436,8 @@ struct GB_gameboy_internal_s { uint16_t dma_current_src; int16_t dma_cycles; bool is_dma_restarting; - bool dma_write_zero; + uint8_t dma_and_pattern; + bool dma_skip_write; uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */ bool hdma_starting; ); diff --git a/Core/memory.c b/Core/memory.c index 8f78de9..977f407 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1466,6 +1466,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) 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_current_src = value << 8; @@ -1654,6 +1655,7 @@ 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) { /* This is cart specific! Everdrive 7X on a CGB-A or 0 behaves differently. */ return; @@ -1670,9 +1672,16 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) else { addr = gb->dma_current_src; } - if (GB_is_cgb(gb)) { - gb->dma_write_zero = true; - if (gb->model < GB_MODEL_CGB_E) return; + 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; + } + 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; + } + if (gb->model < GB_MODEL_CGB_E || addr >= 0xc000) return; } } write_map[addr >> 12](gb, addr, value); @@ -1684,21 +1693,22 @@ void GB_dma_run(GB_gameboy_t *gb) /* Todo: measure this value */ gb->dma_cycles -= 4; gb->dma_steps_left--; - if (unlikely(gb->dma_write_zero)) { - gb->oam[gb->dma_current_dest++] = 0; - gb->dma_write_zero = false; + if (gb->dma_skip_write) { + gb->dma_skip_write = false; + 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->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src) & gb->dma_and_pattern; } else { if (GB_is_cgb(gb)) { - gb->oam[gb->dma_current_dest++] = 0xFF; + gb->oam[gb->dma_current_dest++] = gb->dma_and_pattern; } else { - gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000); + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000) & gb->dma_and_pattern; } } + gb->dma_and_pattern = 0xFF; /* dma_current_src must be the correct value during GB_read_memory */ gb->dma_current_src++; From b7f03dea8dd98fa90ce5d7c7e8e05ff4cee81362 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 5 Jan 2022 21:55:46 +0200 Subject: [PATCH 365/365] Add CGB-A support --- Cocoa/Preferences.xib | 4 ++-- Core/apu.c | 2 +- Core/display.c | 3 +++ Core/gb.c | 5 +++++ Core/gb.h | 2 +- Core/memory.c | 6 +++--- Core/save_state.c | 2 ++ 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 699f264..dcaa161 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -402,7 +402,7 @@ - + @@ -854,7 +854,7 @@ - + diff --git a/Core/apu.c b/Core/apu.c index 76171ff..ce775fb 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -901,7 +901,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) case GB_MODEL_SGB2: case GB_MODEL_SGB2_NO_SFC: case GB_MODEL_CGB_0: - // case GB_MODEL_CGB_A: + case GB_MODEL_CGB_A: case GB_MODEL_CGB_C: if (effective_counter & 8) { effective_counter |= 0xE; // Sometimes F on some instances diff --git a/Core/display.c b/Core/display.c index 1a36916..5ce0f2b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -169,6 +169,9 @@ void GB_display_vblank(GB_gameboy_t *gb) if (gb->model == GB_MODEL_CGB_0) { 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! + } gb->borrowed_border.palette[0] = LE16(colors[index]); gb->borrowed_border.palette[10] = LE16(colors[5 + index]); gb->borrowed_border.palette[14] = LE16(colors[10 + index]); diff --git a/Core/gb.c b/Core/gb.c index 34d9136..632c5a8 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1409,6 +1409,7 @@ static void reset_ram(GB_gameboy_t *gb) break; case GB_MODEL_CGB_0: + case GB_MODEL_CGB_A: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: for (unsigned i = 0; i < gb->ram_size; i++) { @@ -1436,6 +1437,7 @@ static void reset_ram(GB_gameboy_t *gb) /* HRAM */ switch (gb->model) { case GB_MODEL_CGB_0: + case GB_MODEL_CGB_A: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: @@ -1468,6 +1470,7 @@ static void reset_ram(GB_gameboy_t *gb) /* OAM */ switch (gb->model) { case GB_MODEL_CGB_0: + case GB_MODEL_CGB_A: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: @@ -1501,6 +1504,7 @@ static void reset_ram(GB_gameboy_t *gb) /* Wave RAM */ switch (gb->model) { case GB_MODEL_CGB_0: + case GB_MODEL_CGB_A: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: @@ -1578,6 +1582,7 @@ static void request_boot_rom(GB_gameboy_t *gb) case GB_MODEL_CGB_0: type = GB_BOOT_ROM_CGB_0; break; + case GB_MODEL_CGB_A: case GB_MODEL_CGB_B: case GB_MODEL_CGB_C: case GB_MODEL_CGB_D: diff --git a/Core/gb.h b/Core/gb.h index c0a16bd..c116cbc 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -97,7 +97,7 @@ typedef enum { GB_MODEL_SGB2 = 0x101, GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, GB_MODEL_CGB_0 = 0x200, - // GB_MODEL_CGB_A = 0x201, + GB_MODEL_CGB_A = 0x201, GB_MODEL_CGB_B = 0x202, GB_MODEL_CGB_C = 0x203, GB_MODEL_CGB_D = 0x204, diff --git a/Core/memory.c b/Core/memory.c index 977f407..913fb68 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -567,8 +567,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_MODEL_CGB_C: case GB_MODEL_CGB_B: - // case GB_MODEL_CGB_A: - case GB_MODEL_CGB_0: + case GB_MODEL_CGB_A: + case GB_MODEL_CGB_0: addr &= ~0x18; return gb->extra_oam[addr - 0xfea0]; @@ -1228,7 +1228,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_MODEL_CGB_C: case GB_MODEL_CGB_B: - // case GB_MODEL_CGB_A: + case GB_MODEL_CGB_A: case GB_MODEL_CGB_0: addr &= ~0x18; gb->extra_oam[addr - 0xfea0] = value; diff --git a/Core/save_state.c b/Core/save_state.c index e9470bf..e090036 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -318,6 +318,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t case GB_MODEL_SGB2: return true; case GB_MODEL_SGB2_NO_SFC: return true; case GB_MODEL_CGB_0: return true; + case GB_MODEL_CGB_A: return true; case GB_MODEL_CGB_B: return true; case GB_MODEL_CGB_C: return true; case GB_MODEL_CGB_D: return true; @@ -575,6 +576,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe bess_core.full_model = BE32('S2 '); break; case GB_MODEL_CGB_0: bess_core.full_model = BE32('CC0 '); break; + case GB_MODEL_CGB_A: bess_core.full_model = BE32('CCA '); break; case GB_MODEL_CGB_B: bess_core.full_model = BE32('CCB '); break; case GB_MODEL_CGB_C: bess_core.full_model = BE32('CCC '); break; case GB_MODEL_CGB_D: bess_core.full_model = BE32('CCD '); break;